對象的監聽
定義一個defineReactive對Object.defineProperty進行封裝,用於監聽對象的調用和改變
// 傳入的key要是計算屬性格式喲
function defineReactive(obj, key, val) {
const dep = new Dep() // 用於收集依賴的類,具體原理就不講解了
Object.defineProperty(obj, key) {
enumerable: true,
configurable: true,
get: function () { // 每次調用到對象屬性時都會出發這個函數
dep.depend()// window.target是一個方法,用於存放調用的實例的依賴
return val // 返回調用對象時獲取到的值
},
set: function (newVal) {
if (val === newVal) return
for (i = 0; i < dep.length; i ++) {
dep.notify() // 更顯依賴綁定的數據值,實現頁面的重新渲染
}
val = newVal
}
}
}
數組的監聽
數組的監聽和對象略有不同。除了通過賦值操作改變數組外,還可以通過數組的一些自帶方法改變數組,而通過后面那種方式該改變數組setter是監聽不到的。
解決方法:攔截數組的原型,並賦予新的原型。
// 通過整理,數組中能夠改變數組本身的有七個方法
// 數組能夠改變自身的方法:
const arrayProto = Array.prototype
// Object.create(obj) 創建一個新對象,並且使用現有對象obj作為新對象的__proto__
const arrayMethods = Object.create(arrayProto);
[
'push', // 在數組最后追加一個元素
'pop', // 刪除最后一個元素
'unshift', // 在第一個元素位添加一個元素
'shift', // 刪除第一個元素
'splice', // 添加或者刪除元素
'sort', // 排序
'reverse' // 反向排序
].forEach( method => {
// 緩存原始方法
var original = arrayProto[method]
Object.defineProperty(arrayMethods, method, {
value: function mutator (...args) { // args是調用原型方法時傳入的參數
console.log('this', this) // this指向屬性所屬對象
... // 在這里面執行監聽變化的操作
return original.apply(this, args)
}
})
})
module.exports = arrayMethods
只對需要覆蓋原型的數組實例進行原型攔截;避免直接改變Array.proptotype
造成的全局污染
const arrayMethods = require('./3-2攔截器')
// 讓攔截器只覆蓋那些響應式數組的原型,而非構造函數Array的原型
// 考慮到有些瀏覽器不支持原型的情況,要進行判斷
const hasProto = '__ptoto__' in {} // 有酒返回true,沒有就返回false
export class Observe {
// 數組實例; value的實例數組
constructor (value) {
this.value = value
if (Array.isArray(value)) {
// 添加攔截器
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
} else {
// 如果瀏覽器不支持原型,就直接將方法添加到數組上
}
} else {
this.walk(value) // 如果數據是其他類型時執行
}
}
}
// 用攔截器直接覆蓋原型
function protoAugment (value, arrayMethods, arrayKeys) {
value.__proto__ = arrayMethods
}
// 將添加了攔截器的方法追加到數組的屬性中
function copyAugment (value, arrayMethods, keys) {
for(i = 0; i < keys.length; i ++) {
const key = keys[i]
def(value, key, src[key])
}
}
收集依賴:將數組作為對象的屬性傳入defineReactive方法
function defineReactive (data, key, val) {
if (typeof val === 'object') new Observer()
let dep = new dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend() // 收集array的依賴
return value
},
set: function (newVal) {
if (val === newVal) return
dep.notify() // 監聽array的變化
val = newVal
}
})
}
缺陷:像通過直接改變數組項的值(比如:arr[2]=23
)或者通過arr.length改變數組的長度是不能被監聽到的。通過es6的元編程能力可以解決,后續再了解。