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中收集依賴,在攔截器中觸發依賴