常见面试题
1.如何在chrome浏览器上实现10px的字体;
只有 Chrome 才有对字体的限制,Chrome 是支持 CSS3 的,我们可以用缩放的方式来制作10px字体。需要一些计算:10px = 12px * 0.83或者10px = 20px * 0.5;当然我们会优先采用第一个计算方式,因为避免不支持 CSS3 浏览器的情况,我们也可以通过降级处理,将字体变回12px;最后兼容 IE:*font-size:10px;:
.font10px {
font-size: 12px;
transform : scale(0.83,0.83);
*font-size: 10px;
}
还有一些方式可以制作小字体:
- 图片
- 自制字体
- SVG
2.vuex的原理是什么?如何实现的?
vuex则是一个状态管理器,通过全局注入store对象,来实现组件间的状态共享
Vuex有以下几个部分构成:
1)state
state是存储的单一状态,是存储的基本数据。
2)Getters
getters是store的计算属性,对state的加工,是派生出来的数据。就像computed计算属性一样,getter返回的值会根据它的依赖被缓存起来,且只有当它的依赖值发生改变才会被重新计算。
3)Mutations
mutations提交更改数据,使用store.commit方法更改state存储的状态。(mutations同步函数)
4)Actions
actions像一个装饰器,提交mutation,而不是直接变更状态。(actions可以包含任何异步操作)
5)Module
Module是store分割的模块,每个模块拥有自己的state、getters、mutations、actions。
vuex的store挂载注入到组件
1、vue项目中先安装vuex,Vue.use(vuex);// vue的插件机制
2、利用vue的插件机制,使用Vue.use(vuex)时,会调用vuex的install方法,装载vuex
3、vuex是利用vue的mixin混入机制,在beforeCreate钩子前混入vuexInit方法,
vuexInit方法实现了store注入vue组件实例,并注册了vuex store的引用属性$store。store注入过程如下图所示:
vuex的state和getters是如何映射到各个组件实例中响应式更新状态
Vuex的state状态是响应式,是借助vue的data是响应式,将state存入vue实例组件的data中;Vuex的getters则是借助vue的计算属性computed实现数据实时监听
3.Event Loop的定义是什么?
MacroTask(宏任务)
script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。
MicroTask(微任务)
Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver(具体使用方式查看这里)
浏览器中的Event Loop
Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。
JS调用栈
JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。
Event Loop:同步任务和异步任务
Javascript任务被分为同步任务和异步任务,
异步任务分2类:创建宏任务的异步任务(macrotask )和和创建微任务的异步任务(microtask )
1、同步任务会在执行栈中按照顺序等待JS引擎线程依次执行,
2、执行过程中如果遇到创建微任务的异步任务,就将它添加到微任务的任务队列中
3、执行过程中如果遇到创建宏任务的异步任务,就将它添加到宏任务的任务队列中
4、当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行
5、微任务执行完毕后进行渲染
6、渲染结束后再将异步宏任务从队列中调入主线程执行,
7、一直循环直至所有任务执行完毕
不管是setTimeout/setInterval和XHR/fetch代码,在这些代码执行时, 本身是同步任务,而其中的回调函数才是异步任务。
4.vue的生命周期是啥?
beforeCreate(创建前) :组件实例被创建之初,组件的属性生效之前
created(创建后) :组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用
beforeMount(挂载前) :在挂载开始之前被调用:相关的 render 函数首次被调用
mounted(挂载后) :在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdate(更新前) :组件数据更新之前调用,真实DOM还没被渲染
update(更新后) :组件数据更新之后
activated(激活前) :keep-alive专属,组件被激活时调用
deactivated(激活后) :keep-alive专属,组件被销毁时调用
beforeDestory(销毁前) :组件销毁前调用
destoryed(销毁后) :组件销毁前调用
简述每个周期具体适合哪些场景
Vue子组件和父组件执行顺序
加载渲染过程:beforeCreate(父) —> created(父)—>beforeMount(父)—>beforeCreate(子)—>created(子)—>beforeMount(子)—>mounted(子)—>mounted(父)
更新过程:beforeUpdate(父) —> beforeUpdate(子) —> update(子) —> update(父)
销毁过程:beforeDestory(父) —> beforeDestory(子) —> destoryed(子) —> destoryed(父)
简述每个周期具体适合哪些场景
beforeCreate:在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法
create: data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作
beforeMount:执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
mounted:执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
beforeUpdate: 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的,页面还没有和最新的数据保持同步
updated:页面显示的数据和data中的数据已经保持同步了,都是最新的
beforeDestory: Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁
destroyed: 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了
5.当你的代码运行在不同型号的手机,有些型号的手机报错了,你会怎么做?你的思路是什么?
1.首先分析问题出现的原因;
第一点:因为手机型号不同,可以推断手机的浏览器内核不同,浏览器版本不同;
第二点:网络问题或者所需加载的资源不存在。
2.根据对应问题做出解决措施;
3.解决措施如何工程化。
第一点:通过埋点系统建立报警方案,上报错误手机型号和浏览器内核和版本;
第二点:在代码中使用errorboundry对组件进行包裹,将错误信息上报,或者使用try catch对代码进行包裹;
第三点:做向下兼容,在某个组件报错时,展示报错的内容(或者展示其他内容)
第四点:拿到报错手机型号等信息后,在本地或者测试环境,模拟此型号手机,并开启sourcemap,在本地排查问题原因;
6.你在项目中做过的好的地方在哪里?封装过什么高级的组件?
1、项目需求确认阶段分析可复用的功能,提取封装成组件
2、form,table,auth,errorboundry,
7.在工作过程中,你实现了哪些提升代码效率的地方?
1、项目需求确认阶段分析可复用的功能,提取封装成组件
2、webpack打包提起dll文件,加快打包效率
3、引入通用的工具插件
8.你对前端未来的看法是怎样的?
前端在向各种领域扩展,不再局限于web端,现在还包括可桌面端,移动端,app,小程序,甚至于命令行工具zx,前端能都担任的角色和实现的功能越来越丰富,有更好的工程化体验。
我更想参与到一个稳定的项目,或者是业内有知名度的项目,和团队一起做一些有意义的项目
9.你如何进行新知识的学习的?
掘金、mdn、官网,b站,慕课
10.你在项目中遇到过的最大的挑战是什么?
感觉项目中最难的也是最重要的就是沟通
11.JS的继承有哪些?各有哪些优缺点?
prototype和construct的关系
1、牢记两点:①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__和constructor属性
2、__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。
3、prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.proto === Foo.prototype
4、constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function
原型继承
let Super = functioin(name = 'eric') {
this.name = name;
this.getName = function() {
return this.name;
}
}
let Sub = function(sex = 'male') {
this.sex = sex;
}
Sub.prototype = new Super('eric'); //通过改变原型对象实现继承
Sub.prototype.constructor = Sub // 保持构造函数和原型对象的完整性
let sub1 = new Sub('male')
sub2 = new Sub('female');
console.log(sub1.getName()); // eric
console.log(sub1.hasOwnProperty('name')) // false 说明是继承而来的属性
console.log(sub1.getName === sub2.getName) // true,复用了方法
优点: 父类的方法(getName)得到了复用。
缺点: 同理父类的属性(name)也是复用,即子类实例没有自己的属性。
构造函数实现继承
let Super = function(name = 'eric') {
this.name = name;
this.getName = function() {
return this.name;
}
}
let Sub = function(name, sex) {
Super.call(this, name);
this.sex = sex;
}
let sub1 = new Sub('eric', 'male');
let sub2 = new Sub('ada', 'female');
console.log(sub1.name) // 'eric'
console.log(sub1.hasOwnProperty('name')) // true 说明不是继承而来,是自己的属性
console.log(sub1.getName === sub2.getName) // false 方法没有得到复用
优点: 子类的每个实例都有自己的属性(name),不会相互影响。
缺点: 但是继承父类方法的时候就不需要这种特性,没有实现父类方法的复用。
组合式继承
let Super = function(name = 'eric') {
this.name = name;
}
Super.prototype = {
constructor: Super,
getName() {
return this.name;
}
}
let Sub = function(sex) {
Super.call(this, 'eric'); //继承父类属性
this.sex = sex;
}
Sub.prototype = new Super('eric'); //继承父类方法
Sub.prototype.constructor = Sub;
let sub1 = new Sub('male'),
sub2 = new Sub('female');
console.log(sub1.name); // 'eric'
console.log(sub1.hasOwnProperty('name')); // true 自己的属性
console.log(sub1.getName === sub2.getName); // true 复用了方法
console.log(Sub.prototype) // { name: "eric" }
console.log(sub1) // { name: "eric", sex: "male" }
优点: 继承了上述两种方式的优点,摒弃了缺点,复用了方法,子类又有各自的属性。
缺点: 因为父类构造函数被执行了两次,子类的原型对象(Sub.prototype)中也有一份父类的实例属性(name),而且这些属性会被子类实例(sub1,sub2)的属性覆盖掉(即通过sub1.name访问不到Sub.prototype上的name属性),也存在内存浪费。
寄生组合式继承
let Super = function(name = 'eric') {
this.name = name;
}
Super.prototype = {
constructor: Super,
getName() {
return this.name;
}
}
let Sub = function(sex, name) {
Super.call(this, name);
this.sex = sex;
}
// 组合继承的缺点就是在继承父类方法的时候调用了父类构造函数,从而造成内存浪费,
// 现在只要解决了这个问题就完美了。那在复用父类方法的时候,
// 使用Object.create方法也可以达到目的,没有调用父类构造函数,问题解决。
Sub.prototype = Object.create(Super.prototype);
// 当然这个地方也可以使用Object.setPrototypeOf(Sub.prototype, Super.prototype)
// 因为更改一个对象的隐士原型(__proto__)对浏览器和js引擎都是很慢对操作,所以建议使用Object.create()创建一个具有指定原型对象的新对象
Sub.prototype.constructor = Sub;
es6中的class
class Super() {
constructor(props = { name: 'eric' }) {
this.name = props.name;
}
setName(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Sub extends Super {
constructor(props) {
super(props = { sex: 'male' }); // 创建实例,继承父类属性和方法
this.sex = props.sex;
}
}
let sub1 = new Sub({
name: 'eric',
sex: 'male'
})
let sub2 = new Sub({
name: 'eric',
sex: 'female'
})
sub1.setName('ada');
console.log(sub1.getName(),sub2.getName()) // ada,eric,属性没复用,各自实例都有自己的属性。
console.log(sub1.getName === sub2.getName) // true; 复用了父类的方法
console.log(Sub.prototype.sex) // undefined
// 子类原型对象上没有父类构造函数中赋值的属性,不是组合式继承
12.手写一个new
new到底做了什么
1、创建一个新的对象
2、继承父类原型上的方法.
3、添加父类的属性到新的对象上并初始化. 保存方法的执行结果.
4、如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象。
代码如下:
function _new(obj, ...rest){
// 基于obj的原型创建一个新的对象
const newObj = Object.create(obj.prototype);
// 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
const result = obj.apply(newObj, rest);
// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
return (typeof result === 'object' && result !== null ) ? result : newObj;
}
13.手写一个promise
1、new promise时, 需要传递一个executor()执行器,执行器立即执行
2、executor接受两个参数,分别是resolve和reject;
3、promise 有三个状态:pending,fulfilled,or rejected,默认状态是 pending
4、promise 有一个value保存成功状态的值,可以是undefined/thenable/promise
5、promise 只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变
6、promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected
7、如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value
8、如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason
9、如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected
1)、简易版
/**
* 记录状态
* 保存this,避免后期闭包导致this的指向不对
* value 变量用于保存 resolve 或者 reject 中传入的值
* resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回调,
* 因为当执行完 Promise 时状态可能还是等待中,这时候应该把 then 中的回调保存起来用于状态改变时使用
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
const myPromise = function (executor) {
const self = this
self.status = PENDING;
self.value = null;
self.reason = null;
self.fulfilledCallbacks = []
self.rejectedCallbacks = []
function resolve(params) {
if (self.status === PENDING) {
self.status = FULFILLED
self.value = params
self.fulfilledCallbacks.forEach((fn) => fn(self.value))
}
}
function reject(value) {
if (that.status === PENDING) {
that.status = REJECTED;
that.value = value;
//执行回调方法
that.rejectedCallbacks.forEach(myFn => myFn(that.value))
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
myPromise.prototype.then = function (onFulfilled, onRejected) {
const self = this
if (self.status === FULFILLED) {
onFulfilled(self.value)
}
if (self.status === REJECTED) {
onRejected(self.reason)
}
if (this.status === PENDING) {
self.fulfilledCallbacks.push(() => onFulfilled(self.value))
self.rejectedCallbacks.push(() => onRejected(self.value))
}
}
const p = new myPromise((resolve, reject) => {
console.log('hello, myPromise');
// resolve(5);
setTimeout(() => {
resolve(5);
}, 1000)
});
p.then((res) => {
console.log(res);
})
p.then(() => {
console.log('test');
})
这其实是一个发布订阅模式,这种 收集依赖 -> 触发通知 -> 取出依赖执行 的方式,被广泛运用于发布订阅模式的实现
3)、实现then的链式调用
每次调用 then 的时候,我们都重新创建一个 promise 对象,并把上一个 then 的返回结果传给这个新的 promise 的 then 方法
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
const resolvePromise = (promise2, x, resolve, reject) => {
// 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise Promise/A+ 2.3.1
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// Promise/A+ 2.3.3.3.3 只能调用一次
let called;
// 后续的条件要严格判断 保证代码能和别的库一起使用
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
try {
// 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候) Promise/A+ 2.3.3.1
let then = x.then;
if (typeof then === 'function') {
// 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty Promise/A+ 2.3.3.3
then.call(x, y => { // 根据 promise 的状态决定是成功还是失败
if (called) return;
called = true;
// 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
resolvePromise(promise2, y, resolve, reject);
}, r => {
// 只要失败就失败 Promise/A+ 2.3.3.3.2
if (called) return;
called = true;
reject(r);
});
} else {
// 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4
resolve(x);
}
} catch (e) {
// Promise/A+ 2.3.3.2
if (called) return;
called = true;
reject(e)
}
} else {
// 如果 x 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.4
resolve(x)
}
}
14.SSR做过没?有什么优点?如何做SSR?
SSR(Server-Side Rendering),意为服务端渲染
SSR解决方案,后端渲染出完整的首屏的dom结构返回,前端拿到的内容包括首屏及完整spa结构,应用激活后依然按照spa方式运行
SSR主要解决了以下两种问题:
1、seo:搜索引擎优先爬取页面HTML结构,使用ssr时,服务端已经生成了和业务想关联的HTML,有利于seo
2、首屏呈现渲染:用户无需等待页面所有js加载完成就可以看到页面视图(压力来到了服务器,所以需要权衡哪些用服务端渲染,哪些交给客户端)
但是使用SSR同样存在以下的缺点:
复杂度:整个项目的复杂度
库的支持性,代码兼容
性能问题
每个请求都是n个实例的创建,不然会污染,消耗会变得很大
缓存 node serve、 nginx判断当前用户有没有过期,如果没过期的话就缓存,用刚刚的结果。
降级:监控cpu、内存占用过多,就spa,返回单个的壳
服务器负载变大,相对于前后端分离务器只需要提供静态资源来说,服务器负载更大,所以要慎重使用
所以在我们选择是否使用SSR前,我们需要慎重问问自己这些问题:
需要SEO的页面是否只是少数几个,这些是否可以使用预渲染(Prerender SPA Plugin)实现
首屏的请求响应逻辑是否复杂,数据返回是否大量且缓慢
15.谈谈vue和React的区别
原文链接:blog.csdn.net/xgangzai/ar…
共同点
Vue和React存在着很多的共同点:
数据驱动视图
组件化
都使用 Virtual DOM
1. 数据驱动视图
在jquery时代,我们需要频繁的操作DOM来实现页面效果与交互;而Vue和React 解决了这一痛点,采用数据驱动视图方式,隐藏操作DOM的频繁操作。所以我们在开发时,只需要关注数据变化即可,但是二者实现方式不尽相同。
2. 组件化
React与Vue都遵循组件化思想,它们把注意力放在UI层,将页面分成一些细块,这些块就是组件,组件之间的组合嵌套就形成最后的网页界面。
所以在开发时都有相同的套路,比如都有父子组件传递, 都有数据状态管理、前端路由、插槽等。
3. Virtual DOM
Vue与React都使用了 Virtual DOM + Diff算法, 不管是Vue的Template模板+options api 写法, 还是React的Class或者Function写法,最后都是生成render函数,而render函数执行返回VNode(虚拟DOM的数据结构,本质上是棵树)。
当每一次UI更新时,总会根据render重新生成最新的VNode,然后跟以前缓存起来老的VNode进行比对,再使用Diff算法(框架核心)去真正更新真实DOM(虚拟DOM是JS对象结构,同样在JS引擎中,而真实DOM在浏览器渲染引擎中,所以操作虚拟DOM比操作真实DOM开销要小的多)
不同点
Vue和React两者虽然都是用于构建用户界面的框架,但是也有很大的差异,首先二者核心的思想就不同。
1. 核心思想不同
Vue早期开发就尤雨溪大佬,所以定位就是尽可能的降低前端开发的门槛,让更多的人能够更快地上手开发。这就有了vue的主要特点:灵活易用的渐进式框架,进行数据拦截/代理,它对侦测数据的变化更敏感、更精确。
React 从一开始的定位就是提出 UI 开发的新思路。背靠大公司Facebook 的React,从开始起就不缺关注和用户,而且React想要做的是用更好的方式去颠覆前端开发方式。所以React推崇函数式编程(纯组件),数据不可变以及单向数据流,当然需要双向的地方也可以手动实现, 比如借助onChange和setState来实现。
2. 组件写法差异
React推荐的做法是JSX + inline style, 也就是把 HTML 和 CSS 全都写进 JavaScript 中,即 all in js; Vue 推荐的做法是 template 的单文件组件格式(简单易懂,从传统前端转过来易于理解),即 html,css,JS 写在同一个文件(vue也支持JSX写法)
这个差异一定程度上也是由于二者核心思想不同而导致的。
3.diff算法不同
两者流程思路上是类似的:
- 不同的组件产生不同的 DOM 结构。当type不相同时,对应DOM操作就是直接销毁老的DOM,创建新的DOM。
- 同一层次的一组子节点,可以通过唯一的 key 区分。
但是在源码实现上又完全不同:
React的Diff算法核心实现
1、react首先对新集合进行遍历,for( name in nextChildren)。
2、通过唯一key来判断老集合中是否存在相同的节点。如果没有的话创建
3、如果有的话,if (preChild === nextChild )会将节点在新集合中的位置和在老集合中lastIndex进行比较
4、如果if (child._mountIndex < lastIndex) 进行移动操作,否则不进行移动操作。
5、如果遍历的过程中,发现在新集合中没有,但在老集合中有的节点,会进行删除操作
Vue的Diff算法核心实现
updateChildren是vue diff的核心, 过程可以概括为:
旧children和新children各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。
如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明旧children和新children至少有一个已经遍历完了,就会结束比较。
Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
4. 响应式原理不同
Vue
Vue依赖收集,自动优化,数据可变。
Vue递归监听data的所有属性,直接修改。
当数据改变时,自动找到引用组件重新渲染。
React
React基于状态机,手动优化,数据不可变,需要setState驱动新的state替换老的state。当数据改变时,以组件为根目录,默认全部重新渲染, 所以 React 中会需要 shouldComponentUpdate 这个生命周期函数方法来进行控制
16.实现呼吸灯效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.light {
position: absolute;
background: blue;
width: 20px;
height: 20px;
/* 名称,时间间隔,移动方时,延时,次数,方向,不播放时状态 */
/* animation: name duration timing-function delay iteration-count direction fill-mode; */
/* animation: light 2s linear 0s infinite reverse none; */
animation-name: light;
animation-duration: 2s;
animation-iteration-count: infinite;
border-radius: 50%;
}
@keyframes light {
0% {
transform: scale(1, 1);
}
50% {
transform: scale(2, 2);
}
100% {
transform: scale(1, 1);
}
}
</style>
</head>
<body>
<div class='light'></div>
</body>
</html>
17.实现一个三角形
1、通过border实现
.angle {
width: 0px;
height: 0px;
border: 10px solid;
border-color: blue transparent transparent transparent;
}
2、通过canvas实现
3、svg实现
4、直接输入一个三角形字符
18.浏览器接口返回码的含义,自己遇到过哪些?
200 - 成功
301 - 永久重定向(配合location,浏览器自动处理),浏览器会记住这个状态码,下次访问这个网址时会自动跳到location返回的那个网址,一般是网站域名到期或者是换域名了,就会返回301。
302 - 临时重定向(配合location,浏览器自动处理),区别于301,浏览器下次访问时仍然会去访问原来的地址。
304 - 资源未被修改
404 - 资源未找到
403 - 没有权限
500 - 服务器错误
21.localStorage和sessionStorage区别?分别用在什么地方?
29.在技术选型时,你会考虑哪些问题进行技术选型?
技术选型需要考虑的因素
1、项目因素 明确现在项目的规模、重要程度。 项目的需求(特别是非功能性需求)也会限制技术的选型
2、团队因素 考虑团队的因素,也就是人的因素,考虑团队人员的技术组成。 考虑招聘的因素,对于特别小众的技术,可能会因为招不到人而影响到对公司的业务支持。
3、技术因素 技术特性考虑(易用性、可维护性、可扩展性、性能等)、技术成熟度、社区活跃度、架构匹配和演化等。 github上的star数,可以作为一个重要的参考。
技术选型的注意点
1、一定要进行可行性分析,如果不太确定,做个Demo验证一下,如果项目进行到一半,发现原来设想的方案不可选,那会是非常痛苦和浪费时间的事情。
2、不要有思维定式,也不要赶时髦。
3、随着业务发展,很多架构需要不断升级,所以一定要考虑未来如果替换某项技术,是否会很麻烦。可以选择一些标准技术或产品,或者在应用中部署一个适配层,方便未来适配其他技术,自由插拔。
4、架构应该尽可能统一,一个领域避免引入太多相同功能的技术产品。
23.权限菜单由后端返回,前端在菜单配置过程中,如何在不调用后端接口的情况下,展示最新配置好的菜单(我答的是每次配置完菜单后,调用对应接口,但是好像不对)
24.locatorage中的key和value都一样,在不用的页面,如何进行区分?区分的原理是啥?(我直接说的我不会,我感觉面试的时候,不要被他们的思路牵着走,按照你的正确思路来做)
25.如果使用get方法去调用了post方法的接口,会怎么样?
26.purecomponent和一般组件的区别?
20.new操作符做了什么操作?让你实现你会怎么实现?手写一个new操作符的实现。
new到底做了什么
1、创建一个新的对象
2、继承父类原型上的方法.
3、添加父类的属性到新的对象上并初始化. 保存方法的执行结果.
4、如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象。
代码如下:
function _new(obj, ...rest){
// 基于obj的原型创建一个新的对象
const newObj = Object.create(obj.prototype);
// 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
const result = obj.apply(newObj, rest);
// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
return (typeof result === 'object' && result !== null ) ? result : newObj;
}
21.给定一组''''''''''''等双标签元素,
判断当前彝族元素是否符合html标签规范
有效标签必须满足:闭合,span,div以正确顺序闭合
const isValid = (str) => {
if (!str) {
return false
}
const strLength = str.length
const items = []
let startIndex = 0
let compareFlag = false
for (let i = 0; i < strLength; i++) {
if (str.charAt(i) === '<') {
startIndex = i + 1
}
if (str.charAt(i) === '/') {
compareFlag = true
startIndex = i + 1
}
if (str.charAt(i) === '>') {
const item = str.substring(startIndex, i)
if (!compareFlag) {
if (item === 'div' && items.includes('span')) {
return false
}
items.push(item)
continue
}
if (compareFlag) {
if (items[items.length - 1] !== item) {
return false
}
compareFlag = false
items.pop()
continue
}
}
}
return items.length === 0
}
console.log(
isValid('<div></div>'),
isValid('<div><span></span></div>'),
isValid('<span></span><div></div><div></div>'),
isValid('<span><div></div></span>'),
)
22、TCP和http的关系
23、vite和webpack的区别
24、react浅比较
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
// 调用Object.is判断是否相等,相同返回true,不同返回false
if (Object.is(objA, objB)) {
return true;
}
// object.is比较发现不等,但并不代表真的不等,object对象还需要比较
// 这里判断是否是object,如果不是,那直接返回false
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
// 比较对象中的keys长度,不等返回false
if (keysA.length !== keysB.length) {
return false;
}
// 比较对象中相同的key的val是否相等
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) || // 判断在objB中是否有objA中的所有key,比较key是否相同
!Object.is(objA[keysA[i]], objB[keysA[i]]) // 判断同key的value是否相同
) {
return false;
}
}
return true;
}
// 浅比较函数
// 比较了props和nextProps,state和nextState
function shallowCompare(instance, nextProps, nextState) {
return (
!shallowEqual(instance.props, nextProps) ||
!shallowEqual(instance.state, nextState)
);
}
通过如上分析,可以发现,在object比较的时候,Object.is()无法比较对象属性中嵌套对象是否相等,与此同时也没有判断顺序,例如某两个对象属性名交叉相同,而且值都是数值18,那么也会判断相同,但实际上他们在顺序上并不相同,可能经历过增删等操作,虽然结果一致,但并不是严谨的相等判断。
如果是一些复杂的对象比较,建议可以根据业务需求自行写判断逻辑。