數據代理、模板解析、數據綁定、雙向數據綁定【實踐】

一:數據代理

1.什麼是數據代理
  • 數據代理是指通過一個對象代理對另一個對象(在前一個對象內部)中屬性的操作(讀/寫)。
2.實現示例
  • 被代理對象:自有 屬性的查詢【Object.keys(obj)】

  • 代理對象:存取器 屬性的設置【Object.defineProperties(obj,attr,options)】

   let obj = {username:"zhangsan",password:"123456"};
    let proxy = {};
    Object.keys(obj).forEach((key)=>{
        Object.defineProperty(proxy,key,{
            configurable:false,// 不可再次配置該屬性
            enumerable:true,// 可枚舉該屬性
            get: function proxyGetter() {
                return obj[key]; // 定義爲存取器屬性,通過getter方法代理對obj的讀操作。
            },
            set: function proxySetter(newVal) {
                obj[key] = newVal;// 定義爲存取器屬性,通過setter方法代理對obj的寫操作。
            }
        })
    });
3.運行結果【代理成功】

在這裏插入圖片描述

二:模板解析

1.要解析的view
<div id="template">
    <H1>Template Compile Test</H1>
    <H1>{{username}}</H1>
    <H1>{{password}}</H1>
</div>
2.要解析的model
let data = {
    username:"zhangsan",
    password : "123456"
};
3.解析過程
  • 創建documentFragment容器(內存中)
// 1.創建documentFragment(內存中的node容器)
let fragment = document.createDocumentFragment();
  • 導出document中template區域內的節點並填充至documentFragment容器
// 2.導出document中template區域內的節點並填充至documentFragment
let template = document.getElementById('template');
let node;
while(node = template.firstChild){
    // dom節點只能有一個父節點,添加到fragment時,dom就會移除該節點,循環控制template.firstChild的指向會不斷後移
    fragment.appendChild(node)
}
  • 編譯解析documentFragment容器中的節點
// 3.編譯解析documentFragment
let nodeArr = [].slice.call(fragment.childNodes);// 僞數組轉數組準備遍歷
nodeArr.forEach((node)=>{
    // 注意:爲簡化,只解析第一層元素節點的文本節點。
    if(node.nodeType==1){// 3.1 找到要解析的位置
        let oldText = node.firstChild.textContent;// 3.2 獲取解析前的值oldText
        var reg = /\{\{(.*)\}\}/;
        if(reg.test(oldText)){
            let exp = RegExp.$1;
            let newText = data[exp];// 3.3 獲取解析後的值
            node.textContent = typeof newText=='undefined'?'':newText;// 3.4使用newText覆蓋oldText
        }
    }
});
  • 將解析後的documentFragment填充至document的template區域內
// 4.將解析後的documentFragment填充至document的template中
template.appendChild(fragment);
4.運行結果

在這裏插入圖片描述

三:數據綁定

數據綁定綁定的雙方是view和model,model->view的初始化綁定也即上面的模板解析操作,下面探討model->view的更新綁定。

1.實現方案:數據劫持技術
  • 指的是在訪問或者修改對象的某個屬性時,通過一段代碼攔截這個行爲,進行額外的操作或者修改返回結果。
  • 從上也即得到思路,所有需要被劫持的屬性都不能爲數據屬性,而應該修改爲存取器屬性(可通過Object.definePropty方法實現)。
2.實現示例
  • 複製一份模板解析代碼
  • 爲username和password添加id以便獲取
<div id="template">
    <H1>Template Compile Test</H1>
    <H1 id="username">{{username}}</H1>
    <H1 id="password">{{password}}</H1>
</div>
  • 添加如下數據劫持代碼(將需要更新的model數據都從數據屬性變爲存取器屬性)
// ...,這裏以上爲模板解析的代碼
 // 這裏爲簡化:使用mvMap實現view與model一對一的映射,而view與model實際上應是多對多的關係,
    let mvMap = {
        username:document.getElementById('username'),
        password:document.getElementById('password')
    };
    Object.keys(data).forEach((key)=>{
        dataAttr2GetSetAttr(data,key,data[key])
    });
    function dataAttr2GetSetAttr(data,key,val){
        Object.defineProperty(data,key,{
            configurable:false,
            enumerable:true,
            get:function bindGetter(){
                return val
            },
            set:function bindSetter(newVal){
                val= newVal;// 注意:這裏不能使用data[key],不然會一直自調用死循環。
                // model發生更新,通知view更新。
                mvMap[key].textContent = typeof newVal=='undefined'?'':newVal;
            }
        })
    }
