日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
使用ES6的新特性Proxy來實現(xiàn)一個數(shù)據(jù)綁定實例

作為一個前端開發(fā)者,曾踩了太多的“數(shù)據(jù)綁定”的坑。在早些時候,都是通過jQuery之類的工具手動完成這些功能,但是當數(shù)據(jù)量非常大的時候,這些手動的工作讓我非常痛苦。直到使用了VueJS,這些痛苦才得以終結(jié)。

VueJS的其中一個賣點,就是“數(shù)據(jù)綁定”。使用者無需關(guān)心數(shù)據(jù)是怎么綁定到dom上面的,只需要關(guān)注數(shù)據(jù)就好,因為VueJS已經(jīng)自動幫我們完成了這些工作。

這真的非常神奇,我不可救藥地愛上了VueJS,并且把它用到我自己的項目當中。隨著使用的深入,我更加想知道它深入的原理是什么。

VueJS是如何進行數(shù)據(jù)綁定的?

通過閱讀官方文檔,我看到了下面這段話:

把一個普通 Javascript 對象傳給 Vue 實例來作為它的 data 選項,Vue 將遍歷它的屬性,用 Object.defineProperty 將它們轉(zhuǎn)為 getter/setter。

關(guān)鍵詞是Object.definProperty,在MDN文檔里面是這么說的:

Object.defineProperty()方法直接定義一個對象的屬性,或者修改對象當中一個已經(jīng)存在的屬性,并返回這個對象。

讓我們寫個例子來測試一下它。

首先,建立一個鋼鐵俠對象并賦予他一些屬性:

 
 
  1. let ironman = { 
  2.   name: 'Tony Stark', 
  3.   sex: 'male', 
  4.   age: '35' 
  5. }  

現(xiàn)在我們使用Object.defineProperty()方法來對他的一些屬性進行修改,并且在控制臺把所修改的內(nèi)容輸出:

 
 
  1. Object.defineProperty(ironman, 'age', { 
  2.   set (val) { 
  3.     console.log(`Set age to ${val}`) 
  4.     return val 
  5.   } 
  6. }) 
  7.  
  8. ironman.age = '48' 
  9. // --> Set age to 48  

看起來挺完美的。如果把console.log('Set age to ${val}')改為element.innerHTML = val,是不是就意味著數(shù)據(jù)綁定已經(jīng)完成了呢?

讓我們再修改一下鋼鐵俠的屬性:

 
 
  1. let ironman = { 
  2.   name: 'Tony Stark', 
  3.   sex: 'male', 
  4.   age: '35', 
  5.   hobbies: ['girl', 'money', 'game'] 
  6. }  

嗯……他就是一個花花公子?,F(xiàn)在我想把一些“愛好”添加到他身上,并且在控制臺看到對應(yīng)的輸出:

 
 
  1. Object.defineProperty(ironman.hobbies, 'push', { 
  2.   value () { 
  3.     console.log(`Push ${arguments[0]} to ${this}`) 
  4.     this[this.length] = arguments[0] 
  5.   } 
  6. }) 
  7.  
  8. ironman.hobbies.push('wine') 
  9. console.log(ironman.hobbies) 
  10.  
  11. // --> Push wine to girl,money,game 
  12. // --> [ 'girl', 'money', 'game', 'wine' ]  

在此之前,我是使用get()方法去追蹤對象的屬性變化,但是對于一個數(shù)組,我們不能使用這個方法,而是使用value()方法來代替。雖然這招也靈,但是并非最好的辦法。有沒有更好的方法可以簡化這些追蹤對象或數(shù)組屬性變化的方法呢?

在ECMA2015,Proxy是一個不錯的選擇

什么是Proxy?在MDN文檔中是這么說的(誤):

Proxy可以理解成,在目標對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。

Proxy是ECMA2015的一個新特性,它非常強大,但我并不會討論太多關(guān)于它的東西,除了我們現(xiàn)在需要的一個?,F(xiàn)在讓我們一起來新建一個Proxy實例:

 
 
  1. let ironmanProxy = new Proxy(ironman, { 
  2.   set (target, property, value) { 
  3.     target[property] = value 
  4.     console.log('change....') 
  5.     return true 
  6.   } 
  7. }) 
  8.  
  9. ironmanProxy.age = '48' 
  10. console.log(ironman.age) 
  11.  
  12. // --> change.... 
  13. // --> 48  

