开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 setter 和 getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
对需要 observe 的数据对象进行递归遍历,包括子对象的属性,都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
let obj = {
a: 1
}
当 obj.a = 2 的时候,触发渲染,调用 render 函数。
const render = (key, value) => {
console.log(`${key}: ${value}`)
}
const defineReative = (obj, key, value) => {
// 递归遍历,包括子对象的属性
reactive(value)
Object.defineProperty(obj, key, {
get() {
return value
},
set(newVal) {
if (value === newVal) {
return
}
value = newVal
render(key, value)
}
})
}
const reactive = (obj) => {
if (typeof obj === "object") {
for (let key in obj) {
defineReative(obj, key, obj[key])
}
}
}
let obj = {
a: 1
}
reactive(obj)
obj.a = 2
需要通过Object.create实现一个Array.prototype的继承者arraymethods。它访问的方法和Array.prototype上的是一样的。我们不能直接在Array.prototype上对方法进行监听,因为这样会影响到正常方法的调用。
const render = (key, value) => {
console.log(`${key}: ${value}`)
}
const arrPrototype = Array.prototype
const newArrayPrototype = Object.create(arrPrototype);
['push', 'pop', 'shift', 'unshift', 'sort', 'splice', 'reverse'].forEach(methodName => {
newArrayPrototype[methodName] = function () {
arrPrototype[methodName].call(this, ...arguments)
render(methodName, ...arguments)
}
})
const reactive = (obj) => {
if (Array.isArray(obj)) {
obj.__proto__ = newArrayPrototype
}
}
const data = [1, 2, 3, 4]
reactive(data)
data.push(5)
为什么 Vue3.0 要使用 Proxy
Proxy是对整个对象的代理,而Object.defineProperty只能代理某个属性- 对象上新增属性,
Proxy可以监听到,Object.defineProperty不能 - 数组新增修改,
Proxy可以监听到,Object.defineProperty不能 - 若对象内部属性要全部递归代理,
Proxy可以只在调用的时候递归,而Object.definePropery需要一次完成所有递归,性能比Proxy差
const render = (key, val) => {
console.log(`${key}: ${val}`)
}
const reactive = (obj, setBind, getLogger) => {
return new Proxy(obj, {
get(target, key, recevier) {
getLogger(target, key)
return Reflect.get(target, key, recevier)
},
set(target, key, value) {
setBind(key, value)
return Reflect.set(target, key, value)
}
})
}
let obj = {
a: 1
}
let p = reactive(obj, (key, value) => {
render(key, value)
}, (target, key) => {
render(target, key)
})
p.a = 2