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 删除/替换DOM元素的几种方式
May 20 Javascript
JS+CSS简单树形菜单实现方法
Sep 12 Javascript
Bootstrap每天必学之缩略图与警示窗
Nov 29 Javascript
值得分享和收藏的Bootstrap学习教程
May 12 Javascript
JSONP跨域请求实例详解
Jul 04 Javascript
Bootstrap图片轮播组件Carousel使用方法详解
Oct 20 Javascript
JS DOMReady事件的六种实现方法总结
Nov 23 Javascript
使用requirejs模块化开发多页面一个入口js的使用方式
Jun 14 Javascript
jquery点击回车键实现登录效果并默认焦点的方法
Mar 09 jQuery
vue悬浮可拖拽悬浮按钮的实例代码
Aug 20 Javascript
详解解决小程序中webview页面多层history返回问题
Aug 20 Javascript
vue 使用饿了么UI仿写teambition的筛选功能
Mar 01 Vue.js
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实现链结人气统计
2006/10/09 PHP
PHP中cookies使用指南
2007/03/16 PHP
解决MySQL中文输出变成问号的问题
2008/06/05 PHP
php中使用__autoload()自动加载未定义类的实现代码
2013/02/06 PHP
Laravel 5框架学习之向视图传送数据(进阶篇)
2015/04/08 PHP
ecshop添加菜单及权限分配问题
2017/11/21 PHP
网页防止tab键的使用快速解决方法
2013/11/07 Javascript
基于JS实现移动端访问PC端页面时跳转到对应的移动端网页
2020/12/24 Javascript
jQuery+css实现非常漂亮的水平导航菜单效果
2016/07/27 Javascript
微信小程序 触控事件详细介绍
2016/10/17 Javascript
微信小程序左右滑动切换页面详解及实例代码
2017/02/28 Javascript
微信小程序“摇一摇”的实例代码
2017/07/20 Javascript
spring+angular实现导出excel的实现代码
2019/02/27 Javascript
解析原来浏览器原生支持JS Base64编码解码
2019/08/12 Javascript
JavaScript的变量声明与声明提前用法实例分析
2019/11/26 Javascript
javascript+css实现进度条效果
2020/03/25 Javascript
vue 子组件和父组件传值的示例
2020/09/11 Javascript
Python浅拷贝与深拷贝用法实例
2015/05/09 Python
浅析Python中的多条件排序实现
2016/06/07 Python
Python实现的破解字符串找茬游戏算法示例
2017/09/25 Python
Python实现打印螺旋矩阵功能的方法
2017/11/21 Python
Python键盘输入转换为列表的实例
2018/06/23 Python
python列表,字典,元组简单用法示例
2019/07/11 Python
Python中文分词库jieba,pkusegwg性能准确度比较
2020/02/11 Python
Opencv求取连通区域重心实例
2020/06/04 Python
Python 合并拼接字符串的方法
2020/07/28 Python
最好的意大利皮夹克:D’Arienzo
2018/12/04 全球购物
Ever New美国:澳大利亚领先的女装时尚品牌
2019/11/28 全球购物
市场营销专业个人自荐信格式
2013/09/21 职场文书
2014年最新党员对照检查材料汇总
2014/09/15 职场文书
员工工作表现自我评价
2015/03/06 职场文书
2016年少先队活动总结
2016/04/06 职场文书
超级实用的公文标题大全!
2019/07/19 职场文书
详解CSS玩转图片Base64编码
2021/05/25 HTML / CSS
利用Python判断你的密码难度等级
2021/06/02 Python
英国数字版游戏销量周榜公布 《小缇娜的奇幻之地》登顶
2022/04/03 其他游戏