vue2响应式核心之Object.defineProperty()

457 阅读5分钟

如何在对象上添加一个属性?

  • 首先我们能很简单的想到

    const obj = {}
    obj.a = 1  //在obj对象上添加了一个属性名为a,值为1的属性
    console.log(obj.a)  //  1
    obj.hasOwnProperty('a') // true
    obj['b'] = 2  //以[]的方式添加属性
    console.log(obj['b']) //  2
    

    我们可以对该属性进行正常的读取与修改

  • 还有一种比较高级的添加属性方式就是Object.defineProperty()

    例如我们可以通过以下方式给对象添加一个属性:

    const obj = {a:1}
    Object.defineProperty(obj,'b',{
        value:2
    })
    console.log(obj)  //  {a: 1, b: 2}
    

    我们发现已经成功的向obj这个对象上添加了一个属性名为b,值为2的属性,

    但在我们使用这个对象时我们会发现以下情况:

    obj.b = 3  // 当我们尝试将b的值修改为3时
    console.log(obj)  // {a: 1, b: 2} 输出可以看到b的值便没有改变
    // 以及
    Object.keys(obj)  // 当我们尝试读取obj的所有key时,输出为['a']
    for(let item in obj){
        console.log(item)
    }
    // 被输出的同样只有a一个属性
    

    可以发现的是,由Object.defineProperty()定义的属性b变得不可被修改及不可被遍历了,这是为什么呢?接下来我们从javascript的对象属性修饰符开始解答。

属性修饰符

  • 什么是属性修饰符?JavaScript中,属性修饰符是用来控制对象属性行为的标志,这些修饰符定义了该属性如何响应各种操作。其中主要包括了数据描述符与访问器描述符。

数据描述符

数据描述符直接是关联到的是属性的值,主要有以下特性:

  1. value:属性的值,可以是javascript中的任意有效值(各字面量、变量等)
  2. writable:值应为一个布尔值,表示属性的值是否可被修改。默认为false,此时任何试图修改该属性的操作都会被忽略。
  3. enumerable:值应为一个布尔值,表示属性是否可以在for...in循环、Object.keys()Object.getOwnPropertyNames()等操作中被枚举出来。默认为false,此时该属性在迭代对象属性时会被隐藏。
  4. configurable:值应为一个布尔值,表示属性描述符是否可被修改,以及该属性能否可以从对象中被删除。默认为false,此时writableenumerableconfigurable本身以及value都不能被修改,并且该属性不能被删除。
const obj = {}
Object.defineProperty(obj,'a',{
    value:1
})
// 此时我们定义了一个名为a值为1的属性,它不能被修改、遍历
Object.defineProperty(obj,'a',{
    value:1,
    writable:true
})
// 此时属性a可以被修改
Object.defineProperty(obj,'a',{
    value:1,
    writable:true,
    enumerable:true
})
// 此时属性a可以被遍历
Object.defineProperty(obj,'a',{
    value:1,
    writable:true,
    enumerable:true,
    configurable:false
})
delete obj.a  // false
// 此时属性a的修饰符无法更改且属性无法被删除

访问器描述符

访问器描述符使用getset函数来定义属性的读取和写入行为,而不是直接关联一个值,主要具有以下特性:

  • get:为一个函数,无参数,当属性被访问时调用。返回值即为属性值。
  • set:为一个函数,接受一个参数为新的属性值,当属性被修改时调用,没有返回值
const obj = {}
let n = 4
obj.a = n // 当我们想要把一个属性的值关联为一个变量的值时
console.log(obj.a)  // 输出为4
n = 5
console.log(obj.a)  //输出仍为4,我们发现a并没有随n的改变而改变
// 但如果我们使用get与set
Object.defineProperty(obj,'a',{
    get(){
        return n
    },
    set(value){
        n = value
    }
})
console.log(obj.a) // 输出5,
n = 6  // 当我们再去改变n时
console.log(obj.b) // 输出6,我们可以发现我们以及将n与obj的属性a关联了起来

为什么要Object.defineProperty()?

  • 从上述内容我们其实已经可以看出Object.defineProperty() 使我们精确地添加或修改对象上的属性,其实Object.defineProperty()就是允许我们自定义对象属性的属性描述符。

用法

Object.defineProperty(obj,prop,descriptor)该方法有三个参数,分别是:

  1. obj:要定义属性的对象。
  2. prop:字符串,指定要定义或修改的属性键。
  3. descriptor:要定义或修改的属性描述符。

作用

  • 首先我们知道可以通过该方法来设计和控制对象的行为,精确控制属性的特性。
  • 我们可以通过该方法创建不可更改(只读)或不可删除的属性,有助于增强代码的安全性和减少意外修改的风险。
  • 可以定义getter和setter方法,允许你在读取或设置属性值时执行额外的逻辑,这对于数据验证、计算属性或触发副作用(如事件通知)特别有用。

vue2中的Object.defineProperty()

  • 在vue2中,Object.defineProperty()是实现数据绑定和响应式系统的核心技术之一。vue通过遍历数据对象的所有属性,并使用Object.defineProperty()为每个属性添加getter和setter,从而实现数据变化的追踪和视图自动更新。

    主要过程大致如下:

    1. 数据劫持:Vue在初始化时会遍历用户定义的数据对象,对每个属性执行Object.defineProperty()。这个过程被称为“数据劫持”,因为它改变了属性的默认行为。
    2. Getter:vue会为每一个属性定义一个getter,当访问这个属性使会被自动调用,同时其还负责记录依赖。当一个属性被读取时,vue会知道哪些计算属性或视图正在依赖于这个值,从而构建一个依赖关系图。
    3. Setter:vue会为每一个属性定义一个setter,并在其中加入更新机制。当属性值发生变化时,setter会通知vue,vue随后会检查依赖于这个值的所有部分,并执行必要的DOM更新。
    4. 数据更新:应为在vue实例上的数据都由Object.defineProperty()进行了代理,所以当我们读取或修改属性时都会被vue捕获从而实现数据与视图的同步更新。

    这并是Object.defineProperty()在完成vue2响应式中实现的强大功能。

最后谢谢你的阅读,如果觉得写的好的话就给我点个赞吧🥰🥰🥰,哪里写的有问题的地方还望指正。