1、如何追踪变化
如上图,使用拦截器将array.prototype覆盖。当使用array原型中的方法时,其实使用的是拦截器中的方法。通过拦截器,就可以侦听Array的变化。
2、拦截器
拦截器:和array.prototype一样的object,里面的属性一样,只不过可改变数组自身的方法是处理过的。
array原型中可改变自身的方法有7个:push, pop, shift, unshift, splice, sort 和 reverse 。
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto) // 创建新的对象
['push','pop','shift','unshift','splice','sort','reverse'].foreach(function(method){
const original = arrayProto[method] // 缓存原始方法
Object.defineProperty(arrayMethods, method, {
value: function mutator (...args) {
return original.apply(this, args)
},
enumerable:false,
writable:true,
configurable:true
})
})
Object.create
Object.create(proto,[propertiesObject])
proto: 新创建对象的原型对象
[propertiesObject]:可选,添加到新对象的属性,不属于其原型链的属性
详情可参考:https://juejin.im/post/5acd8ced6fb9a028d444ee4e
Object.defineProperty
直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
Object.defineProperty(obj, prop, descriptor)
obj: 要在其上定义属性的对象。
prop: 要定义或修改的属性的名称。
descriptor: 将被定义或修改的属性描述符。
详情可参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
通过Observer将数据转换成响应式的,在Observer中覆盖会被转换成响应式Array的原型。
export class Observer {
constructor (value) {
this.value = value
if (Array.isArray(value)) {
value.__prop__ = arrayMethods // 新增
}else {
this.walk(value)
}
}
}
value.prop = arrayMethods :就是将拦截器赋值给value.prop,通过__prop__覆盖value的原型。如下图所示。
以上是浏览器支持__prop__的情况。当浏览器不支持__prop__该怎么办呢?
vue的做法:如不可以使用__prop__,直接将arrayMethods的方法设置到被侦测的数组上。
const hasProto = '__ prop__' in {}
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
export class Observer {
constructor(value){
this.value = value
if(Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment
augment(value,arrayMethods,arrayKeys)
}else {
this.walk(value)
}
}
...
}
function protoAugment(target,src,keys){
target.__prop__ = src
}
function copyAugment(target,src,keys){
for(let i = 0;i < keys.length;i++){
const key = keys[i]
def(target,key,src[key])
}
}
Object.getOwnPropertyNames()
返回一个由指定对象的所有自身属性的属性名组成的数组。
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
// 类数组对象
var obj = { 0: "a", 1: "b", 2: "c"};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
// 使用Array.forEach输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
console.log(val + " -> " + obj[val]);
});
// 输出
// 0 -> a
// 1 -> b
// 2 -> c
Array和object一样,都是在getter中收集依赖,并将依赖存到def中。
Array在getter中收集依赖,在拦截器中触发依赖