摘要:標簽添加監聽事件文本節點這一步我們操作頁面輸入框,可以看到以下效果,證明監聽事件添加有效。
前言
經過幾天的研究,發現學習框架的底層技術,收獲頗豐,相比只學習框架的使用要來的合算;如果工作急需,快速上手應用,掌握如何使用短期內更加高效;如果有較多的時間來系統學習,建議研究一下框架的等層技術、原理。
Vue、React、Angular三大框架對比1、Vue
Vue是尤雨溪編寫的一個構建數據驅動的Web界面的庫,準確來說不是一個框架,它聚焦在V(view)視圖層。
它有以下的特性:
1.輕量級的框架
2.雙向數據綁定
3.指令
4.插件化
優點:
簡單:官方文檔很清晰,比 Angular 簡單易學。
快速:異步批處理方式更新 DOM。
組合:用解耦的、可復用的組件組合你的應用程序。
緊湊:~18kb min+gzip,且無依賴。
強大:表達式 無需聲明依賴的可推導屬性 (computed properties)。
對模塊友好:可以通過 NPM、Bower 或 Duo 安裝,不強迫你所有的代碼都遵循 Angular 的各種規定,使用場景更加靈活。
缺點:
新生兒:Vue.js是一個新的項目,沒有angular那么成熟。
影響度不是很大:google了一下,有關于Vue.js多樣性或者說豐富性少于其他一些有名的庫。
不支持IE8
2、React
React 起源于 Facebook 的內部項目,用來架設 Instagram 的網站, 并于 2013年 5 月開源。React 擁有較高的性能,代碼邏輯非常簡單,越來越多的人已開始關注和使用它。
它有以下的特性:
1.聲明式設計:React采用聲明范式,可以輕松描述應用。
2.高效:React通過對DOM的模擬,最大限度地減少與DOM的交互。
3.靈活:React可以與已知的庫或框架很好地配合。
優點:
速度快:在UI渲染過程中,React通過在虛擬DOM中的微操作來實現對實際DOM的局部更新。
跨瀏覽器兼容:虛擬DOM幫助我們解決了跨瀏覽器問題,它為我們提供了標準化的API,甚至在IE8中都是沒問題的。
模塊化:為你程序編寫獨立的模塊化UI組件,這樣當某個或某些組件出現問題是,可以方便地進行隔離。
單向數據流:Flux是一個用于在JavaScript應用中創建單向數據層的架構,它隨著React視圖庫的開發而被Facebook概念化。
同構、純粹的javascript:因為搜索引擎的爬蟲程序依賴的是服務端響應而不是JavaScript的執行,預渲染你的應用有助于搜索引擎優化。
兼容性好:比如使用RequireJS來加載和打包,而Browserify和Webpack適用于構建大型應用。它們使得那些艱難的任務不再讓人望而生畏。
缺點:
React本身只是一個V而已,并不是一個完整的框架,所以如果是大型項目想要一套完整的框架的話,基本都需要加上ReactRouter和Flux才能寫大型應用。
3、Angular
Angular是一款優秀的前端JS框架,已經被用于Google的多款產品當中。
它有以下的特性:
1.良好的應用程序結構
2.雙向數據綁定
3.指令
4.HTML模板
5.可嵌入、注入和測試
優點:
模板功能強大豐富,自帶了極其豐富的angular指令。
是一個比較完善的前端框架,包含服務,模板,數據雙向綁定,模塊化,路由,過濾器,依賴注入等所有功能;
自定義指令,自定義指令后可以在項目中多次使用。
ng模塊化比較大膽的引入了Java的一些東西(依賴注入),能夠很容易的寫出可復用的代碼,對于敏捷開發的團隊來說非常有幫助。
angularjs是互聯網巨人谷歌開發,這也意味著他有一個堅實的基礎和社區支持。
缺點:
angular 入門很容易 但深入后概念很多, 學習中較難理解.
文檔例子非常少, 官方的文檔基本只寫了api, 一個例子都沒有, 很多時候具體怎么用都是google來的, 或直接問misko,angular的作者.
對IE6/7 兼容不算特別好, 就是可以用jQuery自己手寫代碼解決一些.
指令的應用的最佳實踐教程少, angular其實很靈活, 如果不看一些作者的使用原則,很容易寫出 四不像的代碼, 例如js中還是像jQuery的思想有很多dom操作.
DI 依賴注入 如果代碼壓縮需要顯示聲明.
通過以上相比較,您更加傾向于學習哪一個呢?
正題:Vue的基本原理1、建立虛擬DOM Tree,通過document.createDocumentFragment(),遍歷指定根節點內部節點,根據{{ prop }}、v-model等規則進行compile;
2、通過Object.defineProperty()進行數據變化攔截;
3、截取到的數據變化,通過發布者-訂閱者模式,觸發Watcher,從而改變虛擬DOM中的具體數據;
4、通過改變虛擬DOM元素值,從而改變最后渲染dom樹的值,完成雙向綁定
完成數據的雙向綁定在于Object.defineProperty()
Vue雙向綁定的實現1、簡易雙綁
首先,我們把注意力集中在這個屬性上:Object.defineProperty。
Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 并返回這個對象。
語法:Object.defineProperty(obj, prop, descriptor)
什么叫做,定義或修改一個對象的新屬性,并返回這個對象呢?
var obj = {}; Object.defineProperty(obj,"hello",{ get:function(){ //我們在這里攔截到了數據 console.log("get方法被調用"); }, set:function(newValue){ //改變數據的值,攔截下來額 console.log("set方法被調用"); } }); obj.hello//輸出為“get方法被調用”,輸出了值。 obj.hello = "new Hello";//輸出為set方法被調用,修改了新值
通過以上方法可以看出,獲取對象屬性值觸發get、設置對象屬性值觸發set,因此我們可以想象到數據模型對象的屬性設置和讀取可以驅動view層的數據變化,view的數據變化傳遞給數據模型對象,在set里面可以做很多事情。
在這基礎上,我們可以做到數據的雙向綁定:
let obj = {}; Object.defineProperty(obj, "name", { set: function(newValue){ console.log("觸發setter"); document.querySelector(".text-box").innerHTML = newValue; document.querySelector(".inp-text").value = newValue; }, get: function(){ console.log("觸發getter"); } }); document.querySelector(".inp-text").addEventListener("keyup", function(e){ obj.name = e.target.value; }, false);
html
以上只是vue的核心思想,通過對象底層屬性的set和get進行數據攔截,vue的虛擬dom又是怎么實現的,且看以下分解。
2、虛擬DOM樹
創建虛擬DOM:
var frag = document.createDocumentFragment();
view層的{{msg}}和v-model的編譯規則如下:
html:
{{ msg }}
{{ msg }}
view層做了多層嵌套,這樣測試更多出現錯誤的可能性。
var container = document.getElementById("container"); //這里我們把vue實例中的data提取出來,更加直觀 var data = { msg: "Hello world!", inpText: "Input text" }; var fragment = virtualDom(container, data); container.appendChild(fragment); //虛擬dom創建方法 function virtualDom(node, data){ let frag = document.createDocumentFragment(); let child; // 遍歷dom節點 while(child = node.firstChild){ compile(child, data); frag.appendChild(child); } return frag; } //編譯規則 function compile(node, data){ let reg = /{{(.*)}}/g; if(node.nodeType === 1){ // 標簽 let attr = node.attributes; for(let i = 0, len = attr.length; i < len; i++){ // console.log(attr[i].nodeName, attr[i].nodeValue); if(attr[i].nodeName === "v-model"){ let name = attr[i].nodeValue; node.value = data[name]; } } if(node.hasChildNodes()){ node.childNodes.forEach((item) => { compile(item, data); // 遞歸 }); } } if(node.nodeType === 3){ // 文本節點 if(reg.test(node.nodeValue)){ let name = RegExp.$1; name = name.trim(); node.nodeValue = data[name]; } } }
解釋:
1、通過virtualDom創建虛擬節點,將目標盒子內所有子節點添加到其內部,注意這里只是子節點;
2、子節點通過compile進行編譯,a:如果節點為元素,其nodeType = 1,b:如果節點為文本,其nodeType = 3,具體可以查看詳情http://www.w3school.com.cn/js...;
3、如果第二步子節點仍有子節點,通過hasChildNodes()來確認,如果有遞歸調用compile方法。
3、響應式原理
核心思想:Object.defineProperty(obj, key, {set, get})
function defineReact(obj, key, value){ Object.defineProperty(obj, key, { set: function(newValue){ console.log(`觸發setter`); value = newValue; console.log(value); }, get: function(){ console.log(`觸發getter`); return value; } }); }
這里是針對data數據的屬性的響應式定義,但是如何去實現vue實例vm綁定data每個屬性,通過以下方法:
function observe(obj, vm){ Object.keys(obj).forEach((key) => { defineReact(vm, key, obj[key]); }) }
vue的構造函數:
function Vue(options){ this.data = options.data; let id = options.el; observe(this.data, this); // 將每個data屬相綁定到Vue的實例上this }
通過以上我們可以實現Vue實例綁定data屬性。
如何去實現Vue,通常我們實例化Vue是這樣的:
var vm = new Vue({ el: "container", data: { msg: "Hello world!", inpText: "Input text" } }); console.log(vm.msg); // Hello world! console.log(vm.inpText); // Input text
實現以上效果,我們必須在vue內部初始化虛擬Dom
function Vue(options){ this.data = options.data; let id = options.el; observe(this.data, this); // 將每個data屬相綁定到Vue的實例上this //------------------------添加以下代碼 let container = document.getElementById(id); let fragment = virtualDom(container, this); // 這里通過vm對象初始化 container.appendChild(fragment); }
這是我們再對Vue進行實例化,則可以看到以下頁面:
至此我們實現了dom的初始化,下一步我們在v-model元素添加監聽事件,這樣就可以通過view層的操作來修改vm對應的屬性值。在compile編譯的時候,可以準確的找到v-model屬相元素,因此我們把監聽事件添加到compile內部。
function compile(node, data){ let reg = /{{(.*)}}/g; if(node.nodeType === 1){ // 標簽 let attr = node.attributes; for(let i = 0, len = attr.length; i < len; i++){ // console.log(attr[i].nodeName, attr[i].nodeValue); if(attr[i].nodeName === "v-model"){ let name = attr[i].nodeValue; node.value = data[name]; // ------------------------添加監聽事件 node.addEventListener("keyup", function(e){ data[name] = e.target.value; }, false); // ----------------------------------- } } if(node.hasChildNodes()){ node.childNodes.forEach((item) => { compile(item, data); }); } } if(node.nodeType === 3){ // 文本節點 if(reg.test(node.nodeValue)){ let name = RegExp.$1; name = name.trim(); node.nodeValue = data[name]; } } }
這一步我們操作頁面輸入框,可以看到以下效果,證明監聽事件添加有效。
到這里我們已經實現了MVVM的,即Model -> vm -> View || View -> vm -> Model 中間橋梁就是vm實例對象。
4、觀察者模式原理
觀察者模式也稱為發布者-訂閱者模式,這樣說應該會更容易理解,更加形象。
訂閱者:
var subscribe_1 = { update: function(){ console.log("This is subscribe_1"); } }; var subscribe_2 = { update: function(){ console.log("This is subscribe_2"); } }; var subscribe_3 = { update: function(){ console.log("This is subscribe_3"); } };
三個訂閱者都有update方法。
發布者:
function Publisher(){ this.subs = [subscribe_1, subscribe_2, subscribe_3]; // 添加訂閱者 } Publisher.prototype = { constructor: Publisher, notify: function(){ this.subs.forEach(function(sub){ sub.update(); }) } };
發布者通過notify方法對訂閱者廣播,訂閱者通過update來接受信息。
實例化publisher:
var publisher = new Publisher(); publisher.notify();
這里我們可以做一個中間件來處理發布者-訂閱者模式:
var publisher = new Publisher(); var middleware = { publish: function(){ publisher.notify(); } }; middleware.publish();
5、觀察者模式嵌入
到這一步,我們已經實現了:
1、修改v-model屬性元素 -> 觸發修改vm的屬性值 -> 觸發set
2、發布者添加訂閱 -> notify分發訂閱 -> 訂閱者update數據
接下來我們要實現:更新視圖,同時把訂閱——發布者模式嵌入。
發布者:
function Publisher(){ this.subs = []; // 訂閱者容器 } Publisher.prototype = { constructor: Publisher, add: function(sub){ this.subs.push(sub); // 添加訂閱者 }, notify: function(){ this.subs.forEach(function(sub){ sub.update(); // 發布訂閱 }); } };
訂閱者:
考慮到要把訂閱者綁定data的每個屬性,來觀察屬性的變化,參數:name參數可以有compile中獲取的name傳參。由于傳入的node節點類型分為兩種,我們可以分為兩訂閱者來處理,同時也可以對node節點類型進行判斷,通過switch分別處理。
function Subscriber(node, vm, name){ this.node = node; this.vm = vm; this.name = name; } Subscriber.prototype = { constructor: Subscriber, update: function(){ let vm = this.vm; let node = this.node; let name = this.name; switch(this.node.nodeType){ case 1: node.value = vm[name]; break; case 3: node.nodeValue = vm[name]; break; default: break; } } };
我們要把訂閱者添加到compile進行虛擬dom的初始化,替換掉原來的賦值:
function compile(node, data){ let reg = /{{(.*)}}/g; if(node.nodeType === 1){ // 標簽 let attr = node.attributes; for(let i = 0, len = attr.length; i < len; i++){ // console.log(attr[i].nodeName, attr[i].nodeValue); if(attr[i].nodeName === "v-model"){ let name = attr[i].nodeValue; // --------------------這里被替換掉 // node.value = data[name]; new Subscriber(node, data, name); // ------------------------添加監聽事件 node.addEventListener("keyup", function(e){ data[name] = e.target.value; }, false); } } if(node.hasChildNodes()){ node.childNodes.forEach((item) => { compile(item, data); }); } } if(node.nodeType === 3){ // 文本節點 if(reg.test(node.nodeValue)){ let name = RegExp.$1; name = name.trim(); // ---------------------這里被替換掉 // node.nodeValue = data[name]; new Subscriber(node, data, name); } } }
既然是對虛擬dom編譯初始化,Subscriber要初始化,即Subscriber.update,因此要對Subscriber作進一步的處理:
function Subscriber(node, vm, name){ this.node = node; this.vm = vm; this.name = name; this.update(); } Subscriber.prototype = { constructor: Subscriber, update: function(){ let vm = this.vm; let node = this.node; let name = this.name; switch(this.node.nodeType){ case 1: node.value = vm[name]; break; case 3: node.nodeValue = vm[name]; break; default: break; } } };
發布者添加到defineReact,來觀察數據的變化:
function defineReact(data, key, value){ let publisher = new Publisher(); Object.defineProperty(data, key, { set: function(newValue){ console.log(`觸發setter`); value = newValue; console.log(value); publisher.notify(); // 發布訂閱 }, get: function(){ console.log(`觸發getter`); if(Publisher.global){ //這里為什么來添加判斷條件,主要是讓publisher.add只執行一次,初始化虛擬dom編譯的時候來執行 publisher.add(Publisher.global); // 添加訂閱者 } return value; } }); }
這一步將訂閱者添加到發布者容器內,對訂閱者改造:
function Subscriber(node, vm, name){ Publisher.global = this; this.node = node; this.vm = vm; this.name = name; this.update(); Publisher.global = null; } Subscriber.prototype = { constructor: Subscriber, update: function(){ let vm = this.vm; let node = this.node; let name = this.name; switch(this.node.nodeType){ case 1: node.value = vm[name]; break; case 3: node.nodeValue = vm[name]; break; default: break; } } };
6、完整效果
html:
{{ msg }}
{{ inpText }}
{{ msg }}
javascript:
function Publisher(){ this.subs = []; } Publisher.prototype = { constructor: Publisher, add: function(sub){ this.subs.push(sub); }, notify: function(){ this.subs.forEach(function(sub){ sub.update(); }); } }; function Subscriber(node, vm, name){ Publisher.global = this; this.node = node; this.vm = vm; this.name = name; this.update(); Publisher.global = null; // 清空 } Subscriber.prototype = { constructor: Subscriber, update: function(){ let vm = this.vm; let node = this.node; let name = this.name; switch(this.node.nodeType){ case 1: node.value = vm[name]; break; case 3: node.nodeValue = vm[name]; break; default: break; } } }; function virtualDom(node, data){ let frag = document.createDocumentFragment(); let child; // 遍歷dom節點 while(child = node.firstChild){ compile(child, data); frag.appendChild(child); } return frag; } function compile(node, data){ let reg = /{{(.*)}}/g; if(node.nodeType === 1){ // 標簽 let attr = node.attributes; for(let i = 0, len = attr.length; i < len; i++){ // console.log(attr[i].nodeName, attr[i].nodeValue); if(attr[i].nodeName === "v-model"){ let name = attr[i].nodeValue; // node.value = data[name]; // ------------------------添加監聽事件 node.addEventListener("keyup", function(e){ data[name] = e.target.value; }, false); new Subscriber(node, data, name); } } if(node.hasChildNodes()){ node.childNodes.forEach((item) => { compile(item, data); }); } } if(node.nodeType === 3){ // 文本節點 if(reg.test(node.nodeValue)){ let name = RegExp.$1; name = name.trim(); // node.nodeValue = data[name]; new Subscriber(node, data, name); } } } function defineReact(data, key, value){ let publisher = new Publisher(); Object.defineProperty(data, key, { set: function(newValue){ console.log(`觸發setter`); value = newValue; console.log(value); publisher.notify(); // 發布訂閱 }, get: function(){ console.log(`觸發getter`); if(Publisher.global){ publisher.add(Publisher.global); // 添加訂閱者 } return value; } }); } // 將data中數據綁定到vm實例對象上 function observe(data, vm){ Object.keys(data).forEach((key) => { defineReact(vm, key, data[key]); }) } function Vue(options){ this.data = options.data; let id = options.el; observe(this.data, this); // 將每個data屬相綁定到Vue的實例vm上 //------------------------ let container = document.getElementById(id); let fragment = virtualDom(container, this); // 這里通過vm對象初始化 container.appendChild(fragment); } var vm = new Vue({ el: "container", data: { msg: "Hello world!", inpText: "Input text" } });
未完待續......
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102997.html
摘要:前言最近在學習框架的基本原理,看了一些技術博客以及一些對源碼的簡單實現,對數據代理數據劫持模板解析變異數組方法雙向綁定有了更深的理解。 前言 最近在學習vue框架的基本原理,看了一些技術博客以及一些對vue源碼的簡單實現,對數據代理、數據劫持、模板解析、變異數組方法、雙向綁定有了更深的理解。于是乎,嘗試著去實踐自己學到的知識,用vue的一些基本原理實現一個簡單的todo-list,完成...
寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Props - 源碼版 今天記錄 Props 源碼流程,哎,這東西,就算是研究過了,也真是會隨著時間慢慢忘記的。 幸好我做...
摘要:當東西發售時,就會打你的電話通知你,讓你來領取完成更新。其中涉及的幾個步驟,按上面的例子來轉化一下你買東西,就是你要使用數據你把電話給老板,電話就是你的,用于通知老板記下電話在電話本,就是把保存在中。剩下的步驟屬于依賴更新 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理依賴更新源碼版如果對依賴收集完 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于...
摘要:背景個人背景就讀于東北某普通二本院校計算機軟件工程專業,現大四,北京實習前端方向,自學,技術棧時間背景大概是在月日準備好簡歷開始投遞秋招差不多已經結束招聘崗位不多,投遞對象為大一些的互聯網公司事件背景第一個入職的是好未來的前端實習崗,待遇工 背景 個人背景 就讀于東北某普通二本院校計算機軟件工程專業,現大四,北京實習 前端方向,自學,vue技術棧 時間背景 大概是在11月9日準備...
摘要:背景個人背景就讀于東北某普通二本院校計算機軟件工程專業,現大四,北京實習前端方向,自學,技術棧時間背景大概是在月日準備好簡歷開始投遞秋招差不多已經結束招聘崗位不多,投遞對象為大一些的互聯網公司事件背景第一個入職的是好未來的前端實習崗,待遇工 背景 個人背景 就讀于東北某普通二本院校計算機軟件工程專業,現大四,北京實習 前端方向,自學,vue技術棧 時間背景 大概是在11月9日準備...
閱讀 2112·2023-04-26 00:41
閱讀 1142·2021-09-24 10:34
閱讀 3573·2021-09-23 11:21
閱讀 4031·2021-09-22 15:06
閱讀 1557·2019-08-30 15:55
閱讀 898·2019-08-30 15:54
閱讀 1829·2019-08-30 15:48
閱讀 550·2019-08-29 13:58