Vue 响应式原理深入浅出(一)

1,379 阅读4分钟

很久之前就想写一点自己对 Vue 的理解,虽然谈不上深刻,但是毕竟对所用的东西有一点好奇,如果你跟我一样,对 Vue 响应式原理有那么一点好奇,那么我们一起来研究一下吧。

在谈 Vue 响应式原理之前,我们需要先了解一下 ES5 的 Object 的一个属性 defineProperty。下面我们来看一段官方的介绍

The Object.defineProperty() method defines a new property directly on an object, or modifies an existing property on an object, and returns the object.

直接在一个对象上定义一个新的属性,或修改一个已经存在的属性。这个方法会返回该对象。

语法:

Object.defineProperty(obj, prop, descriptor)

参数说明:

@params obj //  目标对象 type: Obejct
@params prop // 需要定义的属性 type: String
@params descriptor // 定义的属性所拥有的特性 type: Obejct

通过简单的介绍可以看看到,也就是定义了个对象的某个属性,但是其中的奥秘主要在第三个参数 descriptor。 我们看一下第三个参数可以设置的属性:

  • value
  • writable
  • get ,set
  • configurable
  • enumerable

分别介绍一下每个属性:

1. value

属性的值, 默认为undefined。例如:

var someOne = {}
Object.defineProperty(someOne, 'name', {
    value : 'cover'
})
someOne.name // cover

从表面上看, 貌似和下面的语法是等价的:

someOne.name = 'cover'

那我们继续往下看接下来的属性。

2. writable

该属性是否可写, 如果设置成 false,则任何对该属性改写的操作都无效(但不会报错),默认为 false。

var someOne = { };
Object.defineProperty(someOne, "name", {
    value:"coverguo" , // 由于设定了writable属性为false 导致这个量不可以修改
    writable: false 
});  
console.log(someOne.name); // 输出 coverguo
someOne.name = "monkeyWang";
console.log(someOne.name); // 输出coverguo

3. configurable

如果为 false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化,默认为 false。

var someOne = { };
Object.defineProperty(someOne, "name", {
    configurable: false, // 由于设定了configurable属性为false导致所有属性不可修改
    value: "hello"
});  
// 下面的操作想让 configurable 变为 true 但是不会被允许
Object.defineProperty(someOne, "name", {
    configurable: true,
    value: "hello"
});  // Cannot redefine property: name
var someOne = { };
Object.defineProperty(someOne, "name", {
   configurable: true, // 由于设定了configurable属性为false导致所有属性不可修改
    value: "hello"
});  
// 下面的操作想让 configurable 变为 false 是可以的
Object.defineProperty(someOne, "name", {
   configurable: false,
    value: "hello"
});  

4. enumerable

是否能在for...in循环中遍历出来或在Object.keys中列举出来。默认为 false。

var someOne = {}
Object.defineProperty(someOne,"name",{
  value:3445,
  enumerable:true
})
console.log(Object.keys(someOne));// 打印["name"]

改为false

var someOne = {}
Object.defineProperty(someOne,"name",{
  value:3445,
  enumerable:false //注意咯这里改了
})
console.log(Object.keys(someOne));// 打印[]

for...in 类似,不赘述了。

接下来看看更加关键的几个属性:set 和 get

5. set 和 get

在 descriptor 中不能同时设置访问器(get 和 set)和 wriable 或 value,否则会错,就是说想用 get 和 set,就不能用 writable 或 value 中的任何一个。

set 和 get,他俩干啥用的的。

var someOne= {}
Object.defineProperty(someOne,"name",{
    set: function (newValue) {
      console.log('你设置了name,新的值是' + newValue);
    },
    get: function (value) {
      console.log('你访问了name');
    }
})
someOne.name = 'monkeyWang'// 你设置了name,新值是monkeyWang
console.log(someOne.name)    // 你访问了name

简单来说,这个 “b” 赋值或者取值的时候会分别触发 set 和 get 对应的函数。

演习:

了解了上面这么多,那我们便可以开始 Vue 的基本响应式之旅了:我们从最简单的开始。其中,动态数据绑定就是 Vue 最为基础,最为有用的一个功能。这个系列将分成5部分,一步一步来理解和实现这一功能。ok,我们从最简单的开始。给定任意一个对象,如何监听其属性的读取与变化?也就是说,如何知道程序访问了对象的哪个属性,又改变了哪个属性?

let app1 = new Observer({
  name: 'youngwind',
  age: 25
});

let app2 = new Observer({
  university: 'bupt',
  major: 'computer'
});

// 要实现的结果如下:
app1.data.name // 你访问了 name
app.data.age = 100;  // 你设置了 age,新的值为100
app2.data.university // 你访问了 university
app2.data.major = 'science'  // 你设置了 major,新的值为 science 

感兴趣的小伙伴可以动手去实现这样一个简单的数据绑定。

我的实现可以在这里看到:github.com/monkeyWangs/

下一篇我们继续介绍:如果传入参数对象是一个“比较深”的对象(也就是其属性值也可能是对象),那该怎么办呢?考虑传递回调函数。在实际应用中,当特定数据发生改变的时候,我们是希望做一些特定的事情的,而不是每一次都只能打印出一些信息。所以,我们如何支持传入回调函数的功能?