响应式框架原理1(数据劫持与代理)
继续之前的记录、、
如果数据中某一项变为数组:
let data = {
stage: '状态',
course: {
title: '标题',
author: ['作者1', '作者2'],
publishTime: '出版时间'
}
}
const observe = data => {
if (!data || typeof data !== 'object') {
return
}
Object.keys(data).forEach(key => {
let currentValue = data[key]
observe(currentValue)
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get() {
console.log(`getting ${key} value now, getting value is: `, currentValue)
return currentValue
},
set(newValue) {
currentValue = newValue;
console.log(`setting ${key} value now, setting value is: `, currentValue)
}
})
})
}
observe(data)
给数组中增加一个数据:
data.course.author.push('作者3')
只监听到了 data.course 以及 data.course.author 的读取,而数组 push 行为并没有被拦截。这是因为 Array.prototype 上挂载的方法并不能触发 data.course.author 属性值的 setter,由于这并不属于做赋值操作,而是 push API 调用操作。
对于框架实现来说,显然是不满足要求的,当数组变化时应该也有所感知。
Vue 同样存在这样的问题,它的解决方法是:将数组的常用方法进行重写,进而覆盖掉原生的数组方法,重写之后的数组方法需要能够被拦截。
具体实现逻辑:
const arrExtend = Object.create(Array.prototype)
const arrMethods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
arrMethods.forEach(method => {
const oldMethod = Array.prototype[method]
const newMethod = function(...args) {
oldMethod.apply(this, args)
console.log(`${method} 方法被执行了`)
}
arrExtend[method] = newMethod
})
对于数组的7个原生方法进行重写:
- push
- pop
- shift
- unshift
- splice
- sort
- reverse
核心操作还是调用原生方法:oldMethod.apply(this, args),除此之外可以在调用 oldMethod.apply(this, args) 前后加入需要的任何逻辑。示例代码中加入了一行 console.log。使用时:
// Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Array.prototype = Object.assign(Array.prototype, arrExtend)
let array = [1,2,3]
array.push(4) // push 方法被执行了
对应到原来的代码:
const arrExtend = Object.create(Array.prototype)
const arrMethods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
arrMethods.forEach(method => {
const oldMethod = Array.prototype[method]
const newMethod = function(...args) {
oldMethod.apply(this, args)
console.log(`${method} 方法被执行了`)
}
arrExtend[method] = newMethod
})
Array.prototype = Object.assign(Array.prototype, arrExtend)
let data = {
stage: '状态',
course: {
title: '标题',
author: ['作者1', '作者2'],
publishTime: '出版时间'
}
}
const observe = data => {
if (!data || typeof data !== 'object') {
return
}
Object.keys(data).forEach(key => {
let currentValue = data[key]
observe(currentValue)
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get() {
console.log(`getting ${key} value now, getting value is:`, currentValue)
return currentValue
},
set(newValue) {
currentValue = newValue
console.log(`setting ${key} value now, setting value is`, currentValue)
}
})
})
}
observe(data)
data.course.author.push('作者3')
本质是重写原生方法,这天生不是很安全,也很不优雅,更好的实现
是使用 ES Next 的新特性——Proxy完成对数据的代理。