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系列(3) 全面解析Module模式
Jan 15 Javascript
jQuery实现下拉框左右选择的简单实例
Feb 22 Javascript
浅谈javascript中this在事件中的应用
Feb 15 Javascript
JavaScript中实现Map的示例代码
Sep 09 Javascript
JavaScript中iframe实现局部刷新的几种方法汇总
Jan 06 Javascript
JS正则表达式比较常见用法
Jan 26 Javascript
jQuery绑定事件on()与弹窗的简要概述
Apr 27 Javascript
JavaScript实现图像模糊化的方法实例
Jan 15 Javascript
socket.io学习教程之深入学习篇(三)
Apr 29 Javascript
Angular限制input框输入金额(是小数的话只保留两位小数点)
Jul 13 Javascript
详解react、redux、react-redux之间的关系
Apr 11 Javascript
js实现3D照片墙效果
Oct 28 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一些服务器端特性的配置加强php的安全
2006/10/09 PHP
php根据年月获取季度的方法
2014/03/31 PHP
PHP人民币金额转大写实例代码
2015/10/02 PHP
php将一维数组转换为每3个连续值组成的二维数组
2016/05/06 PHP
详解Yii2 rules 的验证规则
2016/12/02 PHP
php使用curl伪造浏览器访问操作示例
2019/09/30 PHP
javascript基础知识大集锦(一) 推荐收藏
2011/01/13 Javascript
使用jQuery Ajax功能时需要注意的一个问题(内存溢出)
2012/05/30 Javascript
javascript图像处理—仿射变换深度理解
2013/01/16 Javascript
用js一次改变多个input的readonly属性值的方法
2014/06/11 Javascript
jQuery实现HTML5 placeholder效果实例
2014/12/09 Javascript
跨域资源共享 CORS 详解
2016/04/26 Javascript
Bootstrap如何创建表单
2016/10/21 Javascript
原生js实现简单的链式操作
2017/07/04 Javascript
sublime text配置node.js调试(图文教程)
2017/11/23 Javascript
Vue组件开发技巧总结
2018/03/04 Javascript
微信小程序使用swiper组件实现层叠轮播图
2018/11/04 Javascript
新手入门js闭包学习过程解析
2019/10/08 Javascript
vue之延时刷新实例
2019/11/14 Javascript
Vue组件通信中非父子组件传值知识点总结
2019/12/05 Javascript
不依任何赖第三方,单纯用vue实现Tree 树形控件的案例
2020/09/21 Javascript
使用apidoc管理RESTful风格Flask项目接口文档方法
2018/02/07 Python
Django框架封装外部函数示例
2019/05/28 Python
django form和field具体方法和属性说明
2020/07/09 Python
详解Django中views数据查询使用locals()函数进行优化
2020/08/24 Python
如何用python实现一个HTTP连接池
2021/01/14 Python
python压包的概念及实例详解
2021/02/17 Python
谷歌浏览器小字体处理方案即12px以下字体
2013/12/17 HTML / CSS
法国时尚品牌乐都特瑞士站:La Redoute瑞士
2016/09/05 全球购物
来自世界各地的饮料:Flavourly
2019/05/06 全球购物
Herschel Supply Co.美国:背包、手提袋及配件
2020/11/24 全球购物
心理健康心得体会
2014/01/02 职场文书
春季运动会广播稿大全
2014/02/19 职场文书
大学学生会竞选演讲稿
2014/04/25 职场文书
大明湖导游词
2015/02/03 职场文书
怎样写好工作计划
2019/04/10 职场文书