以前每件事都差很少,直到如今才发现差不少。如今才发现理清一件事的原委是多么快乐的一件事,咱们共同勉励。javascript
纸上得来终觉浅,绝知此事要躬行html
懒得扯淡,直接正题vue
PS: 文章略长。java
本文分3个部分来介绍:node
model
-> view
view
-> model
其基于 订阅者-发布者模式,简单的讲就是订阅者订阅数据,一旦订阅的数据变动事后,更新绑定的view视图。git
这里有明确的分工,分别是监听器、发布器和订阅器,这3者相互协做,各司其职。github
过2s数据更改,更新到视图bash
顾名思义,监听器,监听器,监听的就是数据的变化app
建立 发布器;发布器 添加 订阅器; 发布器 通知 订阅器
须要解决订阅者的添加和发布器通知订阅器的时机dom
Object.difineProperty为咱们提供了方便,其语法以下:
var obj = {}; Object.defineProperty(obj, 'a', { enumerable: true, configurable: true, value: 'a' }); console.log(obj.a); // 输出a
definePropery
除了能够定义数值之外,还能够定义 get 和 set 访问器,以下:
var obj = {}; var value = 'a'; Object.defineProperty(obj, 'a', { enumerable: true, configurable: true, get: function () { console.log('获取 key 为 "a" 的值'); return value; }, set: function (val) { console.log('修改 key 为 "a" 的值'); value = val; } }); console.log(obj.a); obj.a = 'b'; console.log(obj.a);
运行结果以下所示:
数据的变化无非就是读和写,由此,咱们能够得出 订阅器的添加和发布器通知订阅器
的时机,就是属性值的获取和重置。
function observe(data) { if (!data || typeof data !== 'object') { return ; } Object.keys(data).forEach((val, key) => { defineReactive(data, val, data[val]); }) } function defineReactive(data, key, val) { observe(val); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { console.log(`获取参数${key},值为${val}`); return val; }, set: function (newValue) { console.log(`修改参数${key},变为${val}`); val = newValue; } }); } var obj = { type: 'object', data: { a: 'a' } } observe(obj);
添加 订阅器;通知 订阅器
function Dep () { this.subs = []; } Dep.prototype.addSub = function(sub){ this.subs.push(sub); }; Dep.prototype.notify = function(){ this.subs.forEach(function(sub, index) { sub.update(); }); };
发布器代码写好了,咱们再从新修改一下监听器代码。主要修改点为:添加订阅器和发布器通知订阅器
function defineReactive(data, key, val) { + var dep = new Dep(); observe(val); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { console.log(`获取参数${key},值为${val}`); + dep.addSub(<watch>); return val; }, set: function (newValue) { + dep.notify(); console.log(`修改参数${key},变为${val}`); val = newValue; }
更新视图
function Watcher (vm, key, cb) { this.vm = vm; this.key = key; this.cb = cb; this.value = this.get(); } Watcher.prototype.update = function(){ var oldValue = this.value; var value = this.vm.data[this.key]; if (oldValue !== value) { this.value = value; this.cb.call(this, this.vm.data[this.key]); } }; Watcher.prototype.get = function(){ Dep.target = this; var value = this.vm.data[this.key]; Dep.target = null; return value; };
订阅器代码写好了,咱们再从新修改一下监听器代码。主要修改点为:如何添加订阅器
enumerable: true, configurable: true, get: function () { - dep.addSub(<watch>); + if (Dep.target) { + dep.addSub(Dep.target); + } return val; }, set: function (newValue) {
function Vue (data, dom, key) { this.data = data; observe(data); dom.innerHTML = data[key]; var watcher = new Watcher(this, key, function (name) { dom.innerHTML = name; }); }
<div id="root"></div>
var vm = new Vue({ name: 'mumu' }, document.getElementById('root'), 'name'); setTimeout(() => { vm.data.name = 'yiyi'; }, 2000);
还有一点,一般数据的变动是直接使用vm.name,而非vm.data.name,其实也很简单,直接使用代理,vm.name读取和写都代理到vm.data.name上便可。
+ var self = this; + Object.keys(this.data).forEach(function(property, index) { + self.proxyProperty(property); + }); + var watcher = new Watcher(this, key, function (name) { dom.innerHTML = name; }); -} +} + +Vue.prototype.proxyProperty = function(property){ + Object.defineProperty(this, property, { + configurable: true, + get: function () { + return this.data[property]; + }, + set: function (value) { + this.data[property] = value; + } + }); +};
详细代码参考github项目
$ git clone https://github.com/doudounannan/vue-like.git $ cd vue-like $ git checkout model2view
上面的实例看起来,有点问题,咱们是写死监听的数据,而后修改dom上的innerHTML,实际中,确定不会这样,须要在dom中绑定数据,而后动态监听数据变化。
首先须要明确编译器
有哪些工做须要作
订阅器
视图绑定数据,2s后数据更新,更新到视图
对dom结构的解析这里使用 文档片断
,其dom操做性能优于其余。
function Compile (options, vm) { this.compile = this; this.vm = vm; this.domEle = document.getElementById(options.el); this.fragment = this.createElement(this.domEle); this.compileElement(this.fragment); this.viewRefresh(); } Compile.prototype.createElement = function (ele) { var fragment = document.createDocumentFragment(); var child = ele.firstChild; while (child) { fragment.appendChild(child); child = ele.firstChild; } return fragment; } Compile.prototype.compileElement = function (el) { var childNodes = el.childNodes; [].slice.apply(childNodes).forEach((node) => { var reg = /\{\{(\w+)\}\}/; var text = node.textContent; if (reg.test(text)) { this.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length > 0) { this.compileElement(node); } }); } Compile.prototype.compileText = function (node, key) { var text = this.vm[key]; var self = this; self.updateText(node, text); new Watcher(this.vm, key, function (newText) { self.updateText(node, newText); }); } Compile.prototype.updateText = function (node, text) { node.textContent = text; } Compile.prototype.viewRefresh = function(){ this.domEle.appendChild(this.fragment); };
详细代码参考github项目
$ git clone https://github.com/doudounannan/vue-like.git $ cd vue-like $ git checkout compile
这个比较简单,在compile 解析时,判断是不是元素节点,若是元素节点中包含指令v-model
,从中读取监听的数据属性,再从 model中读取,除此之外还要绑定一个input事件,用于view -> model
详细代码参考github项目
$ git clone https://github.com/doudounannan/vue-like.git $ cd vue-like $ git checkout view2model
详细代码参考github项目
$ git clone https://github.com/doudounannan/vue-like.git $ cd vue-like $ git checkout event
好比说建立、初始化、更新、销毁等。
详细代码参考github项目
$ git clone https://github.com/doudounannan/vue-like.git $ cd vue-like $ git checkout lifecircle