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 相关文章推荐
textarea焦点的用法实现获取焦点清空失去焦点提示效果
May 19 Javascript
javascript的replace方法结合正则使用实例总结
Jun 16 Javascript
Bootstrap select下拉联动(jQuery cxselect)
Jan 04 Javascript
Node.js+Express+MySql实现用户登录注册功能
Jul 10 Javascript
Node.js学习之地址解析模块URL的使用详解
Sep 28 Javascript
javascript自定义事件功能与用法实例分析
Nov 08 Javascript
JavaScript程序设计高级算法之动态规划实例分析
Nov 24 Javascript
js中如何完美的解析数据
Mar 18 Javascript
webstorm中vue语法的支持详解
May 09 Javascript
JS Object.preventExtensions(),Object.seal()与Object.freeze()用法实例分析
Aug 25 Javascript
通过JS深度判断两个对象字段相同
Jun 14 Javascript
Js跳出两级循环方法代码实例
Sep 22 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自动跳转中英文页面
2008/07/29 PHP
PHP GD 图像处理组件的常用函数总结
2010/04/28 PHP
php 模拟POST|GET操作实现代码
2010/07/20 PHP
PHP导入导出Excel代码
2015/07/07 PHP
javascript 图片上一张下一张链接效果代码
2010/03/12 Javascript
jQuery对象[0]是什么含义?
2010/07/31 Javascript
通过Jscript中@cc_on 语句识别IE浏览器及版本的代码
2011/05/07 Javascript
各浏览器对click方法的支持差异小结
2011/07/31 Javascript
js unicode 编码解析关于数据转换为中文的两种方法
2014/04/21 Javascript
js判断浏览器版本以及浏览器内核的方法
2015/01/20 Javascript
jQuery 遍历函数详解
2015/07/05 Javascript
浅析如何利用JavaScript进行语音识别
2016/10/27 Javascript
进阶之初探nodeJS
2017/01/24 NodeJs
js 动态生成html 触发事件传参字符转义的实例
2017/02/14 Javascript
nodejs开发——express路由与中间件
2017/03/24 NodeJs
js form表单input框限制20个字符,10个汉字代码实例
2019/04/12 Javascript
taro开发微信小程序的实践
2019/05/21 Javascript
使用Typescript开发微信小程序的步骤详解
2021/01/12 Javascript
[47:35]VP vs Pain 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/20 DOTA
为Python的web框架编写前端模版的教程
2015/04/30 Python
Python实现的求解最小公倍数算法示例
2018/05/03 Python
python实现AES和RSA加解密的方法
2019/03/28 Python
python 实现在一张图中绘制一个小的子图方法
2019/07/07 Python
tensorflow-gpu安装的常见问题及解决方案
2020/01/20 Python
Html5如何唤起百度地图App的方法
2019/01/27 HTML / CSS
AmazeUI 图标的示例代码
2020/08/13 HTML / CSS
松本清官方海外旗舰店:日本最大的药妆连锁店
2017/11/21 全球购物
卫校护理专业毕业生求职信
2013/11/26 职场文书
中职毕业生自我鉴定
2014/09/13 职场文书
三严三实心得体会范文
2014/10/13 职场文书
护士个人总结范文
2015/02/13 职场文书
档案工作个人总结
2015/03/03 职场文书
商场收银员岗位职责
2015/04/07 职场文书
同事离别感言
2015/08/04 职场文书
孕妇病假条怎么写
2015/08/17 职场文书
Android基础入门之dataBinding的简单使用教程
2022/06/21 Java/Android