1、angularjs的幾大特性是什么?
雙向數據綁定、依賴注入、模板、指令、MVC/MVVM
2、列舉幾種常見的設計模式,寫出沒個代表的含義?
MVC :model view controller
MVVM :model view viewModel
3、請描述angularjs的運行過程?
angularjs編譯所有的HTML元素標簽,然后在里面查找angular程序的入口 ng-app 每個元素上的指令是把所有指令收集起來根據優先級依次編譯
4、ng-bind和ng-model的區別是什么?
ng-bind只能展示數據 ng-model可以操作數據
5、請描述$scope的特點還有其最大的父類?
隨創建作用域創建的一個變量,就代表controller所代表的作用域,其持有的對象和方法可在當前及其子作用域生效
6、原生js的延遲或回調在angularjs里能完美運行嗎?怎么解決?可以用例子?
不能 需要用$apply來進行傳播
7、{{ array | filter:{‘age’:23}:true }} 這個過濾里的true是什么意思?
是否用angular.equals進行比較后為真才返回
8、自定義過濾創建后返回的是一個什么對象?
返回一個函數對象 并且函數內要返回最后返回的對象
9、ng-repeat循環[1,3,2,4,3,4]數組會報錯嗎?如果會怎么解決?
會因為有重復的內容 track by $index
10、angular常用的服務中value和constant最大的區別是什么?
constant的創建要早于value 并且其可以在config配置中使用 value不行
11、常用服務中factory和service的最大區別是什么?
factory返回的對象當我們使用它的時候手動初始化并返回,而service是當我們第一次使用的時候angular幫我們初始化一次,然后以后使用的時候返回的都是這個對象,factory創建的服務是代表的是其后面函數的返回值,這個返回值可以是任意類型,service不用返回,直接操作的就是自己
12、怎么攔截服務?
在config配置里注入需要攔截的服務的名字+Provider來攔截
13、decorator的作用是什么?和攔截服務的區別是什么?
裝飾器不僅可以應用在我們自己的服務上,也可以對angularjs核心服務進行攔截、中斷甚至替換功能的操作,事實上angularjs的很多測試就是借助$provide.decorator()建立的、請寫一個配置路由的代碼段(只需要寫怎么聲明一個路由和其常用屬性的代碼段)
14、resolve的作用是什么?
如果設置了resolve屬性,angularjs會將列表中的元素都注入到控制器中,列表對象可以是鍵(鍵值是會被注入到控制器中依賴的名字),也可以是工廠(即可以是一個服務的名字)
15、ngRoute默認查找的路由是什么?$routeProvider.otherwise(’/index’)是什么作用?
是/ 設置路由的意外指向到/index
16、$location.path(‘/home’)和$location.url(‘/home’)都可以進行路由跳轉,但是.path方法和.url方法最大的區別是什么?
.url方法:可以在跳轉的同時設置查詢串,返回url的整個路徑; 而.path方法:返回的路徑不包括?后面的部分;
17、什么是跨域,請簡要描述跨域的場景?
協議 域名 端口號有一個不一樣就是跨域,也就是不同域名之間的訪問;
18、常使用的跨域方案就哪兩種?分別描述其利用的原理?
jsonp; post請求設置請求頭 ; jsonp利用的是script可以訪問外部信息的原理發送請求并且利用jsonp協議進行數據交互 post設置請求頭跳過預請求來實現跨域
19、請寫出$http網絡請求的幾種寫法,最少兩種
$http.(url).success(function(data){
}).error(function(error){
}) $http({ method:’’, url:url }).success(function(data){
}).error(function(error){
}) $http({ method:’***’, url:url }).then(function success(data){
},function error(error){
})
var promise = $http({ method:’get’, url:url }); promise.then(function(data){
},function(error){
}) 或者 promise.success(function(data){
}); promise.error(function(error){
});
20、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>
21、ng-show
不存在此問題,因為它不自帶一級作用域。
避免這類問題出現的辦法是,始終將頁面中的元素綁定到對象的屬性(data.x)而不是直接綁定到基本變量(x)上。
詳見 AngularJS 中的作用域
22、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);}
這樣自然是沒什么問題了。
23、對于這種需求,使用一個 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');
24、自定義 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; }})
25、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
26、$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在蘇寧云中心的深入實踐
27、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' })
28、如果通過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'];
29、補充
對于一個 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){ ...}])
30、參考
理解angular中的module和injector,即依賴注入
31、如何看待angular2
相比 Angular1.x,Angular2的改動很大,幾乎算是一個全新的框架。
基于 TypeScript(可以使用 TypeScript 進行開發),在大型項目團隊協作時,強語言類型更有利。
組件化,提升開發和維護的效率。
還有 module 支持動態加載,new router,promise的原生支持等等。
迎合未來標準,吸納其他框架的優點,值得期待,不過同時要學習的東西也更多了(ES next、TS、Rx等)。
參考
浴火重生的Angular