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 逻辑部分,由 <%%>
包裹, js 变量的占位,由 <%= %>
包裹,剩下的是普通的要拼接的html字符串部分。
也就是说,需要用正则找出的部分有3种:
<%%>
逻辑部分的js内容
<%=%>
占位部分的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 변수 자리 표시자🎜, < %= %>
패키지에서 나머지는 연결될 일반적인 🎜html 문자열🎜 부분입니다. 🎜🎜즉, 정규식을 사용하여 찾아야 하는 부분은 세 가지 유형입니다. 🎜<%%>
논리 부분 js 콘텐츠🎜<%=%>
자리 표시자 부분의 js 콘텐츠🎜일반 텍스트 </code >Content🎜</li></ol>🎜 두 번째 항목인 js 자리 표시자 부분도 이어진 텍스트입니다. 그래서 이들을 하나로 묶을 수 있습니다. 즉, <code>js 부분
과 접합 부분
입니다. 🎜🎜정규 추출🎜물론 정규식을 선택하세요!🎜🎜여기에서는 먼저 의사 배열의 내용과 브라우저 콘솔이 의사 배열을 처리하는 방법을 확장하겠습니다. :🎜🎜< img src="https://img.php.cn/upload/article/000/023/547/8e0d30ea29ab06aa5a4bb8787e5fcc7c-0.png" alt=""/>🎜🎜너무 멀리 가지 말고 결론만 말씀드리겠습니다. 🎜🎜 🎜 int 유형의 length 속성🎜이 있는 한, 🎜function 유형의 splice🎜 속성이 있습니다. 그러면 브라우저는 그것이 배열이라고 생각할 것입니다. 🎜🎜내부의 다른 속성이 인덱스별로 정렬되면 배열의 항목처럼 콘솔에 표시될 수도 있습니다. 🎜🎜 이 판단 방법을 🎜오리 타이핑🎜이라고 합니다. 뭔가가 오리처럼 보이고 오리처럼 꽥꽥거리면 오리입니다. 0_o🎜🎜텍스트로 돌아가서, 템플릿의 여러 js 로직이 필요합니다. 순차적으로 추출됩니다. 🎜🎜각 추출마다 추출된 내용을 얻어야 합니다. 이번에는 마지막 인덱스 항목을 일치시킵니다(텍스트 내용을 들어 올리는 데 사용). 그래서 저는 RegExp.prototype.exec를 선택했습니다. 🎜🎜🎜🎜예를 들어, RegExp.prototype.exec 무엇 반환되는 것은 컬렉션(의사 배열)이며 유형은 다음과 같습니다. 🎜
속성/색인 | 설명 | 🎜
---|---|
[0] 🎜 | 모두 일치 strings🎜🎜 |
[1],...[n] 🎜 | 괄호 안의 그룹 캡처🎜🎜 |
index< /code> 🎜<td>일치하는 문자는 원래 문자열의 0부터 시작하는 인덱스에 있습니다🎜🎜<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 중국어 웹사이트의 기타 관련 기사를 참조하세요!
관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
최신 이슈
JavaScript를 사용하지 않고 클릭 시 이미지 소스를 동적으로 수정하는 CSS 전용 방법
img:active와 같은 CSS만 사용하여 마우스 클릭 시 이미지의 소스를 변경해야 합니다.{}
에서 2024-04-06 19:25:49
0
1
505
d3.js를 확대할 때 산점도 값이 유지되지 않습니다.
d3.js를 처음 사용하는 것이므로 양해해 주시기 바랍니다. vue.js 파일에 순수 JavaScript로 구현했습니다. 확대/축소 기능을 사용하여 산점도를 만들...
에서 2024-04-06 18:16:26
0
1
403
공급업체별 의사 요소에 대한 JavaScript 호버 이벤트
나는 아래에 HTMLinput标签。$("input[type='range']::-webkit-slider-thumb").on('hover',fun...
에서 2024-04-06 15:35:24
0
1
274
Javascript/Jquery를 사용하여 버튼 없이 양식 제출
JavaScript 함수를 호출하고 JQUERY/PHP를 사용하여 양식을 실행하여 버튼 없이 양식을 제출하려고 합니다. 페이지를 다시 로드하지 않고 백엔드에서 양...
에서 2024-04-06 14:54:03
0
2
421
관련 주제
더>
|