当您把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 方法把这些 property 全部转为 getter/setter。Vue 响应式原理的核心就是 使用defineProperty“劫持”要响应的数据。Vue 面试时这个问题几乎必问。今天我们就来谈谈 Object.defineProperty 方法。
API
Object.defineProperties(obj, descriptors),允许一次定义多个属性。语法是:
Object.defineProperties(obj, propertyName, descriptor);
例如:
Object.defineProperties(user, 'id', { value: '1002', writable: false }),
Object.defineProperties(user, 'name', { value: 'xiaoming', writable: true }),
Object.defineProperties(user, 'age', { value: 24, writable: true }),
这个方法接收三个参数:
- obj:需要定义属性的对象
- propertyName:属性名称
- descriptor:描述符对象
前面两个参数好理解,描述符对象平常少用,有必要介绍一下。描述符可分为数据属性描述符和访问器描述符,我们先来介绍属性描述符。
数据属性描述符
数据属性描述符对象含值和所有的属性标志。具体而言:
configurable
:默认 false。如果为 true,则此属性可以被删除,这些特性也可以被修改,否则不可以。enumerable
:默认 false。如果为 true,则会被在循环中列出,否则不会被列出。writable
:默认 false。如果为 true,则值可以被修改,否则它是只可读的。value
:默认 undefined。包含这个属性的数据值。
例如:
// 属性描述符:
{
"value": "xiaoming",
"writable": true,
"enumerable": true,
"configurable": true
}
下面我们就来演练一下。
- configurable
let user = {};
Object.defineProperty(user, 'name', {
value: 'xiaoming',
configurable : false
});
console.log(user); // { name: 'xiaoming' }
delete user.name;
console.log(user); // { name: 'xiaoming' }
本示例中,设置 configurable: false
后,delete
运算符就不能删除 name
属性了。
- configurable
let user = {};
Object.defineProperty(user, 'age', {
value: 24,
writable : true
});
console.log(user); // { age: 24}
user.age = 25;
console.log(user.age); // 25
本示例中,设置 age
的 writable
属性值为 true
后,就可以修改 age
的 value
。
- enumerable
let user = {};
Object.defineProperty(user, 'name', {
value: 'xiaoming',
enumerable: true
});
Object.defineProperty(user, 'age', {
value: 24,
enumerable : true
});
for (var k in user){
console.log(user[k]);
// xiaoming
// 24
}
本示例中,设置 name
和 age
的 enumerable
属性值为 true
后,就可以通过迭代器遍历出 name
和 age
的 value
。
通过这几个示例,我们基本了解了 Object.defineProperty
方法的基本用法。前面提到该方法的第三个参可以时访问器描述符,现在我们就来看看访问器描述符。
访问器描述符
访问器属性的描述符与数据属性的不同。对于访问器属性,没有 value
和 writable
,但是有 get 和 set 函数。所以访问器描述符可能有:
- get:一个没有参数的函数,在读取属性时工作,
- set:带有一个参数的函数,当属性被设置时调用,
- enumerable:与数据属性的相同,
- configurable:与数据属性的相同。
例如,例如,我们有一个具有 firstName
和 lastName
属性的对象 user
:
let user = {
firstName: 'Xiaoming',
lastName: 'Li'
};
现在我们想添加一个 fullNam
e 属性,该属性值应该为 'Li Xiaoming'。当然,我们不想复制粘贴已有的信息,因此我们可以使用访问器来实现:
要使用 Object.defineProperty
创建一个 fullName
访问器,我们可以使用 get 和 set 来传递描述符。
let user = {
firstName: 'Xiaoming',
lastName: 'Li'
};
Object.defineProperty(user, 'fullName', {
get() {
return `${this.lastName} ${this.firstName}`;
},
set(value) {
[this.lastName, this.firstName] = value.split(" ");
}
});
console.log(user.fullName); // Li Xiaoming
for(let key in user) console.log(key); // firstName, lastName
不知大家注意到没有,在“enumerable”演练中,我们每定义一个属性都需要调用一次 Object.defineProperties
方法。该方法可以以次定义多个属性吗?可以的,接下来我们就来介绍这一特性。
定义多个属性
Object.defineProperties
方法允许一次定义多个属性,第二个参数为属性对象,第三个参数 descriptor
为可选。
语法是:
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
例如:
Object.defineProperties(user, {
name: { value: 'Xiaoming', writable: true },
age: { value: "Smith", writable: true },
// ...
});
正如本文开篇提到的,Vue 中数据双向绑定就是依赖 Object.defineProperties
方法的。本文的最后我们将使用该方法来模拟 Vue 中 数据双向绑定。
模拟数据双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<input id="name" type="text" />
<div id="result"></div>
<script>
var name = 'Li Xiaoming';
var data = {};
var nameInput = document.getElementById('name');
var result = document.getElementById('result');
// init
nameInput.value = name;
nameInput.addEventListener('input', function (e) {
// watch
name = e.target.value;
data.value = name;
});
// define
Object.defineProperty(data, 'value', {
get() {
console.log(name);
return name;
},
set(value) {
// update
result.innerHTML = value;
},
});
</script>
</body>
</html>
参考
访问 codepen 查看完整代码及最终效果。
本实例中,我们使用 Object.defineProperty
方法把 data
对象的 value
属性转为 getter/setter访问器,name
输入字段添加了对用户输入的监听。当用户输入变化时,会引起 data.value
值改变,从而触发 setter,最后使它关联的组件重新渲染。
评论 (0)