2048
登录
没  有  难  学  的  前  端
登 录
×
<返回上一级

BiuJS[v1.0]说明文档(3):文本编译

javascript作者:猿2048志愿者

logo

BiuJS

BiuJS是一个轻巧的mvvm框架
它实现了数据的双向绑定
并提供一些基本的指令帮助你提升效率,比如$for$model$if$click$style
是的,如你所见,以$开头的指令是它的独特标识
1000行左右的代码量,让应用的开发和加载biu的一瞬完成
BiuJS仓库: 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;

// 0
console.log(reg.lastIndex);

// ["biu", index: 5, input: "I am biu, I do biu things, I appreciate biu things."] 8
console.log(reg.exec(str), reg.lastIndex);

// ["biu", index: 15, input: "I am biu, I do biu things, I appreciate biu things."] 18
console.log(reg.exec(str), reg.lastIndex);

// ["biu", index: 40, input: "I am biu, I do biu things, I appreciate biu things."] 43
console.log(reg.exec(str), reg.lastIndex);

// null 0
console.log(reg.exec(str), reg.lastIndex);

一个文本节点里可能有好几个胡子模板

<div>我叫{{name}},我今年{{age}}岁了</div>

所以我们要用一个循环把它们全抠出来

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);
}
  1. 如果match.index不是从0开始的,那就说明前面还有文本是匹配失败了。虽然匹配失败,我们还是要原样把它组装回去吧。这就是第一部分
  2. 第二部分就是匹配成功了,我们要把它其中的表达式抠出来,转成实际的值,再组装回去。如此循环,直到结束
  3. 如果while循环结束之后,match.index比字符串的长度要小,那说明后面还有文本匹配失败了。一样的,原样把它组装回去

经过这三部分,一个文本节点就算编译完成了

表达式求值

因为BiuJS的表达式并不是真正的表达式,它不支持计算(或者暂时不支持)

所以表达式求值实际上指的是:求嵌套对象的属性的值

<!-- BiuJS支持的表达式 -->
<div>{{aa.bb.cc}}或者{{aa["bb"].cc}}或者{{aa['bb']['cc']}}</div>

<!-- BiuJS不支持的表达式 -->
<div>{{aa + 3}}</div>

先把这些对象或属性名抽取出来,放到一个数组里面

然后再用递归一步一步的向里求值,就可以获得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(),还有印象吗?

这就是传说中的订阅器

这个订阅器的作用是什么呢?其实也很简单

  1. 把模板里抠出来的表达式、BiuJS的实例、还有回调函数打包在一起
  2. 通过触发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上的?”

就是在这个时候挂上去的

不过要马上清空,因为它其实挺忙的,要接不少客

写在后面

以上就是文本编译的过程

欢迎到BiuJS仓库: https://github.com/veedrin/biu了解详情

更欢迎StarFork

本文来源于网络:查看 >
« 上一篇:架构师之路
» 下一篇:Babel入门——ES6转ES5
评论
点击刷新
评论
相关博文
×添加代码片段