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判断用户浏览器是否是XP SP2的IE6
Mar 08 Javascript
Javascript中的回调函数和匿名函数的回调示例介绍
May 12 Javascript
jquery加载图片时以淡入方式显示的方法
Jan 14 Javascript
BootStrap Progressbar 实现大文件上传的进度条的实例代码
Jun 27 Javascript
Node.js开发教程之基于OnceIO框架实现文件上传和验证功能
Nov 30 Javascript
JS判断指定dom元素是否在屏幕内的方法实例
Jan 23 Javascript
jQury Ajax使用Token验证身份实例代码
Sep 22 Javascript
VSCode中如何利用d.ts文件进行js智能提示
Apr 13 Javascript
Vue.js实现的表格增加删除demo示例
May 22 Javascript
jquery ajaxfileuplod 上传文件 essyui laoding 效果【防止重复上传文件】
May 26 jQuery
vue axios基于常见业务场景的二次封装的实现
Sep 21 Javascript
React中使用Vditor自定义图片详解
Dec 25 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 visitFile()遍历指定文件夹函数
2010/08/21 PHP
基于PHP CURL获取邮箱地址的详解
2013/06/03 PHP
用php简单实现加减乘除计算器
2014/01/06 PHP
PHP编程获取各个时间段具体时间的方法
2017/05/26 PHP
关于 byval 与 byref 的区别分析总结
2007/10/08 Javascript
JavaScript delete操作符应用实例
2009/01/13 Javascript
自写简单JS判断是否已经弹出页面
2010/10/20 Javascript
jquery isType() 类型判断代码
2011/02/14 Javascript
javascript窗口宽高,鼠标位置,滚动高度(详细解析)
2013/11/18 Javascript
利用jquery.qrcode在页面上生成二维码且支持中文
2014/02/12 Javascript
jquery使用on绑定a标签无效 只能用live解决
2016/06/02 Javascript
使用json来定义函数,在里面可以定义多个函数的实现方法
2016/10/28 Javascript
解决Mac node版本升级失败的问题
2018/05/16 Javascript
小程序实现左滑删除功能
2018/10/30 Javascript
JQueryDOM之样式操作
2019/03/27 jQuery
JavaScript实现随机点名程序
2020/03/25 Javascript
Electron+vue从零开始打造一个本地播放器的方法示例
2020/10/27 Javascript
JS中锚点链接点击平滑滚动并自由调整到顶部位置
2021/02/06 Javascript
[23:18]Spirit vs Liquid Supermajor小组赛A组 BO3 第二场 6.2
2018/06/03 DOTA
使用Python的web.py框架实现类似Django的ORM查询的教程
2015/05/02 Python
Python的包管理器pip更换软件源的方法详解
2016/06/20 Python
Python根据欧拉角求旋转矩阵的实例
2019/01/28 Python
python基于SMTP协议发送邮件
2019/05/31 Python
Python Django中间件,中间件函数,全局异常处理操作示例
2019/11/08 Python
使用python+whoosh实现全文检索
2019/12/09 Python
Python网络爬虫信息提取mooc代码实例
2020/03/06 Python
Agoda台湾官网:国内外订房2折起
2018/03/20 全球购物
英国在线药房和在线药剂师:Chemist 4 U
2020/01/05 全球购物
size?法国官网:英国伦敦的球鞋精品店
2020/03/15 全球购物
写演讲稿所需要注意的4个条件
2014/01/09 职场文书
数控专业个人求职信范文
2014/02/05 职场文书
幼儿园家长评语大全
2014/04/16 职场文书
村党支部公开承诺书
2014/05/29 职场文书
千与千寻观后感
2015/06/04 职场文书
反腐倡廉观后感
2015/06/08 职场文书
一篇文章弄懂MySQL查询语句的执行过程
2021/05/07 MySQL