和很多刚接触Javascript的初学者一样,一开始我也是使用串接字符串的形式将JSON数据嵌入到HTML中。一开始代码量少,暂时可以接受。但是当页面的结构变得复杂时,它的弱点就变得难以忍受:书写不连贯。每次写一个变量都要打断插入一个+和",很容易出错,不能重复使用。HTML片段都是离散化的数据,重复的部分很难提取出来。标签不能用好,这是HTML5新的标签,标准强烈建议将HTML模板放在标签中,让代码更简洁,当时我的心情是这样的:这TMD吗开什么玩笑?所以后来出了ES6,ES6模板字符串用起来真的很方便。对于比较老的项目,项目没有webpack,gulp等构建工具,ES6语法也用不了。但是,如果我们想学习从这种处理字符串拼接的绝妙方式,我们不妨自己写一个试试,主要思路是可以用ES6语法来模拟ES6模板字符串的功能,后端返回的数据格式一般为JSON,所以我们按照以下规则进行模拟。nts描述实现了一个render(template,context)方法,用context填充模板中的占位符。要求:没有控制流组件(比如循环、条件等),只要有变量替换函数,可以级联的变量也可以展开转义分隔符{和}不要渲染,空白是允许在分隔符和变量之间varobj={name:"February",age:"15"};varstr="{{name}}很厉害,才{{age}}岁";输出:二月很厉害,才15岁。PS:本文需要对正则表达式有一定的了解。如果你不会正则表达式,建议先学习一下,正则也是笔试面试的必备技能。以上链接末尾有很多正规学习的链接。如果是你,你会如何实施?可以先自己写试试,实现起来也不难。先不说我的实现。当我把这个问题交给其他朋友时,感悟是不一样的。先看看几位童鞋的实现,再根据他们找出常见的误区和实现不够优雅的地方。二月童鞋:letstr="{{name}}很好,才{{age}}岁"letobj={name:'February',age:15}functiontest(str,obj){let_s=str.replace(/\{\{(\w+)\}\}/g,'$1')letresultfor(letkinobj){_s=_s.replace(newRegExp(k,'g'),obj[k])}return_s}consts=test(str,obj)基本实现了,但是代码中还有很多问题没有考虑到。首先,Object的键值不一定只是w,如果字符串是这样的:letstr="{{name}}isverypowerful,only{{age}}yearsold"`会输出:February很厉害,February不好,才15岁这里要明白正则分组才明白$1的意思。错误很明显。name,本来是一个不应该被替换的字符串,也被替换了。从代码中可以看出February的思路。代码的目标是str,先用正则表达式匹配{{name}}和{{age}},然后用分组获取括号内的名字和年龄,最后用replace方法替换{{name}}和{{age}}将其替换为姓名和年龄,最后字符串变为姓名。这个名字很厉害,也只是年龄而已。最后,forin循环导致他们被一起替换了。完全没有必要使用forin循环。能用forin的尽量不要用forin。forin会遍历自身和原型链的所有属性。芷沁童鞋:varstr="{{name}}很厉害,才{{age}}岁";varstr2="{{name}}很厉害,才{{age}}岁{{name}}";varobj={name:'周杰伦',age:15};functionfun(str,obj){变种;arr=str.match(/{{[a-zA-Z\d]+}}/g);for(vari=0;i{str=str.replace(newRegExp(`{{${key}}}`,'g'),obj[钥匙]);});returnstr;}conststr="{{name}}很厉害{{name}},只有{{age}}岁";constobj={name:"jawil",age:"15"};console.log(parseString(str,obj));其实这里还是有一些问题。首先,我没有使用for...in循环只是为了考虑不必要的循环,因为for...in循环会遍历原型链中的所有可枚举属性,造成不必要的循环。我们可以简单的看一个例子,看看for...in的恐怖之处。//Chromev63constdiv=document.createElement('div');letm=0;for(letkindiv){m++;}letn=0;console.log(m);//231console.log(Object.keys(div).length);//0一个DOM节点属性有这么多属性。这个例子只是为了让大家看看for在遍历中的效率。不要轻易使用forin循环。通过这个DOM节点,你也可以在一定程度上了解React的VirtualDOM的思想和优势。除了使用forin循环获取obj的键值外,还可以使用Object.key()、Object.getOwnPropertyNames()和Reflect.ownKeys(),那么这几种有什么区别呢?下面简单介绍一下它们的一些区别。for...in循环:会遍历对象本身的属性,以及原型属性,for...in循环只会遍历enumerable(不包括enumerable为false)的属性。Array和Object等内置构造函数创建的对象将继承Object.prototype和String.prototype的不可枚举属性;Object.key():可以获取自身的可枚举属性,但不能获取原型链上的属性;Object.getOwnPropertyNames():可以获取自身的所有属性(包括不可枚举的),但是不能获取原型链上的属性,也不能获取Symbols属性。Reflect.ownKeys:该方法用于返回对象的所有属性,基本上相当于Object.getOwnPropertyNames()和Object.getOwnPropertySymbols的总和。以上可能比较抽象,不够直观。你可以看看我写的DEMO,一切都简单明了。constparent={a:1,b:2,c:3};constchild={d:4,e:5,[Symbol()]:6};孩子.__proto__=父母;Object.defineProperty(child,"d",{enumerable:false});for(varattrinchild){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(replacement)是用来创建新子串的函数,而函数的返回值将替换第一个参数匹配的结果。请参阅此以将函数指定为参数。有了这句话,其实很容易实现,先看具体代码再做下一步分析。functionrender(template,context){returntemplate.replace(/\{\{(.*?)\}\}/g,(match,key)=>context[key]);}const模板="{{name}}很厉害,name才{{age}}岁";constcontext={name:"jawil",age:"15"};console.log(呈现(模板,上下文));可以参考上面的文档分析:这个函数的返回值(obj[key]=jawil)会替换第一个参数(match=={{name}})匹配到的结果。简要分析:.*?是regularfixedcollocation用法,表示非贪心匹配模式,匹配越少越好,什么意思?举一个简单的例子。先看一个例子:源字符串:aatest1
bbtest2
cc正则表达式一:.*
匹配结果一:test1
bbtest2
正则表达式二:.*?
匹配结果二:test1
(这里指的是匹配结果,不是用/g,所以不包括test2
)根据上面的例子,从匹配行为来分析,什么是贪心和非贪心匹配模式。所有的{{name}},{{age}}都可以使用非贪婪匹配方式进行匹配。上面也提到了规则分组。分组匹配名称,即函数的第二个参数key。所以这行代码的意思就很明确了,正则表达式匹配{{name}},分组获取name,然后将{{name}}替换成obj[name](jawil)。当然后来发现还是有一个小问题。如果有空格,则匹配失败,类似于这样写:consttemplate="{{name}}isaverypowerfulname,only{{age}}yearsold";所以在上面的基础上,要把空格去掉,其实很简单,要么用正则表达式,要么用String.prototype.trim()方法。functionrender(template,context){returntemplate.replace(/\{\{(.*?)\}\}/g,(match,key)=>context[key.trim()]);}常量模板="{{name}}很厉害,只有{{age}}岁";constcontext={name:"jawil",age:"15"};console.log(呈现(模板,上下文));将函数挂到String的原型链上,得到最终版本。甚至,我们可以通过修改原型链来实现一些很酷的效果:String.prototype.render=function(context){returnthis.replace(/\{\{(.*?)\}\}/g,(match,键)=>上下文[key.trim()]);};如果{}不是中间的数字,{}本身不需要转义,所以最简洁代码:String.prototype.render=function(context){returnthis.replace(/{{(.*?)}}/g,(match,key)=>context[key.trim()]);};之后,我们可以这样称呼它:“{{name}}很厉害,只有{{age}}岁”。render({name:"jawil",age:"15"});一个小模板字符串的实现,让我体会到实现一个功能并不难,但要做到完美就更难了。需要把基础扎实,有一定的沉淀,然后不断打磨才能更优雅的实现,往往可以从很小的一个点扩展出很多知识点。一张图快速入门正则表达式: