前言

AngularJS
常见面试问题

angular1.x作为经典的mvc框架,可以创建能够复用的组件,也可进行双向数据绑定。国内的vue.js/avaloon.js都是同类型的框架,之前工作以Angular1.x为主,主要做业务系统,以后工作中技术栈可能以vue为主,在此对Angular1.x的使用做一个简单总结,这里使用1.5+版本。下面话不多说,来一起看看详细的介绍:

1. ng-if 跟 ng-show/hide 的区别有哪些?

  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>

AngularJS中 , 指令通过不会创建自己的
$scope,但也有例外。比如ng-controller,
ng-repeat指令会创建自己的子作用域并将它们附加到DOM无素上。

ng-show:
不存在此问题,因为它不自带一级作用域。避免这类问题出现的办法是,始终将页面中的元素绑定到对象的属性(data.x)而不是直接绑定到基本变量(x)上。
详见 AngularJS
中的作用域

为什么绑定在属性上就可以?解释:AngularJS实例教程(二)——作用域与事件
在Angular中,如果两个控制器所对应的视图存在上下级关系,它们的作用域就自动产生继承关系。什么意思呢?
Angular的实现机制其实也就是把这两个控制器中的$scope作了关联,外层的作用域实例成为了内层作用域的原型。(angular作用域之间是原型继承的关系-精通angularjs)
以此类推,整个Angular应用的作用域,都存在自顶向下的继承关系,最顶层的是$rootScope,然后一级一级,沿着不同的控制器往下,形成了一棵作用域的树,这也就像封建社会:天子高高在上,分茅裂土,公侯伯子男,一级一级往下,层层从属。
和这个原理一样,查对象的属性总结

function Person(){
}
Person.prototype = {
  oo:{
    name:"s"
  },
  a:111
} 
var p1 = new Person();
p1.a = 222;//p1增加实例属性a 和 原型中的 a 无关
//p1.oo = {he:"he"};//p1增加实例属性oo 和 原型中的 oo 无关
p1.oo.name = "b";//取到了并改变了原型的oo
//实际是这个
function Outer() {
    this.a = 1;
    this.oo = {
      name: "ss"
    }
}
function Inner() {
}
var outer = new Outer();
Inner.prototype = outer;
var inner = new Inner();
inner.a = 2;//和outer a 无关
inner.oo.name = "aa";
console.log(inner.oo.name);
console.log(outer.oo.name);//发生改变和inner.oo.name一样

基本概念

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

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

 1、依赖注入

3. 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); 
}
})

依赖注入,在angular中到处可见,这里不会照本宣科,只以很直白的方式的简单描述基本使用方式,以$scope注入为例。

4. {{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('timesFilter', function(){
    return function(item, times){
        var result = '';
        for(var i = 0; i < times; i++){
            result += item;
        }
        return result;
    }
})

创建一个controller,注入$scope,有三种写法:

5. 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 的内容

从底层实现上来看,service 调用了 factory,返回其实例;factory 调用了
provider,返回其 $get
中定义的内容。factory 和 service 功能类似,只不过 factory 是普通
function,可以返回任何东西(return
的都可以被访问,所以那些私有变量怎么写,你懂的);service
是构造器,可以不返回(绑定到 this 的都可以被访问);provider 是加强版
factory,返回一个可配置的 factory。

1)隐式注入

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

查看: 之前总结的
脏检查机制。

$digest 循环的上限是 10 次(超过 10次后抛出一个异常,防止无限循环)。
详见 关于 AngularJS
的数据绑定

function HomeController($scope){};

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

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

数据可以直接绑定在$rootScope上。

  • 共用服务
    在 Angular 中,通过 factory 可以生成一个单例对象,在需要通信的模块 a
    和 b 中注入这个对象即可。
  • 基于事件
    这个又分两种方式
  1. 第一种是借助父 controller。在子 controller 中向父 controller
    触发($emit)一个事件,然后在父 controller
    中监听($on)事件,再广播($broadcast)给子 controller
    ,这样通过事件携带的参数,实现了数据经过父 controller,在同级
    controller 之间传播。
  2. 第二种是借助 $rootScope。每个 Angular 应用默认有一个根作用域
    $rootScope,
    根作用域位于最顶层,从它往下挂着各级作用域。所以,如果子控制器直接使用
    $rootScope 广播和接收事件,那么就可实现同级之间的通信。

详见 AngularJS 中 Controller
之间的通信

威尼斯人线上娱乐,例子:

<div ng-app="myapp">
  <div ng-controller="myCtrl1">
    {{counter}}
    <input type="checkbox" ng-change="change()" ng-model="value" />
  </div>
  <div ng-controller='myCtrl2'>
    {{name}}
  </div>
</div>

angular.module("myapp", [])
  .controller("myCtrl1", ["$scope","$location", "$rootScope", function($scope, $location, $rootScope) {

  $scope.counter = 0;
    $scope.change = function(){
      $scope.counter++;
      $rootScope.$broadcast("datachange", $scope.counter)
      console.log($scope.counter)
    }
  }])
  .controller("myCtrl2", ["$scope", function($scope){
    $scope.name = 0;
    $scope.$on("datachange", function(event, val){
      $scope.name = val;
    })
  }])

2)内联注入

8. 一个 angular 应用应当如何良好地分层?

目录结构的划分

  • 对于小型项目,可以按照文件类型组织,比如:

威尼斯人线上娱乐 1

Paste_Image.png

  • 但是对于规模较大的项目,最好按业务模块划分,比如:

威尼斯人线上娱乐 2

Paste_Image.png

modules 下最好再有一个 common 目录来存放公共的东西。

  • 逻辑代码的拆分

