成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

使用ES6的新特性Proxy來實現一個數據綁定實例

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

[[176173]]

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

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

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

VueJS是如何進行數據綁定的?

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

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

關鍵詞是Object.definProperty,在MDN文檔里面是這么說的:

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

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

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

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

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

  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,是不是就意味著數據綁定已經完成了呢?

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

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

嗯……他就是一個花花公子。現在我想把一些“愛好”添加到他身上,并且在控制臺看到對應的輸出:

  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()方法去追蹤對象的屬性變化,但是對于一個數組,我們不能使用這個方法,而是使用value()方法來代替。雖然這招也靈,但是并非最好的辦法。有沒有更好的方法可以簡化這些追蹤對象或數組屬性變化的方法呢?

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

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

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

Proxy是ECMA2015的一個新特性,它非常強大,但我并不會討論太多關于它的東西,除了我們現在需要的一個。現在讓我們一起來新建一個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  

符合預期。那么對于數組呢?

  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...呢?因為每當我觸發push()方法的時候,這個數組的length屬性和body內容都被修改了,所以會引起兩次變化。

實時數據綁定

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

想象一下,我們有一個模板和數據對象:

  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.  

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

  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. })  

除此之外,我們應該實現模板對數據對象的映射,這樣才能用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  

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

為了解決這個問題,再來一個函數:

  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()函數應該是下面這樣子的: 

  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.   }  

一個數據綁定的實例,叫做“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.  

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

后記

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

感謝閱讀!

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2023-11-23 10:21:11

ECMAScriptJavaScript

2020-07-01 07:58:20

ES6JavaScript開發

2017-08-31 14:25:34

前端JavascriptES6

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開發

2022-06-01 09:06:58

ES6數組函數

2020-02-06 09:00:00

ESES2020前端

2024-05-09 08:08:32

SpringBinderJava

2022-08-05 13:14:25

ES2022JavaScript代碼

2012-12-20 11:16:16

IBMdW

2012-06-13 10:26:21

iOS 6

2015-09-16 10:17:42

Node.jsES 6特性

2024-06-18 08:46:06

2009-11-02 15:15:43

Oracle授予用戶權

2021-09-01 10:37:25

鴻蒙HarmonyOS應用

2017-06-03 15:43:54

數據項目框架

2021-08-16 07:05:58

ES6Promise開發語言

2021-02-22 17:17:38

Proxy緩存代碼
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 色网站视频 | 一级看片免费视频 | 国产精品污污视频 | 久久久精品 | 99成人免费视频 | 天天色天天射天天干 | 久久激情网 | 成年人网站在线观看视频 | 91久久国产综合久久91精品网站 | 亚洲精品专区 | 亚洲三区在线观看 | 亚洲乱码一区二区三区在线观看 | 午夜精品久久久久久久久久久久 | 中文字幕亚洲精品 | 九九精品在线 | 天天爱av| 亚洲性综合网 | 国产精品久久久久久久久久免费看 | 91av在线视频观看 | av播播| 免费国产视频 | 99精品久久| 在线亚洲一区 | 免费一级做a爰片久久毛片潮喷 | 久久av一区二区 | 欧美综合一区二区三区 | 欧美自拍视频 | 久久精品视频91 | 中文成人无字幕乱码精品 | 一级国产精品一级国产精品片 | 精品久久国产老人久久综合 | 欧美精品中文字幕久久二区 | 亚洲一区二区三区高清 | 国产精品7777777| 91精品久久久久久久久久入口 | 欧美日产国产成人免费图片 | 午夜影院| 午夜影院网站 | 青青激情网| 一级毛片播放 | 91精品国产91久久久久久吃药 |