Vue 2 响应式原理的理解

781 阅读3分钟

什么是响应式

响应式(reactivity)这个单词从字面理解即可--用中文理解就是打人一拳,被打的人喊疼,用英文理解就是做出反应的意思。所以顾名思义,React框架也是响应式的。但由于本文讨论的是Vue的响应式原理,React框架在本文不讨论。

image.png

按照Vue官方文档的说法,响应式表现为:

数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。

官方文档对响应式原理的解释

摘录官方文档如下:

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。

换句话说,Vue 2的响应式是通过Object.defineProperty()方法实现的。下面单开一个部分讲解Object.defineProperty()方法以及gettersetter的概念。

Object.defineProperty()方法

Object.defineProperty()方法用于在一个对象上定义新属性,或修改现有属性,并return此对象。该方法。MDN的备注中有这样一句话:

应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。

换句话说,该方法类似于一个static方法。

该函数可接受三个参数:

  • obj:要定义属性的对象
  • prop:要定义或修改属性的名称或Symbol
  • descriptor: 属性描述符

返回值为obj

descriptor的解释

descriptor有三种:

  • 通用描述符
  • 数据描述符
  • 存取描述符

通用描述符有两个:

  • configurable: 是否可以改变描述符。默认false
  • enumerable: 当且仅当该属性的enumerable : true时,该属性才会出现在对象的枚举属性中。默认false

数据描述符有两个:

  • value:属性值
  • writable:是否可以被改变,可以理解为起到Java语言中的final修饰符的作用。默认为false,即不可被改变。

存取描述符有两个:

  • get: 属性的getter函数。
  • set: 属性的setter函数。

用Object.defineProperty()与直接赋值给对象的区别

通过赋值操作添加的属性可枚举(enumerable)的,使用for...in方法或Object.keys()方法。也可以使用delete关键字删除属性。

但默认情况下,即上述所有descriptor使用默认值的情况下,使用Object.defineProperty()添加的属性不能被删除。

getter与setter

这两个概念让我想起Java当中类的getXxx的方法与setXxx方法。示例Java代码:

public class Obj{
    private String name;
    
    public Obj(String nameString){
        this.name = nameString;
    }
    
    public String getName(){
        return name;
    }
    
    public void setName(String newName){
        this.name = newName;
    }
}

由于JS不像Java这样publicprivate泾渭分明,所以下面的JS代码可以用上述Java代码有类似思想写出,但还不一样。下面代码已保存在JS Bin中。

const obj = {
    nickname : 'Yuebing',
    get name() {
        return this.nickname;
    },
    set name(newName) {
        this.nickname = newName;
    },
};

// 以下为检验代码
console.log(obj.name); // 输出Yuebing
obj.name = 'zzy';
console.log(obj.nickname); // 输出zzy

上述代码就类似于Vue中的计算属性。计算属性的简单过程如下:

  • 在Java中,实例化了一个Obj对象obj后。由于name是一个private属性,所以不能直接通过obj.name获取name值。但可以通过obj.getName()方法间接获取name值,并通过obj.setName(newName)方法修改name值。
  • 在JS中,由于并无publicprivate的分别,所以我们一定要模拟Java中的private name,便可以通过gettersetter来模拟相关功能。我们不预先定义name属性,而是定义一个nickname属性。然后再用get name()的方法和set name(newName)的方式来达成---实质上表现为牵nickname之发而动name之身。

getter用在Object.defineProperty()身上的用法:

let obj = {
    a : 0,
};

Object.defineProperty(o, 'b', {
    get: function() {
        return this.a + 1;
    }
});