首页 > 编程知识 正文

一行代码实现一个简单的模板字符串替换

时间:2023-05-04 01:58:32 阅读:180102 作者:4680

和许多初次学习Javascript的初学者一样,我最初也以字符串拼接的形式将JSON数据嵌入到HTML中。 开始时的代码量很少,暂时可以接受。 但是,当页面结构变得复杂时,它的弱点开始变得无法忍受:

书写不连贯。 每次写变量都必须断开,插入和。 非常容易出错。 不能再利用。 HTML片段是离散化的数据,很难提取其中重复的部分。 不能很好地利用template标签。 这是HTML5中新添加的标签,默认情况下,强烈建议将HTML模板放入template标签中以简化代码。 我的心情是这样的:

这个TMD是在戏弄我吗?

于是出来后的ES6、ES6的模板字符串真的很好用。 在比较旧的项目中,项目没有web包、gulp等生成工具,无法使用ES6语法。 但是,我想以这个优秀的字符串连接方式为参考,自己写一下。 主要是构想,可以使用ES6语法模拟ES6模板字符串的这个功能。

由于后端返回的通常是JSON的数据格式,因此请遵循以下规则进行模拟:

要求说明实现用context填充template中占位符的render(template,context )方法。 要求:不需要循环、条件等控制流成分,有变量替换功能即可

也可以展开级联变量

转义分隔符{和}不应该呈现,分隔符和变量之间的空格var obj={name: '二月',age:'15'}; var str='{{name}}好厉害,终于{{age}}岁了'; 输出:二月很厉害。 才15岁。PS:本文需要对正则表达式有一定的了解,如果还不了解正则表达式,建议先去学习一下,正则也是面试笔试必备的技能,上面链接末尾有不少正则学习的链接。

如果是你,你会怎么实现?可以先尝试自己写写,实现也不难。

我的实现暂且不论,我把这个问题给其他朋友做的时候,实现得不一样。 我们先看了几个童鞋的实现,然后在他们的基础上找出了常见的误解和实现优雅不够的地方。

(双月童鞋) letstr )、age:15 ) functiontest )岁) letobj )、name: )二月)、age:15 ) functiontest )、obj )

如果字符串是这样的:

