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 相关文章推荐
js打印纸函数代码(递归)
Jun 18 Javascript
按钮JS复制文本框和表格的代码
Apr 01 Javascript
基于jquery的button默认enter事件(回车事件)。
May 18 Javascript
js对列表中第一个值处理与jsp页面对列表中第一个值处理的区别详解
Nov 05 Javascript
window.location.href的用法(动态输出跳转)
Aug 09 Javascript
jQuery扁平化风格下拉框美化插件FancySelect使用指南
Feb 10 Javascript
js基于面向对象实现网页TAB选项卡菜单效果代码
Sep 09 Javascript
Javascript实现数组中的元素上下移动
Apr 28 Javascript
浅谈Node.js轻量级Web框架Express4.x使用指南
May 03 Javascript
js实现股票实时刷新数据案例
May 14 Javascript
Vue动态路由缓存不相互影响的解决办法
Feb 19 Javascript
JS数组转字符串实现方法解析
Sep 04 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
整理的9个实用的PHP库简介和下载
2010/11/09 PHP
mod_php、FastCGI、PHP-FPM等PHP运行方式对比
2015/07/02 PHP
php获取目录中所有文件名及判断文件与目录的简单方法
2017/03/04 PHP
PHP使用正则表达式实现过滤非法字符串功能示例
2018/06/04 PHP
载入进度条 效果
2006/07/08 Javascript
JQuery 网站换肤功能实现代码
2009/11/02 Javascript
百度Popup.js弹出框进化版 拖拽小框架发布 兼容IE6/7/8,Firefox,Chrome
2010/04/13 Javascript
用Javascript来生成ftp脚本的小例子
2013/07/03 Javascript
JavaScript实现存储HTML字符串示例
2014/04/21 Javascript
Javascript中实现trim()函数的两种方法
2015/02/04 Javascript
ECMAScript中函数function类型
2015/06/03 Javascript
一览画面点击复选框后获取多个id值的方法
2016/05/30 Javascript
AngularJS表格样式简单设置方法示例
2017/03/03 Javascript
详解基于mpvue的小程序markdown适配解决方案
2018/05/08 Javascript
VUE预渲染及遇到的坑
2018/09/03 Javascript
Vue中对拿到的数据进行A-Z排序的实例
2018/09/25 Javascript
vue打包之后生成一个配置文件修改接口的方法
2018/12/09 Javascript
JS函数节流和防抖之间的区分和实现详解
2019/01/11 Javascript
jquery弹窗时禁止body滚动条滚动的例子
2019/09/21 jQuery
基于Echarts图表在div动态切换时不显示的解决方式
2020/07/20 Javascript
vue setInterval 定时器失效的解决方式
2020/07/30 Javascript
vue用elementui写form表单时,在label里添加空格操作
2020/08/13 Javascript
JS变量提升及函数提升实例解析
2020/09/03 Javascript
Python文件操作,open读写文件,追加文本内容实例
2016/12/14 Python
Python 实现异步调用函数的示例讲解
2018/10/14 Python
Python读取指定日期邮件的实例
2019/02/01 Python
详解python 模拟豆瓣登录(豆瓣6.0)
2019/04/18 Python
python的pytest框架之命令行参数详解(下)
2019/06/27 Python
PyCharm2020.1.1与Python3.7.7的安装教程图文详解
2020/08/07 Python
HTML5 语音搜索(淘宝店语音搜素)
2013/01/03 HTML / CSS
乡村文明行动实施方案
2014/03/29 职场文书
家长建议怎么写
2014/05/15 职场文书
主要负责人任命书
2014/06/06 职场文书
升学宴演讲稿
2014/09/01 职场文书
mysql知识点整理
2021/04/05 MySQL
解决WINDOWS电脑开机后桌面没有任何图标
2022/04/09 数码科技