符合預期。那么對于數(shù)組呢?

 
 
  1. let ironmanProxy = new Proxy(ironman.hobbies, { 
  2.   set (target, property, value) { 
  3.     target[property] = value 
  4.     console.log('change....') 
  5.     return true 
  6.   } 
  7. }) 
  8.  
  9. ironmanProxy.push('wine') 
  10. console.log(ironman.hobbies) 
  11.  
  12. // --> change... 
  13. // --> change... 
  14. // --> [ 'girl', 'money', 'game', 'wine' ]  

仍然符合預期!但是為什么輸出了兩次change...呢?因為每當我觸發(fā)push()方法的時候,這個數(shù)組的length屬性和body內(nèi)容都被修改了,所以會引起兩次變化。

實時數(shù)據(jù)綁定

解決了最核心的問題,可以考慮其他的問題了。

想象一下,我們有一個模板和數(shù)據(jù)對象:

 
 
  1.  
  2. Hello, my name is {{name}}, I enjoy eatting {{hobbies.food}}

     
  3.  
  4.  
  5. let ironman = { 
  6.   name: 'Tony Stark', 
  7.   sex: 'male', 
  8.   age: '35', 
  9.   hobbies: { 
  10.     food: 'banana', 
  11.     drink: 'wine' 
  12.   } 
  13. }  

通過前面的代碼,我們知道如果想要追蹤一個對象的屬性變化,我們應(yīng)該把這個屬性作為第一個參數(shù)傳入Proxy實例。讓我們一起來創(chuàng)建一個返回新的Proxy實例的函數(shù)吧!

 
 
  1. function $setData (dataObj, fn) { 
  2.     let self = this 
  3.     let once = false 
  4.     let $d = new Proxy(dataObj, { 
  5.       set (target, property, value) { 
  6.         if (!once) { 
  7.           target[property] = value 
  8.           once = true 
  9.           /* Do something here */ 
  10.         } 
  11.         return true 
  12.       } 
  13.     }) 
  14.     fn($d) 
  15.   }  

它可以通過以下的方式被使用:

 
 
  1. $setData(dataObj, ($d) => { 
  2.   /*  
  3.    * dataObj.someProps = something 
  4.    */ 
  5. }) 
  6.  
  7. // 或者 
  8.  
  9. $setData(dataObj.arrayProps, ($d) => { 
  10.   /*  
  11.    * dataObj.push(something) 
  12.    */ 
  13. })  

除此之外,我們應(yīng)該實現(xiàn)模板對數(shù)據(jù)對象的映射,這樣才能用Tony Stark來替換{{name}}。

 
 
  1. function replaceFun(str, data) { 
  2.     let self = this 
  3.     return str.replace(/{{([^{}]*)}}/g, (a, b) => { 
  4.       return data[b] 
  5.     }) 
  6.   } 
  7.  
  8. replaceFun('My name is {{name}}', { name: 'xxx' }) 
  9. // --> My name is xxx  

這個函數(shù)對于如{ name: 'xx', age: 18 }的單層屬性對象運行良好,但是對于如{ hobbies: { food: 'apple', drink: 'milk' } }這樣的多層屬性對象卻無能為力。舉個例子,如果模板關(guān)鍵字是{{hobbies.food}},那么replaceFun()函數(shù)就應(yīng)該返回data['hobbies']['food']。

為了解決這個問題,再來一個函數(shù):

 
 
  1. function getObjProp (obj, propsName) { 
  2.     let propsArr = propsName.split('.') 
  3.     function rec(o, pName) { 
  4.       if (!o[pName] instanceof Array && o[pName] instanceof Object) { 
  5.         return rec(o[pName], propsArr.shift()) 
  6.       } 
  7.       return o[pName] 
  8.     } 
  9.     return rec(obj, propsArr.shift()) 
  10.   } 
  11.  
  12. getObjProp({ data: { hobbies: { food: 'apple', drink: 'milk' } } }, 'hobbies.food') 
  13. // --> return  { food: 'apple', drink: 'milk' }  

