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 相关文章推荐
Function.prototype.call.apply结合用法分析示例
Jul 03 Javascript
详解参数传递四种形式
Jul 21 Javascript
JSP基于Bootstrap分页显示实例解析
Jun 12 Javascript
jQuery实现将div中滚动条滚动到指定位置的方法
Aug 10 Javascript
Javascript json object 与string 相互转换的简单实现
Sep 27 Javascript
基于JavaScript实现前端文件的断点续传
Oct 17 Javascript
React应用中使用Bootstrap的方法
Aug 15 Javascript
AngularJs 延时器、计时器实例代码
Sep 16 Javascript
使用layer弹窗和layui表单实现新增功能
Aug 09 Javascript
JS基于Location实现访问Url、重定向及刷新页面的方法分析
Dec 03 Javascript
详解Vue依赖收集引发的问题
Apr 22 Javascript
vue实现按需加载组件及异步组件功能
May 27 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中cookies使用指南
2007/03/16 PHP
php截取字符串之截取utf8或gbk编码的中英文字符串示例
2014/03/12 PHP
php基于jquery的ajax技术传递json数据简单实例
2016/04/15 PHP
php识别翻转iphone拍摄的颠倒图片
2018/05/17 PHP
PHP耦合设计模式实例分析
2018/08/08 PHP
php使用gearman进行任务分发操作实例详解
2020/02/26 PHP
实例化php类时传参的方法分析
2020/06/05 PHP
jQuery 幻灯片插件(带缩略图功能)
2011/01/24 Javascript
原生javascript实现简单的datagrid数据表格
2015/01/02 Javascript
jQuery实现在最后一个元素之前插入新元素的方法
2015/07/18 Javascript
分享15个大家都熟知的jquery小技巧
2015/12/02 Javascript
原生JavaScript实现滚动条效果
2020/03/24 Javascript
JS实现520 表白简单代码
2018/05/21 Javascript
让你5分钟掌握9个JavaScript小技巧
2018/06/09 Javascript
vue vue-Router默认hash模式修改为history需要做的修改详解
2018/09/13 Javascript
浅谈vue中关于checkbox数据绑定v-model指令的个人理解
2018/11/14 Javascript
关于微信小程序登录的那些事
2019/01/08 Javascript
微信小程序地图导航功能实现完整源代码附效果图(推荐)
2019/04/28 Javascript
简单了解Javscript中兄弟ifream的方法调用
2019/06/17 Javascript
微信小程序实现watch监听
2020/06/04 Javascript
Python3基础之基本数据类型概述
2014/08/13 Python
用于统计项目中代码总行数的Python脚本分享
2015/04/21 Python
python中requests小技巧
2017/05/10 Python
python pandas 对时间序列文件处理的实例
2018/06/22 Python
pandas数据拼接的实现示例
2020/04/16 Python
Jupyter Notebook折叠输出的内容实例
2020/04/22 Python
python给视频添加背景音乐并改变音量的具体方法
2020/07/19 Python
python飞机大战游戏实例讲解
2020/12/04 Python
html5是什么_动力节点Java学院整理
2017/07/07 HTML / CSS
德国购买门票网站:ADticket.de
2019/10/31 全球购物
eDreams德国:南欧领先的在线旅游公司
2020/12/07 全球购物
事业单位公务员的职业生涯规划
2014/01/15 职场文书
2014年仓库管理工作总结
2014/12/17 职场文书
实习计划书范文
2015/01/16 职场文书
学术会议开幕词
2016/03/03 职场文书
MySQL优化常用的19种有效方法(推荐!)
2022/03/17 MySQL