Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Underscore里的 模板引擎 #39

Open
jyzwf opened this issue Jan 11, 2018 · 0 comments
Open

Underscore里的 模板引擎 #39

jyzwf opened this issue Jan 11, 2018 · 0 comments

Comments

@jyzwf
Copy link
Owner

jyzwf commented Jan 11, 2018

总的来说就是利用 正则以及正则子项,将没有匹配到的字符串进行切割,并进行 实体编码转义,然后按照正则子项来拼接匹配到的字符串,接着在继续如上操作,最后传入 new Function() 的函数实体的参数中,返回一个函数,并可以指定参数,最后就是用户传入参数,并进行前面模板中的参数替换

// 三种模板的定义,并且是全局
    // \s : Unicode 任何空白符
    // \S : 任何非 Unicode 空白符之间外的字符
    // 非贪婪匹配
    _.templateSettings = {
        evaluate: /<%([\s\S]+?)%>/g,
        interpolate: /<%=([\s\S]+?)%>/g,
        escape: /<%-([\s\S]+?)%>/g
    };

    // . :除换行符和其他Unicode 行终止符之外的任意字符
    var noMatch = /(.)^/;

    var escapes = {
        "'": "'",
        '\\': '\\',
        '\r': 'r', // 回车
        '\n': 'n', // 换行
        '\u2028': 'u2028', // 行分隔符
        '\u2029': 'u2029' // 段落分隔符
    };

    var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;

    var escapeChar = function (match) {
        return '\\' + escapes[match];
    };


    _.template = function (text, settings, oldSettings) {
        // 如果没有新的 setting ,就将老的 setting  设置为 settings
        if (!settings && oldSettings) settings = oldSettings
        // 合并 setting 和 _.templateSettings,但不进行覆盖
        settings = _.defaults({}, settings, _.templateSettings);

        // 正则的属性:
        //  var  escape = /<%-([\s\S]+?)%>/g
        // escape.source ="d(b+)d"   -> string
        // escape.lastIndex :下一个匹配的索引值

        var matcher = RegExp([
            (settings.escape || noMatch).source,
            (settings.interpolate || noMatch).source,
            (settings.evaluate || noMatch).source
        ].join('|') + '|$', 'g'); // -> /<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g


        var index = 0
        var source = "__p+="

        /**
        * _.template("Using 'with': <%= data.answer %>", {variable: 'data'})({answer: 'no'});

            以上述例子为例讲解


        */
        text.replace(matcher, function (match, escape, interceptor, evaluate, offset) {
            // \n ->\\n    \->\\ 
            // 此时 source ="__p+=Using \'with\': "
            // match =  <%= data.answer %>
            // escape = '  data.answer  '
            source = += text.slice(index, offset).replace(escapeRegExp, escapeChar)
            // 便于下次 slice 
            index = offset + match.length

            if (escape) {
                // 需要对变量进行编码
                // source ="__p+=Using \'with\': '+\n((__t=(  data.answer  ))==null?'':_.escape(__t))+\n'"
                source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
            } else if (interpolate) {
                // 单纯地插入变量
                source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
            } else if (evaluate) {
                // 可以直接执行的 JavaScript 语句
                // 注意 "__p+=",__p 为渲染返回的字符串
                source += "';\n" + evaluate + "\n__p+='";
            }
            // 将匹配到的内容原样返回(Adobe VMs 需要返回 match 来使得 offset 值正常)
            return match;
        })

        // source ="__p+=Using \'with\': '+\n((__t=('  data.answer  '))==null?'':_.escape(__t))+\n'';\n"
        source += "';\n"

        // 如果没有指定  settings.variable 直接用 with 指定作用域
        // 关于 with
        // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/with
        if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

        // 增设 print 函数,返回 所有参数 link 在一起的 字符串
        source = "var __t,__p='',__j=Array.prototype.join," +
            "print=function(){__p+=__j.call(arguments,'');};\n" +
            source + 'return __p;\n';
        /** 
         * source = (
         *      var __t,__p = '',__j = Array.prototype.join,
         *          print = function(){
         *              __p+=__j.call(arguments,"");
         *          }
         * 
         *          __p+=(Using \'with\': 
         *                  ((__t=('  data.answer  '))==null?'':_.escape(__t))
         *              )
         * 
         *          return __p
         * )
         */


        var render;
        try {
            // obj 为传入的 JSON 对象,传入 _ 参数使得函数内部能用 Underscore 的函数
            // source: 一个含有包括函数定义的JavaScript语句的字符串。

            // {variable: 'data'}

            /** 
             * render = function(data=obj,_){
             *      // 函数执行实体
             *   var __t,__p = '',__j = Array.prototype.join,
             *          print = function(){
             *              __p+=__j.call(arguments,"");
             *          }
             * 
             *          __p+=(Using \'with\': 
             *                  ((__t=('  data.answer  '))==null?'':_.escape(__t))
             *              )
             * 
             *          return __p
             * }
             */
            render = new Function(settings.variable || 'obj', '_', source);
        } catch (e) {
            e.source = source;
            throw e;
        }

        var template = function (data) {
            // data = {answer: 'no'}
            // 执行render 
            /** 
             * render = function(data=obj,_){
             *      // 函数执行实体
             *   var __t,
             *      __p = '',
             *      __j = Array.prototype.join,
             *      print = function(){
             *          __p+=__j.call(arguments,"");
             *      }
             * 
             *      __p+=(Using \'with\': 
             *              // data.answer = 'no'
             *              ((__t=('  data.answer  '))==null?'':_.escape(__t))
             *          )
             *      // __p = "Using \'with\':no"
             *      return __p
             * }
             */
            return render.call(this, data, _);
        };

        var argument = settings.variable || 'obj';

        /* 预编译模板对调试不可重现的错误很有帮助. 这是因为预编译的模板可以提供错误的代码行号和堆栈跟踪, 
        有些模板在客户端(浏览器)上是不能通过编译的 在编译好的模板函数上, 有 source 属性可以提供简单的预编译功能. */

        template.source = 'function(' + argument + '){\n' + source + '}';

        return template
    }

上面如有不当,欢迎指正^_^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant