Javascript 字符串模板的简单实现


Posted in Javascript onFebruary 13, 2016

这是源于两年前,当我在做人生中第一个真正意义上的网站时遇到的一个问题

该网站采用前后端分离的方式,由后端的 REST 接口返回 JSON 数据,再由前端渲染到页面上。

同许多初学 Javascript 的菜鸟一样,起初,我也是采用拼接字符串的形式,将 JSON 数据嵌入 HTML 中。开始时代码量较少,暂时还可以接受。但当页面结构复杂起来后,其弱点开始变得无法忍受起来:

  1. 书写不连贯。每写一个变量就要断一下,插入一个 + 和 "。十分容易出错。
  2. 无法重用。HTML 片段都是离散化的数据,难以对其中重复的部分进行提取。
  3. 无法很好地利用 <template> 标签。这是 HTML5 中新增的一个标签,标准极力推荐将 HTML 模板放入 <template> 标签中,使代码更简洁。
  4. 当时我的心情就是这样的:

这TMD是在逗我吗

为了解决这个问题,我暂时放下了手上的项目,花了半个小时实现一个极简易的字符串模板。

需求描述

实现一个 render(template, context) 方法,将 template 中的占位符用 context 填充。要求:

不需要有控制流成分(如 循环、条件 等等),只要有变量替换功能即可
级联的变量也可以展开
被转义的的分隔符 { 和 } 不应该被渲染,分隔符与变量之间允许有空白字符
例子:

render('My name is {name}', {
  name: 'hsfzxjy'
}); // My name is hsfzxjy

render('I am in {profile.location}', {
  name: 'hsfzxjy',
  profile: {
    location: 'Guangzhou'
  }
}); // I am in Guangzhou

render('{ greeting }. \\{ This block will not be rendered }', {
  greeting: 'Hi'
}); // Hi. { This block will not be rendered }

实现

先写下函数的框架:

function render(template, context) {

}

显然,要做的第一件事便是 匹配模板中的占位符。

匹配占位符

匹配的事,肯定是交给正则表达式来完成。那么,这个正则表达式应该长什么样呢?

根据 需求 1、2 的描述,我们可以写出:

var reg = /\{([^\{\}]+)\}/g;

至于需求 3,我第一个想到的概念是 前向匹配,可惜 Javascript 并不支持,只好用一个折衷的办法:

var reg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;
// 若是第一个或第三个分组值不为空,则不渲染
现在,代码应该是这样:

function render(template, context) {

  var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;

  return template.replace(tokenReg, function (word, slash1, token, slash2) {
    if (slash1 || slash2) { // 匹配到转义字符
      return word.replace('\\', ''); // 如果 分隔符被转义,则不渲染
    }

    // ...
  })
}

占位符替换

嗯,正则表达式确定了,接下来要做的便是替换工作。

根据 需求2,模板引擎不仅要能渲染一级变量,更要渲染多级变量。这该怎么做呢?

其实很简单:将 token 按 . 分隔开,逐级查找即可:

var variables = token.replace(/\s/g, '').split('.'); // 切割 token
var currentObject = context;
var i, length, variable;

// 逐级查找 context
for (i = 0, length = variables.length, variable = variables[i]; i < length; ++i)
  currentObject = currentObject[variable];

return currentObject;

不过,有可能 token 指定的变量并不存在,这时上面的代码便会报错。为了更好的体验,代码最好有一定的容错能力:

var variables = token.replace(/\s/g, '').split('.'); // 切割 token
var currentObject = context;
var i, length, variable;

for (i = 0, length = variables.length, variable = variables[i]; i < length; ++i) {
  currentObject = currentObject[variable];
  if (currentObject === undefined || currentObject === null) return ''; // 如果当前索引的对象不存在,则直接返回空字符串。
}

return currentObject;

把所有的代码组合在一起,便得到了最终的版本:

function render(template, context) {

  var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;

  return template.replace(tokenReg, function (word, slash1, token, slash2) {
    if (slash1 || slash2) { 
      return word.replace('\\', '');
    }

    var variables = token.replace(/\s/g, '').split('.');
    var currentObject = context;
    var i, length, variable;

    for (i = 0, length = variables.length, variable = variables[i]; i < length; ++i) {
      currentObject = currentObject[variable];
      if (currentObject === undefined || currentObject === null) return '';
    }

    return currentObject;
  })
}

除去空白行,一共 17 行。

将函数挂到 String 的原型链

