阅读(3377) (1)

AngularJS常见面试题及答案

2018-04-23 16:47:51 更新

Angular是当下非常流行的前端框架,受到了广大前端开发者的喜爱。下面,zijiebao将为大家列出一些经典的Angular面试题以及答案,供大家参考。

timg (1)

Q1、ng-if跟ng-show/hide的区别有哪些?

A:区别主要有两点:

1、ng-if 在后面表达式为 true 的时候才创建dom 节点,而ng-show 是在初始时就创建了,可以用 display:block 和 display:none 来控制显示和不显示。

2、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)上。


Q2、ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?

A:会提示 Duplicates in a repeater are not allowed. 出现这种情况的时候,我们可以通过加 track by $index 来解决。当然,也可以 trace by 任何一个普通的值,只要能唯一性标识数组中的每一项即可(建立 dom 和数据之间的关联)。


Q3、ng-click 中写的表达式,能使用 JS 原生对象上的方法吗?

A:不止是 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);
    }
})


Q4、{{now | 'yyyy-MM-dd'}} 这种表达式里面,竖线和后面的参数通过什么方式可以自定义?

A:filter,格式化数据,接收一个输入,按某规则处理,返回处理结果。


ng 内置的filter主要有九种:

1:date(日期)

2:currency(货币)

3:limitTo(限制数组或字符串长度)

4:orderBy(排序)

5:lowercase(小写)

6:uppercase(大写)

7:number(格式化数字,加上千位分隔符,并接收参数限定小数点位数)

8:filter(处理一个数组,过滤出含有某个子串的元素)

9: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;
    }
})


Q5、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,可以返回任何东西;service 是构造器,可以不返回(绑定到 this 的都可以被访问);provider 是加强版 factory,返回一个可配置的 factory。


Q6、angular 的数据绑定采用什么机制?详述原理

A:采用了脏检查机制。

双向数据绑定是 AngularJS 的核心机制之一。当 view 中有任何一个数据变化时,都会更新到 model 中。如果 model 中的数据有变化时,view 也会同步更新,显然,这需要一个监控。

原理

Angular 在 scope 模型上设置了一个监听队列,这个监听队列可以用来监听数据变化并更新 view 。每次绑定一个东西到 view 上时, AngularJS 就会往 $watch 队列里插入一条 $watch ,用来检测它监视的 model 里是否有变化的东西。当浏览器接收到可以被 angular context 处理的事件时, $digest 循环就会触发,遍历所有的 $watch ,最后更新 dom。


Q7、两个平级界面块 a 和 b,如果 a 中触发一个事件,有哪些方式能让 b 知道?详述原理

A:这个问题换一种说法就是,如何在平级界面模块间进行通信。有两种方法,一种是共用服务,一种是基于事件。

共用服务

在 Angular 中,通过 factory 可以生成一个单例对象,在需要通信的模块 a 和 b 中注入这个对象即可。

基于事件

这个又分两种方式

第一种是借助父 controller。在子 controller 中向父 controller 触发( $emit )一个事件,然后在父 controller 中监听( $on )事件,再广播( $broadcast )给子 controller ,这样通过事件携带的参数,实现了数据经过父 controller,在同级 controller 之间传播。

第二种是借助 $rootScope 。每个 Angular 应用默认有一个根作用域 $rootScope , 根作用域位于最顶层,从它往下挂着各级作用域。所以,如果子控制器直接使用 $rootScope 广播和接收事件,那么就可实现同级之间的通信。


Q8、一个 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 层更薄了)。


Q9、angular 应用常用哪些路由库,各自的区别是什么?

A:Angular1.x 中常用 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'
    })


Q10、分属不同团队进行开发的 angular 应用,如果要做整合,可能会遇到哪些问题,如何解决?

A:可能会遇到不同模块之间的冲突。

比如一个团队所有的开发在 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 中并没有很好的解决办法,所以最好在前期进行统一规划,做好约定,严格按照约定开发,每个开发人员只写特定区块代码。


Q11、angular 的缺点有哪些?

强约束

导致学习成本较高,对前端不友好。

但遵守 AngularJS 的约定时,生产力会很高,对 Java 程序员友好。

不利于SEO

因为所有内容都是动态获取并渲染生成的,搜索引擎没法爬取。

一种解决办法是,对于正常用户的访问,服务器响应 AngularJS 应用的内容;对于搜索引擎的访问,则响应专门针对 SEO 的HTML页面。

性能问题

作为 MVVM 框架,因为实现了数据的双向绑定,对于大数组、复杂对象会存在性能问题。

可以用来 优化 Angular 应用的性能 的办法:

减少监控项(比如对不会变化的数据采用单向绑定)

主动设置索引(指定 track by ,简单类型默认用自身当索引,对象默认使用 $$hashKey ,比如改为 track by item.id )

降低渲染数据量(比如分页,或者每次取一小部分数据,根据需要再取)

数据扁平化(比如对于树状结构,使用扁平化结构,构建一个 map 和树状数据,对树操作时,由于跟扁平数据同一引用,树状数据变更会同步到原始的扁平数据)

另外,对于Angular1.x ,存在 脏检查 和 模块机制 的问题。


Q12、如何看待 angular 1.2 中引入的 controller as 语法?

A:在 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';
}