angular的常見問題

ng-if 跟 ng-show/hide 的區別有哪些?

第一點區別是,ng-if 在後面表達式爲 true 的時候才建立這個 dom 節點,ng-show 是初始時就建立了,用 display:block 和 display:none 來控制顯示和不顯示。javascript

第二點區別是,ng-if 會(隱式地)產生新做用域,ng-switch 、 ng-include 等會動態建立一塊界面的也是如此。css

這樣會致使,在 ng-if 中用基本變量綁定 ng-model,並在外層 div 中把此 model 綁定給另外一個顯示區域,內層改變時,外層不會同步改變,由於此時已是兩個變量了。html

<p>{{name}}</p> <div ng-if="true"> <input type="text" ng-model="name"> </div>

ng-show 不存在此問題,由於它不自帶一級做用域。前端

避免這類問題出現的辦法是,始終將頁面中的元素綁定到對象的屬性(data.x)而不是直接綁定到基本變量(x)上。java

ng-repeat迭代數組的時候,若是數組中有相同值,會有什麼問題,如何解決?

會提示 Duplicates in a repeater are not allowed. 加 track by $index 可解決。固然,也能夠 trace by 任何一個普通的值,只要能惟一性標識數組中的每一項便可(創建 dom 和數據之間的關聯)。git

ng-click 中寫的表達式,能使用 JS 原生對象上的方法嗎?

不止是 ng-click 中的表達式,只要是在頁面中,都不能直接調用原生的 JS 方法,由於這些並不存在於與頁面對應的 Controller 的 $scope 中。程序員

舉個栗子:github

<p>{{parseInt(55.66)}}<p>

會發現,什麼也沒有顯示。ajax

但若是在 $scope 中添加了這個函數:json

$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 的 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 的數據綁定採用什麼機制?詳述原理

髒檢查機制。

雙向數據綁定是 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次後拋出一個異常,防止無限循環)。

兩個平級界面塊 a 和 b,若是 a 中觸發一個事件,有哪些方式能讓 b 知道?詳述原理

這個問題換一種說法就是,如何在平級界面模塊間進行通訊。有兩種方法,一種是共用服務,一種是基於事件。

共用服務

在 Angular 中,經過 factory 能夠生成一個單例對象,在須要通訊的模塊 a 和 b 中注入這個對象便可。

基於事件

這個又分兩種方式

第一種是藉助父 controller。在子 controller 中向父 controller 觸發($emit)一個事件,而後在父 controller 中監聽($on)事件,再廣播($broadcast)給子 controller ,這樣經過事件攜帶的參數,實現了數據通過父 controller,在同級 controller 之間傳播。

第二種是藉助 $rootScope。每一個 Angular 應用默認有一個根做用域 $rootScope, 根做用域位於最頂層,從它往下掛着各級做用域。因此,若是子控制器直接使用 $rootScope 廣播和接收事件,那麼就可實現同級之間的通訊。

一個 angular 應用應當如何良好地分層?

目錄結構的劃分

對於小型項目,能夠按照文件類型組織,好比:

css
js
  controllers
  models
  services
  filters
templates

可是對於規模較大的項目,最好按業務模塊劃分,好比:

css
modules
  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 層更薄了)。

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 應用的性能 的辦法:

  1. 減小監控項(好比對不會變化的數據採用單向綁定)

  2. 主動設置索引(指定 track by,簡單類型默認用自身當索引,對象默認使用 $$hashKey,好比改成 track by item.id

  3. 下降渲染數據量(好比分頁,或者每次取一小部分數據,根據須要再取)

  4. 數據扁平化(好比對於樹狀結構,使用扁平化結構,構建一個 map 和樹狀數據,對樹操做時,因爲跟扁平數據同一引用,樹狀數據變動會同步到原始的扁平數據)

另外,對於Angular1.x ,存在 髒檢查 和 模塊機制 的問題。

移動端

可嘗試 Ionic,但並不完善。

如何看待 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 的 「依賴注入」

栗子

依賴注入是一種軟件設計模式,目的是處理代碼之間的依賴關係,減小組件間的耦合。

舉個栗子,若是沒有使用 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){ ... }])

如何看待angular2

相比 Angular1.x,Angular2的改動很大,幾乎算是一個全新的框架。

基於 TypeScript(可使用 TypeScript 進行開發),在大型項目團隊協做時,強語言類型更有利。

組件化,提高開發和維護的效率。

還有 module 支持動態加載,new router,promise的原生支持等等。

迎合將來標準,吸納其餘框架的優勢,值得期待,不過同時要學習的東西也更多了(ES next、TS、Rx等)。

相關文章
相關標籤/搜索