Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue数据绑定揭秘:Object.defineProperty #8

Open
phenomLi opened this issue Oct 6, 2017 · 0 comments
Open

Vue数据绑定揭秘:Object.defineProperty #8

phenomLi opened this issue Oct 6, 2017 · 0 comments

Comments

@phenomLi
Copy link
Owner

phenomLi commented Oct 6, 2017

相信玩过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是用作扩展原生对象的一种方法。



使用方法

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`里面发布更新信息,做到同步更新视图。根据这个思路我们可以尝试做一个简单的对象读写拦截器。

用Object.defineProperty监听对象的读写

我们先创造一个对象,用作监听:

const obj = {
        name: 'phenom',
        age: 20
    };

这个对象里有两个属性,一个是name,一个是age


接着我们用for in来遍历一下obj`,让其每一个属性都装配上拦截器:

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;
	    }
	}
    });
}

可以很清楚看到这个拦截器是怎么工作的,首先设置enumerableconfigurable都为true(不然怎么被遍历到),然后把当前的值保存到oldVal(这个步骤并不是必要,只是在很多时候都要用到上一次修改的值,这里是为了演示)。然后getter很直接地返回当前的值,在setter里面有一个判断,如果新设置的值不等于当前的值才会把新值赋应用到当前。

注:上面的let不能直接改成var,这里涉及到js的作用域和闭包问题


之后我们来走一波试试,首先我们获取`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框架的时候很有用)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant