持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情
Vue.mixin
的使用场景和原理Vue
组件data为什么必须是个函数?nextTick
在哪里使用?使用原理?computed
和watch
的区别Vue.set
方法是如何实现的Vue
为什么需要虚拟domVue
中diff
算法原理- 既然
Vue
通过数据劫持可以精准探测数据变化,为什么还需要虚拟dom进行diff
检测差异- 请说明
Vue
中key的作用和其原理,谈谈你对它的理解
7. Vue.mixin
的使用场景和原理
mixin我们用的比较多的就是混入生命周期。
通过Vue.mixin来实现逻辑的复用:
Vue.mixin({
beforeCreate(){
// 每个vue组件实例都有该属性了
this.$store = new Store()
// 还可以扩展公共逻辑 比如每个组件销毁时都需要做的某些事情...
}
})
虽然mixin可以实现逻辑的复用,数据的混入复用等等。但是问题在于数据来源不明确。我们在组件中使用某个数据或者某个方法,我们有时候是不知道这个方法或者数据是来自哪里的?父组件传值?还是说provide?...会造成混乱。有时候难以定位错误。
Vue.mixin({
data(){
return {
name:"张三"
}
}
})
Vue.component("my-btn",{
template:`<div>{{name}}</div>`
})
而且对于多个数据,如果组件自己内部定义了和mixin混入的同名属性,可能会导致命名冲突问题。(虽然在组件实例化的过程,mixin的选项和组件自身的选项合并时(mergeOptions方法中)是以用户选项为主)
mixin的核心就是合并属性,内部采用了策略模式进行合并。使用方式就是全局和局部的mixin。
当然针对不同的属性有不同的合并策略。此外:出现命名冲突也是不会报错和提示的
// mixin方法 Vue.mixin
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
// 谁调用 this就是谁 最终会将 mixin选项和Vue.options合并在一起
this.options = mergeOptions(this.options, mixin)
return this
}
}
8. Vue
组件data为什么必须是个函数?
在声明一个组件的时候,其实你data属性不是一个函数,也能正常运行,只是控制台会提醒你,让你把data属性给成函数。
我们知道,我们使用Vue.component定义一个组件的时候,内部实际调用的是Vue.extend方法(我在mini-vue中写过的)。
传送门:mini-vue
**原因:**针对根实例来说(new Vue),一般我们一个项目都是一个根,所以data可以是对象。但是对于组件来说,组件是通过同一个构造函数多次创建实例,如果是同一个对象,所以实例共享一份data,实例的data之间会相互影响。每个组件的数据源应该都是独立的。那就每次都调用data,返回一个独立的数据。
在组件实例化的时候:我们会根据data是否是函数,来进行data函数的执行的。(_init -> initState -> initData)
9. nextTick
在哪里使用?使用原理?
nextTick内部采用了异步任务进行了包装(多个nextTick调用,会被合并成一次,内部会合并回调),最后在异步任务中批处理。
主要应用场景就是异步更新(默认调度的时候,就会添加一个nextTick任务),用户为了获取最终的渲染结果,需要在内部任务执行完成以后去执行用户逻辑。这时候用户需要把对应的逻辑放到nextTick中。
10. computed
和watch
的区别
computed和watch的相同点:
-
底层都是创建了watcher(computed定义的属性可以在模板中使用,但是watch不能在视图中使用)
-
computed默认不会立即执行,只有取值的时候才会执行。内部会维护一个dirty属性,来控制依赖的值是否发生改变。(默认情况下,计算属性需要同步返回结果,有个包可以把computed变成异步的)
-
watch默认用户会提供一个回调函数,数据变化了就调用这个回调。我们可以监控某个数据的变化,数据变化了就执行某些操作
11. Vue.set
方法是如何实现的
Vue只会在定义在data中的数据进行劫持。对于Vue.set方法,我们可以认为这是vue的补丁方法(在创建好实例以后,通过实例进行属性的添加,vue是不会劫持的,不会触发更新视图的操作)
而且,我们数组也无法监控索引和长度,所以我们就想到一个方法,手动触发更新。
如何实现的?
我们给每一个对象都增添一个dep属性(一个属性对应一个dep),在给对象新增属性,或者修改数组索引对应的元素时,手动触发更新。
const vm = new Vue({
data(){
return {
firend:{
name:"张三"
}
}
}
})
vm.firend.age = 22
vm.firend.__ob__.dep.notify() // 手动通知更新
set方法的实现原理就是如此。
export function set (target: Array<any> | Object, key: any, val: any): any {
// 给数组的指定索引位置元素更改
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
// 巧妙的把修改值 转为删除值后在新增值
// vm.$set(vm.movies, 1, "ada") -> vm.splice(1, 1, "aaa")
target.splice(key, 1, val)
return val
}
// 不是新增属性 修改已有的属性直接修改即可,因为是响应式了
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 取出对象上的observer对象
const ob = (target: any).__ob__
// 如果修改的是vue实例 不支持这样做 性能也比较差
// vm.$set(vm.$data, "abc", "acbc") vm.$set(vm, "abc", "acbc")
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 不是响应式对象就直接添加
if (!ob) {
target[key] = val
return val
}
// 定义响应式
defineReactive(ob.value, key, val)
// 更新watcher
ob.dep.notify()
return val
}
12. Vue
为什么需要虚拟dom
虚拟dom的好处是什么?
-
我们写的代码可能要针对不同的平台来使用(weex,web,小程序),虚拟dom的最大好处就是跨平台,不需要考虑跨平台问题。
-
不用关心兼容性问题,可以在上层对应的渲染方法传进来,再通过虚拟dom进行渲染即可。
-
针对更新的时候,用到了diff算法,有了虚拟dom之后,我们就可以通过diff算法来找到最后的差异进行修改真实dom。
13. Vue
中diff
算法原理
diff算法的特点就是平级比较,内部采用了双指针方式进行了优化,优化了常见操作。
采用了递归比较的方式。
针对一个节点的diff算法
- 先拿出更节点进行比较,如果是同一个节点,则比较熟悉,如果不是同一个节点则直接换成最新的即可。
- 同一个节点比较熟悉后,复用老节点
比较子节点
- 一方有儿子,一方没儿子。(无非就是移出节点,新增节点)
- 两方都有儿子时,一层层比较
- 优化比较的方式:
- 先比较两方的头结点,不相同
- 在比较两方的尾节点,不相同
- 开始进行交叉比较,一方的头和另一方的尾进行比较(有两次比较)
- 在上面比较都失败的情况下,就是乱序比较了
- 对于乱序比较,就是维护了一个老虚拟节点子节点的映射表,用新的节点去映射表中去查找此元素是否存在,存在就进行移动,不存在就插入新的节点,最后删除多余的老节点
- 优化比较的方式:
**缺点:**比较时可能出现多出无谓的移动节点情况。
vue在进行diff比较的时候,发现AB节点是需要删除的节点,CD节点命中,可以复用,所以会把CD节点移动到头指针的最前方,然后把FG节点插入,E节点也删除。很明显,CD节点是不需要移动的,我们只是需要把ABE节点删除,然后把FG节点插入到CD节点之间即可。因为CD节点的相对顺序没有发生改变。
14. 既然Vue
通过数据劫持可以精准探测数据变化,为什么还需要虚拟dom进行diff
检测差异
我们根据响应式,的确是可以知道哪里出现了更新。如果让一个变量,一个属性就对应一个watcher,粒度太小,watcher这玩意是比较消耗内存的。
- 如果给每个属性,都去增加watcher,当然也是可以实现响应式更新视图。但是可能在变量age发生改变的时候,触发了name的改变,那么视图又会更新。粒度太小导致也不好控制。
- 降低watcher的数量,让每个组件有一个watcher,某个属性变化了,我们会把整个组件都更新了,那么这个组件内即使依赖了其他的变量,也会在视图上更新最新值。
- 如果不用diff算法,我们需要对每个标签,属性等都比较一次,太暴力,反而性能更低。出现虚拟dom就是为了提高性能的。
- 通过diff算法和响应式原理折中处理了一下。
- 在vue1.x中,就是给每个属性都增加了一个watcher,导致的情况就是页面一大了就容易卡,刷新很慢等情况。
15. 请说明Vue
中key的作用和其原理,谈谈你对它的理解
isSameVNode方法中,会根据key来判断两个元素是否是同一个元素,key不相同说明不是同一个元素(key在动态列表中,不要使用index)。
我们使用key,要尽量保证key的唯一性。这样可以优化diff算法。