本文内容是基于《剖析Vue.js内部运行机制》关于响应式系统部分整理而来。
虽然说Vue3已经发布有两三年了,数据响应也已经用Proxy重构了,但无论是为了学习编程思想,或者是为了面试,都有必要了解Vue2响应式原理是什么。不出意外的话,以后我还会写一篇关于Proxy数据代理的文章。
Object.defineProperty
要理解Vue2的响应式原理,就必须要了解Object.defineProperty。我先来说一下它的基本用法。
/*
obj: 目标对象
prop: 需要操作的目标对象的属性名
descriptor: 描述符
return value 传入对象
*/
Object.defineProperty(obj, prop, descriptor)
这里举一个例子,更便于理解。
const obj = {
name: 'ExMaterial'
}
Object.defineProperty(obj, 'name', {
value: 'EM', // 设置值
writable: false, // 是否可修改
configurable: false, // 是否可删除属性
enumerable: false, // 是否可枚举
})
console.log(obj.name); // EM
delete obj.name
console.log(obj.name); // EM 删除操作不生效
如果还是不懂的话,建议移步MDN,上面有详细的介绍。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
实现observer
首先定义一个 cb 函数,这个函数用来模拟视图更新,调用它即表示视图更新。
function cb (val) {
/* 渲染视图 */
// 此处省略了如何对数据更新,此处又设计其它知识,如diff算法,静态页面标记
// 为便于理解,此处简化
console.log("视图更新啦~");
}
然后,再封装一个响应式函数。
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
return val
},
set: function reactiveSetter(newVal) {
if (newVal === val) return;
cb(newVal) // 触发视图更新
}
})
}
如果一个对象有多个属性的话,上面这样写是远远不够的,此时就可以在上述基础上封装一个函数,对其传入对象的每个属性进行响应式设置。
function observer (value) {
if (!value || (typeof value !== 'object')) {
return;
}
Object.keys(value).forEach((key) => {
defineReactive(value, key, value[key]);
});
}
最后再来封装一个Vue
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data)
}
}
来测试一下
let vue = new Vue({
data: {
test: 'I am test.'
}
});
vue._data.test = "hello world" // 视图更新了
这里给出详解版以及部分优化之后的版本。
// 模拟响应式数据更新后的操作
function cb(val) {
console.log("视图更新了~");
}
// 对要响应的数据进行设置的函数
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
return val
},
set: function reactiveSetter(newVal) {
if (newVal === val) return
cb(newVal)
}
})
}
// 对一个对象多个属性一次设置,全部响应式
// 也可以通过递归,因为一个对象内可能还包括对象。
// 具体代码实现在下面的代码注释中
function observer(val) {
if (!val || (typeof val !== 'object')) {
return
}
Object.keys(val).forEach((key) => {
defineReactive(val, key, val[key])
})
// Object.keys(val).forEach((key) => {
// if (typeof(val[key] === 'object')) {
// observer(val[key])
// }
// defineReactive(val, key, val[key])
// })
}
class Vue {
constructor(options) {
this._data = options.data
observer(this._data)
}
}
let vue = new Vue({
data: {
test: 'I am test.'
}
});
vue._data.test = "hello world" // 视图更新了