BiuJS
BiuJS是一个轻巧的mvvm框架 它实现了数据的双向绑定 并提供一些基本的指令帮助你提升效率,比如$for
,$model
,$if
,$click
,$style
是的,如你所见,以$
开头的指令是它的独特标识 1000行左右的代码量,让应用的开发和加载biu的一瞬完成 :https://github.com/veedrin/biu
编译
现在我们来看看视图
let app = new Biu({ mount: '#app', data: {}, action: {}});
这里传进来的mount
是BiuJS的挂载点,它的意思是说:id="app"
所在的元素以及后代元素现在被BiuJS接管了
这片区域就是我们要编译的范围
function Compiler(mount, vm) { this.vm = vm; if (mount) { let fragment = this.eleToFragment(mount); this.compile(fragment); mount.appendChild(fragment); }}Compiler.prototype.eleToFragment = function(ele) { let fragment = document.createDocumentFragment(); let child; while(child = ele.firstChild) { fragment.appendChild(child); } return fragment;};
要给DOM做手术,我们就要先把它抽出来,暂时装在文档碎片里
编译完以后,再整体的插回原来的位置
胡子模板,也叫文本插值,是作为文本节点,而指令是属于元素节点的,所以我们要分开编译
let regBlank = /^\s+$/;Compiler.prototype.compile = function(ele) { let self = this; if (ele.childNodes && ele.childNodes.length) { Array.from(ele.childNodes).forEach((child) => { if (child.nodeType === 3 && !regBlank.test(child.textContent)) { self.compileText(child); } else if (child.nodeType === 1) { self.compileElement(child); } self.compile(child); }); }};
我们知道文本节点会把元素之间的空隙也算进去,编译它毫无意义,所以排除掉
编译文本
我们先讲文本编译,指令编译会单独抽几个出来在后面文章讲
好,现在假设我们捕捉到了一个文本节点
跟上面一样,编译就是一个分解组装的过程,所以也要用一个文档碎片缓存起来
有一个知识点,exec
方法作用在加了全局匹配修饰符的正则表达式上时,需要多次匹配才能获得所有的结果。正则表达式的lastIndex
属性就是用来标记匹配到哪里了
举个例子
let str = 'I am biu, I do biu things, I appreciate biu things.';let reg = /biu/g;// 0console.log(reg.lastIndex);// ["biu", index: 5, input: "I am biu, I do biu things, I appreciate biu things."] 8console.log(reg.exec(str), reg.lastIndex);// ["biu", index: 15, input: "I am biu, I do biu things, I appreciate biu things."] 18console.log(reg.exec(str), reg.lastIndex);// ["biu", index: 40, input: "I am biu, I do biu things, I appreciate biu things."] 43console.log(reg.exec(str), reg.lastIndex);// null 0console.log(reg.exec(str), reg.lastIndex);
一个文本节点里可能有好几个胡子模板
我叫{ {name}},我今年{ {age}}岁了
所以我们要用一个循环把它们全抠出来
let regMustache = /\{\{(.*?)\}\}/g;let content = ele.textContent.trim();let fragment = document.createDocumentFragment();let i = 0;let match;let text;while (match = regMustache.exec(content)) { if (i < match.index) { text = content.slice(i, match.index); let element = document.createTextNode(text); fragment.appendChild(element); } i = regMustache.lastIndex; let exp = match[1]; let element = document.createTextNode(''); let result = execChain(exp, this.vm); element.textContent = result; fragment.appendChild(element); new Watcher(exp, this.vm, (newValue) => { element.textContent = newValue; });}if (i < content.length) { text = content.slice(i); let element = document.createTextNode(text); fragment.appendChild(element);}
- 如果
match.index
不是从0开始的,那就说明前面还有文本是匹配失败了。虽然匹配失败,我们还是要原样把它组装回去吧。这就是第一部分 - 第二部分就是匹配成功了,我们要把它其中的表达式抠出来,转成实际的值,再组装回去。如此循环,直到结束
- 如果
while
循环结束之后,match.index
比字符串的长度要小,那说明后面还有文本匹配失败了。一样的,原样把它组装回去
经过这三部分,一个文本节点就算编译完成了
表达式求值
因为BiuJS的表达式并不是真正的表达式,它不支持计算(或者暂时不支持)
所以表达式求值实际上指的是:求嵌套对象的属性的值
{ {aa.bb.cc}}或者{ {aa["bb"].cc}}或者{ {aa['bb']['cc']}}{ {aa + 3}}
先把这些对象或属性名抽取出来,放到一个数组里面
然后再用递归一步一步的向里求值,就可以获得aa.bb.cc
的值了
let regChain = /[\[\]\.'"]/;function splitChain(exp) { let arr = exp.split(regChain); if (arr.length === 1) { return arr; } let chain = []; for (let i = 0, len = arr.length; i < len; i++) { arr[i] && chain.push(arr[i]); } return chain;}function execChain(exp, vm) { let chain = splitChain(exp); let temp; function recursion(obj, i) { let prop = obj[chain[i]]; if (prop !== undefined) { temp = prop; i < chain.length && recursion(temp, i + 1); } } recursion(vm.$data, 0); return temp;}
action
方法集合没有嵌套,所以直接取就可以了
这就是表达式转成实际的值的过程
订阅器
上面的文本编译部分有一个new Watcher()
,还有印象吗?
这就是传说中的订阅器
这个订阅器的作用是什么呢?其实也很简单
- 把模板里抠出来的表达式、BiuJS的实例、还有回调函数打包在一起
- 通过触发
getter
将包裹送到订阅数组里面
function Watcher(exp, vm, cb) { this.exp = exp; this.vm = vm; this.update = cb; this.trigger();}Watcher.prototype.trigger = function() { Dep.target = this; execChain(this.exp, this.vm); Dep.target = null;};
打包物品,送到目的地,这不就是快递公司么
之前的文章我们还埋了一个点:“订阅者是什么时候挂到Dep.target
上的?”
就是在这个时候挂上去的
不过要马上清空,因为它其实挺忙的,要接不少客
写在后面
以上就是文本编译的过程
欢迎到: https://github.com/veedrin/biu
了解详情
更欢迎Star
和Fork