Vue监听数据对象变化源码

监听数据对象变化,最容易想到的是建立一个需要监视对象的表,定时扫描其值,有变化,则执行相应操作,不过这种实现方式,性能是个问题,如果需要监视的数据量大的话,每扫描一次全部的对象,需要的时间很长。当然,有些框架是采用的这种方式,不过他们用非常巧妙的算法提升性能,这不在我们的讨论范围之类。

Vue 中数据对象的监视,是通过设置 ES5 的新特性(ES7 都快出来了,ES5 的东西倒也真称不得新)Object.defineProperty() 中的 set、get 来实现的。

目标

与官方文档第一个例子相似,不过也有简化,因为这篇只是介绍下数据对象的监听,不涉及文本解析,所以文本解析相关的直接舍弃了:

<div id="app"></div>

var app = new Vue({

el: 'app',

data: {

message: 'Hello Vue!'

}

});

浏览器显示:

Hello Vue!

在控制台输入诸如:

app.message = 'Changed!'

之类的命令,浏览器显示内容会跟着修改。

Object.defineProperty

引用 MDN 上的定义:

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

与此相生相伴的还有一个 Object.getOwnPropertyDescriptor():

Object.getOwnPropertyDescriptor() 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

下面的例子用一种比较简单、直观的方式来设置 setter、getter:

var dep = [];

function defineReactive(obj, key, val) {

// 有自定义的 property,则用自定义的 property

var property = Object.getOwnPropertyDescriptor(obj, key);

if(property && property.configurable === false) {

return;

}

var getter = property && property.get;

var setter = property && property.set;

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function() {

var value = getter ? getter.call(obj) : val;

dep.push(value);

return value;

},

set: function(newVal) {

var value = getter ? getter.call(obj) : val;

// set 值与原值相同,则不更新

if(newVal === value) {

return;

}

if(setter) {

setter.call(obj, newVal);

} else {

val = newVal;

}

console.log(dep);

}

});

}

var a = {};

defineReactive(a, 'a', 12);

// 调用 getter,12 被压入 dep,此时 dep 值为 [12]

a.a;

// 调用 setter,输出 dep ([12])

a.a = 24;

// 调用 getter,24 被压入 dep,此时 dep 值为 [12, 24]

a.a;

Observer

简单说过 Object.defineProperty 之后,就要开始扯 Observer 了。observer,中文解释为“观察者”,观察什么东西呢?观察对象属性值的变化。故此,所谓 observer,就是给对象的所有属性加上 getter、setter,如果对象的属性还有属性,比如说 {a: {a: {a: 'a'}}},则通过递归给其属性的属性也加上 getter、setter:

function Observer(value) {

this.value = value;

this.walk(value);

}

Observer.prototype.walk = function(obj) {

var keys = Object.keys(obj);

for(var i = 0; i < keys.length; i++) {

// 给所有属性添加 getter、setter

defineReactive(obj, keys[i], obj[keys[i]]);

}

};

var dep = [];

function defineReactive(obj, key, val) {

// 有自定义的 property,则用自定义的 property

var property = Object.getOwnPropertyDescriptor(obj, key);

if(property && property.configurable === false) {

return;

}

var getter = property && property.get;

var setter = property && property.set;

// 递归的方式实现给属性的属性添加 getter、setter

var childOb = observe(val);

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function() {

var value = getter ? getter.call(obj) : val;

dep.push(value);

return value;

},

set: function(newVal) {

var value = getter ? getter.call(obj) : val;

// set 值与原值相同,则不更新

if(newVal === value) {

return;

}

if(setter) {

setter.call(obj, newVal);

} else {

val = newVal;

}

// 给新赋值的属性值的属性添加 getter、setter

childOb = observe(newVal);

console.log(dep);

}

});

}

function observe(value) {

if(!value || typeof value !== 'object') {

return;

}

return new Observer(value);

}

Watcher

