vue2数据双向绑定原理之Object.defineProperty()监听对象属性的变化

Object.defineProperty()监听对象属性的变化

Object.defineProperty()

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

语法

Object.defineProperty(obj, prop, descriptor)

// 参数:
// obj: 要在其上定义属性的对象。
// prop: 要定义或修改的属性名称。
// descriptor: 要被定义或修改的属性描述符。

// 返回:此对象。

给对象添加属性的方式有

  1. 字面量:

    var obj = {
        name1: "aa"
    }
    
  2. 对象.属性名 = 值

    obj.name2 = "bb"
    
  3. 对象[“属性名”] = 值

    obj["name3"]  = "cc"
    
  4. Object.definedProperty()

    Object.defineProperty(obj,"name4",{
        value:"dd",
        writable: false     // value属性值默认是只读的
    })
    

属性描述符

Object.defineProperty(对象, 属性名,{
    configurable:false,
    enumerable:false,
    
    writable:false,
    value:undefined,
    
    get:function(){},
    set:function(){}
})
属性描述符描述
configurable为 true 时,属性才能重新被定义(再写一次Object.defineProperty())。默认为 false。
enumerable为 true 时,该属性才能够出现在对象的枚举属性中,即可以使用for in循环访问。默认为 false。
writable为 true 时,value属性值才能被修改。默认为 false,value属性值是只读的。
value该属性对应的初始值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
get一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行。默认为 undefined
set一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined

描述符可同时具有的键值:

configurableenumerablevaluewritablegetset
数据描述符YesYesYesYesNoNo
存取描述符YesYesNoNoYesYes

注意

  1. 如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。

configurable 和enumerable

  • configurable: 能否再次修改

  • enumerable: 能否枚举

Object.defineProperty(obj,"c",{
    // 不允许再次修改这个属性
    configurable: false,
    // 这个属性能否被for in 循环遍历
    enumerable:true,
    value:100
})

value 和 writable

  • value: 设置初始值

  • writable: 是否可以修改这个属性值

const obj = {
    B:1
}

// 定义只读的对象属性A
Object.defineProperty(obj,"A",{
    value: 1,
    writable: false           // value属性值是只读的
})

console.dir(obj)    // {B:1,A:1}
console.log(obj.A)  // 1
// 给对象的属性赋值,并不会修改属性的值。
obj.A =1001
console.log(obj.A)  // 1

const定义的对象,它的属性还是可以修改的。可以通过writable设置为false来设置只读的属性,真正实现常量的效果。

对已有对象进行封装,以得到一个常量对象:

const obj = {
    a:1,
    b:2
}
function getConst(obj){
    var _obj = {}
    for(var key in obj){
        Object.defineProperty(_obj,key,{
            writable:false,
            value: obj[key]
        })
    }
    return _obj
}
var obj1 = getConst(obj)

console.log("设置之后的值是:",obj1)  // 设置之后的值是: {a: 1, b: 2}

get 和 set

  • 它们与value和writable是互斥的。
  • 一旦使用它们,则这个属性就没有保存属性值的能力
  • 用它们来做拦截器

实现常量对象

不允许修改对象的某个属性,修改了也无效。

  1. 使用get()返回值,定义只读的属性。
  2. 使用set(),在设置值时报错。
const obj = {}
Object.defineProperty(obj,"age",{
    get(){
        return 18
    },
    set(){
        throw new Error("对不起,你没有权限设置age属性!")
    }
})

console.dir(obj)     // {age:18}
console.log(obj.age)   // 18
obj.age = 80
console.log("设置之后的值是:",obj.age)

把一个已有对象设置成只读的对象:

const obj = {
    a:1,
    b:2
}
function getConst(obj){
    var _obj = {}
    for(var key in obj){
        Object.defineProperty(_obj,key,{
            get(){
                return obj[key]
            }
        })
    }
    console.log(key)
    return _obj
}
var obj1 = getConst(obj)

console.log("设置之后的值是:",obj1)   // {a:2,b:2}

实现从数据到视图的变化

监听属性的变化

var obj = {salary:0}
Object.defineProperty(obj,"salary",{
  get:function(){
    console.info("获取属性值")
  },
  set:function(newVal){
    console.info("属性值修改为:"+newVal)
  }
})

obj.salary = 2000; 
obj.salary; 

// 在控制台输出:
// 属性值修改为:2000
// 获取属性值

封装函数监听全部的属性

function observe(obj) {
  Object.keys(obj).forEach(key=>{
    var val = obj[key]
    Object.defineProperty(obj,key,{
      set:function(newVal){
        console.info( `${obj[key]}----->${newVal}`);
      val = newVal;
     },
     get:function(){
      console.info(`get....${key}`)
      return val;
     }
    })
  })
}

var data = {salary:1000,bonus:3000}
observe(data);

data.salary = 2000; // 更新属性值
data.bonus;

// 在控制台输出:
// get....salary
// 1000----->2000
// get....bonus
  • 给对象的所有属性,都加上监听,这个步骤称之为数据劫持

结论

  • Object.defineProperty()是vue2实现的核心原理。vue2中的数据双向绑定就是用它来实现的。
  • 新发布的vue3中使用ES6中的Proxy来代替Object.defineProperty()。参考 Proxy

注意

  1. vue不支持ie8及更低版本,因为Object.definedProperty()在低版本中不可用。

vue2的数据双向绑定实现原理

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

在这里插入图片描述

实现具体步骤

  1. 需要给observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到数据变化
  2. compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
  3. Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
    1. 在自身实例化时往属性订阅器(dep)里面添加自己。
    2. 自身必须有一个update()方法。
    3. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
  4. MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最后利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值