BiuJS
BiuJS是一个轻巧的mvvm框架 它实现了数据的双向绑定 并提供一些基本的指令帮助你提升效率,比如$for
,$model
,$if
,$click
,$style
是的,如你所见,以$
开头的指令是它的独特标识 1000行左右的代码量,让应用的开发和加载biu的一瞬完成 :https://github.com/veedrin/biu
$指令
往下看之前,请大家沐浴更衣,因为我要讲BiuJS的$指令了
{ {vipName}}
JavaScript中的$
已经被jQuery占用了,现在html标签中的$
也被BiuJS占用了
我已经为$指令申请了专利,是真的(假的)
Compiler.prototype.compileElement = function(ele) { let self = this; Array.from(ele.attributes).forEach((attr) => { let name = attr.name; let type; if (name.indexOf('$') === 0) { type = name; } else { return; } let exp = attr.value; let handler = self[type]; if (handler) { handler.call(self, ele, exp, self.vm); } ele.removeAttribute(name); }); };
因为每一个指令的编译规则都是不一样的,所以我们要提取出$指令,然后交给相应的函数处理
注意,handler
需要用call
方法调用,否则handler
内部的this
不会指向Compiler
构造函数
$for
编写html的时候,我们经常会遇到DOM结构一样但数据不一样的情况
这时候如果有一个工具,能够遍历数据,然后插入到相应份拷贝的DOM结构中,简直太好了
$for
指令就是为这个而生的
我们先来看$for
指令的用法
这里的item
和index
可以随意更换成你顺手的单词,只需要记住item
是总数据的一项,如果需要索引则要加一对括号
让我们提取其中的表达式
let regIterate = /^\(([\w\,\s]+)\)/;let split = exp.split('in');let expItem = split[0].trim();let expList = split[1].trim();let expIndex;if (regIterate.test(expItem)) { let match = regIterate.exec(expItem)[1]; split = match.split(','); expItem = split[0].trim(); expIndex = split[1].trim();}
目前$for
指令有一个缺陷,它需要用一层div
包裹起来,虽然页面效果是一样的,但毕竟破坏了DOM结构。如果有好的解决方案,可以在文末留言
let divWrap = document.createElement('div');ele.parentNode.insertBefore(divWrap, ele);ele.parentNode.removeChild(ele);ele.removeAttribute('$for');let cloneOrigin = ele.cloneNode(true);
先插入一个div
,然后把元素移除
这里需要克隆一个元素的副本,因为之后数据变更,我们要拿这个副本去重新编译
因为子元素可能会(应该是一定会)使用带有item
和index
的胡子模板
所以我们要把它们都替换成实际的值
let regItem = new RegExp(`{ {${expItem}}}`, 'g');let regIndex = new RegExp(`{ {${expIndex}}}`, 'g');if (child.nodeType === 3 && !regBlank.test(child.textContent)) { let content = child.textContent.trim(); let str = self.replace$for(content, item, regItem); if (expIndex) { str = self.replace$for(str, index, regIndex); } child.textContent = str;}
replace$for
和文本编译是差不多的,算是简化版
Compiler.prototype.replace$for = function(content, value, reg) { let i = 0; let match; let text; let temp = ''; while (match = reg.exec(content)) { if (i < match.index) { text = content.slice(i, match.index); temp += text; } i = reg.lastIndex; temp += value; } if (i < content.length) { text = content.slice(i); temp += text; } return temp;};
但是到这里还没完,因为$click
指令有可能把item
和index
作为参数传进自己的函数
而item
和index
并不在data
里面,不能用execChain
方法获取实际的值
所以在这里一并把它给编译了
let regCall = /\((.*)\)$/;Array.from(child.attributes).forEach((attr) => { if (attr.name === '$click') { let exp = attr.value; let match = regCall.exec(exp); if (match) { let funcName = exp.slice(0, exp.indexOf('(')); if (match[1] === expItem) { attr.value = `${funcName}('${item}')`; } else if (match[1] === expIndex) { attr.value = `${funcName}('${index}')`; } } }});
最后是订阅器的回调
做法就是先移除div
里面的所有子元素,遍历一下数据,看看新值有几项,然后拿来副本,照上面相应的编译几次
new Watcher(exp, vm, (newValue) => { Array.from(divWrap.childNodes).forEach((child) => { divWrap.removeChild(child); }); newValue.forEach((item, index) => { let cloneNode = cloneOrigin.cloneNode(true); self.compile$for(cloneNode, expItem, item, expIndex, index); divWrap.appendChild(cloneNode); });});
写在后面
以上就是编译$for
指令的过程
欢迎到: https://github.com/veedrin/biu
了解详情
更欢迎Star
和Fork