Vue2.X和MobX中的Object.defineProperty,并与Proxy的对比

如题,Vue是一个js框架,MobX则是React进行状态管理的一个库,两者看起来并没有什么关联,但是由于都使用了Object.defineProperty,使得两者在数据绑定操作上不禁有些相似

由于Vue3.X已经用Proxy重写了他的数据绑定机制,所以顺带了解一下Proxy

本文介绍了什么

  • Object.defineProperty与Proxy的使用
  • Object.defineProperty与Proxy的缺点
  • Object.defineProperty在Vue和MobX中造成的问题和解决办法

Object.defineProperty与Proxy的使用

Object.defineProperty

Object.defineProperty - MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* obj : 要定义属性的对象
* prop : 要定义或修改的属性的名称或 [`Symbol`]
* descriptor : 要定义或修改的属性描述符
* value: 该属性对应的值
* writable
* enumerable
* configurable
* get
* set
*/
var o = {};
Object.defineProperty(o, "a", {
value : 37,
writable : true,
enumerable : true,
configurable : true,
get: function () {},
set: function () {}
});

在 descriptor 中不能 同时设置访问器 (get 和 set) 和 wriable 或 value,否则会错,就是说想用(get 和 set),就不能用(wriable 或 value中的任何一个)

Proxy

Proxy - MDN
es6 Proxy - 阮一峰

1
2
3
4
5
6
7
8
9
10
11
const proxy = new Proxy({}, {
get: function(target, key, receiver) {
return receiver;
}
});

const d = Object.create(proxy);
d.a === d // true
//d对象本身没有a属性,所以读取d.a的时候,会去d的原型proxy对象找。
//这时,receiver就指向d,代表原始的读操作所在的那个对象
});

get和set方法中的receiver参数参考Proxy get中的receiver问题

小结

  • Object.defineProperty是直接对原始对象进行操作并返回,Proxy是进行一个类似多例的操作,不会影响原始对象
  • Object.defineProperty的属性较少,Proxy对数据劫持对handler方法较多,但两者都有最基础的get和set

Object.defineProperty与Proxy的缺点

Object.defineProperty的缺点

因为defineProperty的属性限制,导致了三个问题
1、无法监听到数组的长度变化
2、由于只能劫持对象的属性,对于复杂对象需要对每个属性进行深度遍历
3、由于只能劫持对象的属性,对象属性有新增时,需要将对象的所有属性都进行遍历进行监听
为什么defineProperty不能检测到数组长度的“变化”

数组的length属性被初始化为如下
configurable为false也就是说length属性不能修改,不能删除,所以想我们想要通过get/set方法来监听length属性是不可行的

1
2
3
4
5
{
writable: true,
enumerable: false,
configurable: false,
}

验证对象新增的属性在definproperty中能否被监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let person = Object.defineProperty({age: 20}, 'name', {
get: function() {
console.log('get!!!');
return name;
},
set: function(newName) {
console.log('set!!!');
name = newName;
},
enumerable: true,
configurable: true
});
person.name; // 'get!!!' 'piers'
person.name = 'zhangpeng' // 'set!!!' 'zhangpeng'
person.age; // 'piers' 此时不会被get监听
person.age = '30'; // '30' 此时不会被set监听

Proxy的缺点

  • 由于是es6的特性,所以存在浏览器兼容性问题

小结

  • Object.defineProperty有三个缺点
    1、无法监听到数组的长度变化
    2、由于只能劫持对象的属性,对于复杂对象需要对每个属性进行深度遍历
    3、由于只能劫持对象的属性,对象属性有新增时,需要将对象的所有属性都进行遍历进行监听
  • Proxy有一个缺点
    1、存在浏览器兼容性问题

Object.defineProperty在Vue和MobX中造成的问题

由于Object.defineProperty的三个缺点,在Vue中有如下问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在Vue中这种数组操作和对象操作,不会被Vue监听到
let vm1 = new Vue({
data: {
list: [1, 2, 3, 4]
}
})
let vm2 = new Vue({
data: {
a: 1
}
})
vm1.list[index] = newValue // 非响应式的
vm1.list.length = newLength // 非响应式的
vm2.a = 2 // `vm.a` 是响应式的
vm2.b = 2 // `vm.b` 是非响应式的

对于Object.defineProperty的三个缺点,我们来看Vue和MobX是如何进行处理的

无法监听数组长度属性变化的问题

  • Vue重写了push、pop、shift、unshift、splice、sort、reverse这七个数组方法,是的这些方法可以响应式,参考Vue文档说明
  • MobX则是会在定义的时候,默认把类数组长度预留999个单位
    这样其实不会对性能有损耗,因为js中对数据的遍历除了for循环还有forEach、map、filter、some等,除了for循环外(for,for…of),其他的遍历都是对键值的遍历
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let arr = [1];
    arr[1000] = 1;
    function a () {
    console.time();
    for(let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
    }
    }
    function b () {
    console.time();
    arr.forEach((item) => {
    console.log(item);
    })
    }
    a(); //default: 567.1669921875ms
    b(); //default: 0.81982421875ms

需要对对象的深层属性或者新增属性进行遍历,达到监听的目的

总结

  • Object.defineProperty与Proxy的使用上的区别
    1、Object.defineProperty是直接对原始对象进行操作并返回,Proxy是进行一个类似多例的操作,不会影响原始对象
    2、Object.defineProperty的属性较少,Proxy对数据劫持对handler方法较多,但两者都有最基础的get和set,可以将Proxy看成是Object.defineProperty的加强版
  • Object.defineProperty与Proxy的缺点
    1、Object.defineProperty无法监听到数组的长度变化
    2、Object.defineProperty由于只能劫持对象的属性,对于复杂对象需要对每个属性进行深度遍历
    3、Object.defineProperty由于只能劫持对象的属性,对象属性有新增时,需要将对象的所有属性都进行遍历进行监听
    4、Proxy存在浏览器兼容性问题
  • Object.defineProperty在Vue和MobX中造成的问题和解决办法
    1、Vue通过重写数据的七个方法,MobX通过提前将类数组声明为长度999,避免无法监听数组长度的问题
    2、Vue和MobX都是用遍历的方式来监听深层对象属性和新增的属性