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 相关文章推荐
很酷的javascript loading效果代码
Jun 18 Javascript
javascript 禁用IE工具栏,导航栏等等实现代码
Apr 01 Javascript
用函数模板,写一个简单高效的 JSON 查询器的方法介绍
Apr 17 Javascript
js setTimeout 常见问题小结
Aug 13 Javascript
JS图片切换的具体方法(带缩略图版)
Nov 12 Javascript
js判断页面中是否有指定控件的简单实例
Mar 04 Javascript
JQuery实现动态表格点击按钮表格增加一行
Aug 24 Javascript
jQuery 3.0十大新特性最终版发布
Jul 14 Javascript
JavaScript中 DOM操作方法小结
Apr 25 Javascript
基于js原生和ajax的get和post方法以及jsonp的原生写法实例
Oct 16 Javascript
微信小程序实现底部弹出框
Nov 18 Javascript
Vue3如何理解ref toRef和toRefs的区别
Feb 18 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的拦截器实例分析
2014/11/03 PHP
jQuery向下滚动即时加载内容实现的瀑布流效果
2016/01/07 PHP
php 实现进制相互转换
2016/04/07 PHP
php将文件夹打包成zip文件的简单实现方法
2016/10/04 PHP
老生常谈ThinkPHP中的行为扩展和插件(推荐)
2017/05/05 PHP
yii2实现Ueditor百度编辑器的示例代码
2018/11/02 PHP
用JS剩余字数计算的代码
2008/07/03 Javascript
jQuery 表单验证扩展(三)
2010/10/20 Javascript
IE图片缓存document.execCommand(&quot;BackgroundImageCache&quot;,false,true)
2011/03/01 Javascript
精通Javascript系列之数值计算
2011/06/07 Javascript
在页面中js获取光标/鼠标的坐标及光标的像素坐标
2013/11/11 Javascript
js将控件隐藏及display属性的使用介绍
2013/12/30 Javascript
判断及设置浏览器全屏模式
2014/04/20 Javascript
JavaScript的jQuery库中function的存在和参数问题
2015/08/13 Javascript
js和jq使用submit方法无法提交表单的快速解决方法
2016/05/17 Javascript
详解easyui 切换主题皮肤
2019/04/04 Javascript
详解简单易懂的 ES6 Iterators 指南和示例
2019/09/24 Javascript
vue中实现点击按钮滚动到页面对应位置的方法(使用c3平滑属性实现)
2019/12/29 Javascript
基于vue-draggable 实现三级拖动排序效果
2020/01/10 Javascript
Kali Linux安装ipython2 和 ipython3的方法
2019/07/11 Python
在django中,关于session的通用设置方法
2019/08/06 Python
Python 内置函数globals()和locals()对比详解
2019/12/23 Python
Python PyPDF2模块安装使用解析
2020/01/19 Python
详解HTML5如何使用可选样式表为网站或应用添加黑暗模式
2020/04/07 HTML / CSS
来自世界各地的优质葡萄酒:VineShop24
2018/07/09 全球购物
Skyscanner新西兰:全球领先的旅游搜索网站
2019/08/26 全球购物
别名指示符是什么
2012/10/08 面试题
高中生毕业学习总结的自我评价
2013/11/14 职场文书
设备动力科岗位职责范本
2014/02/23 职场文书
手机银行营销方案
2014/03/14 职场文书
三八节主持词
2014/03/17 职场文书
产品陈列协议书(标准版)
2014/09/17 职场文书
公司离职证明范本
2014/10/17 职场文书
买卖合同协议书范本
2014/10/18 职场文书
预备党员入党思想汇报(范文)
2019/08/14 职场文书
python b站视频下载的五种版本
2021/05/27 Python