乐趣区

关于javascript:对象创建型设计模式Builder

Builder 设计模式的目标就是将一个简单对象的构建与示意拆散,使同样的构建过程能够有不同的示意。

例如有一个 XML 文件解析程序,一种需要可能将一个 xml 文件转换成 json 格局的数据,而另一个需要须要将 xml 文件转换成 csv 文本模式。一种可能的实现形式就是实现一个独立的 xml 文本解析器,解析器从前往后解析文本,遇到不同的标签格局时告诉特定的构建器构建后果。解析实现时由构建器返回最终的构建后果。

上面咱们以创立一个简略的模板引擎为例理解 builder 模式:
对于模板引擎大家肯定很相熟,比方 Mustache、pug、ejs,understore 的 template 办法,这些模板的应用通常都是传入一段模板自字符串,返回一个编译好的函数,通过一个上下文对象作为参数调用这个函数就能够失去一段 html 文本字符串。

比方 pug 的应用形式如下(例子来自于 pug 中文文档):const pug = require('pug');
const compiledFunction = pug.compileFile('template.pug');
console.log(compiledFunction({name: '李莉'}));
// "<p> 李莉的 Pug 代码!</p>"
// 渲染另外一组数据
console.log(compiledFunction({name: '张伟'}));
// "<p> 张伟的 Pug 代码!</p>"

接下来咱们来写一个本人的模板解析工具:
假如当初有一个这样的模板字符串:

    const tpl = `
        <p>{{name}}</p>
        {{if isVip}}
        <p>Welcome!</p>
        {{/endif}}
    `;
    

在下面的模板中 {{}} 示意一个插值,{{if isVip}} …{{/endif}}示意一个条件编译语句,只有在 isVip 为 true 时,之间的 html 内容才会被渲染。

首先定义一个解析器:

class Parser {static parse(tpl, builder) {const htmlReg = /^s*([^{}]+)/;
         const interpReg = /^s*{{s*(w+?)s*}}/;
         const ifReg = /^s*{{ifs+(w+?)s*}}(.*?){{/endif}}/;
         // 去除换行和多余的空格
         tpl = tpl.replace(/n+/g, '').replace(/s{2,}/g,' ');
         while(tpl.length) {
             // 一般标签
             if (htmlReg.test(tpl)) {const match = tpl.match(htmlReg);
                 const label = match[0];
                 tpl = tpl.substr(label.length);
                 // html += (html === ''?'' : '+') + JSON.stringify(label.trim());
                 builder.html(label.trim());
             } else if(ifReg.test(tpl)) { // 条件语句
                 const match = tpl.match(ifReg);
                 const label = match[0];
                 tpl = tpl.substr(label.length);
                 builder.condition(...match.slice(1, 3).map(s => s.trim()));
             } else if(interpReg.test(tpl)) { // 插值语句
                 const match = tpl.match(interpReg);
                 const label = match[0];
                 const name = match[1];
                 tpl = tpl.substr(label.length);
                 // html += '+' + '$$option.' + name;
                 builder.interpolation(name);
             } else {break;}
         }
     }
}

解析器的有一个 parse 办法,该办法承受两个参数,一个待解析的模板字符串以及一个构建器,构建器须要提供至多三个办法 html,interpolattion,condition 这三个办法别离用于解决解析器解析进去的一般 html、插值表达式、以及条件语句。依照雷同的形式能够扩大出其余更多的管制语句。
上面时 builder 类的实现:

class Builder {constructor(ctxName = '$$option') {
         this._result = '';
         this._ctxName = ctxName;
     }
     result() {return this._result;}
     html(html) {this._result += (this._result === ''?'' : '+') + JSON.stringify(html);
     }
     condition(name, content) {
         this._result += this._result === ''?'' : '+';
         this._result += `
         (${this._ctxName}.${name}?${JSON.stringify(content)}:${JSON.stringify('')})
         `;
     }
         interpolation(name) {this._result += (this._result === ''?'' : '+') + `${this._ctxName}.` + name;
     }
}

Builder 类的 result 办法返回最终失去的代码字符串。
联合下面的两个类,实现的编译函数如下:


function compile(tpl) {const builder = new Builder();
     Parser.parse(tpl, builder);
     return new Function('$$option', `return ${builder.result()}`);
}

compile 函数首先构建一个 builder 实例,通过该实例调用 parse 办法,最终返回一个函数用于执行从 builder 失去的代码字符串。

接下来测试一下这个程序:

    const template = compile(tpl);
    console.log(template({name: '小明', isVip: true}));

执行后果如下:

    <p> 小明 </p><p>Welcome!</p>
退出移动版