每次面试,只要提到VUE内容,一定会谈到VUE的双向绑定原理,大家都知道Object.defineProperty,那大家知道这个函数具体的功能吗?接下来我们来一起剖析一下,最后顺便在一起来了解了解VUE是如何使用的。
定义
Object.defineProperty() 方法会直接作用在一个对象上,或者修改一个对象之后返回这个对象。
语法
Object.defineProperty(obj, prop, descriptor)
函数中参数名:
参数名 | 描述 |
---|---|
obj | 要操作的对象 |
prop | 要定义或修改的属性名称 |
descriptor | 将被定义或修改的属性描述符 |
descriptor 中结构如下:
属性名 | 描述 |
---|---|
value | 属性的值 |
writable | 控制此属性是否可写 |
get | 给属性提供的getter方法 |
set | 给属性提供的setter方法 |
configurable | 当属性为true时,该属性的描述符才能够被改变,同时该属性也能从对象中被删除 |
enumerable | 当属性为true时,该属性才能够在对象的枚举中出现 |
用法
给对象创建新属性我们的方法:
var obj = {};
obj.a = "a";
obj['b'] = 'b';
Object.defineProperty(obj, 'c', {
value: 'c'
});
console.log(Object.getOwnPropertyDescriptor(obj, 'c'));
可以看到对于一个对象的属性,存在3个描述属性,此属性平时我们用不到,但是通过Object.defineProperty方法可以精确的控制对象的属性。
enumerable
针对对象中的属性,我们可以精确的控制对应的属性隐藏起来,序列化或者Object.keys不会枚举出来。
var obj = {};
obj.a = "a";
obj['b'] = 'b';
Object.defineProperty(obj, 'c', {
value: 'c',
enumerable: false
});
for(var i in obj) {
console.log(i);
}
console.log(Object.keys(obj));
我们隐藏了属性c,然后通过for... in ... 进行了遍历尝试,以及使用Object.keys()来进行遍历尝试,结果发现,在遍历结果中,已经没有c属性了。
writable
如果希望创建一个不可更改的对象属性,我们可以通过writable来进行控制,如下代码:
var obj = {};
Object.defineProperty(obj, 'c', {
value: 'c',
writable: false
})
obj.c = "a";
obj["c"] = "b";
Object.defineProperty(obj, 'c', {
value: 'd'
})
console.log(obj.c);
我们创建了一个不可修改的c属性之后,通过3中不同的赋值方式,其中普通的2种方式修改之后不会出现错误,但是具体值并未改变。
但是使用Object.defineProperty来进行属性赋值时,会直接报错,错误如下如,直接告知无法重新定义,并且报错:
configurable
因为我们可以通过Object.defineProperty对描述属性进行修改,所以我们隐藏属性也是无意义。
var obj = {};
obj.a = "a";
obj['b'] = 'b';
Object.defineProperty(obj, 'c', {
value: 'c',
configurable: false
});
我们创建一个c属性,并且把配置功能关闭,来我们在尝试一下配置功能
Object.defineProperty(obj, "c", {value : 'd'});
Object.defineProperty(obj, "c", {enumerable : true});
我们在尝试一下普通模式呢?
obj.c = 'new';
console.log(obj);
delete obj.c;
console.log(obj);
我们可以看到,调用传统方式来进行对象属性的修改以及删除时,不会起到任何作用,但是浏览器也不会报错。
但是使用精细化控制的Object.defineProperty进行对象操作时,浏览器会直接报错。
set、get
为了更精细化的管理,Object.defineProperty 提供了对应对象属性的setter和getter函数。
我们获取对应的对象属性时,可以通过getter来进行数据的更改:
var obj = {};
obj.a = "a";
Object.defineProperty(obj, 'a', {
get: () => {
return 'a 已经被我修改了';
}
})
console.log(obj.a);
var obj = {};
obj.a = "a";
var temp = null;
Object.defineProperty(obj, 'a', {
set: (newValue) => {
console.log('原有属性值:', newValue);
temp = newValue + '1';
console.log('经过set方法处理的属性值:', temp);
}
})
obj.a = 'b';
有点意思了,没错当属性值变更时,对象中的set方法会触发,这个时候就是整个系统重新渲染的时候,这也就是MVVM中双向绑定的精华所在。
当然我们还是需要注意一点事情的,Object.defineProperty 的descriptor中存在两种描述符,数据描述符和存储描述符。在函数中,描述符必须是两种性质之一,不能同时是两者。
描述符可同时具有的健值
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | Yes | Yes | Yes | Yes | No | No |
存取描述符 | Yes | Yes | No | No | Yes | Yes |
可能这样我们还是有点疑惑,那我们来点干货:
// 错误情况
Object.defineProperty(obj, 'a', {
value: "value值",
set: (newValue) => {
console.log('原有属性值:', newValue);
}
})
Object.defineProperty(obj, 'a', {
writable: false,
set: (newValue) => {
console.log('原有属性值:', newValue);
}
})
说直白点就是:value或writable 不能与set和get共存
默认值
configurable | enumerable | writable | |
---|---|---|---|
Object.defineProperty默认值 | false | false | false |
普通方式默认值 | true | true | true |
如果使用Object.defineProperty来创建对象属性的话,默认是无法修改,无法枚举。
但是我们采用传统的方式创建的属性默认值则全部为true。
var obj = {};
Object.defineProperty(obj, 'a', {
value: 'a'
})
console.log('a的descriptor:', Object.getOwnPropertyDescriptor(obj, 'a'))
obj.b = 'b';
console.log('b的descriptor:', Object.getOwnPropertyDescriptor(obj, 'b'))
VUE中的使用
VUE初始化时会遍历data中的数据,并且执行以下函数,把VUE中所有需要双向绑定的对象进行劫持代理。
/**
* Define a reactive property on an Object.
*/
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
VUE双向绑定采用了订阅者模式,当获取数据时,会触发get函数,并且此时会添加订阅者,如果数据变更时会触发set函数,对所有订阅者进行notify来通知,之后进行页面的渲染。
这是VUE双向绑定功能中最主要的进行数据劫持的函数。
Object.defineProperty就为大家讲这么多,大家如果想自己写写框架的话,可以尝试多用用Object.defineProperty,可以精准的控制内部对象,避免外接强行改变框架内容,能让框架更健壮。
希望这篇文章能帮助到正在努力中的你。