Vue 源码分析 - 数组变异方法的实现原理
阅读前
- Vue 版本:
v2.6.10; - Vue 2 变异数组文档:Array Change Detection
- 变异数组:
arrayMethods; arrayMethods源码位置:src/core/observer/array.js。arrayMethods被使用的位置:src/core/observer/index.js
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push();pop()shift();unshift();splice();sort();reverse()。
完整代码
包含 2 部分:
- 被引入的
def函数; - 核心逻辑;
def 函数
1 | /** |
核心逻辑
sequenceDiagram
participant User as 用户代码
participant Array as 数组实例 (this)
participant Method as 变异方法 (push/pop等)
participant Mutator as 拦截函数 (mutator)
participant Original as 原生数组方法
participant Observer as 观察者 (this.__ob__)
participant Dep as 依赖管理器 (dep)
User->>Array: arr.push(新元素)
Array->>Mutator: 调用拦截的mutator
activate Mutator
Mutator->>Original: 执行原方法(新元素)
Original-->>Mutator: 返回结果result
Mutator->>Mutator: 检查method类型
alt push/unshift
Mutator->>Mutator: inserted = args
else splice
Mutator->>Mutator: inserted = args.slice(2)
else 其他方法(pop/shift/sort/reverse)
Mutator->>Mutator: inserted = null
end
opt inserted存在
Mutator->>Observer: ob.observeArray(inserted)
Observer-->>Observer: 观察新元素
end
Mutator->>Dep: ob.dep.notify()
Dep-->>Dep: 触发更新通知
Mutator-->>User: 返回result
deactivate Mutator1 | import { def } from '../util/index' |
整段代码执行下来,创建了一个对象 arrayMethods,它的原型指向数组原型,并且它有这么成员方法:
push;pop;shift;unshift;splice;sort;reverse。
很明显,这样还不够!这样还能直接通过vue数组实例的点操作符调用变异方法!还需要将这些挂在vue实例数组的原型链上!
1 | export class Observer { |
在坚持数组时,会将arrayMethods,根据实际情况挂在到当前这个数组的原型链上!在可以设置原型执行时,直接改变当前数组原型指向,改为arrayMethods;否则,直接将arrayMethods的方法,复制到当前数组上,作为当前数组的成员方法!
回看上面的代码(这一处:def(arrayMethods, method, function mutator (...args)),在这一块代码可以发现this.__ob__!经过以上分析,我们知道arrayMethods最后会挂在到vue数组上,那么这个this指向的就是这个数组,那么__ob__应该就是在vue数组原型链上或数组ownProperties上的!
似曾相似,在哪里遇过~
1 | export class Observer { |
在创建Observer实例时,会将当前实例挂在到当前观察数据的__ob__上,对vue数组而言,这个被观察的数据就是它了!
既然__ob__是Observer实例,当然调用劫持数组的方法ob.observeArray(inserted)!这里插一句题外话,虽然新增元素是属于当前数组的,但还是在被劫持这个行为上,他们是相互独立的,所以这里就是不使用ob.ob.observeArray去劫持,而使用inserted.forEach((item) => observe(it))劫持也是可以的~
这里需要当前数组的__ob__,主要是为了通知到这个数组的订阅者!
总结
- 变异方法通过对数组原型方法的拦截对原有方法进行处理,拦截的方式分两种:1. 可以设置原型的情况下,通过改变vue数组的原型指向进行拦截;2. 1不可行的情况下,则在vue数组的ownProperties上创建同名成员方法拦截!
- 编译数组方法可以通知watche,是因为最后调用
ob.dep.notify(),通知了订阅者,这就是本质区别; ob.observeArray(inserted)再次坚持新元素是为了让别的watcher可以订阅新属性。