最終的replaceFun()函數(shù)應(yīng)該是下面這樣子的:

 
 
  1. function replaceFun(str, data) { 
  2.     let self = this 
  3.     return str.replace(/{{([^{}]*)}}/g, (a, b) => { 
  4.       let r = self._getObjProp(data, b); 
  5.       console.log(a, b, r) 
  6.       if (typeof r === 'string' || typeof r === 'number') { 
  7.         return r 
  8.       } else { 
  9.         return self._getObjProp(r, b.split('.')[1]) 
  10.       } 
  11.     }) 
  12.   }  

一個數(shù)據(jù)綁定的實例,叫做“Mog”

不為什么,就叫做“Mog”。

 
 
  1. class Mog { 
  2.   constructor (options) { 
  3.     this.$data = options.data 
  4.     this.$el = options.el 
  5.     this.$tpl = options.template 
  6.     this._render(this.$tpl, this.$data) 
  7.   } 
  8.  
  9.   $setData (dataObj, fn) { 
  10.     let self = this 
  11.     let once = false 
  12.     let $d = new Proxy(dataObj, { 
  13.       set (target, property, value) { 
  14.         if (!once) { 
  15.           target[property] = value 
  16.           once = true 
  17.           self._render(self.$tpl, self.$data) 
  18.         } 
  19.         return true 
  20.       } 
  21.     }) 
  22.     fn($d) 
  23.   } 
  24.  
  25.   _render (tplString, data) { 
  26.     document.querySelector(this.$el).innerHTML = this._replaceFun(tplString, data) 
  27.   } 
  28.  
  29.   _replaceFun(str, data) { 
  30.     let self = this 
  31.     return str.replace(/{{([^{}]*)}}/g, (a, b) => { 
  32.       let r = self._getObjProp(data, b); 
  33.       console.log(a, b, r) 
  34.       if (typeof r === 'string' || typeof r === 'number') { 
  35.         return r 
  36.       } else { 
  37.         return self._getObjProp(r, b.split('.')[1]) 
  38.       } 
  39.     }) 
  40.   } 
  41.  
  42.   _getObjProp (obj, propsName) { 
  43.     let propsArr = propsName.split('.') 
  44.     function rec(o, pName) { 
  45.       if (!o[pName] instanceof Array && o[pName] instanceof Object) { 
  46.         return rec(o[pName], propsArr.shift()) 
  47.       } 
  48.       return o[pName] 
  49.     } 
  50.     return rec(obj, propsArr.shift()) 
  51.   } 
  52.  
  53. }  

使用:

 
 
  1.  
  2.  
  3.      
  4.       

     

  5.         Hello everyone, my name is {{name}}, I am a mini {{lang}} framework for just {{work}}. I can bind data from {{supports.0}}{{supports.1}} and {{supports.2}}. What's more, I was created by {{info.author}}, and was written in {{info.jsVersion}}. My motto is "{{motto}}". 
  6.       

     
  7.     
 
  •      
  •       Motto:      
  •    
     
     
    1.  
    2.  
    3. let template = document.querySelector('#app').innerHTML 
    4.  
    5. let mog = new Mog({ 
    6.   template: template, 
    7.   el: '#app', 
    8.   data: { 
    9.     name: 'mog', 
    10.     lang: 'javascript', 
    11.     work: 'data binding', 
    12.     supports: ['String', 'Array', 'Object'], 
    13.     info: { 
    14.       author: 'Jrain', 
    15.       jsVersion: 'Ecma2015' 
    16.     }, 
    17.     motto: 'Every dog has his day' 
    18.   } 
    19. }) 
    20.  
    21. document.querySelector('#set-motto').oninput = (e) => { 
    22.   mog.$setData(mog.$data, ($d) => { 
    23.     $d.motto = e.target.value 
    24.   }) 
    25. }  

    你可以在這里進行在線體驗。

    后記

    Mog僅僅是一個用于學習數(shù)據(jù)綁定的實驗性質(zhì)的項目,代碼仍然不夠優(yōu)雅,功能也不夠豐富。但是這個小玩具讓我學習了很多。如果你對它有興趣,歡迎到這里把項目fork走,并且加入一些你的想法。

    感謝閱讀!


    本文名稱:使用ES6的新特性Proxy來實現(xiàn)一個數(shù)據(jù)綁定實例
    文章出自:http://m.5511xx.com/article/dpodosc.html