Vue 响应式原理

Flying
2019-04-20 / 0 评论 / 165 阅读 / 正在检测是否收录...

当您把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 方法把这些 property 全部转为 getter/setter。Vue 响应式原理的核心就是 使用defineProperty“劫持”要响应的数据。Vue 面试时这个问题几乎必问。今天我们就来谈谈 Object.defineProperty 方法。

data.png

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
}

下面我们就来演练一下。

  1. 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 属性了。

  1. configurable
let user = {};

Object.defineProperty(user, 'age', {
  value: 24,  
  writable : true
});

console.log(user); // { age: 24}
user.age = 25;
console.log(user.age); // 25

本示例中,设置 agewritable 属性值为 true 后,就可以修改 agevalue

  1. 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
}

本示例中,设置 nameageenumerable 属性值为 true 后,就可以通过迭代器遍历出 nameagevalue

通过这几个示例,我们基本了解了 Object.defineProperty 方法的基本用法。前面提到该方法的第三个参可以时访问器描述符,现在我们就来看看访问器描述符。

访问器描述符

访问器属性的描述符与数据属性的不同。对于访问器属性,没有 valuewritable,但是有 get 和 set 函数。所以访问器描述符可能有:

  • get:一个没有参数的函数,在读取属性时工作,
  • set:带有一个参数的函数,当属性被设置时调用,
  • enumerable:与数据属性的相同,
  • configurable:与数据属性的相同。

例如,例如,我们有一个具有 firstNamelastName 属性的对象 user

let user = {
  firstName: 'Xiaoming',
  lastName: 'Li'
};

现在我们想添加一个 fullName 属性,该属性值应该为 '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,最后使它关联的组件重新渲染。

1

评论 (0)

取消