您好,登錄后才能下訂單哦!
這篇文章主要講解了ES6使用新特性Proxy如何實現數據綁定功能,內容清晰明了,對此有興趣的小伙伴可以學習一下,相信大家閱讀完之后會有幫助。
作為一個前端開發者,曾踩了太多的“數據綁定”的坑。在早些時候,都是通過jQuery
之類的工具手動完成這些功能,但是當數據量非常大的時候,這些手動的工作讓我非常痛苦。直到使用了VueJS
,這些痛苦才得以終結。
VueJS
的其中一個賣點,就是“數據綁定”。使用者無需關心數據是怎么綁定到dom上面的,只需要關注數據就好,因為VueJS
已經自動幫我們完成了這些工作。
這真的非常神奇,我不可救藥地愛上了VueJS
,并且把它用到我自己的項目當中。隨著使用的深入,我更加想知道它深入的原理是什么。
通過閱讀官方文檔,我看到了下面這段話:
把一個普通 Javascript 對象傳給 Vue 實例來作為它的 data 選項,Vue 將遍歷它的屬性,用 Object.defineProperty 將它們轉為 getter/setter。
關鍵詞是Object.definProperty
,在MDN文檔里面是這么說的:
Object.defineProperty()
方法直接定義一個對象的屬性,或者修改對象當中一個已經存在的屬性,并返回這個對象。
讓我們寫個例子來測試一下它。
首先,建立一個鋼鐵俠對象并賦予他一些屬性:
let ironman = { name: 'Tony Stark', sex: 'male', age: '35' }
現在我們使用Object.defineProperty()
方法來對他的一些屬性進行修改,并且在控制臺把所修改的內容輸出:
Object.defineProperty(ironman, 'age', { set (val) { console.log(`Set age to ${val}`) return val } }) ironman.age = '48' // --> Set age to 48
看起來挺完美的。如果把console.log('Set age to ${val}')
改為element.innerHTML = val
,是不是就意味著數據綁定已經完成了呢?
讓我們再修改一下鋼鐵俠的屬性:
let ironman = { name: 'Tony Stark', sex: 'male', age: '35', hobbies: ['girl', 'money', 'game'] }
嗯……他就是一個花花公子。現在我想把一些“愛好”添加到他身上,并且在控制臺看到對應的輸出:
Object.defineProperty(ironman.hobbies, 'push', { value () { console.log(`Push ${arguments[0]} to ${this}`) this[this.length] = arguments[0] } }) ironman.hobbies.push('wine') console.log(ironman.hobbies) // --> Push wine to girl,money,game // --> [ 'girl', 'money', 'game', 'wine' ]
在此之前,我是使用get()
方法去追蹤對象的屬性變化,但是對于一個數組,我們不能使用這個方法,而是使用value()
方法來代替。雖然這招也靈,但是并非最好的辦法。有沒有更好的方法可以簡化這些追蹤對象或數組屬性變化的方法呢?
什么是Proxy
?在MDN文檔中是這么說的(誤):
Proxy可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
Proxy
是ECMA2015的一個新特性,它非常強大,但我并不會討論太多關于它的東西,除了我們現在需要的一個。現在讓我們一起來新建一個Proxy實例:
let ironmanProxy = new Proxy(ironman, { set (target, property, value) { target[property] = value console.log('change....') return true } }) ironmanProxy.age = '48' console.log(ironman.age) // --> change.... // --> 48
符合預期。那么對于數組呢?
let ironmanProxy = new Proxy(ironman.hobbies, { set (target, property, value) { target[property] = value console.log('change....') return true } }) ironmanProxy.push('wine') console.log(ironman.hobbies) // --> change... // --> change... // --> [ 'girl', 'money', 'game', 'wine' ]
仍然符合預期!但是為什么輸出了兩次change...
呢?因為每當我觸發push()
方法的時候,這個數組的length
屬性和body
內容都被修改了,所以會引起兩次變化。
解決了最核心的問題,可以考慮其他的問題了。
想象一下,我們有一個模板和數據對象:
<!-- html template --> <p>Hello, my name is {{name}}, I enjoy eatting {{hobbies.food}}</p> <!-- javascript --> let ironman = { name: 'Tony Stark', sex: 'male', age: '35', hobbies: { food: 'banana', drink: 'wine' } }
通過前面的代碼,我們知道如果想要追蹤一個對象的屬性變化,我們應該把這個屬性作為第一個參數傳入Proxy
實例。讓我們一起來創建一個返回新的Proxy
實例的函數吧!
function $setData (dataObj, fn) { let self = this let once = false let $d = new Proxy(dataObj, { set (target, property, value) { if (!once) { target[property] = value once = true /* Do something here */ } return true } }) fn($d) }
它可以通過以下的方式被使用:
$setData(dataObj, ($d) => { /* * dataObj.someProps = something */ }) // 或者 $setData(dataObj.arrayProps, ($d) => { /* * dataObj.push(something) */ })
除此之外,我們應該實現模板對數據對象的映射,這樣才能用Tony Stark
來替換{{name}}
。
function replaceFun(str, data) { let self = this return str.replace(/{{([^{}]*)}}/g, (a, b) => { return data[b] }) } replaceFun('My name is {{name}}', { name: 'xxx' }) // --> My name is xxx
這個函數對于如{ name: 'xx', age: 18 }
的單層屬性對象運行良好,但是對于如{ hobbies: { food: 'apple', drink: 'milk' } }
這樣的多層屬性對象卻無能為力。舉個例子,如果模板關鍵字是{{hobbies.food}}
,那么replaceFun()
函數就應該返回data['hobbies']['food']
。
為了解決這個問題,再來一個函數:
function getObjProp (obj, propsName) { let propsArr = propsName.split('.') function rec(o, pName) { if (!o[pName] instanceof Array && o[pName] instanceof Object) { return rec(o[pName], propsArr.shift()) } return o[pName] } return rec(obj, propsArr.shift()) } getObjProp({ data: { hobbies: { food: 'apple', drink: 'milk' } } }, 'hobbies.food') // --> return { food: 'apple', drink: 'milk' }
最終的replaceFun()
函數應該是下面這樣子的:
function replaceFun(str, data) { let self = this return str.replace(/{{([^{}]*)}}/g, (a, b) => { let r = self._getObjProp(data, b); console.log(a, b, r) if (typeof r === 'string' || typeof r === 'number') { return r } else { return self._getObjProp(r, b.split('.')[1]) } }) }
不為什么,就叫做“Mog”。
class Mog { constructor (options) { this.$data = options.data this.$el = options.el this.$tpl = options.template this._render(this.$tpl, this.$data) } $setData (dataObj, fn) { let self = this let once = false let $d = new Proxy(dataObj, { set (target, property, value) { if (!once) { target[property] = value once = true self._render(self.$tpl, self.$data) } return true } }) fn($d) } _render (tplString, data) { document.querySelector(this.$el).innerHTML = this._replaceFun(tplString, data) } _replaceFun(str, data) { let self = this return str.replace(/{{([^{}]*)}}/g, (a, b) => { let r = self._getObjProp(data, b); console.log(a, b, r) if (typeof r === 'string' || typeof r === 'number') { return r } else { return self._getObjProp(r, b.split('.')[1]) } }) } _getObjProp (obj, propsName) { let propsArr = propsName.split('.') function rec(o, pName) { if (!o[pName] instanceof Array && o[pName] instanceof Object) { return rec(o[pName], propsArr.shift()) } return o[pName] } return rec(obj, propsArr.shift()) } }
使用:
<!-- html --> <div id="app"> <p> 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>". </p> </div> <div id="input-wrapper"> Motto: <input type="text" id="set-motto" autofocus> </div>
<!-- javascript --> let template = document.querySelector('#app').innerHTML let mog = new Mog({ template: template, el: '#app', data: { name: 'mog', lang: 'javascript', work: 'data binding', supports: ['String', 'Array', 'Object'], info: { author: 'Jrain', jsVersion: 'Ecma2015' }, motto: 'Every dog has his day' } }) document.querySelector('#set-motto').oninput = (e) => { mog.$setData(mog.$data, ($d) => { $d.motto = e.target.value }) }
你可以在這里進行在線體驗。
Mog
僅僅是一個用于學習數據綁定的實驗性質的項目,代碼仍然不夠優雅,功能也不夠豐富。但是這個小玩具讓我學習了很多。如果你對它有興趣,歡迎到這里把項目fork走,并且加入一些你的想法。
看完上述內容,是不是對ES6使用新特性Proxy如何實現數據綁定功能有進一步的了解,如果還想學習更多內容,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。