资讯专栏INFORMATION COLUMN

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

lucas / 864人阅读

摘要:如此循环,直到结束如果循环结束之后,比字符串的长度要小,那说明后面还有文本匹配失败了。

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);

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

我叫{{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上的?”

就是在这个时候挂上去的

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

写在后面

以上就是文本编译的过程

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

更欢迎StarFork

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92286.html

相关文章

  • BiuJS[v1.0]说明文档(4):$if 指令

    摘要:是一个轻巧的框架它实现了数据的双向绑定并提供一些基本的指令帮助你提升效率,比如,,,,是的,如你所见,以开头的指令是它的独特标识行左右的代码量,让应用的开发和加载的一瞬完成仓库指令往下看之前,请大家沐浴更衣,因为我要讲的指令了中的已经被占用 showImg(https://segmentfault.com/img/remote/1460000012478667?w=1920&h=926...

    _ang 评论0 收藏0
  • BiuJS[v1.0]说明文档(1):总体结构

    摘要:是一个轻巧的框架它实现了数据的双向绑定并提供一些基本的指令帮助你提升效率,比如,,,,是的,如你所见,以开头的指令是它的独特标识行左右的代码量,让应用的开发和加载的一瞬完成仓库启动首先我们来看一下是如何启动的是的挂载点,它决定在多大范围的树 showImg(https://segmentfault.com/img/remote/1460000012478667?w=1920&h=926...

    崔晓明 评论0 收藏0
  • BiuJS[v1.0]说明文档(2):数据劫持

    摘要:是一个轻巧的框架它实现了数据的双向绑定并提供一些基本的指令帮助你提升效率,比如,,,,是的,如你所见,以开头的指令是它的独特标识行左右的代码量,让应用的开发和加载的一瞬完成仓库订阅清单前文说到提供了一个强大的接口我们就用它来劫持数据不过在此 showImg(https://segmentfault.com/img/remote/1460000012478667?w=1920&h=926...

    Terry_Tai 评论0 收藏0
  • 另一个OneDrive目录索引应用 OLAINDEX

    摘要:支持通过搭建亲测地址。演示链接演示地址安装使用由于本项目基于开发,新手建议查看的环境要求再进行部署。捐赠提供技术安装服务。 OLAINDEX ✨ Another OneDrive Directory Index. showImg(https://segmentfault.com/img/remote/1460000016747233?w=800&h=400); 此图来自 如有乐享,感谢...

    tigerZH 评论0 收藏0
  • gulp自动化打包(上)

    摘要:自动化打包上文章概述本文分为上下两篇,上篇主要介绍一些常用的插件也是此次打包主要用的插件,而下篇主要以一个项目为例,从本地出合适的版本,压缩合并到最后打成包,发送至指定目录,做一个全面的演示。 gulp自动化打包(上) 文章概述 本文分为上下两篇,上篇主要介绍一些常用的gulp插件(也是此次打包主要用的gulp插件),而下篇主要以一个demo项目为例,从本地checkout出合适的g...

    roland_reed 评论0 收藏0

发表评论

0条评论

lucas

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<