Open phenomLi opened 6 years ago
相信玩过vue的都知道,vue的数据和视图都是双向绑定的,也就是说当数据(data)发生更改时,vue会自动将更改diff到视图层上。那么vue是怎么自动检测到他的数据变动的呢?在这个问题上,angluar用的是脏检查(dirty check),也就是轮询检测,性能较低,而knockout用的是ko.observable函数(兼容IE6还要什么自行车),而vue则用的是Object.defineProperty。
ko.observable
Object.defineProperty
其实在很久之前就听说过Object.defineProperty这个属性,但是只知道是个es5新属性(这也就是为什么vue不兼容IE9的原因之一),具体能干什么没有深究。直到上个学期末,考完试后有两个星期的空余时间,于是打算造个mvvm轮子(也就是后来的Zeta),当时深挖vue双向绑定原理的时候也好好研究了一番这个Object.defineProperty。
正如他的名字一样,Object.defineProperty是为对象设置一些默认的属性,如writeable(可写)和getter(访问器)等,也就是说,Object.defineProperty是用作扩展原生对象的一种方法。
writeable
getter
Object.defineProperty(obj, prop, descriptor);
obj
prop
descriptor
描述符
configurable: 仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除。默认为 false enumerable: 仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false value: 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined writable: 仅当仅当该属性的writable为 true 时,该属性才能被赋值运算符改变。默认为 false get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。undefined set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为undefined。
官方描述已经很清楚了,我们可以为一个对象单独设置访问器和设置器,限制读写限权或者设置默认的值。这些特性都将十分有用,我们可以重写对象的getter和setter,拦截对象的读写情况,也就是等于在对象外面包了一层机关,所以也有人将Object.defineProperty作为对象拦截器。 vue也是通过改写data的getter和setter,监听data对象里面所有属性的变动。vue在getter里面收集所有属性依赖,然后setter里面发布更新信息,做到同步更新视图。根据这个思路我们可以尝试做一个简单的对象读写拦截器。
setter
我们先创造一个对象,用作监听:
const obj = { name: 'phenom', age: 20 };
这个对象里有两个属性,一个是name,一个是age。
name
age
接着我们用for in来遍历一下obj`,让其每一个属性都装配上拦截器:
for in来遍历一下
for(let key in obj) { let oldVal = obj[key]; Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { return oldVal; }, set: newVal => { if(newVal != oldVal){ console.log(`${key}从${oldVal}改变为${newVal}`); oldVal = newVal; } } }); }
可以很清楚看到这个拦截器是怎么工作的,首先设置enumerable和configurable都为true(不然怎么被遍历到),然后把当前的值保存到oldVal(这个步骤并不是必要,只是在很多时候都要用到上一次修改的值,这里是为了演示)。然后getter很直接地返回当前的值,在setter里面有一个判断,如果新设置的值不等于当前的值才会把新值赋应用到当前。
enumerable
configurable
true
oldVal
注:上面的let不能直接改成var,这里涉及到js的作用域和闭包问题
之后我们来走一波试试,首先我们获取obj.name:
obj.name
console.log(obj.name); console.log(obj.age);
控制台输出: 可以正常获取到,说明getter是没问题的。
之后我们来改动一下obj的属性:
obj.name = 'Marshmallow'; obj.name = 'Nougat'; obj.age = '30'; obj.age = '40';
控制台输出:
十分神奇哈哈,现在setter能够捕获到属性的每一次更改情况。
说了这么多,那么究竟Object.defineProperty能用在什么场景呢?就我平时在写轮子的时候总结出来我用到Object.defineProperty的场景有:
1. 在设计MVVM框架的时候做数据双向绑定。 2. 使对象内的所有属性变成只读(const只能使对象变成只读,不能影响对象内的属性) 3. 限制state(状态)的修改权限,阻止直接赋值修改,限制只能用setState方法修改(在设计类React框架的时候很有用)。
state
setState
相信玩过vue的都知道,vue的数据和视图都是双向绑定的,也就是说当数据(data)发生更改时,vue会自动将更改diff到视图层上。那么vue是怎么自动检测到他的数据变动的呢?在这个问题上,angluar用的是脏检查(dirty check),也就是轮询检测,性能较低,而knockout用的是
ko.observable
函数(兼容IE6还要什么自行车),而vue则用的是Object.defineProperty
。其实在很久之前就听说过
Object.defineProperty
这个属性,但是只知道是个es5新属性(这也就是为什么vue不兼容IE9的原因之一),具体能干什么没有深究。直到上个学期末,考完试后有两个星期的空余时间,于是打算造个mvvm轮子(也就是后来的Zeta),当时深挖vue双向绑定原理的时候也好好研究了一番这个Object.defineProperty
。Object.defineProperty是什么
正如他的名字一样,
Object.defineProperty
是为对象设置一些默认的属性,如writeable
(可写)和getter
(访问器)等,也就是说,Object.defineProperty
是用作扩展原生对象的一种方法。使用方法
obj
需要定义属性的对象。prop
需被定义或修改的属性名。descriptor
需被定义或修改的属性的描述符。 问题来了,描述符
是个什么东西,有什么用?我们先看看官方定义:官方描述已经很清楚了,我们可以为一个对象单独设置访问器和设置器,限制读写限权或者设置默认的值。这些特性都将十分有用,我们可以重写对象的
getter
和setter
,拦截对象的读写情况,也就是等于在对象外面包了一层机关,所以也有人将Object.defineProperty
作为对象拦截器。vue也是通过改写data的
getter
和setter
,监听data对象里面所有属性的变动。vue在getter
里面收集所有属性依赖,然后setter
里面发布更新信息,做到同步更新视图。根据这个思路我们可以尝试做一个简单的对象读写拦截器。用Object.defineProperty监听对象的读写
我们先创造一个对象,用作监听:
这个对象里有两个属性,一个是
name
,一个是age
。接着我们用
for in来遍历一下
obj`,让其每一个属性都装配上拦截器:可以很清楚看到这个拦截器是怎么工作的,首先设置
enumerable
和configurable
都为true
(不然怎么被遍历到),然后把当前的值保存到oldVal
(这个步骤并不是必要,只是在很多时候都要用到上一次修改的值,这里是为了演示)。然后getter
很直接地返回当前的值,在setter
里面有一个判断,如果新设置的值不等于当前的值才会把新值赋应用到当前。之后我们来走一波试试,首先我们获取
obj.name
:控制台输出:
可以正常获取到,说明
getter
是没问题的。之后我们来改动一下
obj
的属性:控制台输出:![](https://github.com/phenomLi/myBlog/raw/master/photos/WX20171007-162201.png)
十分神奇哈哈,现在
setter
能够捕获到属性的每一次更改情况。总结
说了这么多,那么究竟
Object.defineProperty
能用在什么场景呢?就我平时在写轮子的时候总结出来我用到Object.defineProperty
的场景有:1. 在设计MVVM框架的时候做数据双向绑定。 2. 使对象内的所有属性变成只读(const只能使对象变成只读,不能影响对象内的属性) 3. 限制
state
(状态)的修改权限,阻止直接赋值修改,限制只能用setState
方法修改(在设计类React框架的时候很有用)。