实现响应式
-
操作类型为数组的属性的属性时不会触发 getter 函数。虽然不可以,但是如果在对操作数组的方法进行修改,使这些方法被调用的时候可以携带一些额外的方法,那么也可以起到 getter 的作用。
ES6 前 JavaScript 没有提供拦截原型方法的方法,
vue 2
中是通过挂载一个拦截器,用拦截器中自定义的方法覆盖Array.prototype
中的方法来实现的。当调用数组方法的时候,沿原型链往上寻找,首先访问到的是拦截器中被改写的方法。逻辑关系如下图:-
定义拦截器
- Array 原型中七个改变数组自身内容的方法,需要对这些方法进行改写
- 用
defineProperty
方法将需要改造的方法进行封装,封装后,访问 push 方法的时候,就是在执行mutator
方法 mutator
函数拥有数组方法原有的功能,也可以添加一些附加操作
const ArrayProto = Array.prototype; const arrayMethods = Object.create(ArrayProto); ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(method => { const original = ArrayProto[method]; Object.defineProperty(arrayMethods, method, { value: function mutator(...args) { //这里就可以添加其他方法了 return original.apply(this, args); enumerable: false, writable: true, configurable: true }) }) 复制代码
-
覆盖 Array 原型
-
覆盖的范围:不能直接覆盖数组原型,这样会造成全局勿让,因此拦截器只针对响应式数组的原型,所以只需要在
Observer
中使用拦截器覆盖那些即将被转换成响应式数组的原型。 -
覆盖的方法:
- 改写数组的
__proto__
属性 - 有些浏览器不支持使用
__proto__
属性,直接将拦截器上的方法放到被侦测的数组身上,数组本身拥有了这些方法,无需向原型链上寻找,同样是实现了对原型方法的覆盖
- 改写数组的
-
import { arrayMethods } from './array' //判断__proto__是否可以用 const hasProto = '__proto__' in {}; const arrayKeys = Object.getOwnPropertyNames(arrayMethods); export class Observer { constructor(value) { this.value = value; if (Array.isArray(value)) { //通过判断是否支持 __proto__ 属性来决定使用哪种覆盖方法 const augment = hasProto ?protoAugment :copyAugment augment(value, arrayMethods, arraykeys) } else { this.walk(value); } } .... } function protoAugment(target, src, keys){ target.__proto__ = src; } function copyAument(target, src, keys){ for(let i = 0; l = keys.length; i < 1; i++){ const key = key[i] def(target, key, src[key]) } } 复制代码
-
收集依赖
Array 在getter中收集依赖
在拦截器中触发依赖
不是像之前那样触发依赖和保存依赖都是在definedProperty
中了,那么需要把依赖列表保存在一个getter和拦截器都能访问到的对象上,即Oberserver
实例上
在 observer 中新建dep实例,然后在defineReactive函数中实例化observer
function defineReactive(data, key, val) {
let childOb = observe(val); //为数组属性新建observer实例
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 收集依赖
dep.depend();
// 收集数组属性的依赖
childOb.dep.depend();
return val;
},
set: function (newVal) {
...
}
})
}
export fucntion observe (value){
if(!isObject(value)){
return
};
return new Observer(value)
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END