实现前端框架数据绑定(数据驱动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,返回定义的空对象