JS - 如何實現一個相似 vue 的雙向綁定 Github JS 實現代碼vue
先來看一張圖:node
這張圖我作個簡要的描述:git
首先建立一個實例對象,分別觸發了 compile 解析指令 和 observer 監聽器,github
compile 解析指令則循環遞歸 解析 相似 v-model 這樣的指令,初始化 data 綁定數據,同時每一個節點建立一個訂閱者 watcher ,數組
observer 監聽器 則利用了 Object.defineProperty() 方法的描述屬性裏邊的 set,get方法,來監聽數據變化,app
get 方法是在建立實例對象,生成dom節點的時候都會觸發,固:在compile 解析編譯的時候,依次給每個節點添加了一個訂閱者到主題對象 Depdom
set 方法則是數據發生改變了,通知Dep訂閱器裏的全部wachter,而後找到對應訂閱者 wachter 觸發對應 update 更新視圖函數
簡單的說明就是這樣了。this
雙向綁定原理spa
vue數據雙向綁定是經過數據劫持結合發佈者-訂閱者模式的方式來實現的。
具體點兒
Vue雙向數據綁定的原理就是利用了 Object.defineProperty() 這個方法從新定義了對象獲取屬性值(get)和設置屬性值(set)的操做來實現的2.實現一個監聽器Observer,用來劫持並監聽全部屬性,若是有變更的,就通知訂閱者。
3.實現一個訂閱者Watcher,每個Watcher都綁定一個 update,watcher 能夠收到屬性的變化通知並執行相應的 update ,從而更新視圖。
4.實現MVVM,雙向綁定
如下實踐裏邊的幾個方法我就不作介紹了,感興趣可查詢
Object.defineProperty()
createDocumentFragment()
Object.keys()
話很少說:直接上代碼:實現一個解析器Compile
/* 第一步 1,建立文檔碎片,劫持全部dom節點,重繪dom節點 2,重繪dom節點,初始化文檔碎片綁定數據 實現文檔編譯 compile */ function getDocumentFragment(node, vm) { var flag = document.createDocumentFragment(); var child; while (child = node.firstChild) { /* while (child = node.firstChild) 至關於 child = node.firstChild while (child) */ compile(child, vm); flag.appendChild(child); } node.appendChild(flag); } function compile(node, vm) { /* nodeType 返回數字,表示當前節點類型 1 Element 表明元素 Element, Text, 2 Attr 表明屬性 Text, EntityReference 3 Text 表明元素或屬性中的文本內容。 . . . 更多請查看文檔 */ if (node.nodeType === 1) { // 獲取當前元素的attr屬性 var attr = node.attributes; for (let i = 0; i < attr.length; i++) { // nodeName 是attr屬性 key 即名稱 , 匹配自定義 v-m if (attr[i].nodeName === 'v-m') { // 獲取當前值 即 v-m = "test" 裏邊的 test let name = attr[i].nodeValue; // 當前節點輸入事件 node.addEventListener('keyup', function (e) { vm[name] = e.target.value; }); // 頁面元素寫值 vm.data[name] 即 vm.data['test'] 即 MVVM node.value = vm.data[name]; //最後移除標籤中的 v-m 屬性 node.removeAttribute('v-m'); // 爲每個節點建立一個 watcher new Watcher(vm, node, name, "input"); } } /* 繼續遞歸調用 文檔編譯 實現 視圖更新 ; */ if (child = node.firstChild) { /* if (child = node.firstChild) 至關於 child = node.firstChild id(child) */ compile(child, vm); } } if (node.nodeType === 3) { let reg = /\{\{(.*)\}\}/; if (reg.test(node.nodeValue)) { let name = RegExp.$1.trim(); node.nodeValue = vm.data[name]; // 爲每個節點建立一個 watcher new Watcher(vm, node, name, "text"); } } }
實現一個監聽器Observer
/* 第二步 實現一個數據監聽 1,獲取當前實例對象的 data 屬性 key observer(當前實例對象 data ,當前實例對象) 2,使用 Object.defineProperty 方法 實現監聽 */ function observe(data, vm) { Object.keys(data).forEach(function (key) { defineReactive(vm, key, data[key]); }); } function defineReactive(vm, key, val) { /* Object.defineProperty obj 要在其上定義屬性的對象。 prop 要定義或修改的屬性的名稱。 descriptor 將被定義或修改的屬性描述符。 描述符有不少,就包括咱們要市用 set , get 方法 */ var dep = new Dep(); Object.defineProperty(vm, key, { get: function () { /* if (Dep.target) dep.addSub(Dep.target); 看到這段代碼不要差別,生成每個 dom節點,都會走 get 方法 這裏爲每個節點 添加一個訂閱者 到主題對象 Dep */ if (Dep.target) dep.addSub(Dep.target); console.log(val) return val; }, set: function (newValue) { if (newValue === val) return; val = newValue; console.log(val + "=>" + newValue) // 通知全部訂閱者 dep.notify(); } }); }
實現一個訂閱者Watcher
/* 第三步 1,實現一個 watcher 觀察者/訂閱者 訂閱者原型上掛在兩個方法 分別是 update 渲染視圖 2,定義一個消息訂閱器 很簡單,維護一個數組,用來收集訂閱者 消息訂閱器原型掛載兩個方法 分別是 addSub 添加一個訂閱者 notify 數據變更 通知 這個訂閱者的 update 方法 */ function Watcher(vm, node, name, nodeType) { Dep.target = this; this.vm = vm; this.node = node; this.name = name; this.nodeType = nodeType; this.update(); console.log(Dep.target) Dep.target = null; } Watcher.prototype = { update: function () { /* this.node 指向當前修改的 dom 元素 this.vm 指向當前 dom 的實例對象 根據 nodeType 類型 賦值渲染頁面 */ if (this.nodeType === 'text') { this.node.nodeValue = this.vm[this.name] } if (this.nodeType === 'input') { this.node.value = this.vm[this.name] } } } function Dep() { this.subs = []; } Dep.prototype = { addSub: function (sub) { this.subs.push(sub); }, notify: function () { this.subs.forEach(function (sub) { sub.update(); }); } }
實現相似Vue的MVVM
/* 建立一個構造函數,並生成實例化對象 vm */ function Vue(o) { this.id = o.el; this.data = o.data; observe(this.data, this); getDocumentFragment(document.getElementById(this.id), this); } var vm = new Vue({ el: 'app', data: { msg: 'HiSen', test: 'Hello,MVVM' } });
也許看到最後你們也沒有看出個因此然,曾幾什麼時候的我跟大家同樣,看來看去,就是這麼幾段代碼;建議:拿下個人源碼,本身跑一跑,看一看,是騾子是馬拉出來溜溜。