Research on source code of underscore.js (6)

Overview

I have wanted to study the underscore source code for a long time. Although the underscore.js library is a bit outdated, I still want to learn about the library's architecture, functional programming and the writing of common methods, but there is nothing else to study. , so let's end the wish to study the underscore source code.

underscore.js source code research (1)
underscore.js source code research (2)
underscore.js source code research (3)
underscore.js source code research (4)
underscore.js source code research (5)
underscore.js source code research (6)
underscore. js source code research (7)
underscore.js source code research (8)

References: official notes of underscore.js , undersercore source code analysis , undersercore source code analysis segmentfault

Template engine improvements

This blog post is mainly about making some improvements to the small template engine built before .

braces support

In theory, if you write tpl in the following way, you can output curly braces:

//定义模板和数据
const tpl = 'Students:' +
    //注意这里后面有一个大括号
    '{ for(i = 0; i < data.students.length; i++){ }' +
    '{{ data.students[i].name }}' +
    //插入语句,这个语句是一个大括号
    '{ } }';

But when it actually matches, it will match {}instead of {}}, so we need to change the rules of regular expressions. According to the writing method of underscore.js, we use ERB-style rules:

const rules = {
    //插值,对应变量
    interpolate: /<%=([\s\S]+?)%>/,
    //逻辑,对应语句
    evaluate: /<%([\s\S]+?)%>/
};

//2个正则合在一起,先替换变量,再替换语句
const matcher = new RegExp([
    rules.interpolate.source,
    rules.evaluate.source
].join('|'), 'g');

In this way, the curly brackets can be implemented, and the code is as follows. (need to import the above code first)

//定义模板和数据
const tpl = 'Students:' +
    //注意这里只有一个大括号!!!
    '<% for(i = 0; i < data.students.length; i++){ %>' +
    '<%= data.students[i].id %>' +
    '<%= data.students[i].name %>' +
    '<% } %>';
const data = {
    students: [{
        id: 1,
        name: ' haha '
    },{
        id: 2,
        name: ' yaya '
    }]
};

//render函数
function render(tpl, data) {
    let concating = 'let content = "";\n';
    let index = 0;
    //仍然是replace里面的第二个参数是函数的形式
    tpl.replace(matcher, (match, interpolate, evaluate, offset) => {
        //添加非模板的内容
        if (tpl.slice(index, offset)) {
            concating += 'content += "' + tpl.slice(index, offset) + '";\n';
        }
        //记录偏移量
        index = offset + match.length;
        //变量需要添加到content里面
        if (interpolate) {
            concating += 'content +=' + interpolate + ';\n';
        //语句不需要添加到content里面,而且不要分号
        } else if (evaluate) {
            concating += evaluate + '\n';
        }
    })
    concating += 'return content;';
    //以concating为内容,定义一个函数,参数是obj
    const renderFunc = new Function('obj', concating);
    return renderFunc(data);
}

//输出,结果为Students:1 haha 2 yaya
console.log(render(tpl, data));

The code for the renderFunc function it generates looks like this:

(function(obj
/*``*/) {
let content = "";
content += "Students:";
 for(i = 0; i < data.students.length; i++){ 
content += data.students[i].id ;
content += data.students[i].name ;
 }
return content;
})

string escape

Sometimes we want to add newlines and other strings to adjust the format of the output , which means we want to be able to parse a template like the following:

//注意下面的/n换行符
const tpl = 'Students: \n' +
    //注意这里只有一个大括号!!!
    '<% for(i = 0; i < data.students.length; i++){ %>' +
    '<%= data.students[i].id %>' +
    '<%= data.students[i].name %>' +
    '\n' +
    '<% } %>';

However, an error was reported, because we \ncaused a newline in the process of building a function with a string, so the statement was interrupted, resulting in an error. The solution is to \nescape the pair as \\n.

First we define the escape rules:

//定义转义规则
var escapes = {
    "'": "'",
    '\\': '\\',
    '\r': 'r',
    '\n': 'n',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
}

//定义转义正则
var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;

//定义转义函数
var escapeChar = function(match) {
    return '\\' + escapes[match];
}

Then we rewrite the render function to escape when adding non-template content :

function render(tpl, data) {
    let concating = 'let content = "";\n';
    let index = 0;
    tpl.replace(matcher, (match, interpolate, evaluate, offset) => {
        //添加非模板的内容
        if (tpl.slice(index, offset)) {
            //这里进行转义
            concating += 'content += "' + tpl.slice(index, offset).replace(escapeRegExp, escapeChar) + '";\n';
        }
        //记录偏移量
        index = offset + match.length;
        //变量需要添加到content里面
        if (interpolate) {
            concating += 'content +=' + interpolate + ';\n';
        //语句不需要添加到content里面,而且不要分号
        } else if (evaluate) {
            concating += evaluate + '\n';
        }
    })
    concating += 'return content;';
    //以concating为内容,定义一个函数,参数是obj
    const renderFunc = new Function('obj', concating);
    return renderFunc(data);
}