甚至,我们可以通过修改原型链,实现一些很酷的效果:

String.prototype.render = function (context) {
  return render(this, context);
};

之后,我们便可以这样调用啦:

"{greeting}! My name is { author.name }.".render({
  greeting: "Hi",
  author: {
    name: "hsfzxjy"
  }
});
// Hi! My name is hsfzxjy.
Javascript 相关文章推荐
表格 隔行换色升级版
Nov 07 Javascript
拖动table标题实现改变td的大小(css+js代码)
Apr 16 Javascript
JavaScript:Div层拖动效果实例代码
Aug 06 Javascript
jQuery UI 实现email输入提示实例
Aug 15 Javascript
JavaScript、tab切换完整版(自动切换、鼠标移入停止、移开运行)
Jan 05 Javascript
深入理解JavaScript单体内置对象
Jun 06 Javascript
JavaScript提升性能的常用技巧总结【经典】
Jun 20 Javascript
轻松掌握JavaScript单例模式
Aug 25 Javascript
JavaScript常用代码书写规范的超全面总结
Sep 11 Javascript
深入理解vue.js中$watch的oldvalue与newValue
Aug 07 Javascript
详解javascript常用工具类的封装
Jan 30 Javascript
一篇文章看懂JavaScript中的回调
Jan 05 Javascript
javascript基础知识分享之类与函数化
Feb 13 #Javascript
JavaScript正则表达式的分组匹配详解
Feb 13 #Javascript
js HTML5 Ajax实现文件上传进度条功能
Feb 13 #Javascript
js随机生成26个大小写字母
Feb 12 #Javascript
jquery实现具有嵌套功能的选项卡
Feb 12 #Javascript
基于jquery实现动态竖向柱状条特效
Feb 12 #Javascript
原生javascript实现自动更新的时间日期
Feb 12 #Javascript
You might like
php中文本数据翻页(留言本翻页)
2006/10/09 PHP
php遍历解析xml字符串的方法
2016/05/05 PHP
详解php命令注入攻击
2019/04/06 PHP
Jquery选择器 $实现原理
2009/12/02 Javascript
javaScript call 函数的用法说明
2010/04/09 Javascript
一款Jquery 分页插件的改造方法(服务器端分页)
2011/07/11 Javascript
Javascript面向对象设计一 工厂模式
2011/12/20 Javascript
A标签触发onclick事件而不跳转的多种解决方法
2013/06/27 Javascript
如何检测JavaScript的各种类型
2016/07/30 Javascript
js cookie实现记住密码功能
2017/01/17 Javascript
用file标签实现多图文件上传预览
2017/02/14 Javascript
js时间戳与日期格式之间转换详解
2017/12/11 Javascript
Vue-Router2.X多种路由实现方式总结
2018/02/09 Javascript
Rollup处理并打包JS文件项目实例代码
2018/05/31 Javascript
element-ui 的el-button组件中添加自定义颜色和图标的实现方法
2018/10/26 Javascript
理解生产者消费者模型及在Python编程中的运用实例
2016/06/26 Python
python使用pipeline批量读写redis的方法
2019/02/18 Python
详解python列表(list)的使用技巧及高级操作
2019/08/15 Python
关于Python3 lambda函数的深入浅出
2019/11/27 Python
python cv2读取rtsp实时码流按时生成连续视频文件方式
2019/12/25 Python
python numpy数组复制使用实例解析
2020/01/10 Python
python常用运维脚本实例小结
2020/02/14 Python
Python实现中英文全文搜索的示例
2020/12/04 Python
python中Mako库实例用法
2020/12/31 Python
HTML5 本地存储之如果没有数据库究竟会怎样
2013/04/25 HTML / CSS
英国专业美容产品在线:Mylee(从指甲到脱毛)
2020/07/06 全球购物
如何撰写一封出色的求职信
2014/04/27 职场文书
反对形式主义、官僚主义、享乐主义和奢靡之风整改措施
2014/09/17 职场文书
英语教师个人工作总结
2015/02/09 职场文书
三方合作意向书范本
2015/05/09 职场文书
2015学校年度工作总结
2015/05/11 职场文书
村党总支部公开承诺书2016
2016/03/25 职场文书
数据库连接池
2021/04/06 MySQL
图文详解Nginx版本平滑升级方案
2021/09/15 Servers
用JS创建一个录屏功能
2021/11/11 Javascript
nginx日志格式分析和修改
2022/04/28 Servers