JS 템플릿 엔진이 정말 많습니다. 예전에는 art-template을 자주 사용했고, 가끔 vue를 템플릿 엔진으로 사용하기도 했습니다.
까지...
연초에는 아직 이전 프로젝트 팀에 있었습니다. 당시 코드 사양은 [외부 코드]를 무단으로 사용할 수 없습니다, 囧였습니다.
필요하면 쓰지만 나중에는 어떤 이유로 사용되지 않습니다. 나중에는 우리가 직접 생산 라인을 나누어서 일련의 빌드를 만들었고, 몇 달 동안 사용해 본 후 저는 이 작은 코드를 좀 더 대중적인 표준에 맞게 다시 작성하여 모두와 공유했습니다.
https://github.com/shalldie/mini-tpl
가장 먼저 할 일은 템플릿 구문을 선택하는 것이고, ejs 구문이 첫 번째 선택입니다. 왜냐하면 대중은 이러한 지시문을 배울 필요가 없기 때문입니다. 템플릿 엔진.
jsp 또는 asp/asp.net을 작성했다면 바로 시작할 수 있습니다.
<body> <p id="root"></p> <script id="tplContent" type="text/html"> <ul> <% for(var i=0; i<data.length; i++){ var item = data[i]; if(item.age < 30){%> <li>我的名字是<%=item.name%>,我的年龄是<%=item.age%></li> <%}else{%> <li>my name is <%=item.name%>,my age is a sercet.</li> <%}%> <% } %> </ul> </script> <script src="../build/mini-tpl.min.js"></script> <script> var data = [{ name: 'tom', age: 12 }, { name: 'lily', age: 24 }, { name: 'lucy', age: 55 }]; var content = document.getElementById('tplContent').innerHTML; var result = miniTpl(content, data); document.getElementById('root').innerHTML = result; </script> </body>
이렇게 사용하고 싶다면 어떻게 구현하는지 분석해보겠습니다.
1 const content = 'console.log("hello world");'; 2 3 let func = new Function(content); 4 5 func(); // hello world
new Function ([arg1[, arg2[, ...argN]],] functionBody)
functionBody 함수 정의를 포함한 JavaScript 문이 포함된 <code>JavaScript语句的<strong>字符串</strong>
。
使用Function构造器生成的函数,并不会在创建它们的上下文中创建闭包;它们一般在全局作用域中被创建。
当运行这些函数的时候,它们只能访问自己的本地变量和全局变量,不能访问Function构造器被调用生成的上下文的作用域。(MDN)
也就是说:
可以用 new Function 来动态的创建一个函数,去执行某动态生成的函数定义js语句。
通过 new Function 生成的函数,作用域在全局。
那么传参有3种:把变量放到全局(扯淡)
、函数传参
、用call/apply把值传给函数的this
。
最初我用的是 call 来传值,如今想了想不太优雅,换成了用参数传递。也就是这样:
const content = 'console.log(data);'; let func = new Function('data', content); func('hello world'); // hello world
到此为止,雏形有了。下面来拆分。
先看模板:
<% for(var i=0; i<data.length; i++){ var item = data[i]; if(item.age < 30){%> <li>我的名字是<%=item.name%>,我的年龄是<%=item.age%></li> <%}else{%> <li>my name is <%=item.name%>,my age is a sercet.</li> <%}%> <% } %>
js 逻辑部分,由 a4558806285d5b7820bdaa0b90aa4d26
包裹, js 变量的占位,由 332000003288cabbdff89f9a8e5a919b
包裹,剩下的是普通的要拼接的html字符串部分。
也就是说,需要用正则找出的部分有3种:
a4558806285d5b7820bdaa0b90aa4d26
逻辑部分的js内容
4eed03230346664e54006e16f8581c1e
占位部分的js内容
其它的纯文本
内容
其中第2项,js占位的部分,也属于拼接文本。所以可以放在一起,就是 js部分
,拼接部分
。
当然是选择正则表达式啊!
这里先跟大家扩展一下关于伪数组方面的内容,以及浏览器的控制台如何看待伪数组:
不扯远,直接说结论:
只要有 int类型的 length属性,有 function类型 的 splice属性。 那么浏览器就会认为他是一个数组。
如果里面的其它属性按照索引来排序,甚至还可以像数组里面的项那样在控制台展示出来。
这种判断方式叫 duck typing ,如果一个东西长得像鸭子,而且叫起来像鸭子,,,那么它就是鸭子 0_o
回到正文,这个需要多次从模板中,把 js逻辑部分 和 文本 依次提取出来。
对于每一次提取,都要获取提取出的内容,本次匹配最后的索引项(用于提起文本内容)。所以我选择了 RegExp.prototype.exec 。
举个例子,RegExp.prototype.exec 返回的是一个集合(伪数组),它的类型是这样的:
属性/索引 | 描述 |
---|---|
[0] |
匹配的全部字符串 |
[1],...[n] |
括号中的分组捕获 |
index |
匹配到的字符位于原始字符串的基于0的索引值 |
input string | 입니다. Function 생성자를 사용하여 생성된 함수는 생성된 컨텍스트에서 클로저를 생성하지 않으며 일반적으로 전역 범위에서 생성됩니다. |
변수를 전역적으로 입력(말도 안되는)
, 함수별로 매개변수 전달
, 호출/적용 사용 함수의 this
에 값을 전달합니다. 🎜/** * 从原始模板中提取 文本/js 部分 * * @param {string} content * @returns {Array<{type:number,txt:string}>} */ function transform(content) { var arr = []; //返回的数组,用于保存匹配结果 var reg = /<%(?!=)([\s\S]*?)%>/g; //用于匹配js代码的正则 var match; //当前匹配到的match var nowIndex = 0; //当前匹配到的索引 while (match = reg.exec(content)) { // 保存当前匹配项之前的普通文本/占位 appendTxt(arr, content.substring(nowIndex, match.index)); //保存当前匹配项 arr.push({ type: 1, //js代码 txt: match[1] //匹配到的内容 }); //更新当前匹配索引 nowIndex = match.index + match[0].length; } //保存文本尾部 appendTxt(arr, content.substr(nowIndex)); return arr; } /** * 普通文本添加到数组,对换行部分进行转义 * * @param {Array<{type:number,txt:string}>} list * @param {string} content */ function appendTxt(list, content) { content = content.replace(/\r?\n/g, "\n"); list.push({ txt: content }); }🎜바로 그거야, 프로토타입이 여기 있어. 아래에서 분해해 보겠습니다. 🎜🎜템플릿 분할🎜🎜 먼저 템플릿을 살펴보세요. 🎜
/** * 模板 + 数据 =》 渲染后的字符串 * * @param {string} content 模板 * @param {any} data 数据 * @returns 渲染后的字符串 */ function render(content, data) { data = data || {}; var list = ['var tpl = "";']; var codeArr = transform(content); // 代码分割项数组 for (var i = 0, len = codeArr.length; i < len; i++) { var item = codeArr[i]; // 当前分割项 // 如果是文本类型,或者js占位项 if (!item.type) { var txt = 'tpl+="' + item.txt.replace(/<%=(.*?)%>/g, function (g0, g1) { return '"+' + g1 + '+"'; }) + '"'; list.push(txt); } else { // 如果是js代码 list.push(item.txt); } } list.push('return tpl;'); return new Function('data', list.join('\n'))(data); }🎜 🎜js 논리 부분🎜,
a4558806285d5b7820bdaa0b90aa4d26
로 래핑됨, 🎜 js 변수 자리 표시자🎜, c1f9a78d7d2cc9c5a069fc7eff4fb06c
패키지에서 나머지는 연결될 일반적인 🎜html 문자열🎜 부분입니다. 🎜🎜즉, 정규식을 사용하여 찾아야 하는 부분은 세 가지 유형입니다. 🎜a4558806285d5b7820bdaa0b90aa4d26
논리 부분 js 콘텐츠🎜4eed03230346664e54006e16f8581c1e
자리 표시자 부분의 js 콘텐츠🎜일반 텍스트
Content🎜js 부분
과 접합 부분
입니다. 🎜🎜정규 추출🎜물론 정규식을 선택하세요!🎜🎜여기에서는 먼저 의사 배열의 내용과 브라우저 콘솔이 의사 배열을 처리하는 방법을 확장하겠습니다. :🎜🎜🎜🎜너무 멀리 가지 말고 결론만 말씀드리겠습니다. 🎜🎜 🎜 int 유형의 length 속성🎜이 있는 한, 🎜function 유형의 splice🎜 속성이 있습니다. 그러면 브라우저는 그것이 배열이라고 생각할 것입니다. 🎜🎜내부의 다른 속성이 인덱스별로 정렬되면 배열의 항목처럼 콘솔에 표시될 수도 있습니다. 🎜🎜 이 판단 방법을 🎜오리 타이핑🎜이라고 합니다. 뭔가가 오리처럼 보이고 오리처럼 꽥꽥거리면 오리입니다. 0_o🎜🎜텍스트로 돌아가서, 템플릿의 여러 js 로직이 필요합니다. 순차적으로 추출됩니다. 🎜🎜각 추출마다 추출된 내용을 얻어야 합니다. 이번에는 마지막 인덱스 항목을 일치시킵니다(텍스트 내용을 들어 올리는 데 사용). 그래서 저는 RegExp.prototype.exec를 선택했습니다. 🎜🎜🎜🎜예를 들어, RegExp.prototype.exec 무엇 반환되는 것은 컬렉션(의사 배열)이며 유형은 다음과 같습니다. 🎜
속성/색인 | 설명 | 🎜
---|---|
[0] 🎜 |
모두 일치 strings🎜🎜 |
[1],...[n] 🎜 |
괄호 안의 그룹 캡처🎜🎜 |
index 🎜<td>일치하는 문자는 원래 문자열의 0부터 시작하는 인덱스에 있습니다🎜🎜</td>
<tr><td>
<code>입력 🎜🎜원래 문자열🎜🎜🎜🎜通过这样,就可以拿到匹配到的 js 逻辑部分,并通过 index 和本次匹配到的内容,来获取每个js逻辑部分之间的文本内容项。 要注意,在全局匹配模式下,正则表达式会接着上次匹配的结果继续匹配新的字符串。 /** * 从原始模板中提取 文本/js 部分 * * @param {string} content * @returns {Array<{type:number,txt:string}>} */ function transform(content) { var arr = []; //返回的数组,用于保存匹配结果 var reg = /<%(?!=)([\s\S]*?)%>/g; //用于匹配js代码的正则 var match; //当前匹配到的match var nowIndex = 0; //当前匹配到的索引 while (match = reg.exec(content)) { // 保存当前匹配项之前的普通文本/占位 appendTxt(arr, content.substring(nowIndex, match.index)); //保存当前匹配项 arr.push({ type: 1, //js代码 txt: match[1] //匹配到的内容 }); //更新当前匹配索引 nowIndex = match.index + match[0].length; } //保存文本尾部 appendTxt(arr, content.substr(nowIndex)); return arr; } /** * 普通文本添加到数组,对换行部分进行转义 * * @param {Array<{type:number,txt:string}>} list * @param {string} content */ function appendTxt(list, content) { content = content.replace(/\r?\n/g, "\\n"); list.push({ txt: content }); } 得到了js逻辑项 和 文本内容 ,就可以把他们拼在一起,来动态生成一个function。要注意的是,文本内容中,包含 js占位项,这个地方要转换一下。 /** * 模板 + 数据 =》 渲染后的字符串 * * @param {string} content 模板 * @param {any} data 数据 * @returns 渲染后的字符串 */ function render(content, data) { data = data || {}; var list = ['var tpl = "";']; var codeArr = transform(content); // 代码分割项数组 for (var i = 0, len = codeArr.length; i < len; i++) { var item = codeArr[i]; // 当前分割项 // 如果是文本类型,或者js占位项 if (!item.type) { var txt = 'tpl+="' + item.txt.replace(/<%=(.*?)%>/g, function (g0, g1) { return '"+' + g1 + '+"'; }) + '"'; list.push(txt); } else { // 如果是js代码 list.push(item.txt); } } list.push('return tpl;'); return new Function('data', list.join('\n'))(data); } 这样就完成了简易的模板引擎,不要觉得拼字符串慢。 在现代浏览器(IE8开始)中,特地对字符串的操作做了大量的优化,用 += 拼字符串,要比用数组 push 再 join 的方式快很多很多,即使放到IE7(IE6不清楚)中,我这里测试也是拼字符串快。。。 |
위 내용은 간단한 js 템플릿 엔진 작성 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!