以前每件事都差很少,直到如今才發現差不少。如今才發現理清一件事的原委是多麼快樂的一件事,咱們共同勉勵。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