Object.defineProperty的问题

2019-05-221931次阅读javascript

关于Object.defineProperty的介绍看这里,今天又看到《数据劫持 OR 数据代理》一文,我就转载收藏记录一下关于Object.defineProperty的问题。

 

不能监听数组的变化

let arr = [1,2,3]
let obj = {}

Object.defineProperty(obj, 'arr', {
  get () {
    console.log('get arr')
    return arr
  },
  set (newVal) {
    console.log('set', newVal)
    arr = newVal
  }
})

obj.arr.push(4) // 只会打印 get arr, 不会打印 set
obj.arr = [1,2,3,4] // 这个能正常 set

数组的以下几个方法不会触发set

  • push

  • pop

  • shift

  • unshift

  • splice

  • sort

  • reverse

Vue 把这些方法定义为变异方法 (mutation method),指的是会修改原来数组的方法。与之对应则是非变异方法 (non-mutating method),例如 filter, concat, slice 等,它们都不会修改原始数组,而会返回一个新的数组。Vue官网有相关文档讲述这个问题。

Vue的做法是把这些方法重写来实现数组的劫持。一个极简的实现如下:

const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

const arrayAugmentations = [];

aryMethods.forEach((method)=> {
  // 这里是原生Array的原型方法
  let original = Array.prototype[method];
  
  // 将 push, pop 等封装好的方法定义在对象 arrayAugmentations 的属性上
  // 注意:是实例属性而非原型属性
  arrayAugmentations[method] = function () {
    console.log('我被改变啦!');
    // 调用对应的原生方法并返回结果
    return original.apply(this, arguments);
  };

});

let list = ['a', 'b', 'c'];

// 将我们要监听的数组的原型指针指向上面定义的arrayAugmentations空数组对象
// 这样就能在调用 push, pop 这些方法时走进我们刚定义的方法,多了一句 console.log
list.__proto__ = arrayAugmentations;
list.push('d');  // 我被改变啦!

// 这个 list2 是个普通的数组,所以调用 push 不会走到我们的方法里面。
let list2 = ['a', 'b', 'c'];
list2.push('d');  // 不输出内容

 

必须遍历对象的每个属性

使用 Object.defineProperty() 多数要配合 Object.keys() 和遍历,于是多了一层嵌套。如:

Object.keys(obj).forEach(key => {
  Object.defineProperty(obj, key, {
    // ...
  })
})

Object.keys(obj)

参数:obj

要返回其枚举自身属性的对象。

返回值

一个表示给定对象的所有可枚举属性的字符串数组。数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。

 

必须深层遍历嵌套的对象

所谓的嵌套对象,是指类似

let obj = {
  info: {
    name: 'eason'
  }
}

如果是这一类嵌套对象,那就必须逐层遍历,直到把每个对象的每个属性都调用 Object.defineProperty() 为止。 Vue 的源码中就能找到这样的逻辑 (叫做 walk 方法)。

上一篇: 理解defineProperty以及getter、setter  下一篇: ES6之Proxy  

Object.defineProperty的问题相关文章