Observer 通过设置数据对象的 getter、setter 来达到监听数据变化的目的。数据被获取,被设置、被修改,都能监听到,且能做出相应的动作。

现在还有一个问题就是,谁让你监听的?

这个发出指令的就是 Watcher,只有 Watcher 获取数据才触发相应的操作;同样,修改数据时,也只执行 Watcher 相关操作。

那如何讲 Observer、Watcher 两者关联起来呢?全局变量!这个全局变量,只有 Watcher 才做修改,Observer 只是读取判断,根据这个全局变量的值不同而判断是否 Watcher 对数据进行读取,这个全局变量可以附加在 dep 上:

dep.target = null;

根据以上所述,简单整理下,代码如下:

function Watcher(data, exp, cb) {

this.data = data;

this.exp = exp;

this.cb = cb;

this.value = this.get();

}

Watcher.prototype.get = function() {

// 给 dep.target 置值,告诉 Observer 这是 Watcher 调用的 getter

dep.target = this;

// 调用 getter,触发相应响应

var value = this.data[this.exp];

// dep.target 还原

dep.target = null;

return value;

};

Watcher.prototype.update = function() {

this.cb();

};

function Observer(value) {

this.value = value;

this.walk(value);

}

Observer.prototype.walk = function(obj) {

var keys = Object.keys(obj);

for(var i = 0; i < keys.length; i++) {

// 给所有属性添加 getter、setter

defineReactive(obj, keys[i], obj[keys[i]]);

}

};

var dep = [];

dep.target = null;

function defineReactive(obj, key, val) {

// 有自定义的 property,则用自定义的 property

var property = Object.getOwnPropertyDescriptor(obj, key);

if(property && property.configurable === false) {

return;

}

var getter = property && property.get;

var setter = property && property.set;

// 递归的方式实现给属性的属性添加 getter、setter

var childOb = observe(val);

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function() {

var value = getter ? getter.call(obj) : val;

// 如果是 Watcher 监听的,就把 Watcher 对象压入 dep

if(dep.target) {

dep.push(dep.target);

}

return value;

},

set: function(newVal) {

var value = getter ? getter.call(obj) : val;

// set 值与原值相同,则不更新

if(newVal === value) {

return;

}

if(setter) {

setter.call(obj, newVal);

} else {

val = newVal;

}

// 给新赋值的属性值的属性添加 getter、setter

childOb = observe(newVal);

// 按序执行 dep 中元素的 update 方法

for(var i = 0; i < dep.length; i++) {

dep[i].update();

}

}

});

}

function observe(value) {

if(!value || typeof value !== 'object') {

return;

}

return new Observer(value);

}

var data = {a: 1};

new Observer(data);

new Watcher(data, 'a', function(){console.log('it works')});

data.a =12;

data.a =14;

上面基本实现了数据的监听,bug 肯定有不少,不过只是一个粗糙的 demo,只是想展示一个大概的流程,没有扣到非常细致。

Dep

上面几个例子,dep 是个全局的数组,但凡 new 一个 Watcher,dep 中就要多一个 Watcher 实例,这时候不管哪个 data 更新,所有的 Watcher 实例的 update 都会执行,这是不可接受的。

Dep 抽象出来,单独搞一个构造函数,不放在全局,就能解决了:

function Dep() {

this.subs = [];

}

Dep.prototype.addSub = function(sub) {

this.subs.push(sub);

};

Dep.prototype.notify = function() {

var subs = this.subs.slice();

for(var i = 0; i < subs.length; i++) {

subs[i].update();

}

}

利用 Dep 将上面的代码改写下就好了(当然,此处的 Dep 代码也不完全,只是一个大概的意思罢了)。

Vue 实例代理 data 对象

官方文档中有这么一句话:

每个 Vue 实例都会代理其 data 对象里所有的属性。

var data = { a: 1 };

var vm = new Vue({data: data});

vm.a === data.a // -> true

// 设置属性也会影响到原始数据

vm.a = 2

data.a // -> 2

// ... 反之亦然

data.a = 3