作为一个 MVVM 框架,Angular 应用本身就应该按照
模型,视图模型(控制器),视图来划分。

这里逻辑代码的拆分,主要是指尽量让 controller
这一层很薄。提取共用的逻辑到 service 中
(比如后台数据的请求,数据的共享和缓存,基于事件的模块间通信等),提取共用的界面操作到
directive
中(比如将日期选择、分页等封装成组件等),提取共用的格式化操作到 filter
中等等。

在复杂的应用中,也可以为实体建立对应的构造函数,比如硬盘(Disk)模块,可能有列表、新建、详情这样几个视图,并分别对应的有
controller,那么可以建一个 Disk
构造函数,里面完成数据的增删改查和验证操作,有跟 Disk 相关的
controller,就注入 Disk
构造器并生成一个实例,这个实例就具备了增删改查和验证方法。这样既层次分明,又实现了复用(让
controller 层更薄了)。

参考
AngularJS在苏宁云中心的深入实践

app.controller('HomeController',['$scope',function($scope){ }])

9.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:官方说了,从1.2开始,把ngRoute独立出来了,需要下载angular-route.js并把ngRoute加进去。

var app = angular.module('ngRouteApp', ['ngRoute']);
app.config(function($routeProvider) {
      $routeProvider
        .when('/main', {
          templateUrl: "main.html",
          controller: 'MainCtrl'
        })
        .otherwise({
          redirectTo: '/tabs'
        });

ui.router示例angular 系列八
ui-router详细介绍及ngRoute工具区别

angular.module(‘YIJIEBUYI').config(['$stateProvider',
function($stateProvider) {
    $stateProvider.state('blog',{
        url:'/blog',
        views:{
            'container':{templateUrl:'templates/blog/layout.html'}
        }
    }).state('blog.index',{
        url:'/index',
        views:{
            'container':{templaterUrl:'templates/blog/index.html'}
        }
    })
});

3)显式注入

10. 如果通过angular的directive规划一套全组件化体系,可能遇到哪些挑战?

没有自己用 directive 做过一全套组件,讲不出。

能想到的一点是,组件如何与外界进行数据的交互,以及如何通过简单的配置就能使用吧。

app.controller(‘HomeController',HomeController);

HomeController.$inject=[‘$scope'];

function HomeController($scope){

}

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

注意:由于第一种注入方式,是通过对函数做toString操作,然后使用正则匹配出参数名称,来实现注入,所以这种方式不能对代码进行压缩混淆处理。

12.angular 的缺点有哪些?

  • 强约束

导致学习成本较高,对前端不友好。
但遵守 AngularJS 的约定时,生产力会很高,对 Java 程序员友好。

  • 不利于 SEO

因为所有内容都是动态获取并渲染生成的,搜索引擎没法爬取。
一种解决办法是,对于正常用户的访问,服务器响应 AngularJS
应用的内容;对于搜索引擎的访问,则响应专门针对 SEO 的HTML页面。

  • 性能问题

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

的办法:

  • 减少监控项(比如对不会变化的数据采用单向绑定)
  • 主动设置索引(指定 track by,简单类型默认用自身当索引,对象默认-用
    $$hashKey,比如改为 track by item.id)
  • 降低渲染数据量(比如分页,或者每次取一小部分数据,根据需要再取)
  • 数据扁平化(比如对于树状结构,使用扁平化结构,构建一个 map
    和树状数据,对树操作时,由于跟扁平数据同一引用,树状数据变更会同步到原始的扁平数据)

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

 2、directive

13. 如何看待 angular 1.2 中引入的 controller as 语法?

没用过,自己去查解释

指令系统,我认为是angular1.x版本中最强大也是最复杂的部分,angular作者本身做后端出身,所以在整个指令周期也符合语言处理过程:经过编译(compile函数,会返回link函数)、链接处理(link函数)。

14. 详述 angular 的 “依赖注入”

原文解释很详细
原理

AngularJS 是通过构造函数的参数名字来推断依赖服务名称的,通过
toString()来找到这个定义的 function
对应的字符串,然后用正则解析出其中的参数(依赖项),再去依赖映射中取到对应的依赖,实例化之后传入。
JavaScript里的依赖注入
js依赖注入

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

1)指令最基本配置

15. 如何看待angular2

相比 Angular1.x,Angular2的改动很大,几乎算是一个全新的框架。

  • 基于 TypeScript(可以使用 TypeScript
    进行开发),在大型项目团队协作时,强语言类型更有利。
  • 组件化,提升开发和维护的效率。
  • 还有 module 支持动态加载,new router,promise的原生支持等等。
    迎合未来标准,吸纳其他框架的优点,值得期待,不过同时要学习的东西也更多了(ES
    next、TS、Rx等)。
app.directive(‘dire',function(){

return function(){

  return {

    template/templateUrl:'', //模版

    scope:{} //为true或为对象表示隔离作用域

    restrict 'ACEM'//使用方式

    link:function(scope,ele,attrs,controllers){}

    compile:function(ele,attr){return function(){}}//如果此函数存在,link函数会被忽略,因为compile函数会返回link函数

    }

  }

});

2)关于绑定策略

独立作用域父子作用域之间交换数据的方式,主要有三种(或说四种)

  @绑定,指令属性的值可以使用表达式,但是得出来的值一定是字符串;

  &绑定,表示引用绑定,主要绑定父作用域中函数,实现关注点的注入

  =绑定,表示双向数据绑定

  <绑定,表示单向绑定

注意:对于&绑定的使用,主要是为了实现子作用域到父作用域的传递,个人比较喜欢vue中父子交互的方式:props
in,event out。所以这里我一般使用 scope.$emit(‘xxx’,data),来实现子传父。