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使用ajaxSubmit()提交表单示例
Apr 04 Javascript
jQuery中slideUp()方法用法分析
Dec 24 Javascript
jQuery中使用each处理json数据
Apr 23 Javascript
JS中生成随机数的用法及相关函数
Jan 09 Javascript
概述一个页面从输入URL到页面加载完的过程
Dec 16 Javascript
Node.js文件编码格式的转换的方法
Apr 27 Javascript
Vue中 v-if 和v-else-if页面加载出现闪现的问题及解决方法
Oct 12 Javascript
微信小程序从注册账号到上架(图文详解)
Jul 17 Javascript
layer 刷新某个页面的实现方法
Sep 05 Javascript
Vue+iview+webpack ie浏览器兼容简单处理
Sep 20 Javascript
vue的三种图片引入方式代码实例
Nov 19 Javascript
解决vue自定义全局消息框组件问题
Nov 22 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
星际RPG字典
2020/03/04 星际争霸
PHP封装的XML简单操作类完整实例
2017/11/13 PHP
Jquery图形报表插件 jqplot简介及参数详解
2012/10/10 Javascript
jQuery Masonry瀑布流插件使用详解
2014/11/17 Javascript
推荐6款基于jQuery实现图片效果插件
2014/12/07 Javascript
javascript读取文本节点方法小结
2016/12/15 Javascript
nodejs操作mongodb的增删改查功能实例
2017/11/09 NodeJs
对mac下nodejs 更新到最新版本的最新方法(推荐)
2018/05/17 NodeJs
Vue-router的使用和出现空白页,路由对象属性详解
2018/09/03 Javascript
vue集成百度UEditor富文本编辑器使用教程
2018/09/21 Javascript
详解vue-router导航守卫
2019/01/19 Javascript
详解vue-router的Import异步加载模块问题的解决方案
2020/05/13 Javascript
详解javascript void(0)
2020/07/13 Javascript
微信小程序实现多行文字滚动
2020/11/18 Javascript
python自动化工具日志查询分析脚本代码实现
2013/11/26 Python
Sublime开发python程序的示例代码
2018/01/24 Python
python文本数据相似度的度量
2018/03/12 Python
python bmp转换为jpg 并删除原图的方法
2018/10/25 Python
Python进阶之@property动态属性的实现
2019/04/01 Python
Python判断字符串是否xx开始或结尾的示例
2019/08/08 Python
Python中itertools的用法详解
2020/02/07 Python
Linux安装Python3如何和系统自带的Python2并存
2020/07/23 Python
Python如何读写CSV文件
2020/08/13 Python
calendar在python3时间中常用函数举例详解
2020/11/18 Python
武汉某公司的C#笔试题面试题
2015/12/25 面试题
水果超市创业计划书
2014/01/27 职场文书
个人简历自我评价范文
2014/02/04 职场文书
献爱心倡议书
2014/04/14 职场文书
奥林匹克运动会口号
2014/06/19 职场文书
三好生演讲稿
2014/09/12 职场文书
预备党员2014年第四季度思想汇报范文
2014/10/25 职场文书
2014年学校后勤工作总结
2014/12/06 职场文书
社区青年志愿者活动总结
2015/05/06 职场文书
检讨书格式
2015/05/07 职场文书
2016年党员创先争优承诺书
2016/03/25 职场文书
mysql的数据压缩性能对比详情
2021/11/07 MySQL