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 使用手册(四)
Sep 23 Javascript
javascript动态创建链接的方法
May 13 Javascript
JavaScript实现文字跟随鼠标特效
Aug 06 Javascript
jquery实现简单Tab切换菜单效果
Jul 17 Javascript
JQuery控制DIV的选取实现方法
Sep 18 Javascript
JS实现简单的tab切换选项卡效果
Sep 21 Javascript
你有必要知道的10个JavaScript难点
Jul 25 Javascript
微信小程序多列选择器range-key使用详解
Mar 30 Javascript
vue本地打开build后生成的dist文件夹index.html问题
Sep 04 Javascript
Vue页面切换和a链接的本质区别详解
Nov 12 Javascript
详解Vue template 如何支持多个根结点
Feb 10 Javascript
JavaScript 实现拖拽效果组件功能(兼容移动端)
Nov 11 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专用数组排序类ArraySortUtil用法实例
2015/04/03 PHP
php实现将上传word文件转为html的方法
2015/06/03 PHP
PHP大文件分割上传 PHP分片上传
2017/08/28 PHP
PHP从零开始打造自己的MVC框架之入口文件实现方法详解
2019/06/03 PHP
学习ExtJS accordion布局
2009/10/08 Javascript
ASP.NET中使用后端代码注册脚本 生成JQUERY-EASYUI的界面错位的解决方法
2010/06/12 Javascript
JavaScript高级程序设计 XML、Ajax 学习笔记
2011/09/10 Javascript
JavaScript中valueOf函数与toString方法深入理解
2012/12/02 Javascript
JavaScript设计模式之适配器模式介绍
2014/12/28 Javascript
解析javascript中鼠标滚轮事件
2015/05/26 Javascript
图解Sublime Text3使用技巧
2015/12/21 Javascript
javascript this详细介绍
2016/09/19 Javascript
Node.js使用orm2进行update操作时关联字段无法修改的解决方法
2017/06/13 Javascript
vue中v-model动态生成的实例详解
2017/10/27 Javascript
微信小程序图片选择区域裁剪实现方法
2017/12/02 Javascript
javascript 通过键名获取键盘的keyCode方法
2017/12/31 Javascript
Vue中Quill富文本编辑器的使用教程
2018/09/21 Javascript
对vue中v-if的常见使用方法详解
2018/09/28 Javascript
layui radio点击事件实现input显示和隐藏的例子
2019/09/02 Javascript
Vue3为什么这么快
2020/09/23 Javascript
[01:20]DOTA2更新全新英雄 天涯墨客现已加入游戏
2018/08/25 DOTA
用python代码做configure文件
2014/07/20 Python
Python微信企业号开发之回调模式接收微信端客户端发送消息及被动返回消息示例
2017/08/21 Python
python实现机械分词之逆向最大匹配算法代码示例
2017/12/13 Python
python 的 scapy库,实现网卡收发包的例子
2019/07/23 Python
TensorFlow基于MNIST数据集实现车牌识别(初步演示版)
2019/08/05 Python
python 函数的缺省参数使用注意事项分析
2019/09/17 Python
使用Python爬取弹出窗口信息的实例
2020/03/14 Python
Haggar官网:美国男装品牌
2020/02/16 全球购物
传统软件工程与面向对象的软件工程有什么区别
2012/05/31 面试题
自考自我鉴定范文
2013/10/30 职场文书
《孙权劝学》教学反思
2014/04/23 职场文书
2016年师德学习心得体会
2016/01/12 职场文书
解决go在函数退出后子协程的退出问题
2021/04/30 Golang
解决goland 导入项目后import里的包报红问题
2021/05/06 Golang
教你使用VS Code的MySQL扩展管理数据库的方法
2022/01/22 MySQL