Vue原理之mustach总结

127 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

喜欢就点个赞吧🎈😊

🎉 前言

mustach是vue中模板引擎的核心思想,其主要的思想是将模板字符进行tokens操作,再将获得的tokens操作与data进行结合,生成字符串在放到页面中进行展示。是非常重要的底层原理的探索。

image.png

🎉 发展过程

创建DOM的历程分为以下四个阶段:

  • 纯DOM法:非常笨拙,没有实战价值
  • 数组join法:曾几何时非常流行,是曾经的前端必会知识
  • ES6的反引号法:ES6中新增的${a}语法糖,很好用
  • 模板引擎:解决数据变为视图的最优雅的方法

🎉 mustach的原理

image.png

🎉 将模板字符串转化为tokens

🎇 Scanner

在这个部分中主要用到了两个部分一个是存在一个Scanner的扫描类,通过扫描类将需要保存的内容进行抽取,其中有两个函数比较重要,一个是用来去字符的scanUtil函数另一个是scan跳过“{{”和“}”的函数。

/* 
    扫描器类
*/
export default class Scanner {
    constructor(templateStr) {
        // 将模板字符串写到实例身上
        this.templateStr = templateStr;
        // 指针
        this.pos = 0;
        // 尾巴,一开始就是模板字符串原文
        this.tail = templateStr;
    }

    // 功能弱,就是走过指定内容,没有返回值
    scan(tag) {
        if (this.tail.indexOf(tag) == 0) {
            // tag有多长,比如{{长度是2,就让指针后移多少位
            this.pos += tag.length;
            // 尾巴也要变,改变尾巴为从当前指针这个字符开始,到最后的全部字符
            this.tail = this.templateStr.substring(this.pos);
        }
    }

    // 让指针进行扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字
    scanUtil(stopTag) {
        // 记录一下执行本方法的时候pos的值
        const pos_backup = this.pos;
        // 当尾巴的开头不是stopTag的时候,就说明还没有扫描到stopTag
        // 写&&很有必要,因为防止找不到,那么寻找到最后也要停止下来
        while (!this.eos() && this.tail.indexOf(stopTag) != 0) {
            this.pos++;
            // 改变尾巴为从当前指针这个字符开始,到最后的全部字符
            this.tail = this.templateStr.substring(this.pos);
        }

        return this.templateStr.substring(pos_backup, this.pos);
    }

    // 指针是否已经到头,返回布尔值。end of string
    eos() {
        return this.pos >= this.templateStr.length;
    }
};

🎇 nestTokens函数是将提取出出来的字符转化为tokens

函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项。 在这里边有一个比较巧妙的地方,就是collector的使用,它会随着折叠token不断的往数组里边进行添加,这样就能形成不断折叠的数组tokens


export default function nestTokens(tokens) {
    // 结果数组
    var nestedTokens = [];
    // 栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前操作的这个tokens小数组
    var sections = [];
    // 收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组
    // 收集器的指向会变化,当遇见#的时候,收集器会指向这个token的下标为2的新数组
    var collector = nestedTokens;

    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];

        switch (token[0]) {
            case '#':
                // 收集器中放入这个token
                collector.push(token);
                // 入栈
                sections.push(token);
                // 收集器要换人。给token添加下标为2的项,并且让收集器指向它
                collector = token[2] = [];
                break;
            case '/':
                // 出栈。pop()会返回刚刚弹出的项
                sections.pop();
                // 改变收集器为栈结构队尾(队尾是栈顶)那项的下标为2的数组
                collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
                break;
            default:
                // 甭管当前的collector是谁,可能是结果nestedTokens,也可能是某个token的下标为2的数组,甭管是谁,推入collctor即可。
                collector.push(token);
        }
    }

    return nestedTokens;
};

🎉 将tokens展开并且与data进行结合返回字符串

这一个环节要单独处理token中第一个字符为#的部分,将利用不断递归的方式对token与data进行结合。

函数的功能是让tokens数组变为dom字 符串


export default function renderTemplate(tokens, data) {
    // 结果字符串  
    var resultStr = '';
    // 遍历tokens
    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        // 看类型
        if (token[0] == 'text') {
            // 拼起来
            resultStr += token[1];
        } else if (token[0] == 'name') {
            // 如果是name类型,那么就直接使用它的值,当然要用lookup
            // 因为防止这里是“a.b.c”有逗号的形式
            resultStr += lookup(data, token[1]);
        } else if (token[0] == '#') {
            resultStr += parseArray(token, data);
        }
    }

    return resultStr;
}

单独处理token为“#”的内数组,将其进行拼串操作。

export default function parseArray(token, data) {
    // 得到整体数据data中这个数组要使用的部分
    var v = lookup(data, token[1]);
    // 结果字符串
    var resultStr = '';
    // 遍历v数组,v一定是数组
    // 注意,下面这个循环可能是整个包中最难思考的一个循环
    // 它是遍历数据,而不是遍历tokens。数组中的数据有几条,就要遍历几条。
    for (let i = 0; i < v.length; i++) {
        // 这里要补一个“.”属性
        // 拼接
        resultStr += renderTemplate(token[2], {
            ...v[i],
            '.': v[i]
        });
    }
    return resultStr;
};

🎉 在mustach中的一个面试题

功能是可以在dataObj对象中,寻找用连续点符号的keyName属性 比如,dataObj是

    {
        a: {
            b: {
                c: 100
            }
        }
    }
那么lookup(dataObj, 'a.b.c')结果就是100,解决不能连续使用点富豪的操作。
function lookup(dataObj, keyName) {
    // 看看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];
};

🎉总结

了解mustch的底层原理是非常重要的, 有助于帮助我们更好的了解Vue的设计过程和模式!!