vue源码之mustache模板引擎 03

366 阅读2分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

手写将tokens嵌套起来

新增nestTokens.js

我们如果有一些嵌套循环,如图:

image.png 那之前的方法就行不通了,因为有多个#和.。 这里我们要引用一种数据结构叫做栈。先进后出(First In Last Out,FILO),就可以实现嵌套。
我们期望的结果:

image.png

image.png

分析

  • 首先,我们需要一个整合最后结果的数组,我们命名为nestedTokens;
  • 然后,我们需要一个可以存放栈结构的变量,我们命名为section;
  • 再然后,我们还需要一个可以指向原数组第三项(即新创建的数组)的变量,我们命名为collector;
  • 最后,我们需要一个记录当前子数组的变量,我们命名为token 完整代码 nestedTokens.js:
export default function nestTokens(tokens) {
    var nestedTokens = [];
    var section = [];

    var collector = nestedTokens;

    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        if (token[0] == "#") {
            collector.push(token);
            section.push(token);
            collector = token[2] = [];
        } else if (token[0] == "/") {
            section.pop();
            collector = section.length > 0 ? section[section.length - 1][2] : nestedTokens;
        } else {
            collector.push(token);
        }
    }
    return nestedTokens;
}

var collector = nestedTokens:可以保证collector指向的数组与最后需要返回的数组是同一个数组。

collector = token[2] = []:这一行代码,也就是让collector指向第一项为#的子数组,因为带有#的数组说明他需要遍历出一个子数组,我们先用collector指向它,说明它后面需要结束遍历。

image.png 也就是图中框出来的即将诞生的第三项 => token[2]

collector = section.length > 0 ? section[section.length - 1][2] : nestedTokens:结束这一层遍历,返回上一层继续遍历直至结束。

一些修改

templateTokens.js的返回值要更改成为nestToken(tokens)了,也就是要返回已经嵌套好的数组了(记得要import新增的函数). image.png

将tokens注入数据

我们分为:

  • tokens[0] == text的普通字符串,直接连接;
  • token[0] == name的数据,需要注入后连接;
  • token[0] == #的数组,需要循环后连接; 这里我们需要注意一点,我们的属性有可能是嵌套对象,即对象属性全称是a.b.c,这个时候如果我们直接通过data["a.b.c"]的方法来配对数据会发现得到undified。

因此我们需要构建一个新的函数,来拆分属性名中带有"."的属性。(这个地方很有可能成为面试题,即拆分属性名得到数据中对应的属性值)

export default function lookUp(dataObj, keyName) {
    // 先判断属性名中是否包含点符号
    if (keyName.indexOf(".") != -1 && keyName != ".") {
        var keys = keyName.split(".");
        var temp = dataObj;
        for (let i = 0; i < keys.length; i++) {
            temp = temp[keys[i]];
        };
        return temp;
    }
    return dataObj[keyName];
}

思路:先将譬如"a.b.c"这一类的属性名拆分成数组,在数据对象dataObj中层层向下寻找,直到找到属性名为c的属性对应的属性值。

image.png

将tokens数组变为dom字符串

renderTemplate.js:

export default function renderTemplate(tokens, data) {
    var resultStr = "";
    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        if (token[0] == "text") {
            resultStr += token[1];
        } else if (token[0] == "name") {
            resultStr += lookUp(data, token[1]);
        } else if (token[0] == "#") {
            resultStr += parseArray(token, data);
        }
    }
    return resultStr;
}

思路:判断每个tokens的子数组的token[0]是text还是name,还是#,如果是text,则将对应的token[1]连接到结果字符串上;如果是name,则可以通过上面的lookUp函数返回对应的那一段字符串;如果是#,这个时候就说明有数组需要遍历取值了,则和下面的函数利用递归得到对应字符串。

通过递归实现嵌套数组的数据注入

parseArray.js:

export default function parseArray(token, data) {
    var v = lookUp(data, token[1]);
    var resultStr = "";
    for (let i = 0; i < v.length; i++) {
        resultStr += renderTemplate(token[2], {
            ...v[i],
            ".": v[i]
        });
    }
    return resultStr;
}

思路:这个函数的作用就是得到子数组对应的那一段字符串。这里要注意,传入的第一个参数是token,而不是tokens,也就是说这一个函数只对包含数组的token子数组有效。

var v = lookUp(data, token[1]):这一句的作用是,得到数组名称对应的数组,这里一定得到的是数组而不是一个值。原因是只有包含数组的token我们才调用这个函数来注入数据。

最终的index.js只有几行:

window.templateEngine = {
    render(templateStr, data) {
        var tokens = templateToTokens(templateStr);
        var domStr = renderTemplate(tokens, data);
        return domStr;
    },
}

var tokens = templateToTokens(templateStr) -- 调用templateToTokens函数,将模板字符串变为tokens数组;
var domStr = renderTemplate(tokens, data) -- 调用renderTemplate函数,将tokens数组变为dom字符串。

最后!!!来运行!!!!

image.png

以前内容:

Vue源码之mustache模板引擎 01

vue源码之mustache模板引擎 02