一、前言 在vue的視圖層與modal層進行數據交互的時,視圖層的數據傳入到modal層,modal層通過defineProperty來劫持每個元素,並綁定監聽事件進行監聽,一旦監聽到數據變化,就通過defineProperty的set函數重新更新視圖層。 二、使用Object.defineProp ...
一、前言
在vue的視圖層與modal層進行數據交互的時,視圖層的數據傳入到modal層,modal層通過defineProperty來劫持每個元素,並綁定監聽事件進行監聽,一旦監聽到數據變化,就通過defineProperty的set函數重新更新視圖層。
二、使用Object.defineProperty( )
實現核心方法就是前文所說的Object.defineProperty( )。如果要對所有屬性都進行監聽的話,那麼可以通過遞歸方法遍歷所有屬性值,並對其進行Object.defineProperty( )處理。
// 1.實現一個監聽器Observer,用來劫持並監聽所有屬性,如果有變動的,就通知訂閱者 function Observer(data) { this.data = data; this.walk(data); } Observer.prototype = { walk: function(data) { var self = this; Object.keys(data).forEach(function(key) { self.defineReactive(data, key, data[key]); }); }, defineReactive: function(data, key, val) { var dep = new Dep(); var childObj = observe(val); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function getter () { if (Dep.target) { dep.addSub(Dep.target); } return val; }, set: function setter (newVal) { if (newVal === val) { return; } val = newVal; dep.notify(); } }); } }; function observe(value, vm) { if (!value || typeof value !== 'object') { return; } return new Observer(value); }; function Dep () { this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } }; Dep.target = null;
三、實現Watcher進行監聽
我們只要在訂閱者Watcher初始化的時候出發對應的get函數去執行添加訂閱者操作即可,只要獲取對應的屬性值就可以觸發get函數,核心原因就是因為我們使用了Object.defineProperty( )進行數據監聽。
// 2.實現一個訂閱者Watcher,可以收到屬性的變化通知並執行相應的函數,從而更新視圖。 function Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get(); // 將自己添加到訂閱器的操作 } Watcher.prototype = { update: function() { this.run(); }, run: function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { Dep.target = this; // 緩存自己 var value = this.vm.data[this.exp] // 強制執行監聽器里的get函數 Dep.target = null; // 釋放自己 return value; } };
四、最後利用Compile解析和綁定dom節點
解析器Compile實現步驟:
1.解析模板指令,並替換模板數據,初始化視圖;
2.將模板指令對應的節點綁定對應的更新函數,初始化相應的訂閱器;
為瞭解析模板,首先需要獲取到dom元素,然後對含有dom元素上含有指令的節點進行處理,因此這個環節需要對dom操作比較頻繁,所有可以先建一個fragment片段,將需要解析的dom節點存入fragment片段里再進行處理:
// 3.實現一個解析器Compile,可以掃描和解析每個節點的相關指令,並根據初始化模板數據以及初始化相應的訂閱器。 function Compile(el, vm) { this.vm = vm; this.el = document.querySelector(el); this.fragment = null; this.init(); } Compile.prototype = { init: function () { if (this.el) { this.fragment = this.nodeToFragment(this.el); this.compileElement(this.fragment); this.el.appendChild(this.fragment); } else { console.log('Dom元素不存在'); } }, nodeToFragment: function (el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; while (child) { // 將Dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild } return fragment; }, compileElement: function (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; var text = node.textContent; if (self.isElementNode(node)) { self.compile(node); } else if (self.isTextNode(node) && reg.test(text)) { self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); } }); }, compile: function(node) { var nodeAttrs = node.attributes; var self = this; Array.prototype.forEach.call(nodeAttrs, function(attr) { var attrName = attr.name; if (self.isDirective(attrName)) { var exp = attr.value; var dir = attrName.substring(2); if (self.isEventDirective(dir)) { // 事件指令 self.compileEvent(node, self.vm, exp, dir); } else { // v-model 指令 self.compileModel(node, self.vm, exp, dir); } node.removeAttribute(attrName); } }); }, compileText: function(node, exp) { var self = this; var initText = this.vm[exp]; this.updateText(node, initText); new Watcher(this.vm, exp, function (value) { self.updateText(node, value); }); }, compileEvent: function (node, vm, exp, dir) { var eventType = dir.split(':')[1]; var cb = vm.methods && vm.methods[exp]; if (eventType && cb) { node.addEventListener(eventType, cb.bind(vm), false); } }, compileModel: function (node, vm, exp, dir) { var self = this; var val = this.vm[exp]; this.modelUpdater(node, val); new Watcher(this.vm, exp, function (value) { self.modelUpdater(node, value); }); node.addEventListener('input', function(e) { var newValue = e.target.value; if (val === newValue) { return; } self.vm[exp] = newValue; val = newValue; }); }, updateText: function (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }, modelUpdater: function(node, value, oldValue) { node.value = typeof value == 'undefined' ? '' : value; }, isDirective: function(attr) { return attr.indexOf('v-') == 0; }, isEventDirective: function(dir) { return dir.indexOf('on:') === 0; }, isElementNode: function (node) { return node.nodeType == 1; }, isTextNode: function(node) { return node.nodeType == 3; } }
這樣就大功告成了,此文章參考https://www.cnblogs.com/libin-1/p/6893712.html,完整源代碼下載,請點擊這裡獲取;