1. 程式人生 > >javascript模板引擎之EL表示式

javascript模板引擎之EL表示式

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表示式物件的結構如下:
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);
1. 首先,一個el表示式被編譯成了一個函式, 這個函式的主體直接返回el表示式.
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>