实现前端框架数据绑定(数据驱动DOM)

一: 完整代码

function  $(element){    //传入响应式区域的dom根节点,返回响应式数据

    let  $$  =  {};    //响应式数据

    function  getElements(el){    //传入一个元素节点,返回一个保存着该元素本身及所有子元素的数组
        var  elementList  =  [],    //存储所有的元素节点(最终结果)
            tempList  =  [],    //暂时性的存储有子节点的元素节点
            parentElement  =  [];    //永远保存上一级的元素
            parentElement.push(el);
        elementList.push(parentElement[0]);    //把根元素推入list
        while(parentElement.length  !=  0){
            for(let  i  =  0,  len  =  parentElement.length;  i  <  len;  ++i){
                let  childElement  =  parentElement[i].childNodes;
                for(let  j  =  0,  lens  =  childElement.length;  j  <  lens;  ++j){
                    if(childElement[j].nodeType  ==  1){
                        elementList.push(childElement[j]);
                        if(childElement[j].hasChildNodes()){
                            tempList.push(childElement[j]);
                        }
                    }
                }
            }
            parentElement  =  tempList;
            tempList  =  [];
        }
        return  elementList;
    }

    function  attrHanding(elements){    //传入一个元素列表,返回bindData函数需要格式的数组
        let  list  =  [];
        for(let  i  =  0,  len  =  elements.length;  i  <  len;  ++i){
            let  temp  =  elements[i].attributes;
            for(let  j  =  0,  lens  =  temp.length;  j  <  lens;  ++j){
                let  temps  =  temp[j],
                    testinga  =  temps.nodeName.indexOf(‘a:‘),
                    testingb  =  temps.nodeName.indexOf(‘s:‘);
                if(testinga  !=  -1  ||  testingb  !=  -1){
                    let  tem  =  temps.nodeName.split(‘&‘),
                        end  =  ‘‘,
                        tems  =  (tem[0].split(‘:‘))[1];
                    tems  =  tems.split(‘‘);
                    for(let  x  =  0,  l  =  tems.length;  x  <  l;  ++x){
                        if(tems[x]  !=  ‘-‘){
                            end  =  end  +  tems[x];
                        }else{
                            ++x;
                            end  =  end  +  tems[x].toUpperCase();
                        }
                    }
                    list.push(tem[1],  elements[i]);
                    if(testinga  !=  -1){
                        list.push(end,  ‘‘,  temp[j].nodeValue);
                    }else{
                        list.push(‘style‘,  end,  temp[j].nodeValue);
                    }
                }
            }
        }
        return  list;
    }

    function  bindData(s){    //需要给bind传入数组,5个一组:(dom替代的名字,要操作的dom元素,要操作的属性名1,要操作的属性名2,dom替代的名字初始值)
        for(let  i  =  0,  l  =  s.length;  i  <  l;  i  +=  5){
            //把$$里的数据和dom绑定到一起,$$发生变动时,自动修改dom
            Object.defineProperty($$,  s[i],  {
                get:  function(){
                    return  this[‘_‘  +  s[i]];
                },
                set:  function(v){
                    this[‘_‘  +  s[i]]  =  v;
                    if(s[i  +  3]  ==  ""){
                        s[i  +  1][s[i  +  2]]  =  v;
                    }else{
                        s[i  +  1][s[i  +  2]][s[i  +  3]]  =  v;
                    }
                }
            });
            $$[s[i]]  =  s[i  +  4];    //初始值
        }
    }

    bindData(attrHanding(getElements(element)));

    return  $$;
}

二:语法说明

1:对于一般的html元素属性,可以这样绑定 a:属性名&绑定的变量名=‘初始值‘

比如: a:title&tit="Hello DataBinding"(将title属性和变量tit绑定在一起)

2:对于行内样式,需要这样绑定 s:属性名&绑定的变量名=‘初始值‘

比如: s:color&color="red"(将行内样式color属性和color变量绑定在一起)

3:引入完整代码后,把你需要控制的区域作为一个元素传入$()函数,它会返回一个对象,你需要接收它,里面包含了所有响应式的元素属性

比如:我定义了data变量来接受,那么我就可以用data.变量名=" "这样的方式来操纵它

三:需要注意的点

因为html不区分大小写,所以变量名需要小写,对于包含大写字母的属性,比如innerHTML,需要这样写:inner-h-t-m-l

四:案例演示

新建一个文件夹,进入,新建一个databinding.js文件,把完整代码粘贴进去,保存,再新建一个html文件,粘贴以下代码,保存,然后双击打开,在浏览器里应该可以看到效果

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据绑定</title>
</head>
<body>
<div id="root">
<p a:title&tit="Hello" s:font-weight&weight="300">请把鼠标移动到我身上</p>
<button onclick="coarsen()">点击我让字体变粗</button>
</div>
<script src="./databinding.js"></script>
<script>
var data = $(document.getElementById(‘root‘));
data.tit = "如果看到我,说明你绑定成功";
function coarsen(){
data.weight = "700";
}
</script>
</body>
</html>

五:实现思路及代码组织

遍历需要控制区域的所有元素(封装函数getElements),

遍历每个元素的所有属性,找到符合语法的属性进行解析(封装函数attrHanding),

利用Object.defineProperty()函数重定义get和set(封装函数bindData),

然后定义函数$,在$函数内定义一个空对象{}用于存放响应式属性变量,

再把前面的三个函数放到$函数内,依次嵌套调用bindData attrHanding getElements,返回定义的空对象