vue 核心原理之 mustache 底层核心原理及实现

一、mustache 核心原理
  1. mustache 库的机理,模版字符串编译为 tokens,数据结合 tokens 解析为 dom 字符串。
  2. tokens 是一个JS的嵌套数组,就是模板字符串的JS表示。它是“抽象语法树”、“虚拟节点”等等的开山鼻祖。
  3. 循环情况下的tokens,当模板字符串中有循环存在时,它将被编译为嵌套更深的tokens
  4. 双重循环情况下的tokens,当循环是双重的,那么tokens会更深一层。
  5. mustache库底层重点要做两个事情,如下所示:
  • 将模板字符串编译为tokens形式
  • tokens结合数据,解析为dom字符串
二、mustache 原理实现
  1. Scanner,扫描器类,如下所示:
  • constructor时将模板字符串写到实例身上,指定指针,尾巴,一开始就是模板字符串原文。
  • scan时功能弱,就是走过指定内容,没有返回值。tag有多长,比如{{长度是2,就让指针后移多少位。尾巴也要变,改变尾巴为从当前指针这个字符开始,到最后的全部字符。
  • scanUtil时让指针进行扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字。记录一下执行本方法的时候pos的值。当尾巴的开头不是stopTag的时候,就说明还没有扫描到stopTag,写&&很有必要,因为防止找不到,那么寻找到最后也要停止下来。改变尾巴为从当前指针这个字符开始,到最后的全部字符。
  • eos时指针是否已经到头,返回布尔值。
  1. Scanner,代码如下所示:
export default class Scanner {
    constructor(templateStr) {
        this.templateStr = templateStr;
        this.pos = 0;
        this.tail = templateStr;
    }

    scan(tag) {
        if (this.tail.indexOf(tag) == 0) {
            this.pos += tag.length;
            this.tail = this.templateStr.substring(this.pos);
        }
    }

    scanUtil(stopTag) {
        const pos_backup = this.pos;
        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);
    }

    eos() {
        return this.pos >= this.templateStr.length;
    }
};
  1. parseTemplateToTokens,将模板字符串变为tokens数组,如下所示:
  • 创建扫描器,让扫描器工作,收集开始标记出现之前的文字。尝试写一下去掉空格,智能判断是普通文字的空格,还是标签中的空格。标签中的空格不能去掉,比如<div class="box">不能去掉class前面的空格。空白字符串,判断是否在标签里。如果这项不是空格,拼接上。如果这项是空格,只有当它在标签内的时候,才拼接上。存起来,去掉空格。过双大括号。
  • 收集开始标记出现之前的文字。这个words就是{{}}中间的东西。判断一下首字符。存起来,从下标为1的项开始存,因为下标为0的项是#。存起来,从下标为1的项开始存,因为下标为0的项是/。存起来。过双大括号。
  • 返回折叠收集的tokens
  1. parseTemplateToTokens,代码如下所示:
import Scanner from './Scanner.js';
import nestTokens from './nestTokens.js';

export default function parseTemplateToTokens(templateStr) {
    var tokens = [];
    var scanner = new Scanner(templateStr);
    var words;
    while (!scanner.eos()) {
        words = scanner.scanUtil('{{');
        if (words != '') {
            let isInJJH = false;
            var _words = '';
            for (let i = 0; i < words.length; i++) {
                if (words[i] == '<') {
                    isInJJH = true;
                } else if (words[i] == '>') {
                    isInJJH = false;
                }
                if (!/\s/.test(words[i])) {
                    _words += words[i];
                } else {        
                    if (isInJJH) {
                        _words += ' ';
                    }
                }
            }
            tokens.push(['text', _words]);
        }
        scanner.scan('{{');
        words = scanner.scanUtil('}}');
        if (words != '') {
            if (words[0] == '#') {
                tokens.push(['#', words.substring(1)]);
            } else if (words[0] == '/') {
                tokens.push(['/', words.substring(1)]);
            } else {
                tokens.push(['name', words]);
            }
        }
        scanner.scan('}}');
    }

    return nestTokens(tokens);
}
  1. nestTokens,函数的功能是折叠tokens,将#/之间的tokens能够整合起来,作为它的下标为3的项,如下所示:
  • 结果数组,栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前操作的这个tokens小数组。
  • 收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组。收集器的指向会变化,当遇见#的时候,收集器会指向这个token的下标为2的新数组。
  • 收集器中放入这个token,入栈,收集器要换人。给token添加下标为2的项,并且让收集器指向它。
  • 出栈。pop()会返回刚刚弹出的项。改变收集器为栈结构队尾(队尾是栈顶)那项的下标为2的数组。
  • 甭管当前的collector是谁,可能是结果nestedTokens,也可能是某个token的下标为2的数组,甭管是谁,推入collctor即可。
  1. nestTokens,代码如下所示:
export default function nestTokens(tokens) {
    var nestedTokens = [];
    var sections = [];
    var collector = nestedTokens;

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

        switch (token[0]) {
            case '#':
                collector.push(token);
                sections.push(token);
                collector = token[2] = [];
                break;
            case '/':
                sections.pop();
                collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
                break;
            default:
                collector.push(token);
        }
    }

    return nestedTokens;
};
  1. lookup,功能是可以在dataObj对象中,寻找用连续点符号的keyName属性,如下所示:
  • 看看keyName中有没有点符号,但是不能是.本身。如果有点符号,那么拆开。
  • 设置一个临时变量,这个临时变量用于周转,一层一层找下去。每找一层,就把它设置为新的临时变量。
  • 如果这里面没有点符号,就返回。
  1. lookup,代码如下所示:
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];
};
  1. parseArray,处理数组,结合renderTemplate实现递归,如下所示:
  • 得到整体数据data中这个数组要使用的部分,结果字符串。
  • 遍历v数组,v一定是数组。注意,下面这个循环可能是整个包中最难思考的一个循环。
  • 它是遍历数据,而不是遍历tokens。数组中的数据有几条,就要遍历几条。这里要补一个“.”属性,然后拼接。
  1. parseArray,代码如下所示:
import lookup from './lookup.js';
import renderTemplate from './renderTemplate.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;
};
  1. renderTemplate,函数的功能是让tokens数组变为dom字符串,如下所示:
  • 结果字符串,遍历tokens,看类型,拼起来。
  • 如果是name类型,那么就直接使用它的值,当然要用lookup。因为防止这里是“a.b.c”有逗号的形式。
  1. renderTemplate,代码如下所示:
import lookup from './lookup.js';
import parseArray from './parseArray.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;
}
  1. index,全局提供SSG_TemplateEngine对象,如下所示:
  • 渲染方法,调用parseTemplateToTokens函数,让模板字符串能够变为tokens数组。
  • 调用renderTemplate函数,让tokens数组变为dom字符串。
  1. index,代码如下所示:
import parseTemplateToTokens from './parseTemplateToTokens.js';
import renderTemplate from './renderTemplate.js';

window.SSG_TemplateEngine = {
    render(templateStr, data) {
        var tokens = parseTemplateToTokens(templateStr);
        var domStr = renderTemplate(tokens, data);
        
        return domStr;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值