AngularJS 面试题集锦
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> <divng-if="true"> <inputtype="text"ng-model="name"> </div>
ng-show不存在此问题,因为它不自带一级作用域。
避免这类问题出现的办法是,始终将页面中的元素绑定到对象的属性(data.x)而不是直接绑定到基本变量(x)上。
详见AngularJS中的作用域
ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?
会提示Duplicatesinarepeaterarenotallowed.加trackby$index可解决。当然,也可以traceby任何一个普通的值,只要能唯一性标识数组中的每一项即可(建立dom和数据之间的关联)。
ng-click中写的表达式,能使用JS原生对象上的方法吗?
不止是ng-click中的表达式,只要是在页面中,都不能直接调用原生的JS方法,因为这些并不存在于与页面对应的Controller的$scope中。
举个栗子:
<p>{{parseInt(55.66)}}<p>
会发现,什么也没有显示。
但如果在$scope中添加了这个函数:
$scope.parseInt=function(x){ returnparseInt(x); }
这样自然是没什么问题了。
对于这种需求,使用一个filter或许是不错的选择:
<p>{{13.14|parseIntFilter}}</p> app.filter('parseIntFilter',function(){ returnfunction(item){ returnparseInt(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-ddhh:mm:ss');
自定义filter
//形式 app.filter('过滤器名称',function(){ returnfunction(需要过滤的对象,过滤器参数1,过滤器参数2,...){ //...做一些事情 return处理后的对象; } }); //栗子 app.filter('timesFilter',function(){ returnfunction(item,times){ varresult=''; for(vari=0;i<times;i++){ result+=item; } returnresult; } })
factory、service和provider是什么关系?
factory
把service的方法和数据放在一个对象里,并返回这个对象
app.factory('FooService',function(){ return{ target:'factory', sayHello:function(){ return'hello'+this.target; } } });
service
通过构造函数方式创建service,返回一个实例化对象
app.service('FooService',function(){ varself=this; this.target='service'; this.sayHello=function(){ return'hello'+self.target; } });
provider
创建一个可通过config配置的service,$get中返回的,就是用factory创建service的内容
app.provider('FooService',function(){ this.configData='initdata'; this.setConfigData=function(data){ if(data){ this.configData=data; } } this.$get=function(){ varself=this; return{ target:'provider', sayHello:function(){ returnself.configData+'hello'+this.target; } } } }); //此处注入的是FooService的provider app.config(function(FooServiceProvider){ FooServiceProvider.setConfigData('configdata'); });
从底层实现上来看,service调用了factory,返回其实例;factory调用了provider,返回其$get中定义的内容。factory和service功能类似,只不过factory是普通function,可以返回任何东西(return的都可以被访问,所以那些私有变量怎么写,你懂的);service是构造器,可以不返回(绑定到this的都可以被访问);provider是加强版factory,返回一个可配置的factory。
详见AngularJS之FactoryvsServicevsProvider
angular的数据绑定采用什么机制?详述原理
脏检查机制。
双向数据绑定是AngularJS的核心机制之一。当view中有任何数据变化时,会更新到model,当model中数据有变化时,view也会同步更新,显然,这需要一个监控。
原理就是,Angular在scope模型上设置了一个监听队列,用来监听数据变化并更新view。每次绑定一个东西到view上时AngularJS就会往$watch队列里插入一条$watch,用来检测它监视的model里是否有变化的东西。当浏览器接收到可以被angularcontext处理的事件时,$digest循环就会触发,遍历所有的$watch,最后更新dom。
举个栗子
<buttonng-click="val=val+1">increase1</button>
click时会产生一次更新的操作(至少触发两次$digest循环)
按下按钮
浏览器接收到一个事件,进入到angularcontext
$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应用应当如何良好地分层?
目录结构的划分
对于小型项目,可以按照文件类型组织,比如:
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层更薄了)。
参考AngularJS在苏宁云中心的深入实践
angular应用常用哪些路由库,各自的区别是什么?
Angular1.x中常用ngRoute和ui.router,还有一种为Angular2设计的newrouter(面向组件)。后面那个没在实际项目中用过,就不讲了。
无论是ngRoute还是ui.router,作为框架额外的附加功能,都必须以模块依赖的形式被引入。
区别
ngRoute模块是Angular自带的路由模块,而ui.router模块是基于ngRoute模块开发的第三方模块。
ui.router是基于state(状态)的,ngRoute是基于url的,ui.router模块具有更强大的功能,主要体现在视图的嵌套方面。
使用ui.router能够定义有明确父子关系的路由,并通过ui-view指令将子路由模版插入到父路由模板的<divui-view></div>中去,从而实现视图嵌套。而在ngRoute中不能这样定义,如果同时在父子视图中使用了<divng-view></div>会陷入死循环。
示例
ngRoute
varapp=angular.module('ngRouteApp',['ngRoute']); app.config(function($routeProvider){ $routeProvider .when('/main',{ templateUrl:"main.html", controller:'MainCtrl' }) .otherwise({redirectTo:'/tabs'});
ui.router
varapp=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应用的性能的办法:
减少监控项(比如对不会变化的数据采用单向绑定)
主动设置索引(指定trackby,简单类型默认用自身当索引,对象默认使用$$hashKey,比如改为trackbyitem.id)
降低渲染数据量(比如分页,或者每次取一小部分数据,根据需要再取)
数据扁平化(比如对于树状结构,使用扁平化结构,构建一个map和树状数据,对树操作时,由于跟扁平数据同一引用,树状数据变更会同步到原始的扁平数据)
另外,对于Angular1.x,存在脏检查和模块机制的问题。
移动端
可尝试Ionic,但并不完善。
参考如何看2015年1月Peter-PaulKoch对Angular的看法?
如何看待angular1.2中引入的controlleras语法?
最根本的好处
在angular1.2以前,在view上的任何绑定都是直接绑定在$scope上的
functionmyCtrl($scope){ $scope.a='aaa'; $scope.foo=function(){ ... } }
使用controllerAs,不需要再注入$scope,controller变成了一个很简单的javascript对象(POJO),一个更纯粹的ViewModel。
functionmyCtrl(){ //使用vm捕获this可避免内部的函数在使用this时导致上下文改变 varvm=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,所以坑不再存在)。
<divng-controller="TestCtrlasvm"> <p>{{name}}</p> <divng-if="vm.name"> <inputtype="text"ng-model="vm.name"> </div> </div>
问题
使用controllerAs会遇到的一个问题是,因为没有注入$scope,导致$emit、$broadcast、$on、$watch等$scope下的方法无法使用。这些跟事件相关的操作可以封装起来统一处理,或者在单个controller中引入$scope,特殊对待。
参考angularcontrollerassyntaxvsscope
详述angular的“依赖注入”
栗子
依赖注入是一种软件设计模式,目的是处理代码之间的依赖关系,减少组件间的耦合。
举个栗子,如果没有使用AngularJS,想从后台查询数据并在前端显示,可能需要这样做:
varanimalBox=document.querySelector('.animal-box'); varhttpRequest={ get:function(url,callback){ console.log(url+'requested'); varanimals=['cat','dog','rabbit']; callback(animals); } } varrender=function(el,http){ http.get('/api/animals',function(animals){ el.innerHTML=animals; }) } render(httpRequest,animalBox);
但是,如果在调用render的时候不传参数,像下面这样,会报错,因为找不到el和http(定义的时候依赖了,运行的时候不会自动查找依赖项)
render();
//TypeError:Cannotreadproperty'get'ofundefined
而使用AngularJS,可以直接这样
functionmyCtrl=($scope,$http){ $http.get('/api/animals').success(function(data){ $scope.animals=data; }) }
也就是说,在AngularApp运行的时候,调用myCtrl,自动做了$scope和$http两个依赖性的注入。
原理
AngularJS是通过构造函数的参数名字来推断依赖服务名称的,通过toString()来找到这个定义的function对应的字符串,然后用正则解析出其中的参数(依赖项),再去依赖映射中取到对应的依赖,实例化之后传入。
简化一下,大概是这样:
varinject={ //存储依赖映射关系 storage:{}, //注册依赖 register:function(name,resource){ this.storage[name]=resource; }, //解析出依赖并调用 resolve:function(target){ varself=this; varFN_ARGS=/^function\s*[^\(]*\(\s*([^\)]*)\)/m; varSTRIP_COMMENTS=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; fnText=target.toString().replace(STRIP_COMMENTS,''); argDecl=fnText.match(FN_ARGS)[1].split(/,?/g); varargs=[]; argDecl.forEach(function(arg){ if(self.storage[arg]){ args.push(self.storage[arg]); } }) returnfunction(){ target.apply({},args); } } }
使用这个injector,前面那个不用AngularJS的栗子这样改造一下就可以调用了
inject.register('el',animalBox); inject.register('ajax',httpRequest); render=inject.resolve(render); render();
问题
因为AngularJS的injector是假设函数的参数名就是依赖的名字,然后去查找依赖项,那如果按前面栗子中那样注入依赖,代码压缩后(参数被重命名了),就无法查找到依赖项了。
//压缩前 functionmyCtrl=($scope,$http){ ... } //压缩后 functionmyCtrl=(a,b){ ... }
所以,通常会使用下面两种方式注入依赖(对依赖添加的顺序有要求)。
数组注释法
myApp.controller('myCtrl',['$scope','$http',function($scope,$http){ ... }])
显式$inject
myApp.controller('myCtrl',myCtrl); functionmyCtrl=($scope,$http){ ... } myCtrl.$inject=['$scope','$http'];
补充
对于一个DI容器,必须具备三个要素:依赖项的注册,依赖关系的声明和对象的获取。
在AngularJS中,module和$provide都可以提供依赖项的注册;内置的injector可以获取对象(自动完成依赖注入);依赖关系的声明,就是前面问题中提到的那样。
下面是个栗子
//对于module,传递参数不止一个,代表新建模块,空数组代表不依赖其他模块 //只有一个参数(模块名),代表获取模块 //定义myApp,添加myApp.services为其依赖项 angular.module('myApp',['myApp.services']); //定义一个servicesmodule,将services都注册在这个module下面 angular.module('myApp.services',[]) //$provider有factory,service,provider,value,constant //定义一个HttpService angular.module('myApp.services').service('HttpService',['$http',function($http){ ... }])
参考
[AngularJS]自己实现一个简单的依赖注入
理解angular中的module和injector,即依赖注入
AngularJS中的依赖注入实际应用场景
如何看待angular2
相比Angular1.x,Angular2的改动很大,几乎算是一个全新的框架。
基于TypeScript(可以使用TypeScript进行开发),在大型项目团队协作时,强语言类型更有利。
组件化,提升开发和维护的效率。
还有module支持动态加载,newrouter,promise的原生支持等等。
迎合未来标准,吸纳其他框架的优点,值得期待,不过同时要学习的东西也更多了(ESnext、TS、Rx等)。
以上就是对AngularJS面试题的资料整理,后续继续补充相关资料谢谢大家对本站的支持!