Then we execute it again, and the output is as follows, which is exactly what we want:

Students:
1 haha
2 yaya

Let's take a look at the renderFunc function it generates:

(function(obj
/*``*/) {
let content = "";
content += "Students: \n";
 for(i = 0; i < data.students.length; i++){ 
content += data.students[i].id ;
content += data.students[i].name ;
content += "\n";
 }
return content;
})

Precompiled

Above we used this template like this:

console.log(render(tpl, data));

Obviously, the render function, the tpl template and the data data are coupled together, which means that if we need to modify the data data, we need to call the render function to re-render the tpl once , which is very time-consuming.

So we're going to precompile. The principle is that in the render function, we use a closure to store the concating (that is, the result of parsing the tpl by the render function) , and then the next time the data is changed, the stored concating data is directly used without recompiling to generate the concating data. code show as below:

//只接受tpl参数
function render(tpl) {
    let concating = 'let content = "";\n';
    let index = 0;
    tpl.replace(matcher, (match, interpolate, evaluate, offset) => {
        //添加非模板的内容
        if (tpl.slice(index, offset)) {
            concating += 'content += "' + tpl.slice(index, offset).replace(escapeRegExp, escapeChar) + '";\n';
        }
        //记录偏移量
        index = offset + match.length;
        //变量需要添加到content里面
        if (interpolate) {
            concating += 'content +=' + interpolate + ';\n';
        //语句不需要添加到content里面,而且不要分号
        } else if (evaluate) {
            concating += evaluate + '\n';
        }
    })
    concating += 'return content;';
    //以concating为内容,定义一个函数,参数是obj
    const renderFunc = new Function('obj', concating);
    //这里我们返回一个函数,形成一个闭包
    return function(data){
        return renderFunc(data);
    };
}

The whole code to use is like this:

//定义转义规则
var escapes = {
    "'": "'",
    '\\': '\\',
    '\r': 'r',
    '\n': 'n',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
}

//定义转义正则
var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;

//定义转义函数
var escapeChar = function(match) {
    return '\\' + escapes[match];
}

//为了方便,我们把规则封装在一个对象里面
const rules = {
    //插值,对应变量
    interpolate: /<%=([\s\S]+?)%>/,
    //逻辑,对应语句
    evaluate: /<%([\s\S]+?)%>/
};

//2个正则合在一起,先替换变量,再替换语句
const matcher = new RegExp([
    rules.interpolate.source,
    rules.evaluate.source
].join('|'), 'g');

//定义模板
const tpl = 'Students: \n' +
    //注意这里只有一个大括号!!!
    '<% for(i = 0; i < data.students.length; i++){ %>' +
    '<%= data.students[i].id %>' +
    '<%= data.students[i].name %>' +
    '\n' +
    '<% } %>';

//定义数据
let data = {
    students: [{
        id: 1,
        name: ' haha '
    },{
        id: 2,
        name: ' yaya '
    }]
};

//render函数
function render(tpl) {
    let concating = 'let content = "";\n';
    let index = 0;
    tpl.replace(matcher, (match, interpolate, evaluate, offset) => {
        //添加非模板的内容
        if (tpl.slice(index, offset)) {
            concating += 'content += "' + tpl.slice(index, offset).replace(escapeRegExp, escapeChar) + '";\n';
        }
        //记录偏移量
        index = offset + match.length;
        //变量需要添加到content里面
        if (interpolate) {
            concating += 'content +=' + interpolate + ';\n';
        //语句不需要添加到content里面,而且不要分号
        } else if (evaluate) {
            concating += evaluate + '\n';
        }
    })
    concating += 'return content;';
    //以concating为内容,定义一个函数,参数是obj
    const renderFunc = new Function('obj', concating);
    //这里我们返回一个函数,形成一个闭包
    return function(data){
        return renderFunc(data);
    };
}

//先进行编译
var tplCompile = render(tpl);
//输出
console.log(tplCompile(data));
//修改数据
data = {
    students: [{
        id: 1,
        name: ' haha '
    },{
        id: 2,
        name: ' yaya '
    },{
        id: 3,
        name: ' jaja '
    }]
};
//输出
console.log(tplCompile(data));

The output is as follows:

Students: 
1 haha 
2 yaya 

Students: 
1 haha 
2 yaya 
3 jaja 

other

Sometimes, we need to escape the value inserted into the template , otherwise known as escape. For example, if we insert a <script>tag, then we need to escape the <. At this time, we must add a third rule to the regular rule declared at the beginning. We will not discuss it here. If you are interested, you can refer to underscore. js source code .

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325120518&siteId=291194637