深入理解Object.defineProperty

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Object.defineProperty相关的知识,希望对你有一定的参考价值。

参考技术A 对象的属性有以下几种:

1.在使用Object.defineProperty、Object.defineProperties 或 Object.create 函数的情况下添加数据属性,writable、enumerable和configurable默认值为false。
2.使用对象直接量创建的属性,writable、enumerable和configurable特性默认为true。

Object.defineProperty的理解思路

Object.defineProperty大家都不陌生,废话不多说,从Vue源码开始,已删除部分关联不大的代码

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,//非必要参数
  shallow?: boolean//非必要参数
) {
  
  //此处的property会在下面做解释
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  
  //Object.defineProperty 的set和get属性在没有处理前,默认值是undefined
  //arguments.length === 2也就意味着没有传入val参数
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
    
      //注意,上面已经说明,getter默认值是undefined
      const value = getter ? getter.call(obj) : val
      return value
    },
    set: function reactiveSetter (newVal) {
    
      //注意,上面已经说明,setter默认值是undefined
      const value = getter ? getter.call(obj) : val
      
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        //注意,此处,下面会做解释
        val = newVal
      }
      
    }
  })
}
问题1: const property = Object.getOwnPropertyDescriptor(obj, key)是什么?

简洁的说,Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。详细了解请看这

let data = {
    form:{
        name: \'ShaneHu\',
        sex: \'male\'
    }
}
let descriptor = Object.getOwnPropertyDescriptor(data, \'form\');
console.log(descriptor)

以上代码执行结果:

{
    "value": {
        "name": "ShaneHu",
        "sex": "male"
    },
    "writable": true,
    "enumerable": true,
    "configurable": true
}

用Object.defineProperty处理data对象后:

let data = {
    form:{
        name: \'ShaneHu\',
        sex: \'male\'
    }
}

function observe(data, key) {
    //声明内部变量
    let value = data[key]
    Object.defineProperty(data, key, {
        get() {
            return value
        },
        set(newValue) {
            if (value === newValue) return;
            value = newValue
        }
    })
}

observe(data,\'form\')

let descriptor = Object.getOwnPropertyDescriptor(data, \'form\');
console.log(descriptor)

以上代码执行结果:

{
    "enumerable": true,
    "configurable": true,
    "get": get(),
    "set": set(newValue)
}

所以,const value = getter ? getter.call(obj) : val返回的其实是前面赋值过的 obj[key]。

//Object.defineProperty 的set和get属性在没有处理前,默认值是undefined
//arguments.length === 2也就意味着没有传入val参数
if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
}
那么,问题2:为什么是val=newVal,而不是obj[key]=newVal
if (setter) {
    setter.call(obj, newVal)
} else {
    val = newVal
}

起初,我写Object.defineProperty的时候,是直接在set方法里面,直接obj[key]=newVal,结果自然是报错的。
以下是我根据Vue源码改写的简版数据劫持,便于理解:

function observe(data, key, val) {
    //声明内部变量
    let value = val || data[key];
    Object.defineProperty(data, key, {
        get() {
            return value
        },
        set(newValue) {
            if (value === newValue) return;
            value = newValue
        }
    })
}

1.这个方法是一个闭包,value是一个函数内的局部变量;
2.set方法,我们可以理解为不直接修改data对象下某个属性的值,而是修改value这个局部变量的值;
3.因此,get方法,返回的也不是data对象下某个属性的值,而是value这个局部变量;

以上是关于深入理解Object.defineProperty的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出Object.defineProperty()

深入了解Object.defineProperty

深入浅出Object.defineProperty()

《深入理解JavaScript》—— ECMAScript5 新特性

深入浅出Object.defineProperty()

理解Object.defineProperty()