Angular面試題
一、ng-show/ng-hide與ng-if的區別?
第一點區別是,ng-if在后面表達式為true的時候才創建這個dom節點,ng-show是初始時就創建了,用display:block和display:none來控制顯示和不顯示。
第二點區別是,ng-if會(隱式地)產生新作用域,ng-switch、ng-include等會動態創建一塊界面的也是如此。
這樣會導致,在ng-if中用基本變量綁定ng-model,并在外層div中把此model綁定給另一個顯示區域,內層改變時,外層不會同步改變,因為此時已經是兩個變量了。
{{name}}
ng-show不存在此問題,因為它不自帶一級作用域。
避免這類問題出現的辦法是,始終將頁面中的元素綁定到對象的屬性(data.x)而不是直接綁定到基本變量(x)上。
二、解釋下什么是$rootScrope以及和$scope的區別?
通俗的說$rootScrope 頁面所有$scope的父親。
我們來看下如何產生$rootScope和$scope吧。
step1:Angular解析ng-app然后在內存中創建$rootScope。
step2:angular回繼續解析,找到{{}}表達式,并解析成變量。
step3:接著會解析帶有ng-controller的div然后指向到某個controller函數。這個時候在這個controller函數變成一個$scope對象實例。
三、表達式{{yourModel}}是如何工作的?
它依賴于$interpolation服務,在初始化頁面html后,它會找到這些表達式,并且進行標記,于是每遇見一個{{}},則會設置一個$watch。而$interpolation會返回一個帶有上下文參數的函數,最后該函數執行,則算是表達式$parse到那個作用域上。
四、Angular中的digest周期是什么?
每個digest周期中,angular總會對比scope上model的值,一般digest周期都是自動觸發的,我們也可以使用$apply進行手動觸發。
五、如何取消$timeout,以及停止一個$watch()?
停止$timeout我們可以用cancel:
var customTimeout = $timeout(function () {
// your code
}, 1000);
$timeout.cancel(customTimeout);
停掉一個$watch:
// .$watch()會返回一個停止注冊的函數
function that we store to a variable
var deregisterWatchFn = $rootScope.$watch(‘someGloballyAvailableProperty', function (newVal) {
if (newVal) {
// we invoke that deregistration function, to disable the watch
deregisterWatchFn();
...
}
});
六、Angular Directive中restrict中分別可以怎樣設置?scope中@,=,&有什么區別?
restrict中可以分別設置:
A匹配屬性
E匹配標簽
C匹配class
M 匹配注釋
當然你可以設置多個值比如AEC,進行多個匹配。
在scope中,@,=,&在進行值綁定時分別表示
@獲取一個設置的字符串,它可以自己設置的也可以使用{{yourModel}}進行綁定的;
= 雙向綁定,綁定scope上的一些屬性;
& 用于執行父級scope上的一些表達式,常見我們設置一些需要執行的函數
< 進行單向綁定。
angular.module('docsIsolationExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.alertName = function() {
alert('directive scope &');
}
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
scope: {
clickHandle: '&'
},
template: 'Click Me',
controller: function($scope) {
$scope.testClick = function() {
$scope.clickHandle();
}
}
};
});
七、列出至少三種實現不同模塊之間通信方式?
1、Service
2、events,指定綁定的事件
3、使用 $rootScope
4、controller之間直接使用$parent, $$childHead等、。。。
5、directive 指定屬性進行數據綁定
八、有哪些措施可以改善Angular性能
1.官方提倡的,關閉debug,$compileProvider
myApp.config(function ($compileProvider) {
$compileProvider.debugInfoEnabled(false);
});
2.使用一次綁定表達式即{{::yourModel}}
3.減少watcher數量
4.在無限滾動加載中避免使用ng-repeat
5.使用性能測試的小工具去挖掘你的angular性能問題,我們可以使用簡單的console.time()也可以借助開發者工具以及Batarang
console.time("TimerName");
//your code
console.timeEnd("TimerName");
九、你認為在Angular中使用jQuery好么?
這是一個開放性的問題,盡管網上會有很多這樣的爭論,但是普遍還是認為這并不是一個特別好的嘗試。其實當我們學習Angular的時候,我們應該做到從0去接受angular的思想,數據綁定,使用angular自帶的一些api,合理的路由組織和,寫相關指令和服務等等。angular自帶了很多api可以完全替代掉jquery中常用的api,我們可以使用angular.element,$http,$timeout,ng-init等。
我們不妨再換個角度,如果業務需求,而對于一個新人(比較熟悉jQuery)的話,或許你引入jQuery可以讓它在解決問題,比如使用插件上有更多的選擇,當然這是通過影響代碼組織來提高工作效率,隨著對于angular理解的深入,在重構時會逐漸摒棄掉當初引入jquery時的一些代碼。(?Po主就是這樣的人,希望不要被嘲笑,業務卻是趕著走)
所以我覺得兩種框架說完全不能一起用肯定是錯的,但是我們還是應該盡力去遵循angular的設計。
十、如何進行angular的單元測試
我們可以使用karam+jasmine 進行單元測試,我們通過ngMock引入angular app然后自行添加我們的測試用例。 一段簡單的測試代碼:
describe('calculator', function () {
beforeEach(module('calculatorApp'));
var $controller;
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
describe('sum', function () {
it('1 + 1 should equal 2', function () {
var $scope = {};
var controller = $controller('CalculatorController', { $scope: $scope });
$scope.x = 1;
$scope.y = 2;
$scope.sum();
expect($scope.z).toBe(3);
});
});
});
十一、ng-repeat迭代數組的時候,如果數組中有相同值,會有什么問題,如何解決?
會提示Duplicates in a repeater are not allowed.加track by $index可解決。當然,也可以trace by任何一個普通的值,只要能唯一性標識數組中的每一項即可(建立dom和數據之間的關聯)。
十二、{{now | 'yyyy-MM-dd'}}這種表達式里面,豎線和后面的參數通過什么方式可以自定義?
filter,格式化數據,接收一個輸入,按某規則處理,返回處理結果。
內置filter
1、ng內置的filter有九種:
1.date(日期)
2.currency(貨幣)
3.limitTo(限制數組或字符串長度)
4.orderBy(排序)
5.lowercase(小寫)
6.uppercase(大寫)
7.number(格式化數字,加上千位分隔符,并接收參數限定小數點位數)
8.filter(處理一個數組,過濾出含有某個子串的元素)
9.json(格式化json對象)
2、filter有兩種使用方法,
1)是直接在頁面里:
{{now | date : 'yyyy-MM-dd'}}
2)是在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是什么關系?
1.factory
把service的方法和數據放在一個對象里,并返回這個對象
app.factory('FooService', function(){
return {
target: 'factory',
sayHello: function(){
return 'hello ' + this.target;
}
}
});
2.service
通過構造函數方式創建service,返回一個實例化對象
app.service('FooService', function(){
var self = this;
this.target = 'service';
this.sayHello = function(){
return 'hello ' + self.target;
}
});
3.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的provider
app.config(function(FooServiceProvider){
FooServiceProvider.setConfigData('config data');
});
從底層實現上來看,service 調用了 factory,返回其實例;factory 調用了 provider,返回其 $get 中定義的內容。factory 和 service 功能類似,只不過 factory 是普通 function,可以返回任何東西(return 的都可以被訪問,所以那些私有變量怎么寫,你懂的);service 是構造器,可以不返回(綁定到 this 的都可以被訪問);provider 是加強版 factory,返回一個可配置的 factory。
十四、angular的數據綁定采用什么機制?詳述原理
1.臟檢查機制。
2.雙向數據綁定是AngularJS的核心機制之一。當view中有任何數據變化時,會更新到model,當model中數據有變化時,view也會同步更新,顯然,這需要一個監控。
原理就是,Angular在scope模型上設置了一個 監聽隊列,用來監聽數據變化并更新view。每次綁定一個東西到view上時AngularJS就會往$watch隊列里插入一條$watch,用來檢測它監視的model里是否有變化的東西。當瀏覽器接收到可以被angular context處理的事件時,$digest循環就會觸發,遍歷所有的$watch,最后更新dom。
舉個栗子
increase 1
click時會產生一次更新的操作(至少觸發兩次$digest循環)
按下按鈕
瀏覽器接收到一個事件,進入到angular context
$digest循環開始執行,查詢每個$watch是否變化
由于監視$scope .val的$watch報告了變化,因此強制再執行一次$digest循環
新的$digest循環未檢測到變化
瀏覽器拿回控制器,更新$scope .val新值對應的dom
$digest循環的上限是10次(超過10次后拋出一個異常,防止無限循環)。
十五、兩個平級界面塊a和b,如果a中觸發一個事件,有哪些方式能讓b知道?詳述原理
這個問題換一種說法就是,如何在平級界面模塊間進行通信。有兩種方法,一種是共用服務,一種是基于事件。
(1)共用服務
在Angular中,通過factory可以生成一個單例對象,在需要通信的模塊a和b中注入這個對象即可。
(2)基于事件
這個又分兩種方式
第一種是借助父controller。在子controller中向父controller觸發($emit)一個事件,然后在父controller中監聽($on)事件,再廣播($broadcast)給子controller,這樣通過事件攜帶的參數,實現了數據經過父controller,在同級controller之間傳播。
第二種是借助$rootScope。每個Angular應用默認有一個根作用域$rootScope, 根作用域位于最頂層,從它往下掛著各級作用域。所以,如果子控制器直接使用$rootScope廣播和接收事件,那么就可實現同級之間的通信。
十六、一個angular應用應當如何良好地分層?
目錄結構的劃分
1、對于小型項目,可以按照文件類型組織,比如:
css
Js ?{
controllers
models
services
filters
}
templates
2、但是對于規模較大的項目,最好按業務模塊劃分,比如:
css
Modules
account
controllers
models
services
filters
templates
disk
controllers
models
services
filters
templates
modules下最好再有一個common目錄來存放公共的東西。
3.邏輯代碼的拆分
作為一個MVVM框架,Angular應用本身就應該按照 模型,視圖模型(控制器),視圖來劃分。
這里邏輯代碼的拆分,主要是指盡量讓controller這一層很薄。提取共用的邏輯到service中 (比如后臺數據的請求,數據的共享和緩存,基于事件的模塊間通信等),提取共用的界面操作到directive中(比如將日期選擇、分頁等封裝成組件等),提取共用的格式化操作到filter中等等。
在復雜的應用中,也可以為實體建立對應的構造函數,比如硬盤(Disk)模塊,可能有列表、新建、詳情這樣幾個視圖,并分別對應的有controller,那么可以建一個Disk構造函數,里面完成數據的增刪改查和驗證操作,有跟Disk相關的controller,就注入Disk構造器并生成一個實例,這個實例就具備了增刪改查和驗證方法。這樣既層次分明,又實現了復用(讓controller層更薄了)。
十七、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指令將子路由模版插入到父路由模板的
中去,從而實現視圖嵌套。而在ngRoute中不能這樣定義,如果同時在父子視圖中 使用了會陷入死循環。示例
//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規劃一套全組件化體系,可能遇到哪些挑戰?
件如何與外界進行數據的交互,以及如何通過簡單的配置就能使用吧。
十九、分屬不同團隊進行開發的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的缺點有哪些?
1.強約束
導致學習成本較高,對前端不友好。
但遵守AngularJS的約定時,生產力會很高,對Java程序員友好。
2.不利于SEO
因為所有內容都是動態獲取并渲染生成的,搜索引擎沒法爬取。
一種解決辦法是,對于正常用戶的訪問,服務器響應AngularJS應用的內容;對于搜索引擎的訪問,則響應專門針對SEO的HTML頁面。
3..性能問題
作為MVVM框架,因為實現了數據的雙向綁定,對于大數組、復雜對象會存在性能問題。
可以用來優化Angular應用的性能 的辦法:
減少監控項(比如對不會變化的數據采用單向綁定)
主動設置索引(指定track by,簡單類型默認用自身當索引,對象默認使用$$hashKey,比如改為track by item.id)
降低渲染數據量(比如分頁,或者每次取一小部分數據,根據需要再取)
數據扁平化(比如對于樹狀結構,使用扁平化結構,構建一個map和樹狀數據,對樹操作時,由于跟扁平數據同一引用,樹狀數據變更會同步到原始的扁平數據)
另外,對于Angular1.x,存在 臟檢查 和 模塊機制 的問題。
4.移動端
可嘗試Ionic,但并不完善。
參考如何看2015年1月Peter-Paul Koch對Angular的看法?
如何看待angular 1.2中引入的controller as語法?
5.最根本的好處
在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,所以坑不再存在)。
{{name}}
問題
使用controllerAs會遇到的一個問題是,因為沒有注入$scope,導致$emit、$broadcast、$on、$watch等$scope下的方法無法使用。這些跟事件相關的操作可以封裝起來統一處理,或者在單個controller中引入$scope,特殊對待。
栗子
依賴注入是一種軟件設計模式,目的是處理代碼之間的依賴關系,減少組件間的耦合。
舉個栗子,如果沒有使用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
//定義一個HttpService
angular.module('myApp.services').service('HttpService', ['$http', function($http){
...
}])
二十一、compile和link的區別: 看到一個比較6的答案。性能力(性能和能力)
編譯的時候,compile轉換dom,碰到綁定監聽器的地方就先存著,有幾個存幾個,到最后匯總成一個link函數,一并執行,提升了性能。
function compile(tElement, tAttrs, transclude) { ... }tElement為編譯前的element
function link(scope, iElement, iAttrs, controller) { ... } ?iElement為編譯后的element,已經與作用域關聯起來,所以可以數據綁定
如果指令只進行DOM的修改,不進行數據綁定,那么配置在compile函數中,如果指令要進行數據綁定,那么配置在link函數中。
《
二十二、.?$apply()和$digest()的區別
安全性:$apply()可以接收一個參數作為function(),這個function會被包裝到一個try…catch塊中,所以一旦有異常發生,該異常會被$exceptionHandler service處理。
$apply會使ng進入$digest cycle ,并從$rootScope開始遍歷(深度優先)檢查數據變更。
$digest僅會檢查該scope和它的子scope,當你確定當前操作僅影響它們時,用$digest可以稍微提升性能。