- 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源码分析系列五: 响应式原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程序员的自我修养
陈逸鹤 / 清华大学出版社 / 2017-5 / 49.00
程序员作为一个职业、也作为一个群体,正逐渐从幕后走向前台,并以他们自己的能力加速改变着世界,也改变着人们生活的方方面面。然而,对于程序员,特别是年轻程序员们来说,如何理解自己的职业与发展,如何看待自己的工作与生活,这些问题往往比那些摆在面前的技术难题更让他们难以解答。 这本书从一个成熟程序员、一名IT管理者的角度,以杂记的形式为大家分享关于国内程序员职业生涯、个人发展、编程中的实践与认知乃至......一起来看看 《程序员的自我修养》 这本书的介绍吧!