如果你在學習一種前端框架,如vue、angular等,那麼你一定不會對數據的單向綁定陌生。
何為數據的單向綁定?
傳統開發模式下,如使用jQuery開發,我們想將一個變量顯示到html中,首先要定義一個變量name,然後通過jq代碼操作dom將變量放到HTML中,如果name發生修改,還要再次通過jq代碼操作dom將新的變量值放到HTML中。這就是傳統的MVC框架,其中的Model和View是我們通過代碼聯繫在一起的。
在MVVM框架中,我們不再過多的關注數據與視圖間的操作,而是使用一種新的機制,數據的單/雙向綁定。
我在剛接觸angular的時候感覺這個綁定的機制簡直不要再神奇,但是當慢慢深入學習後發現其實原理非常簡單,通過簡短的js代碼就可以實現一個簡單的數據單向綁定。
Proxy對象
Proxy可以當做在目標對象之前架設一層攔截,或者説是代理。任何對該對象的訪問都要先經過這個代理。那麼也就是説我們可以通過Proxy對象攔截到外界對一個對象的訪問。
ES6中將Proxy標準化了,提供了Proxy構造函數,用來生成Proxy實例。下面就是官方的定義:
let p = new Proxy(target, handler);
- target:用Proxy包裝的目標對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)
- handler:一個對象,其屬性是當執行一個操作時定義代理的行為的函數。
再看一個栗子:
let p = new Proxy({}, {
get: function(target, name){
return name in target ? target[name] : 37;
}
});
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
栗子中通過Proxy對象攔截了p對象的訪問,當對象中不存在屬性名時返回37,如果有就返回屬性值。
到這已經瞭解了Proxy對象的工作方式,我們就要用Proxy來做點事了。
單向綁定
首先
如果你接觸過一些mvvm框架,那麼一定會對下面的代碼非常熟悉
<div id="app">
姓名:{{name}}
<br> 年齡:{{age}}
</div>
“{{}}”叫做插值表達式,vue、angular中都是使用這種方式進行數據的單向綁定。在這我們也使用這種格式綁定數據。
然後
let el = document.getElementById('app');
let template = el.innerHTML;
上面兩步,首先獲取到根元素,然後將根元素下的html保存下來。這裏為什麼要保存原始的html呢?
因為接下來的數據變化會重新編譯標籤,既然要重新編譯,那麼就要保留最初的狀態,否則編譯一次後,第二次就無法正常編譯了。
再然後
let _data = {
name: '_BuzzLy',
age: 25
};
定義一個_data對象,這個對象是給我們接下來創建的Proxy對象用的,並不是暴露出去供訪問的。
接下來
let data = new Proxy(_data, {
// 試圖設置數據時調用
// 參數:_data,屬性名,值
set(obj, name, value) {
obj[name] = value;
// 數據變了
console.log(`數據變了,設置 ${name}=>${value}`);
// 數據改變後重新渲染
render();
},
// 試圖獲取數據的時調用,默認要什麼就返回什麼
// get() {}
});
這步我們創建一個data,這個data就是對外的,是一個Proxy對象。
Proxy是原生的對象,可以將真正的數據對象隱藏,我們修改的是代理對象。
相當於我們想修改_data一定要經過一步代理,告訴代理我們要修改的對象及修改的值,然後代理去幫我們操作。
在修改完成後調用render函數去重新渲染視圖。
最後
上面已經完成了最重要的部分,當一個數據發生改變後調用了render函數,這個函數就是將改變後的數據重新渲染到視圖中去。
function render() {
el.innerHTML = template.replace(/\{\{w+\}\}/g, str => {
console.log(str); // 匹配出來的 {{name}} {{age}}
// 截取字符串,得到屬性key值
str = str.substring(2, str.length - 2);
// 從真實數據中拿到對應屬性的值返回,替換{{key}}
return _data[str];
})
}
這裏的實現很簡單,就是匹配到html中{{key}},然後拿到key,再去_data中找到值即可。
注意這裏的template,這就是我們在上面為什麼要保存一份原始的html原因。
現在你可以去嘗試手動修改data的值看看效果了。
data.name = 'halo wode';
data.age = 18;
到這一個最簡單的數據單向綁定就實現了。
完整代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
姓名:{{name}}
<br> 年齡:{{age}}
</div>
</body>
<script>
let el = document.getElementById('app');
let template = el.innerHTML;
let _data = {
name: '_BuzzLy',
age: 25
};
let data = new Proxy(_data, {
set(obj, name, value) {
obj[name] = value;
render();
}
});
render();
function render() {
el.innerHTML = template.replace(/\{\{\w+\}\}/g, str => {
str = str.substring(2, str.length - 2);
return _data[str];
});
}
</script>
</html>