- vue.js采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty来触发各个属性的getter以及setter,在数据变动时发布消息给订阅者,并触发相应的监听回调。
-
第一步
- 初始化Vue实例,将Vue实例上绑定 dep 属性(依赖收集)
- 调用Vue原型上的 _observe() 以及 _compile() 方法。、
-
第二步
- 通过 _observe() 方法重写data对象的setter/getter方法,当我们对data对象的属性进行改变的时候,能够发布消息给订阅者(Watcher),触发监听函数(Watcher原型上的update()方法)
-
第三步
- 通过 _compile() 方法解析模板字符串,即 v-model/v-click/v-html等
- 在解析模板的同时,往dep中添加相应的监听器。
- 在这里操作Vue实例中的 $data
-
第四步
- 通过Watcher构造函数,收集需要监听的元素
- 在构造函数的原型上定义 update()方法,通过数据的改变从而改变视图。
- 最后上代码(删除注释说明的话,核心代码150行不到)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
body {
line-height: 120px;
text-align: center;
background: #fff;
color: yellow;
}
h1 {
background: red;
display: inline-block;
width: auto;
padding: 12px 24px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id="app">
<form>
<input type="text" v-model="number" />
<button type="button" v-click="increment">increment</button>
</form>
<h1 v-html="number"></h1>
</div>
<script>
function Vue(options) {
this._init(options);
}
Vue.prototype._init = function(options) {
this.$options = options;
this.$el = document.querySelector(options.el);
this.$data = options.data;
this.$methods = options.methods;
// 依赖收集: 对dom进行编译解析(解析指令或模板语法)的时候收集依赖,在数据改变的时候(setter 中)进行更新。
this.dep = {};
this._observe(this.$data);
this._compile(this.$el);
};
Vue.prototype._observe = function(obj) {
var value;
var _this = this;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
// 收集依赖,对所有属性都进行一个监听,在这里是 number
// 在 dep 对象中添加一个 number 属性,其值是一个数组,数组中存放的是 Watcher 实例
// 如果发现 number 发生了改变,就在 setter 中循环遍历notice,执行 Watcher 实例的 update 方法,统一更新 number
_this.dep[key] = {
notice: []
};
value = _this.$data[key]; // 将 value 赋值为最初是的 number 值
var dep = _this.dep[key];
Object.defineProperty(_this.$data, key, {
get() {
return value;
},
set(newVal) {
value = newVal;
dep.notice.forEach(item => {
// 这里的item就是Watcher实例,可以调用update()方法,通知更新
// 有几处用到了 number 属性,number.notice 就有几个 Watcher 实例
// notice: {
// attr: "number",
// el: Input,
// name: "input",
// value: "value",
// vm: {...}
// }
item.update();
});
}
});
}
}
};
Vue.prototype._compile = function(root) {
// #app 根元素
var nodes = root.children; // [form, h1]
var _this = this;
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
if (node.children.length) {
this._compile(node);
}
if (node.hasAttribute("v-click")) {
// 下面这种方式,有点问题,当立即执行函数执行完后,attrVal泄露出去了
// 导致解析 v-model 的时候,拿到的 attrVal 的值时 increment,而不是number
// 要注意
// 用这种方式也可以实现,那么在解析'v-model'的时候,需要将当前 (解析'v-model') if语句中var出来的attrVal传入到立即执行函数中去
// 或者我们统一使用ES6中的 let 来声明 attrVal 变量。
// var attrVal = node.getAttribute('v-click');
// node.addEventListener('click', (function () {
// return _this.$methods[attrVal].bind(_this.$data);
// })())
// 这种方式就是当立即执行函数被销毁之后,var出来的attrVal不会泄露出来,污染别的变量,但是可以通过闭包可以访问得到。
node.onclick = (function() {
var attrVal = node.getAttribute("v-click");
// 注意:methods方法里面用的 this,指的是 options 里面的 data,所以需要将方法的上下文半绑定为 data
return _this.$methods[attrVal].bind(_this.$data);
})();
}
if (node.hasAttribute("v-model") && node.tagName === "INPUT") {
var attrVal = node.getAttribute("v-model");
node.addEventListener(
"input",
(function(i) {
// 因为 input 用到了 number,所以需要将 dep.number.notice 中添加 Watcher 实例,
// 在 number 改变时,input 的值就需要改变
_this.dep[attrVal].notice.push(
new Watcher("input", node, _this, attrVal, "value")
);
return function() {
// 当我们在 input 里面输入数据的时候,就会触发 number 的 setter 属性
_this.$data[attrVal] = nodes[i].value;
};
})(i)
);
}
if (node.hasAttribute("v-html")) {
var attrVal = node.getAttribute("v-html");
_this.dep[attrVal].notice.push(
new Watcher("h1", node, _this, attrVal, "innerHTML")
);
}
}
};
class Watcher {
constructor(name, el, vm, attr, value) {
// name: input
// el: current element
// vm
// attr: number
// value: 元素的value (innerHTML, input.value)
this.name = name;
this.el = el;
this.vm = vm;
this.attr = attr;
this.value = value;
this.update();
}
update() {
this.el[this.value] = this.vm.$data[this.attr];
}
}
window.onload = function() {
let vm = new Vue({
el: "#app",
data: {
number: 0
},
methods: {
increment() {
this.number++;
}
}
});
};
</script>
</body>
</html>
复制代码
以上所述就是小编给大家介绍的《Vue源码分析系列五: 响应式原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Programming Collective Intelligence
Toby Segaran / O'Reilly Media / 2007-8-26 / USD 39.99
Want to tap the power behind search rankings, product recommendations, social bookmarking, and online matchmaking? This fascinating book demonstrates how you can build Web 2.0 applications to mine the......一起来看看 《Programming Collective Intelligence》 这本书的介绍吧!