ES6使用新特性Proxy實現(xiàn)的數(shù)據(jù)綁定功能實例
本文實例講述了ES6使用新特性Proxy實現(xiàn)的數(shù)據(jù)綁定功能。分享給大家供大家參考,具體如下:
項目地址:https://github.com/jrainlau/mog
在線體驗:https://codepen.io/jrainlau/pen/YpyBBY
作為一個前端開發(fā)者,曾踩了太多的“數(shù)據(jù)綁定”的坑。在早些時候,都是通過jQuery之類的工具手動完成這些功能,但是當(dāng)數(shù)據(jù)量非常大的時候,這些手動的工作讓我非常痛苦。直到使用了VueJS,這些痛苦才得以終結(jié)。
VueJS的其中一個賣點,就是“數(shù)據(jù)綁定”。使用者無需關(guān)心數(shù)據(jù)是怎么綁定到dom上面的,只需要關(guān)注數(shù)據(jù)就好,因為VueJS已經(jīng)自動幫我們完成了這些工作。
這真的非常神奇,我不可救藥地愛上了VueJS,并且把它用到我自己的項目當(dāng)中。隨著使用的深入,我更加想知道它深入的原理是什么。
VueJS是如何進行數(shù)據(jù)綁定的?
通過閱讀官方文檔,我看到了下面這段話:
把一個普通 Javascript 對象傳給 Vue 實例來作為它的 data 選項,Vue 將遍歷它的屬性,用 Object.defineProperty 將它們轉(zhuǎn)為 getter/setter。
關(guān)鍵詞是Object.definProperty,在MDN文檔里面是這么說的:
Object.defineProperty()方法直接定義一個對象的屬性,或者修改對象當(dāng)中一個已經(jīng)存在的屬性,并返回這個對象。
讓我們寫個例子來測試一下它。
首先,建立一個鋼鐵俠對象并賦予他一些屬性:
let ironman = {
name: 'Tony Stark',
sex: 'male',
age: '35'
}
現(xiàn)在我們使用Object.defineProperty()方法來對他的一些屬性進行修改,并且在控制臺把所修改的內(nèi)容輸出:
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,是不是就意味著數(shù)據(jù)綁定已經(jīng)完成了呢?
讓我們再修改一下鋼鐵俠的屬性:
let ironman = {
name: 'Tony Stark',
sex: 'male',
age: '35',
hobbies: ['girl', 'money', 'game']
}
嗯……他就是一個花花公子。現(xiàn)在我想把一些“愛好”添加到他身上,并且在控制臺看到對應(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()方法去追蹤對象的屬性變化,但是對于一個數(shù)組,我們不能使用這個方法,而是使用value()方法來代替。雖然這招也靈,但是并非最好的辦法。有沒有更好的方法可以簡化這些追蹤對象或數(shù)組屬性變化的方法呢?
在ECMA2015,Proxy是一個不錯的選擇
什么是Proxy?在MDN文檔中是這么說的(誤):
Proxy可以理解成,在目標(biāo)對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
Proxy是ECMA2015的一個新特性,它非常強大,但我并不會討論太多關(guān)于它的東西,除了我們現(xiàn)在需要的一個?,F(xiàn)在讓我們一起來新建一個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
符合預(yù)期。那么對于數(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...呢?因為每當(dāng)我觸發(fā)push()方法的時候,這個數(shù)組的length屬性和body內(nèi)容都被修改了,所以會引起兩次變化。
實時數(shù)據(jù)綁定
解決了最核心的問題,可以考慮其他的問題了。
想象一下,我們有一個模板和數(shù)據(jù)對象:
<!-- 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'
}
}
通過前面的代碼,我們知道如果想要追蹤一個對象的屬性變化,我們應(yīng)該把這個屬性作為第一個參數(shù)傳入Proxy實例。讓我們一起來創(chuàng)建一個返回新的Proxy實例的函數(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)
}
它可以通過以下的方式被使用:
$setData(dataObj, ($d) => {
/*
* dataObj.someProps = something
*/
})
// 或者
$setData(dataObj.arrayProps, ($d) => {
/*
* dataObj.push(something)
*/
})
除此之外,我們應(yīng)該實現(xiàn)模板對數(shù)據(jù)對象的映射,這樣才能用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
這個函數(shù)對于如{ name: 'xx', age: 18 }的單層屬性對象運行良好,但是對于如{ hobbies: { food: 'apple', drink: 'milk' } }這樣的多層屬性對象卻無能為力。舉個例子,如果模板關(guān)鍵字是{{hobbies.food}},那么replaceFun()函數(shù)就應(yīng)該返回data['hobbies']['food']。
為了解決這個問題,再來一個函數(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])
}
})
}
一個數(shù)據(jù)綁定的實例,叫做“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
})
}
你可以在這里進行在線體驗。
后記
Mog僅僅是一個用于學(xué)習(xí)數(shù)據(jù)綁定的實驗性質(zhì)的項目,代碼仍然不夠優(yōu)雅,功能也不夠豐富。但是這個小玩具讓我學(xué)習(xí)了很多。如果你對它有興趣,歡迎到這里把項目fork走,并且加入一些你的想法。
感謝閱讀!
感興趣的朋友可以使用在線HTML/CSS/JavaScript代碼運行工具:http://tools.jb51.net/code/HtmlJsRun測試上述代碼運行效果。
更多關(guān)于JavaScript相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《javascript面向?qū)ο笕腴T教程》、《JavaScript錯誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運算用法總結(jié)》
希望本文所述對大家JavaScript程序設(shè)計有所幫助。
相關(guān)文章
JS動畫實現(xiàn)回調(diào)地獄promise的實例代碼詳解
這篇文章主要介紹了JS動畫實現(xiàn)回調(diào)地獄promise的實例代碼詳解,需要的朋友可以參考下2018-11-11
Cropper.js入門之在HTML中實現(xiàn)交互式圖像裁剪
這篇文章主要為大家介紹了Cropper.js入門之在HTML中實現(xiàn)交互式圖像裁剪示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05
JS基于開關(guān)思想實現(xiàn)的數(shù)組去重功能【案例】
這篇文章主要介紹了JS基于開關(guān)思想實現(xiàn)的數(shù)組去重功能,簡單分析了開關(guān)思想的原理,并結(jié)合具體實例形式分析了javascript基于開關(guān)思想實現(xiàn)數(shù)組去重相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
探索JavaScript函數(shù)的無限可能(函數(shù)基本概念)
JavaScript中的函數(shù)是一種重要的編程概念,它允許我們封裝可重用的代碼塊,并在需要時進行調(diào)用,本文將深入介紹JavaScript函數(shù)的各個方面,包括函數(shù)定義和調(diào)用、參數(shù)和返回值、作用域和閉包、高階函數(shù)以及常見的函數(shù)應(yīng)用場景,感興趣的朋友一起看看吧2023-08-08
Javascript獲取background屬性中url的值
Javascript獲取CSS中屬性值方法有很多,今天工作的時候遇到了一個問題是需要利用Javascript獲取css中background-img屬性url的值,通過查閱網(wǎng)站的資料找到了解決方法,現(xiàn)在將方法分享給大家,有需要的朋友們們可以參考借鑒,下面來一起看看吧。2016-10-10