vm.a // -> 3

这种代理看起来很麻烦,其实也是可以通过 Object.defineProperty 来实现的:

function Vue(options) {

var data = this.data = options.data;

var keys = Object.keys(data);

var i = keys.length;

while(i--) {

proxy(this, keys[i];

}

}

function proxy(vm, key) {

Object.defineProperty(vm, key, {

configurable: true,

enumerable: true,

// 直接获取 vm.data[key] 的值

get: function() {

return vm.data[key];

},

// 设置值的时候直接设置 vm.data[key] 的值

set: function(val) {

vm.data[key] = val;

}

};

}

捏出一个 Vue,实现最初目标

var Vue = (function() {

var Watcher = function Watcher(vm, exp, cb) {

this.vm = vm;

this.exp = exp;

this.cb = cb;

this.value = this.get();

};

Watcher.prototype.get = function get() {

Dep.target = this;

var value = this.vm._data[this.exp];

Dep.target = null;

return value;

};

Watcher.prototype.addDep = function addDep(dep) {

dep.addSub(this);

};

Watcher.prototype.update = function update() {

this.run();

};

Watcher.prototype.run = function run() {

this.cb.call(this.vm);

}

var Dep = function Dep() {

this.subs = [];

};

Dep.prototype.addSub = function addSub(sub) {

this.subs.push(sub);

};

Dep.prototype.depend = function depend() {

if(Dep.target) {

Dep.target.addDep(this);

}

};

Dep.prototype.notify = function notify() {

var subs = this.subs.slice();

for(var i = 0; i < subs.length; i++) {

subs[i].update();

}

};

Dep.target = null;

var Observer = function Observer(value) {

this.value = value;

this.dep = new Dep();

this.walk(value);

};

Observer.prototype.walk = function walk(obj) {

var keys = Object.keys(obj);

for(var i = 0; i < keys.length; i++) {

defineReactive(obj, keys[i], obj[keys[i]]);

}

};

function defineReactive(obj, key, val) {

var dep = new Dep();

var property = Object.getOwnPropertyDescriptor(obj, key);

if(property && property.configurable === false) {

return;

}

var getter = property && property.get;

var setter = property && property.set;

var childOb = observe(val);

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function reactiveGetter() {

var value = getter ? getter.call(obj) : val;

if(Dep.target) {

dep.depend();

if(childOb) {

childOb.dep.depend();

}

}

return value;

},

set: function reactiveSetter(newVal) {

var value = getter ? getter.call(obj) : val;

if(newVal === value) {

return;

}

if(setter) {

setter.call(obj, newVal);

} else {

val = newVal;

}

childOb = observe(newVal);

dep.notify();

}

});

}

function observe(value) {

if(!value || typeof value !== 'object') {

return;

}

return new Observer(value);

}

function Vue(options) {

var vm = this;

this._el = options.el;

var data = this._data = options.data;

var keys = Object.keys(data);

var i = keys.length;

while(i--) {

proxy(this, keys[i]);

}

observe(data);

var elem = document.getElementById(this._el);

elem.innerHTML = vm.message;

new Watcher(this, 'message', function() {

elem.innerHTML = vm.message;

});

}

function proxy(vm, key) {

Object.defineProperty(vm, key, {

configurable: true,

enumerable: true,

get: function proxyGetter() {

return vm._data[key];

},

set: function proxySetter(val) {

vm._data[key] = val;

}

});

}

return Vue;

})();

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Document</title>

<script type="text/javascript" src="vue.js"></script>

</head>

<body>

<div id="app"></div>

<script type="text/javascript">

var app = new Vue({

el: 'app',

data: {

message: 'aaaaaaaaaaaaa'

}

});

</script>

</body>

</html>

参考资料:

vue 源码分析之如何实现 observer 和 watcher

vue早期源码学习系列之一:如何监听一个对象的变化

以上是 Vue监听数据对象变化源码 的全部内容, 来源链接: utcz.com/z/349872.html

回到顶部