運算式(Expressions)是類Javascript的程式碼片段,通常放置在綁定地區中(如{{expression}})。運算式通過$parse服務(http://code.angularjs.org/1.0.2/docs/api/ng.$parse)解析執行。
例如,以下是angular中有效運算式:
- 1+2
- 3*10 | currency
- user.name
一、Angular運算式 vs. Js 運算式
這很容易讓人將angular視圖運算式聯想為javascript運算式,但這並不完全正確,因為angular不是通過javascript的eval()對錶達式進行求值。你可以將angular運算式想象為帶有以下差異的javascript運算式:
- 屬性求值:所有屬性的求值是對於scope的,而javascript是對於window對象的。
- 寬容(forgiving):運算式求值,對於undefined和null,angular是寬容的,但Javascript會產生NullPointerExceptions(-_-!!!!怎麼我沒見過)。
- 沒有流程式控制制語句:在angular運算式裡,我們不能做以下任何的事:條件分支、迴圈、拋出異常。
- 過濾器(filters):我們可以就將運算式的結果傳入過濾器鏈(filter chains)。例如將日期對象轉換為本地指定的人類可讀的格式。
另一方面,如果我們想(在angular運算式中)執行任意的Javascript代碼,我們可以將那些代碼寫到Controller的一個方法中並調用它。如果我們想在javascript中eval()一個angular運算式,可以使用$eval()方法。
<!DOCTYPE HTML><html lang="zh-cn" ng-app="ExpressionTest"><head> <meta charset="UTF-8"> <title>expression-e1</title> <style type="text/css"> .ng-cloak { display: none; } </style></head><body ng-controller="MyCtrl">1 + 2 = {{1+2}}<br/>Expression:<input type="text" ng-model="expr"/><button ng-click="addExp(expr)">Evaluate</button><ul> <li ng-repeat="expr in exprs"> [<a ng-click="removeExp($index)" href="">X</a>] <tt>{{expr}}</tt>=><span ng-bind="$parent.$eval(expr)"></span> </li></ul><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> var app = angular.module("ExpressionTest", []); app.controller("MyCtrl", function ($scope) { var exprs = $scope.exprs = []; $scope.expr = "3*10|currency"; $scope.addExp = function(expr) { exprs.push(expr); }; $scope.removeExp = function (index) { exprs.splice(index, 1); }; });</script></body></html>
二、屬性求值(Property Evaluation)
angular的運算式解析環境的上下文是scope,而javascript則是window(應該是指strict 模式evel的時候),angular需要通過$window訪問global window對象。例如,如果我們需要在運算式中調用定義在window對象上的alert(),我們需要使用$window.alert()。這樣做的用意是避免意外訪問了公用屬性(global state)(一個同源的小BUG?a common source of subtle bugs)。
<!DOCTYPE HTML><html lang="zh-cn" ng-app="PropertyEvaluation"><head> <meta charset="UTF-8"> <title>PropertyEvaluation</title> <style type="text/css"> .ng-cloak { display: none; } </style></head><body><div ng-controller="MyCtrl"> Name: <input ng-model="name" type="text"/> <button ng-click="greet()">Greet</button></div><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> var app = angular.module("PropertyEvaluation", []); app.controller("MyCtrl", function ($scope,$window) { $scope.name = "Kitty"; $scope.greet = function() { $window.alert("Hello " + $scope.name); }; });</script></body></html>
三、Forgiving(寬容,容錯?)
運算式求值對undefined和null是寬容的。在javascript中,當a不是object的時候,對a.b.c求值,那麼將會拋出一個異常。有時候這對於通用語言來說是合理的,而運算式求值主要用於資料繫結,一般形式如下:
{{a.b.c}}
如果a不存在,沒有任何顯示似乎比拋出異常更加合理(除非我們等待服務端響應,不一會兒就會被定義)。如果運算式求值時不夠寬容,那麼我們如此混亂地寫綁定代碼:
{{((a||{}).b||{}).c}} //這……
相似地,引用一個函數a.b.c()時,如果它是undefined或者null,那麼簡單地返回undefined。
四、沒有控制流程程語句(No Control Flow Statements)
我們不可以在運算式中寫流程式控制制語句。背後的原因是,angular的核心體系是應用的邏輯應當在controller(的scope)裡面,而不是在view裡面。如果我們需要在視圖運算式中加入條件分支、迴圈或者拋出異常的話,可以委託javascript方法去代替(可以調用scope中的方法)。
五、過濾器(Filters)
當我們向使用者呈現資料時,我們可能需要將資料從原始格式轉換為友好(可讀性強)的格式。例如,我們有一個資料對象需要在顯示給使用者之前根據地區進行格式化。我們可以將運算式傳遞給一連串的過濾器,如:
name | uppercase
這運算式求值器可簡單地傳遞name的值到uppercase過濾器中。
鏈式過濾器使用這種文法:
value | filter1 | filter2
我們也可以傳送用冒號分割的參數到filter中,例如,以兩位小數的格式顯示123:
123 | number:2
六、首碼”$”
我們可能會感到奇怪,首碼”$”的意義是什嗎?它是angular為了使本身的API名稱能夠區別於其他的API而使用的一個簡單的首碼(防止衝突)。如果angular不使用$,那麼對a.length()求值將返回undefined。因為a和angular本身都沒有定義這個屬性。
考慮到angular將來的版本可能會選擇增加length這個方法,這將令這個運算式的行為發生改變。更糟糕的是,我們開發人員可能會建立一個length屬性,那麼將與angular發生衝突。這個問題存在因為angular通過增加方法擴充了當前存在的對象。通過加入首碼”$”,angular保留了特定的namespace,所以angular的開發人員與使用angular的開發人員都可以和諧共處。