web前端的渲染发展历程-vue模板引擎

122 阅读3分钟

模板引擎是将数据变成视图的最优雅的方式

没使用vue的v-for,了解前端的渲染发展历程

1.使用原生要先获取dom节点然后appendChild image.png

2.es6的模板字符串

image.png

3.mustache是一个js库,使用语法:mustache.render(模板字符串,数据data) image.png

可以使用
<script type='text/template'>
存储字符串,代码可以高亮,方便编程**    

image.png

1.定义一个扫描类,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){
            this.pos +=tag.length
            this.tail = this.templateStr.substring(this.pos)
        }
    }
    // 遇到指定的字符串就停止,然后返回之前到停止的字符串
    scanUtil(stoTag){
        // 记录上一次停止的位置
        const pos_backUp = this.pos
        while(this.tail.indexOf(stoTag)!=0&&!this.eos()){
            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
    }
}

2.将截取的字符串转换为tokens,遇到#,提取#作为第一个元素,第二个去掉#号,标记['#',去掉#取剩下的]

import Scanner from "./Scanner.js"
// 将截取的字符串转换为tokens
export default function parseTemplateTokens(templateStr) {
    let tokens = []
    // 实例一个扫描器
    var scanner = new Scanner(templateStr)
    var words
    while (!scanner.eos()) {
        words = scanner.scanUtil('{{')
        scanner.scan('{{')
        if (!words == '') {
            tokens.push(['text', words])
        }
        words = scanner.scanUtil('}}')
        scanner.scan('}}')
        if (!words == '') {
            // 遇到#,提取#作为第一个元素,第二个去掉#号
            if (words[0] == '#') {
                tokens.push(['#', words.substring(1)])
            } else if (words[0] == '/') {
                tokens.push(['/', words.substring(1)])
            }else{
                tokens.push(['name', words])
            }
        }
    }
    return tokens
}

3.将其处理为多维数组,遇到#就给当前数组第二个元素生成一个[], 出栈就判断是不是还有栈,有就追加到当前数组第二个元素,没有就追加到最外一个数组,结束多维数组

// 将tokens处理成嵌套的tokens
export default function (tokens) {
    // 最终结果
    let nestedTokens = []
    // 栈结构,存放小tokens。栈顶的tokens数组中当前操作的这个tokens的第三项小数组
    let sections = []
    /**
     * 收集器,默认指向nestedTkens结果数组
     * 当匹配到 # 的时候,改变收集器的指向,指向到当前token中新开辟的下一个维度的token
     * 当匹配到 / 的时候,改变收集器的指向,判断当前栈sections是否有值?有值,则指向当前的栈顶;没值,则指向最终结果 nestedTokens
     */
    let 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]= []
                console.log(collector, token[2],'collector')
                break;
            case '/':
                // 出栈
                sections.pop()
                //将其赋值给sections最后一项的第二个元素,如果没有栈就直接给最外面的数组的赋值
                collector = sections.length ?
                    sections[sections.length - 1][2] :
                    nestedTokens
                break;
            default:
                collector.push(token)
                break;
        }
    }
    return nestedTokens
}

4.将tokens和data结合,生成DOM字符串

// 将tokens和data结合,生成DOM字符串
export default function (tokens, data) {
    console.log('tokens', tokens, data)
    let result = []
    //简单的例子追加生成字符串。今天开始学习mustache,我好开心啊
    for (let index = 0; index < tokens.length; index++) {
        let token =tokens[index]
        if (token[0] == 'text') {
            result += token[1]
        } else if (token[0] == 'name') {
            result += data[token[1]]
        }

    }
    console.log(result)
    return result
}

5.lookup函数,识别a.b.c和.

/**
 * 根据传入的字符串寻找对象中的值
 * @param {object} data 
 * @param {string} keyName 
 */
export default function lookup(dataObj, keyName) {
    var temp = dataObj
    // 传入的keyName可能是a.b.c,也可能是.
    if (keyName.indexOf('.') != 0  && keyName !== '.') {
        //将其转换为数组
        const keys = keyName.split('.')
        for (let index = 0; index < keys.length; index++) {
            const key = keys[index]
            // 有值才往下找
            if (temp[key]) {
                temp = temp[key]
            } else {
                throw new Error('寻找的值不存在')
            }
        }
    } else {
        temp = temp[keyName]
    }
    return temp
}

6.处理数组、对象循环,结合renderTemplate实现递归,遍历数组的第二项,然后将里面的每一个元素展示到dom里面

import lookup from "./lookup"
import renderTemplate from "./renderTemplate";
/**
 * 处理数组、对象循环,结合renderTemplate实现递归
 * @param {array} token 
 * @param {object} data 
 */
 export default function (token, data) {
    console.log('token处理数组', token)
    let resultStr = ''
    // data中的实际数据,决定循环的方式
    const v = lookup(data, token[1])
    for (let i = 0; i < v.length; i++) {
        resultStr +=renderTemplate(token[2],{
            //展开对象,然后添加属性名为'.': v[i]
            ...v[i],
            '.': v[i]
        })
        
    }
    return resultStr
 }