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 在光标定位的地方插入文字的插件
May 10 Javascript
jQuery-Tools-overlay 使用介绍
Jul 14 Javascript
jQuery判断复选框是否勾选的原理及示例
May 21 Javascript
JavaScript常用验证函数实例汇总
Nov 25 Javascript
AngularJS模块管理问题的非常规处理方法
Apr 29 Javascript
javascript中利用柯里化函数实现bind方法【推荐】
Apr 29 Javascript
使用bootstrap3开发响应式网站
May 12 Javascript
jquery遍历json对象集合详解
May 18 Javascript
将input框中输入内容显示在相应的div中【三种方法可选】
May 08 Javascript
利用JQUERY实现多个AJAX请求等待的实例
Dec 14 jQuery
vue2实现搜索结果中的搜索关键字高亮的代码
Aug 29 Javascript
Layui数据表格判断编辑输入的值,是否为我需要的类型详解
Oct 26 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
欧美媒体选出10年前最流行的17部动画
2017/01/18 日漫
php使用ZipArchive提示Fatal error: Class ZipArchive not found in的解决方法
2014/11/04 PHP
PHP网站建设的流程与步骤分享
2015/09/25 PHP
php结合mysql与mysqli扩展处理事务的方法
2016/06/29 PHP
CI框架(CodeIgniter)公共模型类定义与用法示例
2017/08/10 PHP
PHP高精确度运算BC函数库实例详解
2017/08/15 PHP
PHP7使用ODBC连接SQL Server2008 R2数据库示例【基于thinkPHP5.1框架】
2019/05/06 PHP
JavaScript XML操作 封装类
2009/07/01 Javascript
javascript实现滑动解锁功能
2014/12/31 Javascript
jQuery选择器实例应用
2017/01/05 Javascript
tab栏切换原理
2017/03/22 Javascript
dropload.js插件下拉刷新和上拉加载使用详解
2017/10/20 Javascript
探索Vue高阶组件的使用
2018/01/08 Javascript
vue移动端实现红包雨效果
2020/06/23 Javascript
从Vuex中取出数组赋值给新的数组,新数组push时报错的解决方法
2018/09/18 Javascript
Vue.js的复用组件开发流程完整记录
2018/11/29 Javascript
Vue中props的详解
2019/05/16 Javascript
vue实现的封装全局filter并统一管理操作示例
2020/02/02 Javascript
通过实例解析js可枚举属性与不可枚举属性
2020/12/02 Javascript
8个非常实用的Vue自定义指令
2020/12/15 Vue.js
Python制作词云的方法
2018/01/03 Python
Python实现的自定义多线程多进程类示例
2018/03/23 Python
PyQt5每天必学之像素图控件QPixmap
2018/04/19 Python
python实现烟花小程序
2019/01/30 Python
Django中在xadmin中集成DjangoUeditor过程详解
2019/07/24 Python
用python写爬虫简单吗
2020/07/28 Python
微信端html5页面调用分享接口示例
2018/03/14 HTML / CSS
新加坡时尚网上购物:Zalora新加坡
2016/07/26 全球购物
override和overload的区别
2016/03/09 面试题
党的群众路线教育实践活动心得体会900字
2014/03/07 职场文书
五心教育心得体会
2014/09/04 职场文书
2014年群众路线教育实践活动整改措施
2014/09/24 职场文书
房屋租赁协议书
2014/10/18 职场文书
教师党员个人总结
2015/02/10 职场文书
运动会闭幕式主持词
2015/07/01 职场文书
教你用Java Swing实现自助取款机系统
2021/06/11 Java/Android