vue响应式原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter,这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里面添加自己
- 自身必须有一个 update()方法
- 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。
第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。
讲一下event-loop
js分为主线程、调用栈和任务队列,主线程调用执行栈,先执行同步任务,再执行异步任务,等待异步回调结果,这些回调在任务队列里面等待主线程空闲时调用,先执行微任务回调,再执行下面的宏任务回调,微任务比较常见的就是Promise和MutationObserver(监听DOM变化),宏任务就是js执行代码,setTimeout,setInterval等等,dom渲染不在事件循环中,事件循环只有js
参考:zhuanlan.zhihu.com/p/55511602
说说为什么使用Vue.nextTick()及原理
因为vue数据更新不会立马更新dom,而是会把多次更新操作汇总一次更新,这时候就会导致拿最新的dom数据不一定是最新的,所以这么使用。 原理就是通过MutationObserver监听dom变化,回调拿到最新的dom数据。nexttick实现其实是看支持方式,支持哪个采用哪个,查看支持promise,查看支持mutationobserver,查看支持setimmediate,查看支持settimeout,最终执行传入的方法
为什么要使用vue的key
key可以保持高效渲染,但是不能使用index,要使用唯一值比如id,如果使用index,在进行增删操作的时候,在这个item下面的dom都会相应有变化,因为key都变了,所以被重新渲染,这会影响性能,如果是个select,还会导致因使用index导致的bug。
Vue性能优化你们是怎么做的?
- 图片懒加载
- 路由懒加载
- 全局引入组件改为局部引入
- 组件引入方式改为异步引入
- cdn优化,在index.html引入vue、vue-router、axios等链接,在webpack的config文件修改externals
- 开启gzip,productionGzip改为true
- 关闭默认开启prefetch(预先加载模块)
- 生产环境关闭sourcemap
- webpack js css 压缩
- webpack 对图片进行压缩
- webpack 用splitChunks拆分大的chunk文件
- 骨架屏
- 利用vue分析工具分析出比较大的包,使用轻量级的工具库,比如将moment改为dayjs
- 不修改的纯展示数据可以使用Object.freeze冻结数据
- 服务器渲染
ES6 数组内对象去重
let person = [
{id: 0, name: "小明"},
{id: 1, name: "小张"},
{id: 2, name: "小李"},
{id: 3, name: "小孙"},
{id: 1, name: "小周"},
{id: 2, name: "小陈"},
];
let obj = {};
let peon = person.reduce((cur,next) => {
obj[next.id] ? "" : obj[next.id] = true && cur.push(next);
return cur;
},[]) // 设置cur默认类型为数组,并且初始值为空的数组
console.log(peon)
// or
let obj = {}
let cur = []
person.map((v,i,a) => {
obj[v.id] ? "" : obj[v.id] = true && cur.push(v)
})
console.log(cur)
vue中slot和slot-scope有什么区别?
slot是默认插槽,比如在子组件中想要自定义一些内容的话就需要使用到。
slot-scope是作用域插槽,"父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。",比如一个列表循环,有一个数据想要放到循环中的子组件中去,但是因为父组件作用域的关系,导致子组件里没法拿到item数据,所以这时候就需要通过传入item数据,再通过slot-scope拿到item数据。
vue自定义指令怎么使用,钩子函数有哪些?
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
- bind:只调用一次,指令第一次绑定到元素时调用
- inserted:被绑定元素插入父节点时调用
- update:所在组件的 VNode 更新时调用
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
- unbind:只调用一次,指令与元素解绑时调用
钩子函数参数
- el:指令所绑定的元素,可以用来直接操作 DOM
- binding:一个对象,里面包含很多具体的信息
- vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用