数据代理、模板解析、数据绑定、双向数据绑定【实践】

一:数据代理

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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章