ng-if 跟 ng-show/hide 的區別有哪些?
第一點區別是, ng-if 在後面運算式為 true 的時候才建立這個 dom-節點, ng-show 是初始時就建立了,用 display:block 和 display:none 來控制顯示和不顯示。
第二點區別是, ng-if 會(隱式地)產生新範圍, ng-switch 、 ng-include 等會動態建立一塊介面的也是如此。
這樣會導致,在 ng-if 中用基本變數綁定 ng-model ,並在外層 div 中把此 model 綁定給另一個顯示地區,內層改變時,外層不會同步改變,因為此時已經是兩個變數了。
<p>{{name}}</p><div ng-if="true"> <input type="text" ng-model="name"></div>
ng-show 不存在此問題,因為它不內建一級範圍。
避免這類問題出現的辦法是,始終將頁面中的元素繫結到對象的屬性(data.x)而不是直接綁定到基本變數(x)上。
詳見 AngularJS 中的範圍
ng-repeat迭代數組的時候,如果數組中有相同值,會有什麼問題,如何解決?
會提示 Duplicates in a repeater are not allowed. 加 track by $index 可解決。當然,也可以 trace by 任何一個普通的值,只要能唯一性標識數組中的每一項即可(建立 dom 和資料之間的關聯)。
ng-click 中寫的運算式,能使用 JS 原生對象上的方法嗎?
不止是 ng-click 中的運算式,只要是在頁面中,都不能直接調用原生的 JS 方法,因為這些並不存在於與頁面對應的 Controller 的 $scope 中。
舉個栗子:
<p>{{parseInt(55.66)}}<p>
會發現,什麼也沒有顯示。
但如果在 $scope 中添加了這個函數:
$scope.parseInt = function(x){ return parseInt(x);}
這樣自然是沒什麼問題了。
對於這種需求,使用一個 filter 或許是不錯的選擇:
<p>{{13.14 | parseIntFilter}}</p>app.filter('parseIntFilter', function(){ return function(item){ return parseInt(item); }})
{{now | 'yyyy-MM-dd'}} 這種運算式裡面,豎線和後面的參數通過什麼方式可以自訂?
filter,格式化資料,接收一個輸入,按某規則處理,返回處理結果。
內建 filter
ng 內建的 filter 有九種:
date(日期)
currency(貨幣)
limitTo(限制數組或字串長度)
orderBy(排序)
lowercase(小寫)
uppercase(大寫)
number(格式化數字,加上千位分隔字元,並接收參數限定小數點位元)
filter(處理一個數組,過濾出含有某個子串的元素)
json(格式化 json 對象)
filter 有兩種使用方法,一種是直接在頁面裡:
<p>{{now | date : 'yyyy-MM-dd'}}</p>
另一種是在 js 裡面用:
// $filter('過濾器名稱')(需要過濾的對象, 參數1, 參數2,...)
$filter('date')(now, 'yyyy-MM-dd hh:mm:ss');
自訂 filter
// 形式app.filter('過濾器名稱',function(){ return function(需要過濾的對象,過濾器參數1,過濾器參數2,...){ //...做一些事情 return 處理後的對象; }}); // 栗子app.filter('timesFilter', function(){ return function(item, times){ var result = ''; for(var i = 0; i < times; i++){ result += item; } return result; }})
factory、service 和 provider 是什麼關係?
factory
把 service 的方法和資料放在一個對象裡,並返回這個對象
app.factory('FooService', function(){ return { target: 'factory', sayHello: function(){ return 'hello ' + this.target; } }});
service
通過建構函式方式建立 service,返回一個執行個體化對象
app.service('FooService', function(){ var self = this; this.target = 'service'; this.sayHello = function(){ return 'hello ' + self.target; }});
provider
建立一個可通過 config 配置的 service,$get 中返回的,就是用 factory 建立 service 的內容
app.provider('FooService', function(){ this.configData = 'init data'; this.setConfigData = function(data){ if(data){ this.configData = data; } } this.$get = function(){ var self = this; return { target: 'provider', sayHello: function(){ return self.configData + ' hello ' + this.target; } } }});// 此處注入的是 FooService 的 providerapp.config(function(FooServiceProvider){ FooServiceProvider.setConfigData('config data');});
從底層實現上來看,service 調用了 factory,返回其執行個體;factory 調用了 provider,返回其 $get 中定義的內容。factory 和 service 功能類似,只不過 factory 是普通 function,可以返回任何東西(return 的都可以被訪問,所以那些私人變數怎麼寫,你懂的);service 是構造器,可以不返回(綁定到 this 的都可以被訪問);provider 是加強版 factory,返回一個可配置的 factory。
詳見 AngularJS 之 Factory vs Service vs Provider
angular 的資料繫結採用什麼機制?詳述原理
髒檢查機制。
雙向資料繫結是 AngularJS 的核心機制之一。當 view 中有任何資料變化時,會更新到 model ,當 model 中資料有變化時,view 也會同步更新,顯然,這需要一個監控。
原理就是,Angular 在 scope 模型上設定了一個 監聽隊列,用來監聽資料變化並更新 view 。每次綁定一個東西到 view 上時 AngularJS 就會往 $watch 隊列裡插入一條 $watch ,用來檢測它監視的 model 裡是否有變化的東西。當瀏覽器接收到可以被 angular context 處理的事件時, $digest 迴圈就會觸發,遍曆所有的 $watch ,最後更新 dom。
舉個栗子
<button ng-click="val=val+1">increase 1</button>
click 時會產生一次更新的操作(至少觸發兩次 $digest 迴圈)
按下按鈕
瀏覽器接收到一個事件,進入到 angular context
$digest 迴圈開始執行,查詢每個 $watch 是否變化
由於監視 $scope .val 的 $watch 報告了變化,因此強制再執行一次 $digest 迴圈
新的 $digest 迴圈未檢測到變化
瀏覽器拿回控制器,更新 $scope .val 新值對應的 dom
$digest 迴圈的上限是 10 次(超過 10次後拋出一個異常,防止無限迴圈)。
詳見 關於 AngularJS 的資料繫結
兩個平級介面塊 a 和 b,如果 a 中觸發一個事件,有哪些方式能讓 b 知道?詳述原理
這個問題換一種說法就是,如何在平級介面模組間進行通訊。有兩種方法,一種是共用服務,一種是基於事件。
共用服務
在 Angular 中,通過 factory 可以產生一個單例對象,在需要通訊的模組 a 和 b 中注入這個對象即可。
基於事件
這個又分兩種方式
第一種是藉助父 controller。在子 controller 中向父 controller 觸發( $emit )一個事件,然後在父 controller 中監聽( $on )事件,再廣播( $broadcast )給子 controller ,這樣通過事件攜帶的參數,實現了資料經過父 controller,在同級 controller 之間傳播。
第二種是藉助 $rootScope 。每個 Angular 應用預設有一個根範圍 $rootScope , 根範圍位於最頂層,從它往下掛著各級範圍。所以,如果子控制器直接使用 $rootScope 廣播和接收事件,那麼就可實現同級之間的通訊。
詳見 AngularJS 中 Controller 之間的通訊
一個 angular 應用應當如何良好地分層?
目錄結構的劃分
對於小型項目,可以按照檔案類型組織,比如:
cssjs controllers models services filterstemplates
但是對於規模較大的項目,最好按業務模組劃分,比如:
cssmodules account controllers models services filters templates disk controllers models services filters templates
modules 下最好再有一個 common 目錄來存放公用的東西。
邏輯代碼的拆分
作為一個 MVVM 架構,Angular 應用本身就應該按照 模型,視圖模型(控制器),視圖來劃分。
這裡邏輯代碼的拆分,主要是指盡量讓 controller 這一層很薄。提取共用的邏輯到 service 中 (比如後台資料的請求,資料的共用和緩衝,基於事件的模組間通訊等),提取共用的介面操作到 directive 中(比如將日期選擇、分頁等封裝成組件等),提取共用的格式化操作到 filter 中等等。
在複雜的應用中,也可以為實體建立對應的建構函式,比如硬碟(Disk)模組,可能有列表、建立、詳情這樣幾個視圖,並分別對應的有 controller,那麼可以建一個 Disk 建構函式,裡面完成資料的增刪改查和驗證操作,有跟 Disk 相關的 controller,就注入 Disk 構造器並產生一個執行個體,這個執行個體就具備了增刪改查和驗證方法。這樣既層次分明,又實現了複用(讓 controller 層更薄了)。
參考 AngularJS在蘇寧雲中心的深入實踐
angular 應用常用哪些路由庫,各自的區別是什嗎?
Angular1.x 中常用 ngRoute 和 ui.router,還有一種為 Angular2 設計的 new router (面向組件)。後面那個沒在實際項目中用過,就不講了。
無論是 ngRoute 還是 ui.router,作為架構額外的附加功能,都必須以 模組依賴 的形式被引入。
區別
ngRoute 模組是 Angular 內建的路由模組,而 ui.router 模組是基於 ngRoute模組開發的第三方模組。
ui.router 是基於 state (狀態)的, ngRoute 是基於 url 的,ui.router模組具有更強大的功能,主要體現在視圖的嵌套方面。
使用 ui.router 能夠定義有明確父子關係的路由,並通過 ui-view 指令將子路由模版插入到父路由模板的 <div ui-view></div> 中去,從而實現視圖嵌套。而在 ngRoute 中不能這樣定義,如果同時在父子視圖中 使用了 <div ng-view></div> 會陷入死迴圈。
樣本
ngRoute
var app = angular.module('ngRouteApp', ['ngRoute']);app.config(function($routeProvider){ $routeProvider .when('/main', { templateUrl: "main.html", controller: 'MainCtrl' }) .otherwise({ redirectTo: '/tabs' });
ui.router
var app = angular.module("uiRouteApp", ["ui.router"]);app.config(function($urlRouterProvider, $stateProvider){ $urlRouterProvider.otherwise("/index"); $stateProvider .state("Main", { url: "/main", templateUrl: "main.html", controller: 'MainCtrl' })
如果通過angular的directive規劃一套全組件化體系,可能遇到哪些挑戰?
沒有自己用 directive 做過一全套組件,講不出。
能想到的一點是,組件如何與外界進行資料的互動,以及如何通過簡單的配置就能使用吧。
分屬不同團隊進行開發的 angular 應用,如果要做整合,可能會遇到哪些問題,如何解決?
可能會遇到不同模組之間的衝突。
比如一個團隊所有的開發在 moduleA 下進行,另一團隊開發的代碼在 moduleB 下
angular.module('myApp.moduleA', []) .factory('serviceA', function(){ ... }) angular.module('myApp.moduleB', []) .factory('serviceA', function(){ ... }) angular.module('myApp', ['myApp.moduleA', 'myApp.moduleB'])
會導致兩個 module 下面的 serviceA 發生了覆蓋。
貌似在 Angular1.x 中並沒有很好的解決辦法,所以最好在前期進行統一規劃,做好約定,嚴格按照約定開發,每個開發人員唯寫特定區塊代碼。
angular 的缺點有哪些?
強約束
導致學習成本較高,對前端不友好。
但遵守 AngularJS 的約定時,生產力會很高,對 Java 程式員友好。
不利於 SEO
因為所有內容都是動態擷取並渲染產生的,搜尋引擎沒法爬取。
一種解決辦法是,對於正常使用者的訪問,伺服器響應 AngularJS 應用的內容;對於搜尋引擎的訪問,則響應專門針對 SEO 的HTML頁面。
效能問題
作為 MVVM 架構,因為實現了資料的雙向繫結,對於大數組、複雜物件會存在效能問題。
可以用來 最佳化 Angular 應用的效能 的辦法:
減少監控項(比如對不會變化的資料採用單向綁定)
主動設定索引(指定 track by ,簡單類型預設用自身當索引,對象預設使用 $$hashKey ,比如改為 track by item.id )
降低渲染資料量(比如分頁,或者每次取一小部分資料,根據需要再取)
資料扁平化(比如對於樹狀結構,使用扁平化結構,構建一個 map 和樹狀資料,對樹操作時,由於跟扁平資料同一引用,樹狀資料變更會同步到原始的扁平資料)
另外,對於Angular1.x ,存在 髒檢查 和 模組機制 的問題。
移動端
可嘗試 Ionic,但並不完善。
參考 如何看2015年1月Peter-Paul Koch對Angular的看法?
如何看待 angular 1.2 中引入的 controller as 文法?
最根本的好處
在 angular 1.2 以前,在 view 上的任何綁定都是直接綁定在 $scope 上的
function myCtrl($scope){ $scope.a = 'aaa'; $scope.foo = function(){ ... }}
使用 controllerAs,不需要再注入 $scope ,controller 變成了一個很簡單的 javascript 對象(POJO),一個更純粹的 ViewModel。
function myCtrl(){ // 使用 vm 捕獲 this 可避免內部的函數在使用 this 時導致上下文改變 var vm = this; vm.a = 'aaa';}
原理
從源碼實現上來看,controllerAs 文法只是把 controller 這個對象的執行個體用 as 別名在 $scope 上建立了一個屬性。
if (directive.controllerAs) { locals.$scope[directive.controllerAs] = controllerInstance;}
但是這樣做,除了上面提到的使 controller 更加 POJO 外,還可以避免遇到 AngularJS 範圍相關的一個坑(就是上文中 ng-if 產生一級範圍的坑,其實也是 javascript 原型鏈繼承中實值型別繼承的坑。因為使用 controllerAs 的話 view 上所有欄位都綁定在一個引用的屬性上,比如 vm.xx,所以坑不再存在)。
<div ng-controller="TestCtrl as vm"> <p>{{name}}</p> <div ng-if="vm.name"> <input type="text" ng-model="vm.name"> </div></div>
問題
使用 controllerAs 會遇到的一個問題是,因為沒有注入 $scope ,導致 $emit 、 $broadcast 、 $on 、 $watch 等 $scope 下的方法無法使用。這些跟事件相關的操作可以封裝起來統一處理,或者在單個 controller 中引入 $scope ,特殊對待。
參考 angular controller as syntax vs scope
詳述 angular 的 “依賴注入”
栗子
依賴注入是一種軟體設計模式,目的是處理代碼之間的依賴關係,減少組件間的耦合。
舉個栗子,如果沒有使用 AngularJS,想從後台查詢資料並在最上層顯示,可能需要這樣做:
var animalBox = document.querySelector('.animal-box');var httpRequest = { get: function(url, callback){ console.log(url + ' requested'); var animals = ['cat', 'dog', 'rabbit']; callback(animals); }}var render = function(el, http){ http.get('/api/animals', function(animals){ el.innerHTML = animals; })}render(httpRequest, animalBox);
但是,如果在調用 render 的時候不傳參數,像下面這樣,會報錯,因為找不到 el 和 http(定義的時候依賴了,啟動並執行時候不會自動尋找依賴項)
render();
// TypeError: Cannot read property 'get' of undefined
而使用 AngularJS,可以直接這樣
function myCtrl = ($scope, $http){ $http.get('/api/animals').success(function(data){ $scope.animals = data; })}
也就是說,在 Angular App 啟動並執行時候,調用 myCtrl,自動做了 $scope 和 $http 兩個依賴性的注入。
原理
AngularJS 是通過建構函式的參數名字來推斷依賴服務名稱的,通過 toString() 來找到這個定義的 function 對應的字串,然後用正則解析出其中的參數(依賴項),再去依賴映射中取到對應的依賴,執行個體化之後傳入。
簡化一下,大概是這樣:
var inject = { // 儲存依賴映射關係 storage: {}, // 註冊依賴 register: function(name, resource){ this.storage[name] = resource; }, // 解析出依賴並調用 resolve: function(target){ var self = this; var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; fnText = target.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS)[1].split(/, ?/g); var args = []; argDecl.forEach(function(arg){ if(self.storage[arg]){ args.push(self.storage[arg]); } }) return function(){ target.apply({}, args); } }}
使用這個 injector,前面那個不用 AngularJS 的栗子這樣改造一下就可以調用了
inject.register('el', animalBox);inject.register('ajax', httpRequest);render = inject.resolve(render);render();
問題
因為 AngularJS 的 injector 是假設函數的參數名就是依賴的名字,然後去尋找依賴項,那如果按前面栗子中那樣注入依賴,代碼壓縮後(參數被重新命名了),就無法尋找到依賴項了。
// 壓縮前function myCtrl = ($scope, $http){ ...}// 壓縮後function myCtrl = (a, b){ ...}
所以,通常會使用下面兩種方式注入依賴(對依賴添加的順序有要求)。
數組注釋法
myApp.controller('myCtrl', ['$scope', '$http', function($scope, $http){ ...}])
顯式 $inject
myApp.controller('myCtrl', myCtrl);function myCtrl = ($scope, $http){ ...}myCtrl.$inject = ['$scope', '$http'];
補充
對於一個 DI 容器,必須具備三個要素:依賴項的註冊,依賴關係的聲明和對象的擷取。
在 AngularJS 中,module 和 $provide 都可以提供依賴項的註冊;內建的 injector 可以擷取對象(自動完成依賴注入);依賴關係的聲明,就是前面問題中提到的那樣。
下面是個栗子
// 對於 module,傳遞參數不止一個,代表建立模組,空數組代表不依賴其他模組// 只有一個參數(模組名),代表擷取模組// 定義 myApp,添加 myApp.services 為其依賴項angular.module('myApp', ['myApp.services']);// 定義一個 services module,將 services 都註冊在這個 module 下面angular.module('myApp.services', [])// $provider 有 factory, service, provider, value, constant// 定義一個 HttpServiceangular.module('myApp.services').service('HttpService', ['$http', function($http){ ...}])
參考
[AngularJS] 自己實現一個簡單的依賴注入
理解angular中的module和injector,即依賴注入
AngularJS中的依賴注入實際應用情境
如何看待angular2
相比 Angular1.x,Angular2的改動很大,幾乎算是一個全新的架構。
基於 TypeScript(可以使用 TypeScript 進行開發),在大型項目團隊協作時,強語言類型更有利。
組件化,提升開發和維護的效率。
還有 module 支援動態載入,new router,promise的原生支援等等。
迎合未來標準,吸納其他架構的優點,值得期待,不過同時要學習的東西也更多了(ES next、TS、Rx等)。
以上就是對AngularJS 面試題的資料整理,後續繼續補充相關資料謝謝大家對本站的支援!