上次我們已經分析了vue.js是通過Object.defineProperty以及發布訂閱模式來進行數據劫持和監聽,并且實現了一個簡單的demo。今天,我們就基于上一節的代碼,來實現一個MVVM類,將其與html結合在一起,并且實現v-model以及{{}}語法。
tips:本節新增代碼(去除注釋)在一百行左右。使用的Observer和Watcher都是延用上一節的代碼,沒有修改。
接下來,讓我們一步步來,實現一個MVVM類。
構造函數
首先,一個MVVM的構造函數如下(和vue.js的構造函數一樣):
class MVVM { constructor({ data, el }) {  this.data = data;  this.el = el;  this.init();  this.initDom(); }}和vue.js一樣,有它的data屬性以及el元素。
初始化操作
vue.js可以通過this.xxx的方法來直接訪問this.data.xxx的屬性,這一點是怎么做到的呢?其實答案很簡單,它是通過Object.defineProperty來做手腳,當你訪問this.xxx的時候,它返回的其實是this.data.xxx。當你修改this.xxx值的時候,其實修改的是this.data.xxx的值。具體可以看如下代碼:
class MVVM { constructor({ data, el }) {  this.data = data;  this.el = el;  this.init();  this.initDom(); } // 初始化 init() {  // 對this.data進行數據劫持  new Observer(this.data);  // 傳入的el可以是selector,也可以是元素,因此我們要在這里做一層處理,保證this.$el的值是一個元素節點  this.$el = this.isElementNode(this.el) ? this.el : document.querySelector(this.el);  // 將this.data的屬性都綁定到this上,這樣用戶就可以直接通過this.xxx來訪問this.data.xxx的值  for (let key in this.data) {   this.defineReactive(key);  } } defineReactive(key) {  Object.defineProperty(this, key, {   get() {    return this.data[key];   },   set(newVal) {    this.data[key] = newVal;   } //前端全棧學習交流圈:866109386  })//面向1-3年前端開發人員 }//幫助突破技術瓶頸,提升思維能力。 // 是否是屬性節點 isElementNode(node) {  return node.nodeType === 1; }}在完成初始化操作后,我們需要對this.$el的節點進行編譯。目前我們要實現的語法有v-model和{{}}語法,v-model這個屬性只可能會出現在元素節點的attributes里,而{{}}語法則是出現在文本節點里。
fragment
在對節點進行編譯之前,我們先考慮一個現實問題:如果我們在編譯過程中直接操作DOM節點的話,每一次修改DOM都會導致DOM的回流或重繪,而這一部分性能損耗是很沒有必要的。因此,我們可以利用fragment,將節點轉化為fragment,然后在fragment里編譯完成后,再將其放回到頁面上。
class MVVM { constructor({ data, el }) {  this.data = data;  this.el = el;//前端全棧交流學習圈:866109386  this.init();//針對1-3年前端開發人員  this.initDom();//幫助突破技術瓶頸,提升思維能力。 } initDom() {  const fragment = this.node2Fragment();  this.compile(fragment);  // 將fragment返回到頁面中  document.body.appendChild(fragment); } // 將節點轉為fragment,通過fragment來操作DOM,可以獲得更高的效率 // 因為如果直接操作DOM節點的話,每次修改DOM都會導致DOM的回流或重繪,而將其放在fragment里,修改fragment不會導致DOM回流和重繪 // 當在fragment一次性修改完后,在直接放回到DOM節點中 node2Fragment() {  const fragment = document.createDocumentFragment();  let firstChild;  while(firstChild = this.$el.firstChild) {   fragment.appendChild(firstChild);  }  return fragment; }}            
新聞熱點
疑難解答
圖片精選