自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用ES6的新特性Proxy來實(shí)現(xiàn)一個數(shù)據(jù)綁定實(shí)例

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

[[176173]]

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

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

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

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

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

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

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

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

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

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

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

現(xiàn)在我們使用Object.defineProperty()方法來對他的一些屬性進(jìn)行修改,并且在控制臺把所修改的內(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可以理解成,在目標(biāo)對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對外界的訪問進(jìn)行過濾和改寫。

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

  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  

符合預(yù)期。那么對于數(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' ]  

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

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

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

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

  1. <!-- html template --> 
  2. <p>Hello, my name is {{name}}, I enjoy eatting {{hobbies.food}}</p> 
  3.  
  4. <!-- javascript --> 
  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實(shí)例。讓我們一起來創(chuàng)建一個返回新的Proxy實(shí)例的函數(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)該實(shí)現(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 }的單層屬性對象運(yùn)行良好,但是對于如{ 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ù)綁定的實(shí)例,叫做“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. <!-- html --> 
  2.  
  3.     <div id="app"
  4.       <p> 
  5.         Hello everyone, my name is <span>{{name}}</span>, I am a mini <span>{{lang}}</span> framework for just <span>{{work}}</span>. I can bind data from <span>{{supports.0}}</span>, <span>{{supports.1}}</span> and <span>{{supports.2}}</span>. What's more, I was created by <span>{{info.author}}</span>, and was written in <span>{{info.jsVersion}}</span>. My motto is "<span>{{motto}}</span>"
  6.       </p> 
  7.     </div> 
  8.     <div id="input-wrapper"
  9.       Motto: <input type="text" id="set-motto" autofocus>     </div>   
  1. <!-- javascript --> 
  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.  

你可以在這里進(jìn)行在線體驗(yàn)。

后記

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

感謝閱讀!

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2023-11-23 10:21:11

ECMAScriptJavaScript

2017-08-31 14:25:34

前端JavascriptES6

2020-07-01 07:58:20

ES6JavaScript開發(fā)

2016-01-21 11:05:52

ES6JavaScript新特性

2021-12-13 11:54:13

SetEs6接口

2023-03-01 15:39:50

JavaScrip對象屬性ES6

2023-05-28 23:49:38

JavaScrip開發(fā)

2022-06-01 09:06:58

ES6數(shù)組函數(shù)

2020-02-06 09:00:00

ESES2020前端

2012-12-20 11:16:16

IBMdW

2024-05-09 08:08:32

SpringBinderJava

2022-08-05 13:14:25

ES2022JavaScript代碼

2021-08-16 07:05:58

ES6Promise開發(fā)語言

2024-06-18 08:46:06

2015-09-16 10:17:42

Node.jsES 6特性

2012-06-13 10:26:21

iOS 6

2020-11-16 08:10:04

ES6迭代器JavaScript

2022-09-21 12:46:39

開發(fā)JavaScrip代碼

2009-11-02 15:15:43

Oracle授予用戶權(quán)

2017-06-03 15:43:54

數(shù)據(jù)項(xiàng)目框架
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號