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 urldecode URL解码的问题
Jan 08 Javascript
JavaScript 实现类的多种方法实例
May 01 Javascript
JS写的贪吃蛇游戏(个人练习)
Jul 08 Javascript
jquery 扑捉回车键事件代码
Apr 24 Javascript
JS解析XML文件和XML字符串详解
Apr 17 Javascript
Bootstrap模态框水平垂直居中与增加拖拽功能
Nov 09 Javascript
js return返回多个值,通过对象的属性访问方法
Feb 21 Javascript
jquery ui sortable拖拽后保存位置
Apr 27 jQuery
详解在AngularJS的controller外部直接获取$scope
Jun 02 Javascript
浅谈对Angular中的生命周期钩子的理解
Jul 31 Javascript
JS设计模式之单例模式(一)
Sep 29 Javascript
js中位数不足自动补位扩展padLeft、padRight实现代码
Apr 06 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
第4章 数据处理-php正则表达式-郑阿奇(续)
2011/07/04 PHP
PHP反射使用实例和PHP反射API的中文说明
2014/07/02 PHP
PHP 获取指定地区的天气实例代码
2017/02/08 PHP
html读出文本文件内容
2007/01/22 Javascript
小议javascript 设计模式 推荐
2009/10/28 Javascript
JS实现简易图片轮播效果的方法
2015/03/25 Javascript
每天一篇javascript学习小结(String对象)
2015/11/18 Javascript
javascript创建含数字字母的随机字符串方法总结
2016/08/01 Javascript
jQuery中$.ajax()方法参数解析
2016/10/22 Javascript
js学习总结之DOM2兼容处理this问题的解决方法
2017/07/27 Javascript
从对象列表中获取一个对象的方法,依据关键字和值
2017/09/20 Javascript
bootstrap模态框嵌套、tabindex属性、去除阴影的示例代码
2017/10/17 Javascript
node.js利用socket.io实现多人在线匹配联机五子棋
2018/05/31 Javascript
浅谈Angular7 项目开发总结
2018/12/19 Javascript
Vue项目安装插件并保存
2019/01/28 Javascript
使用webpack编译es6代码的方法步骤
2019/04/28 Javascript
Javascript数组方法reduce的妙用之处分享
2019/06/10 Javascript
微信小程序实现上传多个文件 超过10个
2020/03/30 Javascript
vue实现整屏滚动切换
2020/06/29 Javascript
将Python中的数据存储到系统本地的简单方法
2015/04/11 Python
微信跳一跳python代码实现
2018/01/05 Python
详谈pandas中agg函数和apply函数的区别
2018/04/20 Python
python使用matplotlib模块绘制多条折线图、散点图
2020/04/26 Python
利用python list完成最简单的DB连接池方法
2019/08/09 Python
从0到1使用python开发一个半自动答题小程序的实现
2020/05/12 Python
python interpolate插值实例
2020/07/06 Python
在线吉他课程,学习如何弹吉他:Fender Play
2019/02/28 全球购物
Club Monaco加拿大官网:设计师男女服装
2019/09/29 全球购物
销售副总经理岗位职责
2013/12/11 职场文书
会议开场欢迎词
2014/01/15 职场文书
岗位竞聘书范文
2014/03/31 职场文书
公司任命书模板
2014/06/06 职场文书
科技活动周标语
2014/10/08 职场文书
2016中秋节晚会开场白
2015/11/26 职场文书
幼儿园中班教学反思
2016/03/03 职场文书
Javascript使用integrity属性进行安全验证
2021/11/07 Javascript