javascript模板引擎之EL表示式
阿新 • • 發佈:2019-01-31
javascript模板引擎中對EL表示式的支援很麻煩,主要原因是因為變數是存在另外的一個類似於map的物件,
要執行一個表示式,就必須得解釋整個表示式,並掃描所有變數,對每一個變數依次從作用域中查詢,最後計算結果。下面介紹的方法使用預編譯機制來避免解釋表示式。
我在前篇文章中曾經介紹了一個使用jstl語法的javascript版的模板引擎,地址:javascript 模板 javascript-jstl
它同時支援兩種語法:
1. jstl語法的模板語言2. 原生的javascript語法
同時還支援el表示式,但是對於el表示式的支援也不是太好,因為它對el表示式的寫法有特殊要求,例如:
<span>userName: ${user.userName}</span> <span>userAge: ${user.userAge}</span>
需要寫成
<span>userName: ${this.user.userName}</span>
<span>userAge: ${this.user.userAge}</span>
以下內容都將以上面的文件片段為例.
具體原因我在前篇文章中已經做過介紹,此處再贅述一遍:對於一篇文件中所有的el表示式,編譯器會生成一個el表示式表,大概的程式碼會是這樣的:
var exprTable = { expr1: function(){ return this.user.userName; }, expr2: function(){ return this.user.userAge; } };
當要執行一個el表示式物件的時候,會從el表示式表中查詢對應的表示式, 通過name引用,一個el表示式物件的結構如下:
1. 首先,一個el表示式被編譯成了一個函式, 這個函式的主體直接返回el表示式.var Expression = function(name){ this.name = name; }; var ExpressionContext = function(exprTable){ this.exprTable = exprTable; }; ExpressionContext.prototype.eval = function(expression, pageContext){ var expr = this.exprTable[expression.name]; return expr.apply(pageContext); }; var pageContext = {}; pageContext.user = {userName: "test1", userAge: 21}; var expressionContext = new ExpressionContext(exprTable); var result = expressionContext.eval(new Expression("expr1"), pageContext);
2. 在pageContext上執行這個函式,返回的結果就是這個el表示式的結果.
所有的el表示式都是在編譯期被編譯成了函式,可以做到只編譯一次,多次執行。
上面介紹的方法的一個很大的缺陷就是要求el表示式中所有變數都必須以this開頭。下面的方法通過javascript本身的機制避免使用this關鍵字。
javascript有個關鍵字with,我們就使用它來避免在表示式中使用this,而且上面的程式碼僅需要做一點點改動:
var exprTable = {
expr1: function(){
with(this)
{
return user.userName;
}
},
expr1: function(){
with(this)
{
return user.userAge;
}
}
};
下面是一段測試程式碼,測試程式碼沒有使用預編譯機制:
var pageContext = {};
pageContext.user = {"userName": "test1", "age": 21};
pageContext.userList = [
{"name": "test1", "age": 21},
{"name": "test2", "age": 22},
{"name": "test3", "age": 23},
{"name": "test4", "age": 24},
{"name": "test5", "age": 25}
];
var ExpressionContext = {};
ExpressionContext.evaluate = function(expression, pageContext){
var f = new Function("x", "with(this){return " + expression + "}");
return f.apply(pageContext);
};
var result = ExpressionContext.evaluate("user.userName", pageContext);
alert(result);
新版的javascript版本的模板引擎已經重新設計,不再支援原生的javascript語法,僅支援jstl語法,支援自定義標籤,下載地址:javascript-template
測試程式碼:
<script name="source1" type="text/template">
<h3>System Time: <fmt:formatDate value="${new Date()}" pattern="yyyy-MM-dd HH:mm:ss SSS"/></h3>
<h3>cacheTag test</h3>
<app:cache key="cache1" expires="10">
<fmt:formatDate value="${new Date()}" pattern="yyyy-MM-dd HH:mm:ss SSS"/>
</app:cache>
<h3>ChooseTag test</h3>
<c:choose>
<c:when test="${userList.length > 6}"><div>test1</div></c:when>
<c:when test="${userList.length > 7}"><div>test2</div></c:when>
<c:otherwise><div>test3</div></c:otherwise>
</c:choose>
<h3>OutTag test</h3>
<c:out escapeXml="true"><div>this is content</div></c:out>
<c:out><div>this is content</div></c:out>
<h3>CommentTag test</h3>
<c:comment>
<div>Hello World !</div>
<c:out><div>this is content</div></c:out>
</c:comment>
<h3>EL test</h3>
<div>${com.skin.util.StringUtil.trim(" 123 ")}</div>
<h3>Include test</h3>
<t:include file="includePage1"/>
<h3>ActionTag test</h3>
<c:action className="com.mytest.action.HelloAction" method="execute" page="actionPage1">
<c:param name="a" value="1"/>
<c:param name="b">2</c:param>
</c:action>
<h3>ForEachTag test1</h3>
<c:forEach begin="1" end="3" step="1" var="myvar" varStatus="status">myvar: ${myvar} </c:forEach>
<h3>ForEachTag test2</h3>
<c:forEach items="1,2,3" var="myvar" varStatus="status">myvar: ${myvar} </c:forEach>
<h3>ForEachTag test3</h3>
<c:set var="rows" value="${Math.floor((userList.length + 1) / 2)}"/>
<table border="1">
<c:forEach items="${userList}" var="user" varStatus="status">
<c:set var="rowNum" value="${Math.floor((status.index + 2) / 2)}"/>
<!-- rowNum: ${rowNum}, rows: ${rows} -->
<c:if test="${(status.index % 2) == 0}"><tr></c:if>
<c:if test="${rowNum < rows}"><td style="width: 300px;"></c:if>
<c:if test="${rowNum >= rows}">
<td style="width: 200px;" test="1">
</c:if>
<div>rows: ${rows}, rowNum: ${rowNum}, status.index: ${status.index}</div>
<div>user.userName: ${user.userName}</div>
<div>user.birthday: <fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd HH:mm:ss SSS"/></div>
</td>
<!-- ${status.index} ${(status.index + 1) % 2} -->
<c:if test="${(status.index + 1) % 2 == 0}"></tr></c:if>
</c:forEach>
<c:if test="${userList.length % 2 != 0}"><td class="nbb"> </td></tr></c:if>
</table>
<div style="height: 20px;"></div>
<div class="scrollpage">
<app:scrollpage pageNum="${pageNum}" pageSize="${pageSize}" total="${userCount}" className="pagebar" href="javascript: runTest(%s, ${pageSize})"/>
</div>
</script>
<!-- Template -->
<script name="includePage1" type="text/template">
<h3>This is "includePage1" content!</h3>
<p>這是一個被包含的頁面,被包含的頁面會和父頁面一起編譯。因此你可以訪問父頁面的物件!</p>
<table border="1">
<tr>
<td colspan="3" style="background-color: #c0c0c0;">UserList</td>
</tr>
<tr>
<td>Name</td>
<td>Age</td>
<td>Birthday</td>
</tr>
<c:forEach items="${userList}" var="user" varStatus="status">
<tr>
<td>${user.userName}</td>
<td>${user.userAge}</td>
<td><fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd"/></td>
</tr>
</c:forEach>
</table>
</script>
<!-- Template -->
<script type="text/template" name="actionPage1">
<h3>This is "actionPage1" content!</h3>
<p>這是一個單獨的頁面,它使用一個全新的PageContext物件進行渲染,因此在這個頁面你不能訪問父頁面的物件!</p>
<p>actionMessage: ${actionMessage} undefinedVariable: ${undefinedVariable}</p>
</script>