letstr='{name}}name好厉害,终于输出{{age}}岁'`二月很厉害,才15岁。 这里不知道正则分组的话,就不知道$1的意思。 错误很明显。 本来字符串不能被替换的name也被替换。 从代码中可以看出,是二月的想法

的作用目标是str,首先通过正则匹配得出{{name}和{{age}},然后分组获取括号中的name、age,最后用replace方法将{{name}和{age}放在name和age上不要使用for in,尽量不要使用for in。 for in遍历自身和原型链的所有属性。 (志钦)童鞋) varstr=({name} )好厉害,终于(age ) )岁; var str2='{{name}}}厉害的name害才是{{age}}岁{{name} '; var obj={name: '周杰伦',age: 15}; functionfun(str,obj ) { var arr; ARR=str.match(/{[a-za-zd]}/g ); for(varI=0; iarr.length; I ) (arr ) I )=arr ) I ).replace )/{|}/g,''); str=str.replace({{}arr[I]} )、obj[arr[i]] ); } return str; }console.log(fun(str,obj ); console.log(fun(str2,obj ) ); 想法是正确的。 最后知道要替换整个name和age。 不是像二月的童鞋一样最后替换name,而是所有的执行都没有问题。 虽然实现了,但我有点感觉到了。 我们讨论的是一行代码,也就是代码越少越好。

小童鞋: functiona(str,obj ) { var st

r1 = str; for (var key in obj) { var re = new RegExp("{{" + key + "}}", "g"); str1 = str1.replace(re, obj[key]); } console.log(str1);}const str = "{{name}}很厉name害{{name}},才{{age}}岁";const obj = { name: "jawil", age: "15" };a(str, obj);

实现的已经简单明了了,就是把 obj 的 key 值遍历,然后拼成 {{key}},最后用 obj[key] 也就是 value 把 {{key}} 整个给替换了,思路很好,跟我最初的版本一个样。

我的实现: function parseString(str, obj) { Object.keys(obj).forEach(key => { str = str.replace(new RegExp(`{{${key}}}`,'g'), obj[key]); }); return str;}const str = "{{name}}很厉name害{{name}},才{{age}}岁";const obj = { name: "jawil", age: "15" };console.log(parseString(str, obj));

其实这里还是有些问题的,首先我没用 for...in 循环就是为了考虑不必要的循环,因为 for...in 循环会遍历原型链所有的可枚举属性,造成不必要的循环。

我们可以简单看一个例子,看看 for...in的可怕性。

// Chrome v63const div = document.createElement('div');let m = 0;for (let k in div) { m++;}let n = 0;console.log(m); // 231console.log(Object.keys(div).length); // 0

一个 DOM 节点属性竟然有这么多的属性,列举这个例子只是让大家看到 for in 遍历的效率问题,不要轻易用 for in循环,通过这个 DOM 节点之多也可以一定程度了解到 React 的 Virtual DOM 的思想和优越性。

除了用 for in 循环获取 obj 的 key 值,还可以用 Object.key() 获取,Object.getOwnPropertyNames() 以及 Reflect.ownKeys()也可以获取,那么这几种有啥区别呢?这里就简单说一下他们的一些区别。

for...in循环:会遍历对象自身的属性,以及原型属性, for...in 循环只遍历可枚举(不包括 enumerable为 false )属性。像 Array 和 Object 使用内置构造函数所创建的对象都会继承自 Object.prototype 和 String.prototype 的不可枚举属性;

Object.key():可以得到自身可枚举的属性,但得不到原型链上的属性;

Object.getOwnPropertyNames():可以得到自身所有的属性(包括不可枚举),但得不到原型链上的属性, Symbols 属性也得不到.

Reflect.ownKeys:该方法用于返回对象的所有属性,基本等同于 Object.getOwnPropertyNames() 与 Object.getOwnPropertySymbols 之和。

上面说的可能比较抽象,不够直观。可以看个我写的 DEMO,一切简单明鸟。

const parent = { a: 1, b: 2, c: 3};const child = { d: 4, e: 5, [Symbol()]: 6};child.__proto__ = parent;Object.defineProperty(child, "d", { enumerable: false });for (var attr in child) { console.log("for...in:", attr);// a,b,c,e}console.log("Object.keys:", Object.keys(child));// [ 'e' ]console.log("Object.getOwnPropertyNames:", Object.getOwnPropertyNames(child)); // [ 'd', 'e' ]console.log("Reflect.ownKeys:", Reflect.ownKeys(child)); // [ 'd', 'e', Symbol() ] 最后实现

上面的实现其实已经很简洁了,但是还是有些不完美的地方,通过 MDN 首先我们先了解一下 replace 的用法。

通过文档里面写的 str.replace(regexp|substr, newSubStr|function) ,我们可以发现 replace 方法可以传入 function 回调函数,

function (replacement) 一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果。参考这个指定一个函数作为参数。

有了这句话,其实就很好实现了,先看看具体代码再做下一步分析。

function render(template, context) { return template.replace(/{{(.*?)}}/g, (match, key) => context[key]);}const template = "{{name}}很厉name害,才{{age}}岁";const context = { name: "jawil", age: "15" };console.log(render(template, context));

可以对照上面文档的话来做分析:该函数的返回值(obj[key]=jawil)将替换掉第一个参数(match=={{name}})匹配到的结果。

简单分析一下:.*? 是正则固定搭配用法,表示非贪婪匹配模式,尽可能匹配少的,什么意思呢?举个简单的例子。

先看一个例子:

源字符串:aa<div>test1</div>bb<div>test2</div>cc正则表达式一:<div>.*</div>匹配结果一:<div>test1</div>bb<div>test2</div>正则表达式二:<div>.*?</div>匹配结果二:<div>test1</div>(这里指的是一次匹配结果,不使用/g,所以没包括<div>test2</div>)

根据上面的例子,从匹配行为上分析一下,什是贪婪与非贪婪匹配模式。

利用非贪婪匹配模就能匹配到所有的{{name}},{{age}},上面的也说到过正则分组,分组匹配到的就是 name,也就是 function 的第二个参数 key。

所以这行代码的意思就很清楚,正则匹配到{{name}},分组获取 name,然后把 {{name}} 替换成 obj[name](jawil)。

当然后来发现还有一个小问题,如果有空格的话就会匹配失败,类似这种写法:

const template = "{{name }}很厉name害,才{{age }}岁";

所以在上面的基础上还要去掉空格,其实也很简单,用正则或者 String.prototype.trim() 方法都行。

function render(template, context) { return template.replace(/{{(.*?)}}/g, (match, key) => context[key.trim()]);}const template = "{{name }}很厉name害,才{{age }}岁";const context = { name: "jawil", age: "15" };console.log(render(template, context));

将函数挂到 String 的原型链,得到最终版本

甚至,我们可以通过修改原型链,实现一些很酷的效果:

String.prototype.render = function (context) { return this.replace(/{{(.*?)}}/g, (match, key) => context[key.trim()]);};

如果{}中间不是数字,则{}本身不需要转义,所以最终最简洁的代码:

String.prototype.render = function (context) { return this.replace(/{{(.*?)}}/g, (match, key) => context[key.trim()]);};

之后,我们便可以这样调用啦:

"{{name}}很厉name害,才{{ age }}岁".render({ name: "jawil", age: "15" }); 收获

通过一个小小的模板字符串的实现,领悟到要把一个功能实现不难,要把做到完美真是难上加难,需要对基础掌握牢固,有一定的沉淀,然后不断地打磨才能比较优雅的实现,通过由一个很小的点往往可以拓展出很多的知识点。

一张图快速入门正则表达式:

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。