对象的监听
定义一个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的元编程能力可以解决,后续再了解。