怎么给对象定义响应式属性,大家可能都会想到Object.defineProperty,如下:
let obj = {};
Object.defineProperty(obj, 'name',{
get(){
console.log('get', 'name');
return obj.name;
},
set(val){
console.log('set', val)
}
})
obj.name
obj.name = '李四'
我们在取值和赋值的时候,会有以下输出:
也就是说我们在取值和赋值的时候,利用Object.defineProperty做了拦截,即分别调用了get和set方法。
为了使用方便,可以把以上代码封装成为一个函数,如下:
function defineReactive(obj, key, val){
Object.defineProperty(obj, key,{
get(){
console.log('get', key);
return val;
},
set(newVal){
if(newVal != val){
console.log('set', newVal)
val = newVal;
}
}
})
}
函数defineReactive很有意思,他是一个闭包结构,每调用一次就会形成一个闭包,使用时如下:
let obj = {};
defineReactive(obj, 'name', '张三')
obj.name = '李四'
当我们有很多属性时,该怎么办呢?总不能调用很多次吧,如:
defineReactive(obj, 'name', '张三')
defineReactive(obj, 'age', '18')
defineReactive(obj, 'gender', '男')
...
显然是不合理的,我们可以定义一个observe函数,去遍历对象的所有属性,整体代码如下:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
return val;
},
set(newVal) {
if (newVal != val) {
console.log('set', newVal)
val = newVal;
}
}
})
}
function observe(obj) {
if (typeof obj !== 'object' || obj == null) return;
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}
let obj = {
name: '张三',
age: '18'
};
observe(obj)
obj.name
obj.name = '李四'
通过以上代码可以发现,运行结果依然没有变,通过observe函数遍历obj所有属性并都定义成了相应式了,但这有个问题:
如果对象的一个属性值还是一个对象怎么办?
let obj = {
name: '张三',
age: '18',
info:{
content: '自我介绍',
work: '工作经历'
}
}
observe(obj)
obj.inf.content
这时按我们所想应该输出:get into 、get content,实际输出是什么样的呢?如下:
这是为什么呢?原因也很简单,我们只监测了对象的第一层,若对象的值还是对象以及更深层的监测,我们并没有做,解决办法就是调用递归监测,即在defineReactive函数里再调用下oberve函数即可,如下:
function defineReactive(obj, key, val) {
// 递归监测
observe(val);
// 定义响应式
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
return val;
},
set(newVal) {
if (newVal != val) {
console.log('set', newVal)
val = newVal;
}
}
})
}
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
};
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}
let obj = {
name: '张三',
age: '18',
info: {
content: '自我介绍',
work: '工作经历'
}
}
observe(obj)
obj.info.content
obj.info.content = '自我介绍 很棒'
此时输出的结果就如我们所想了,如下:
通过以上观察我们还得到一个结论:我们在对obj这个对象的属性做响应式的时候是obj已经创建了所有属性,那我们再调用observe函数后,再给对象添加一个新属性,这时新属性也会有响应式吗?
let obj = {
name: '张三',
age: '18',
info: {
content: '自我介绍',
work: '工作经历'
}
}
observe(obj)
obj.gender = '男'
发现并没有任何输出,即没有调用get函数,也没有调用set函数。这时是不是就想到了Vue里面的this.$set函数了,而这$set函数干了什么事呢,其实也很简单就是调用了一次defineReactive函数,如下:
// 相应响应式
function defineReactive(obj, key, val) {
// 递归监测
observe(val);
// 定义响应式
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
return val;
},
set(newVal) {
if (newVal != val) {
console.log('set', newVal)
val = newVal;
}
}
})
}
// 遍历属性
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
};
Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key]));
}
// 新增属性添加响应式
function set(obj, key, val) {
defineReactive(obj, key, val)
}
let obj = {
name: '张三',
age: '18',
info: {
content: '自我介绍',
work: '工作经历'
}
}
observe(obj)
set(obj, 'gender', '男');
obj.gender
obj.gender = '女'
这时就解决了新添加的属性没有响应式的问题,输出如下:
以上就是简单模拟了Vue中的defineReactive、observe和set的三个函数,希望对你理解有所帮助,nice ~~