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 相关文章推荐
jQuery 表单验证扩展代码(二)
Oct 20 Javascript
深入理解JavaScript定时机制
Oct 27 Javascript
Bootstrap弹出框之自定义悬停框标题、内容和样式示例代码
Jul 11 Javascript
js编写简单的计时器功能
Jul 15 Javascript
微信小程序富文本渲染引擎的详解
Sep 30 Javascript
jQuery 实现左右两侧菜单添加、移除功能
Jan 02 jQuery
vue2.0之多页面的开发的示例
Jan 30 Javascript
JS实现的邮箱提示补全效果示例
Jan 30 Javascript
JS编写兼容IE6,7,8浏览器无缝自动轮播
Oct 12 Javascript
分享5个小技巧让你写出更好的 JavaScript 条件语句
Oct 20 Javascript
js中Generator函数的深入讲解
Apr 07 Javascript
Echarts动态加载多条折线图的实现代码
May 24 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 基于Yii框架中使用smarty模板的方法详解
2013/06/13 PHP
PHP判断浏览器、判断语言代码分享
2015/03/05 PHP
JQuery 表格操作(交替显示、拖动表格行、选择行等)
2009/07/29 Javascript
不一样的文字闪烁 轮番闪烁
2009/11/11 Javascript
解析JSON对象与字符串之间的相互转换
2013/12/18 Javascript
js事件绑定快捷键以ctrl+k为例
2014/09/30 Javascript
JavaScript操作XML文件之XML读取方法
2015/06/09 Javascript
Highcharts学习之数据列
2016/08/03 Javascript
详解vue mint-ui源码解析之loadmore组件
2017/10/11 Javascript
vue-cli脚手架引入弹出层layer插件的几种方法
2019/06/24 Javascript
vue.js实现三级菜单效果
2019/10/19 Javascript
JS数组的常用10种方法详解
2020/05/08 Javascript
动态创建类实例代码
2009/10/07 Python
python实现将html表格转换成CSV文件的方法
2015/06/28 Python
python3.4用函数操作mysql5.7数据库
2017/06/23 Python
使用sklearn进行对数据标准化、归一化以及将数据还原的方法
2018/07/11 Python
对Python的zip函数妙用,旋转矩阵详解
2018/12/13 Python
python读取目录下最新的文件夹方法
2018/12/24 Python
Python 字符串、列表、元组的截取与切片操作示例
2019/09/17 Python
python numpy生成等差数列、等比数列的实例
2020/02/25 Python
解决echarts中饼图标签重叠的问题
2020/05/16 Python
python 生成器需注意的小问题
2020/09/29 Python
CSS3 mask 遮罩的具体使用方法
2017/11/03 HTML / CSS
html5 Canvas画图教程(3)—canvas出现1像素线条模糊不清的原因
2013/01/09 HTML / CSS
欧洲有机婴儿食品最大的市场:Organic Baby Food(供美国和加拿大)
2018/03/28 全球购物
开普敦通行证:Cape Town Pass
2019/07/18 全球购物
机修工岗位职责
2013/11/24 职场文书
日语专业毕业生求职信
2013/12/04 职场文书
小学生家长寄语
2014/04/02 职场文书
软件售后服务承诺书
2014/05/21 职场文书
学习十八大的心得体会
2014/09/01 职场文书
课外访万家心得体会
2014/09/03 职场文书
离婚协议书包括哪些内容
2014/10/16 职场文书
本溪关门山导游词
2015/02/09 职场文书
保卫工作个人总结
2015/03/03 职场文书
MongoDB balancer的使用详解
2021/04/30 MongoDB