3.運行結果(第一步)

在這裏插入圖片描述

4.運行結果(第二步)

在這裏插入圖片描述

四:雙向數據綁定

由以上模板解析和數據綁定的實踐實例瞭解了model->view的單向綁定,接下只需加上view->model的單向綁定即可實現雙向綁定。

1.實現方案:借用事件機制訪問model
2.實現示例
  • 複製三:數據綁定產生的代碼
  • view上加上input輸入框,如下
<input type="text" id="input">
<div id="template">
    <H1>Template Compile Test</H1>
    <H1 id="username">{{username}}</H1>
    <H1 id="password">{{password}}</H1>
</div>
  • 爲該輸入框添加事件
let inputNode = document.getElementById('input');
inputNode.addEventListener('input', function (e) {
    let oldVal = data.username;
    var newValue = e.target.value;
    if (oldVal === newValue) {
        return;
    }
    data.username = newValue;
});
3.運行結果

在這裏插入圖片描述

二三四最終生成的html測試文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>雙向數據綁定</title>
</head>
<body>
<input type="text" id="input">
<div id="template">
    <H1>Template Compile Test</H1>
    <H1 id="username">{{username}}</H1>
    <H1 id="password">{{password}}</H1>
</div>
<script type="text/javascript">
    let data = {
        username:"zhangsan",
        password : "123456"
    };
// 二:模板解析
    // 1.創建documentFragment(內存中的node容器)
    let fragment = document.createDocumentFragment();
    // 2.導出document中template區域內的節點並填充至documentFragment
    let template = document.getElementById('template');
    let node;
    while(node = template.firstChild){
        // dom節點只能有一個父節點,添加到fragment時,dom就會移除該節點,循環控制template.firstChild的指向會不斷後移
        fragment.appendChild(node)
    }
    // 3.編譯解析documentFragment
    let nodeArr = [].slice.call(fragment.childNodes);// 僞數組轉數組準備遍歷
    nodeArr.forEach((node)=>{
        // 注意:爲簡化,只解析第一層元素節點的文本節點。
        if(node.nodeType==1){// 3.1 找到要解析的位置
        let oldText = node.firstChild.textContent;// 3.2 獲取解析前的值oldText
        var reg = /\{\{(.*)\}\}/;
        if(reg.test(oldText)){
            let exp = RegExp.$1;
            let newText = data[exp];// 3.3 獲取解析後的值
            node.textContent = typeof newText=='undefined'?'':newText;// 3.4使用newText覆蓋oldText
        }
    }
    });
    // 4.將解析後的documentFragment填充至document的template中
    template.appendChild(fragment);


// 三:數據綁定
    // 這裏爲簡化:使用mvMap實現view與model一對一的映射,而view與model實際上應是多對多的關係,
    let mvMap = {
        username:document.getElementById('username'),
        password:document.getElementById('password')
    };
    Object.keys(data).forEach((key)=>{
        dataAttr2GetSetAttr(data,key,data[key])
    });
    function dataAttr2GetSetAttr(data,key,val){
        Object.defineProperty(data,key,{
            configurable:false,
            enumerable:true,
            get:function bindGetter(){
                return val
            },
            set:function bindSetter(newVal){
                val= newVal;// 注意:這裏不能使用data[key],不然會一直自調用死循環。
                // model發生更新,通知view更新。
                mvMap[key].textContent = typeof newVal=='undefined'?'':newVal;
            }
        })
    }
// 四:雙向數據綁定
    let inputNode = document.getElementById('input');
    inputNode.addEventListener('input', function (e) {
        let oldVal = data.username;
        var newValue = e.target.value;
        if (oldVal === newValue) {
            return;
        }
        data.username = newValue;
    });
</script>
</body>
</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章