Object.defineProperty

355 阅读2分钟

Vue正是使用了它实现了响应式数据功能

Vue中定义响应式数据的代码:

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  .....
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

Object.defineProperty

Object.defineProperty这个函数传递了三个参数

第一个参数代表要设置的对象,第二个参数代表要设置的对象的键值,第三个参数是一个配置对象,对象里面可以设置参数如下:

**value**: 对应key的值 **configurable**:是否可以删除该key或者重新配置该key **enumerable**:是否可以遍历该key **writable**:是否可以修改该key **get**: 获取该key值时调用的函数 **set**: 设置该key值时调用的函数

我们通过例子来了解一下这些属性

 let people = {}
 people['name'] = '张三'
 console.log(Object.getOwnPropertyDescriptor(people,'name'))

Object.getOwnPropertyDescriptor可以获取对象某个key的描述对象,打印结果如下:

{
    value: "张三",    writable: true, //改写
    enumerable: true, //设置
    configurable: true //遍历
}
从上可知,该key对应的属性我们可以改写、可以重新设置或者删除、可以遍历。我们试试来修改一下这些属性,

试试**configurable,**代码如下:

Object.defineProperty(people, 'name', {
      configurable: false
})

执行上述代码成功后,将无法重新设置或删除name属性,比如delete people['name'],你会发现返回为false,即无法删除了。

试试**enumerable,**代码如下:

let people = {}
people["name"] = "张三"
people["age"] = 25
Object.defineProperty(people, "age", {
     enumerable: false
})
for(let key in people){
     console.log("key:" + key + "————value:" +  people[key])
}

打印结果为

key:name——value:张三

为什么只打印了name没有打印age?

因为我们将age设置了不可遍历(enumerable:false),那我们的for循环就取不到了,但是我们用people[“age”]能取到

Observer类中有下面一行代码:

def(value, '__ob__', this);
def是个工具函数,目的是想给value添加一个key为__ob__,值为this,为什么不直接value.__ob__ = this ? 因为程序下面要遍历value对其子内容进行递归设置,如果直接用value.__ob__这种方式,在遍历时会取到,而我们是想它遍历时无法取到,所以def函数是利用Object.defineProperty给value添加属性,同时将enumerable设置为false。
**get和set?**

类似于在获取对象值和设置对象值时加了一个代理,当值发生改变时通知View视图更新。

let people = {}
Object.defineProperty(people, "name", {
      get: function(){
           console.log("getter called!")
      },
      set: function(newVal){
            console.log("setter called! newVal is:" + newVal)
      }
})
当我们访问people["name"]时便会打印getter called,当我们设置people["name"]="李四"时,打印setter called!newVal is 李四。Vue源码通过这种方式实现了访问属性时收集依赖,设置属性时View视图更新,源码里有一句dep.notify,里面便是通知视图更新的相关操作。