ES6使用新特性Proxy實(shí)現(xiàn)的數(shù)據(jù)綁定功能實(shí)例
本文實(shí)例講述了ES6使用新特性Proxy實(shí)現(xiàn)的數(shù)據(jù)綁定功能。分享給大家供大家參考,具體如下:
項(xiàng)目地址:https://github.com/jrainlau/mog
在線體驗(yàn):https://codepen.io/jrainlau/pen/YpyBBY
作為一個(gè)前端開(kāi)發(fā)者,曾踩了太多的“數(shù)據(jù)綁定”的坑。在早些時(shí)候,都是通過(guò)jQuery
之類的工具手動(dòng)完成這些功能,但是當(dāng)數(shù)據(jù)量非常大的時(shí)候,這些手動(dòng)的工作讓我非常痛苦。直到使用了VueJS
,這些痛苦才得以終結(jié)。
VueJS
的其中一個(gè)賣點(diǎn),就是“數(shù)據(jù)綁定”。使用者無(wú)需關(guān)心數(shù)據(jù)是怎么綁定到dom上面的,只需要關(guān)注數(shù)據(jù)就好,因?yàn)?code>VueJS已經(jīng)自動(dòng)幫我們完成了這些工作。
這真的非常神奇,我不可救藥地愛(ài)上了VueJS
,并且把它用到我自己的項(xiàng)目當(dāng)中。隨著使用的深入,我更加想知道它深入的原理是什么。
VueJS是如何進(jìn)行數(shù)據(jù)綁定的?
通過(guò)閱讀官方文檔,我看到了下面這段話:
把一個(gè)普通 Javascript 對(duì)象傳給 Vue 實(shí)例來(lái)作為它的 data 選項(xiàng),Vue 將遍歷它的屬性,用 Object.defineProperty 將它們轉(zhuǎn)為 getter/setter。
關(guān)鍵詞是Object.definProperty
,在MDN文檔里面是這么說(shuō)的:
Object.defineProperty()
方法直接定義一個(gè)對(duì)象的屬性,或者修改對(duì)象當(dāng)中一個(gè)已經(jīng)存在的屬性,并返回這個(gè)對(duì)象。
讓我們寫個(gè)例子來(lái)測(cè)試一下它。
首先,建立一個(gè)鋼鐵俠對(duì)象并賦予他一些屬性:
let ironman = { name: 'Tony Stark', sex: 'male', age: '35' }
現(xiàn)在我們使用Object.defineProperty()
方法來(lái)對(duì)他的一些屬性進(jìn)行修改,并且在控制臺(tái)把所修改的內(nèi)容輸出:
Object.defineProperty(ironman, 'age', { set (val) { console.log(`Set age to ${val}`) return val } }) ironman.age = '48' // --> Set age to 48
看起來(lái)挺完美的。如果把console.log('Set age to ${val}')
改為element.innerHTML = val
,是不是就意味著數(shù)據(jù)綁定已經(jīng)完成了呢?
讓我們?cè)傩薷囊幌落撹F俠的屬性:
let ironman = { name: 'Tony Stark', sex: 'male', age: '35', hobbies: ['girl', 'money', 'game'] }
嗯……他就是一個(gè)花花公子?,F(xiàn)在我想把一些“愛(ài)好”添加到他身上,并且在控制臺(tái)看到對(duì)應(yīng)的輸出:
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()
方法去追蹤對(duì)象的屬性變化,但是對(duì)于一個(gè)數(shù)組,我們不能使用這個(gè)方法,而是使用value()
方法來(lái)代替。雖然這招也靈,但是并非最好的辦法。有沒(méi)有更好的方法可以簡(jiǎn)化這些追蹤對(duì)象或數(shù)組屬性變化的方法呢?
在ECMA2015,Proxy是一個(gè)不錯(cuò)的選擇
什么是Proxy
?在MDN文檔中是這么說(shuō)的(誤):
Proxy可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫。
Proxy
是ECMA2015的一個(gè)新特性,它非常強(qiáng)大,但我并不會(huì)討論太多關(guān)于它的東西,除了我們現(xiàn)在需要的一個(gè)?,F(xiàn)在讓我們一起來(lái)新建一個(gè)Proxy實(shí)例:
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
符合預(yù)期。那么對(duì)于數(shù)組呢?
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' ]
仍然符合預(yù)期!但是為什么輸出了兩次change...
呢?因?yàn)槊慨?dāng)我觸發(fā)push()
方法的時(shí)候,這個(gè)數(shù)組的length
屬性和body
內(nèi)容都被修改了,所以會(huì)引起兩次變化。
實(shí)時(shí)數(shù)據(jù)綁定
解決了最核心的問(wèn)題,可以考慮其他的問(wèn)題了。
想象一下,我們有一個(gè)模板和數(shù)據(jù)對(duì)象:
<!-- 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' } }
通過(guò)前面的代碼,我們知道如果想要追蹤一個(gè)對(duì)象的屬性變化,我們應(yīng)該把這個(gè)屬性作為第一個(gè)參數(shù)傳入Proxy
實(shí)例。讓我們一起來(lái)創(chuàng)建一個(gè)返回新的Proxy
實(shí)例的函數(shù)吧!
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) }
它可以通過(guò)以下的方式被使用:
$setData(dataObj, ($d) => { /* * dataObj.someProps = something */ }) // 或者 $setData(dataObj.arrayProps, ($d) => { /* * dataObj.push(something) */ })
除此之外,我們應(yīng)該實(shí)現(xiàn)模板對(duì)數(shù)據(jù)對(duì)象的映射,這樣才能用Tony Stark
來(lái)替換{{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
這個(gè)函數(shù)對(duì)于如{ name: 'xx', age: 18 }
的單層屬性對(duì)象運(yùn)行良好,但是對(duì)于如{ hobbies: { food: 'apple', drink: 'milk' } }
這樣的多層屬性對(duì)象卻無(wú)能為力。舉個(gè)例子,如果模板關(guān)鍵字是{{hobbies.food}}
,那么replaceFun()
函數(shù)就應(yīng)該返回data['hobbies']['food']
。
為了解決這個(gè)問(wèn)題,再來(lái)一個(gè)函數(shù):
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()
函數(shù)應(yīng)該是下面這樣子的:
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]) } }) }
一個(gè)數(shù)據(jù)綁定的實(shí)例,叫做“Mog”
不為什么,就叫做“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 }) }
你可以在這里進(jìn)行在線體驗(yàn)。
后記
Mog
僅僅是一個(gè)用于學(xué)習(xí)數(shù)據(jù)綁定的實(shí)驗(yàn)性質(zhì)的項(xiàng)目,代碼仍然不夠優(yōu)雅,功能也不夠豐富。但是這個(gè)小玩具讓我學(xué)習(xí)了很多。如果你對(duì)它有興趣,歡迎到這里把項(xiàng)目fork走,并且加入一些你的想法。
感謝閱讀!
感興趣的朋友可以使用在線HTML/CSS/JavaScript代碼運(yùn)行工具:http://tools.jb51.net/code/HtmlJsRun測(cè)試上述代碼運(yùn)行效果。
更多關(guān)于JavaScript相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《javascript面向?qū)ο笕腴T教程》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
相關(guān)文章
javascript html5輕松實(shí)現(xiàn)拖動(dòng)功能
這篇文章主要為大家詳細(xì)介紹了javascript html5輕松實(shí)現(xiàn)拖動(dòng)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03查看圖片(前進(jìn)后退)功能實(shí)現(xiàn)js代碼
前進(jìn)后退實(shí)現(xiàn)的前提是:images文件夾下圖片的命名是從1~5.jpg有規(guī)律的,感興趣的朋友可以參考下哈,希望可以幫助到你2013-04-04Javascript訪問(wèn)html頁(yè)面的控件的方法詳細(xì)分析
這段時(shí)間在公司比較的空閑,決定研究研究javascript訪問(wèn)html控件,這是很普遍的,這里我系統(tǒng)的研究javascript的訪問(wèn)方式,測(cè)試通過(guò)并有下面一些研究成就,供大家分享和補(bǔ)充。2008-08-08javascript 極速 隱藏/顯示萬(wàn)行表格列只需 60毫秒
隱藏表格列 這種方式的效率極低。例如,隱藏一個(gè)千行表格的某列,在我的筆記本(P4 M 1.4G,768M內(nèi)存)上執(zhí)行需要約 4000毫秒的時(shí)間,令人無(wú)法忍受。2009-03-03關(guān)于javascript中this關(guān)鍵字(翻譯+自我理解)
在傳統(tǒng)面向?qū)ο笳Z(yǔ)言中,this關(guān)鍵字是個(gè)很乖的小孩,從不亂跑,該是誰(shuí)的就是誰(shuí)的??墒窃贘avaScript中,我們發(fā)現(xiàn)它不那么乖,有時(shí)甚至把我們搞的暈頭轉(zhuǎn)向的。所以有必要對(duì)它稍微做個(gè)總結(jié)。2010-10-10