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 面試題的資料整理,後續繼續補充相關資料謝謝大家對本站的支持!