如何理解JavaScript中Object.defineProperty【一】

前言

当我们了解一个方法时,建议从以下几个维度着手
1、方法的定义
2、了解方法的使用场景
3、在场景中解决什么问题
带着这样的好奇心,去学习、研究,我们可能更好的理解、掌握、运用它
复制代码

定义

此方法,可以直接在对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

语法

Object.defineProperty(obj, prop, descriptor)

参数

obj

要在其上定义属性的对象
复制代码

prop

需要定义或修改的属性名称
复制代码

descriptor

需要定义或修改的属性描述符
复制代码

返回

被传递给函数的对象
复制代码

属性描述符

  • 数据描述符
是一个具有值的属性,该值可能是可写的,也可能不是可写的。
复制代码
  • 存取描述符
是由getter-setter函数对描述的属性。
复制代码

二者不可同时存在

参数列表
参数名默认值描述
共存参数
configurablefalse为true时,对应的属性可以被修改个删除
enumerablefalse为true时,对应的属性才能够出现在对象的枚举属性中
数据描述符
valueundefined该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
writablefalse为true时,对应的属性的value才能被赋值运算符改变。
存取描述符
getundefined给对应属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
setundefined给对应属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。
描述符可同时具有的键值
  configurable enumerable value writable get set 数据描述符 Yes Yes Yes Yes No No 存取描述符 Yes Yes No No Yes Yes
数据描述符
  • 明确指定所有的选项【避免来自继承的属性描述被覆盖】
显式
// 冻结 Object.prototype,明确指定所有的选项
var person={};
person.name = 'taro' // 下面执行后被覆盖
Object.defineProperty(person,"name",{
  configurable: true,
  enumerable: true,
  writable:true,
  value:"zhangjinlong"
});
console.log(person.name);//zhangjinlong
person.name="dragon";
console.log(person.name);//dragon 
复制代码
  • 阻断继承【避免来自继承的属性描述被覆盖】
注:
如果对象中不存在指定的属性,Object.defineProperty()就创建这个属性。
当描述符中省略某些字段时,这些字段将使用它们的默认值。拥有布尔值的字
段的默认值都是false。value,get和set字段的默认值为undefined。一个没
有get/set/value/writable定义的属性被称为“通用的”,并被“键入”为一个数
据描述符。
复制代码
var person={};
var discriptor = Object.create(null) // 没有继承的属性
// 默认没有 enumerable,没有 configurable,没有 writable
discriptor.value = 'zhangjinlong'
Object.defineProperty(person,"name",discriptor);
console.log(person.name);// zhangjinlong
person.name="dragon";
console.log(person.name);//zhangjinlong 
复制代码
  • 属性描述为空对象
var person={};
// 默认没有 enumerable,没有 configurable,没有 writable
discriptor.value = 'zhangjinlong'
Object.defineProperty(person,"name",{});
console.log(person.name);// undefined
person.name="dragon";
console.log(person.name);//undefined
复制代码
存取描述符
var obj = {}; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(obj, "key", {
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true
});

// 对象obj拥有了属性key,值为37
console.log(obj.key)

// 在对象中添加一个属性与存取描述符的示例
var bValue;
Object.defineProperty(obj, "name", {
  get : function(){
    return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});

obj.name = 'zhangjinlong';
// 对象obj拥有了属性name,值为zhangjinlong
obj.name = 'dragon';
// 对象obj调用属性的set方法,值为dragon 【数据描述符】
obj.key = 38;
// 对象obj调用属性的value,值改为38 【存取描述符】
复制代码
数据描述符和存取描述符同时用的时候(error)
var o = {}
// 数据描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
  value: 0x9f91102, 
  get: function() { 
    return 0xdeadbeef; 
  } 
});
// err Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
    at Function.defineProperty (<anonymous>)
    at <anonymous>:3:8
复制代码

使用场景

  • 在vue框架下的使用

先看一段代码

/**
 * Define a property.
 */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}
复制代码

这算是vue框架最核心的代码了,对比上面的概念,不难理解discriptor是一个数据属性符

再看另一段,vue响应式代码,有删减

// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
}
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
    }
})
复制代码

上面这段代码,算是vue架构最核心的东西了,不难看出,这里是用到了descriptor的存取属性符

看到这里,可能会有疑问,vue用Object.defineProperty到底解决了什么问题,是什么给尤雨溪【vue创始人】启发,开发出MVVM模式的框架

解决什么问题

  • 响应式编程,实现双向绑定(MVVM)

这是官网的一个图解,对应vue的源码解析部分,下一篇 基于Object.defineProperty之后,vue源码解析【二】 我会具体再阐述,这里只探究defineProperty部分以及大概使用流程

看个例子:

var vm = new Vue({
      // view
      template: `<div><span>{{name}}</span></div>`,
      // state
      data: {
        name: 'zhangjinlong'
      },
      // actions
      mounted: function () {
        this.setName()
      },
      // actions
      methods: {
        setName () {
            this.name = 'dragon'
        }
      }
    })
复制代码

看下上面代码运行的逻辑,一一拆解:

第一步:初始化state,属性name被赋值为zhangjinlong

// state
data: {name: 'zhangjinlong'}
复制代码

// 初始化属性name,并赋予get和set方法 如图:

第二步:声明式变量name映射到模板,执行 存取描述符的get方法获取值

// view
template: `<div><span>{{name}}</span></div>`
复制代码

用VNode实例化node节点,把属性值以形参形式传入VNode方法,text指向属性值(这里是zhangjinlong)

根据node节点信息创建html对象和元素【这里创建的是虚拟dom】

返回创建的虚拟dom(html对象和元素)(vm._c指向创建的对象),最终呈现在屏幕上
第三步:action操作,改变声明式变量name,执行 存取描述符的set方法进行赋值

// actions
methods: {setName () { this.name = 'dragon' }}}
复制代码

执行setName()方法,this.name指向字符串dragon,此时执行存取描述符的set方法,传入的新值 "dragon"与原值"zhangjinlong"进行比对

1、如果新值与原值相等直接返回,不做赋值
2、如果新值与原值不相等,进行赋值操作
3、借助代理proxy完成,代理赋值后续处理
复制代码

进行全二叉树遍历,发现有不同,就覆盖原node对象.更新虚拟dom,最终渲染到界面上。

总结:

1、用defineProperty解决了,双向数据流传输的问题
2、vue框架根据这一特点,加上依赖收集(dep)、依赖触发(dep),观察者(Observer),侦测(watcher),Proxy(代理)实现了双向绑定,响应式编程,解决了传统的单项数据流以及复杂dom操作的痛点
3、上面亲测,可能不排除有不到位的或不妥的地方,希望亲们能批评指正
复制代码

上面只是简单的走了一下流程,下一篇我会着重分析vue源码,更透彻的理解这个框架,敬请期待。。。

基于Object.defineProperty之后,vue源码解析【二】

转载于:https://juejin.im/post/5c8a3ae2e51d450d85653693

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值