1.ng-if和ng-show/hide的区别?
angularJS中的ng-show、ng-hide、ng-if指令都可以用来控制dom元素的显示或隐藏
ng-show、ng-hide的元素的显示或隐藏是通过改变CSS的display属性值来实现的。
ng-if指令可以根据表达式的值在DOM中生成或移除一个元素
ng-if同no-show和ng-hide指令最本质的区别是,它不是通过CSS显示或隐藏DOM节点,而是删除或者新增结点。ng-if会新建作用域,而ng-show和ng-hide则不会。
2.ng-repeat迭代数组的时候,如果数组中有相同的值,会有什么问题,如何解决?
会报错 解决方法第二种更常用
// 业务上自己生成唯一的id
item in items track by item.id
//或者直接拿循环的索引变量$index来用
item in items track by $index
3.npm是什么?写出安装express的命令?
npm(node package manager)node的包管理工具,npm提供了一个重用其他开发者代码的方式,npm也能够分享开发者自己的代码,同时也提供了一个便捷的方式来管理不同版本的代码,这些被重用的代码被称为包(packages),有时候也被称为模块(modules)。一个包通常包含了一个或者多个文件,同时也包含了一个定义了这个包元信息的文件"package.json"。
npm install -g express -registry=https://registry.npm.taobao.org
4.如何控制网页在网络传输过程中的数据量?
减少数据量吧. 最显著的方法是启用GZIP 压缩
保持良好的编程习惯,避免重复的CSS,JavaScript代码,多余的HTML标签和属性
5.前端优化
减少http请求次数,少用全局变量, 图片,js预载,避免重定向,使Ajax可缓存,CSS Sprites,使用浏览器缓存,脚本放最底下,组件压缩等
6.一个字符串中最多的次数?
var str = "zhaochucichuzuiduodezifu";
var json = {};
for (var i = 0; i < str.length; i++) {
var char = str.charAt(i);
//char就是对象json的一个属性,json[char]是属性值,json[char]控制出现的次数
if (json[char]) {
//次数加1
json[char]++;
} else {
//若第一次出现,次数记为1
json[char] = 1;
}
}
console.log(json); //输出的是完整的对象,记录着每一个字符及其出现的次数
//遍历对象,找到出现次数最多的字符和次数
var max = 0;
var maxChar = null;
for (var key in json) {
if (max < json[key]) {
//max始终储存次数最大的那个
max = json[key];
//那么对应的字符就是当前的key
maxChar = key;
}
}
console.log("最多的字符是" + maxChar);
console.log("出现的次数是" + max);
7.浏览器在url中后面的“=”(等号两边有数字或者变量),此时浏览器做了什么?
浏览器向DNS服务器查找输入URL对应的IP地址。
DNS服务器返回网站的IP地址。
浏览器根据IP地址与目标web服务器在80端口上建立TCP连接
浏览器获取请求页面的html代码。
浏览器在显示窗口内渲染HTML。
窗口关闭时,浏览器终止与服务器的连接。
8.es6和es7和js的关系?
ES全称——ECMAScript,在我的理解下前面的ECMA取自国际标准化组织ECMA,后面的JavaScript,简而言之,它是把JavaScript进行了规范化的处理。
9.angular的路由跳转,是怎么做到的,框架的核心是什么?
AngularJs四大核心理念:MVC,模块化,指令系统,双向数据绑定
分别用程序解释AngularJs四大核心理念
MVC,数据模型层,视图层,控制层
angularJS和原生JS不互通,从而避免全局污染。
angularJS所有的东西都在$scope里,需要的数据都在$scope上找。
angularJS一切皆数据
ng-model:数据从哪里来
ng-bind:数据到哪里去
ng-app:那块归AngularJS管
10.依赖注入的理解?
思考一下,如果对象需要获得其对依赖的控制权,有哪几种方式?
1.在对象内部自行创建依赖的实例
2.将依赖定义为全局的,然后通过全局变量去引用
3.在需要的地方通过参数去传递
依赖注入就是通过第三种方式去实现的,通过依赖注入可以出去对依赖关系的硬编码。
我们先来看看一个实例,angular中如何使用依赖注入。
angular.module('test',[]).controller('TestController', function($scope, $location){
})
我们给模块注册一个控制器,控制器接受两个参数$scope以及$location,这两个参数是angular内置的服务,那么控制器被调用的时候这些服务是如何由谁注入进去的呢?
在angular通过$injector服务来管理依赖关系的查询和实例化。
推断式注入声明
在上面的例子中,没有任何声明,angularjs认为参数的名称就是依赖的名称,angular根据参数的名称在已注册的服务中进行查找,然后通过$injector将这些参数注入进实例对象
injector.invoke(function($scope, $location){})
因为此处是根据参数的名称进行注入的,因此参数的顺序没有关系。
但是在生产环境中,为了缩短网页的加载的时间,我们通常会将js文件进行压缩,参数的名字会被别名替代,这个时候根据参数名称就行注入就行不通了。
显示注入声明
通过显示的方法来明确定义函数的依赖关系,即使源代码被压缩了,也能够正常运行。通过$inject属性来显示的进行注入。函数对象的$inject属性是一个数组,其元素是字符串,其值为需要注入的服务名称。
angular.module('test',[]).controller('TestController', TestController);
function TestController($scope, $location){
}
TestController.$injector = ['$scope','$location']
行内注入声明
行内的注入声明其实和显示注入声明效果一样,只是在函数定义的时候从行内将参数传入,可以避免在使用过程中使用临时变量。
angular.module('test',[]).controller('TestController', ['$scope', '$location',function($scope, $location){
}])
Angular算是将后端开发工程化引入前端的先驱之一,而Dependency injection依赖注入(后面简称为DI)又是Angular内部运作的核心功能,所以要深入理解Angular有必要先理解这一核心概念。
维基百科对依赖注入的解释:
在软件工程中,依赖注入是实现控制反转的一种软件设计模式,一个依赖是一个被其他对象(client)调用的对象(服务),注入则是将被依赖的对象(service)实例传递给依赖对象(client)的行为。将被依赖的对象传给依赖者,而不需要依赖者自己去创建或查找所需对象是DI的基本原则。 依赖注入允许程序设计遵从依赖倒置原则(简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合) 调用者(client)只需知道服务的接口,具体服务的查找和创建由注入者(injector)负责处理并提供给client,这样就分离了服务和调用者的依赖,符合低耦合的程序设计原则。
依赖注入中的角色
从维基百科解释可知, DI中包含三个角色,调用者(client), 服务(service)和注入者 (injector),下面开始介绍本文的主题 Angular的依赖注入。
Angular依赖注入分析
先看看下面这段 hello,world代码 (注意:设置了严格模式或压缩混淆代码后 下面的代码不能正常工作,后面有解释)
angular.module('myApp', [])
.controller('Ctl', function ($scope, $log) {
$scope.name = 'leonwgc';
$log.log('hello,world');
});
上面这段代码就用到了angular的依赖注入,代码首先创建了一个myApp模块,然后在此模块中创建了Ctl控制器,创建控制器函数的第二个参数则是控制器的构造函数,构造函数声明了对$scope和$log服务的依赖。 当构造函数执行时, 即可获得$scope和$log服务实例,进行操作。 从我们前面对DI的了解,
$scope和$log是由注入器injector 提供,知道了injector的存在,我们直接从
angular的源码中将其找出,如下:
function createInternalInjector(cache, factory) {
// 中间一段略去...
// 调用client
function invoke(fn, self, locals, serviceName) {
if (typeof locals === 'string') {
serviceName = locals;
locals = null;
}
var args = [],
// 查询依赖
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
// 为省手机流量中间一段略去...
// 遍历$inject数组调用getService获取服务....
//开始执行client , args则是依赖的全部服务,injector都为我们创建好了
return fn.apply(self, args);
}
// 中间一段略去...
// 这里返回公开的injector对象
return {
// 执行DI方法,比如上面的控制器函数
// invoke方法首先就是调用annotate取得依赖
// 然后调用get取得服务
// 如果缓存中没有服务,get内部调用instantiate创建服务并缓存
// 最后利用function.apply传入依赖并执行
invoke: invoke,
// 实例化(创建)服务
instantiate: instantiate,
// 获取服务(如果缓存中有,直接从缓存拿,没有则调用instantiate创建并放入缓存,下次直接从缓存拿)
get: getService,
// 获得依赖服务
annotate: createInjector.$$annotate,
// 检查缓存中是否包含服务
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix)
|| cache.hasOwnProperty(name);
}
};
}
源码中查询依赖的源码如下:
function annotate(fn, strictDi, name) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn === 'function') {
// 如果我们直接给函数添加了$inject依赖
// 则直接返回依赖,后面不做处理
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
if (strictDi) {
if (!isString(name) || !name) {
name = fn.name || anonFn(fn);
}
throw $injectorMinErr('strictdi',
'{0} is not using explicit annotation...', name);
}
// 针对直接在构造函数中使用服务的情况
// 使用function.toString() 然后正则匹配出依赖的对象
// 所以上面例子如果混淆了代码就呵呵了
// 最后存入$inject数组
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
}
//给构造函数添加$inject属性
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
// 如果是数组格式,则依赖对象是数组的第一个到倒数第二个对象
// 要调用的函数则是数组的最后一个元素
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
// 返回依赖数组
return $inject;
}
看了上面的源码片段和解释,想必大家对angular的依赖注入有了整体的认识。
下面是另外两种推荐的声明依赖的方式:
1. 数组注释 (推荐), js压缩混淆不会有影响。
angular.module('myApp', [])
.controller('Ctl', ['$scope', '$log', function ($scope, $log) {
$scope.name = 'leonwgc';
$log.log('hello,world');
}]);
2.$inject 属性 ,js压缩混淆不会有影响
angular.module('myApp', [])
.controller('Ctl', Ctrl);
function Ctrl($scope, $log) {
$scope.name = 'leonwgc';
$log.log('hello,world');
}
// 给构造函数添加$inject属性,
// $inject是一个数组,元素是依赖的服务名.
Ctrl.$inject = ["$scope", "$log"];
今天重温了一遍angularjs的依赖注入,在这里呢,我做了一个总结,希望帮助大家理解和使用。有不对的地方请大家指出来。
首先 angular js的依赖注入我们可以理解为是jq中封装的小组件(自己理解的)。只不过在angular中这些小组件是需要注入到控制器中的,及哪个控制器需要调用这个小组件,那么就在对应的控制器中注入这个小组件。
angular 中有5个核心组件 分别是 value factory service constant provider
我们先创建一个模块
var myApp = angualr.module(“myApp”,[]);
首先我们来说明:
第一个是 value
value 官方文档说的是
一个简单的javascript对象,用于向控制器传递值(在配置阶段);
这个好理解,就是给一个值绑定为一个变量,他与constant的区别在于他可以修改,但不能注入到config中。只不过这个变量是在配置阶段绑定
语法为:
myApp.value("valueName",valueValue);
其中valueName 为“变量”名,是需要注入到控制器的,而valueValue是指,可以是数值,字符串和函数
第二个是factory 这是一个我们经常会使用得到的核心组件,通常我们称其为工厂模式,其理解为时一个函数用来返回值或者函数
通常情况下 在实践项目中 factory 用来进行购物车的相关编辑;
语法如下:
myAPP.factory("factoryName",function(){
return {
//返回函数和值
xxx:function(params){...return..},
yy:function(params){.....}
}
})
当在控制器中调用的时候直接使用factoryName.xxx(params)来调用,点语法。
第三个是service 它是一个可注入的构造器,在AngularJS
中它是单例的,用它在Controller
中通信或者共享数据都很合适,在官方文档中将他归类到了 service中 单独使用的话是作为自定义服务来的。
语法为:
myAPP.service("serviceName", function () {
this.XXX = function(){ //可以是值也可以是函数;
.....
} ;
});
在service
里面可以不用返回东西,因为AngularJS
会调用new
关键字来创建对象。但是返回一个自定义对象好像也不会出错。
第四个是 constant
定义常量用的,这货定义的值当然就不能被改变,它可以被注入到任何地方
语法为:
myApp.constant("constantName","constantValue")
constantName 为常量名
constantValue 为常量值
通常我们用来定义不会改变的url 或接口名称
第五个是 provider
provider
是他们的老大,上面的几乎(除了constant
)都是provider
的封装,provider
必须有一个$get
方法,当然也可以说provider
是一个可配置的factory.
使用provider的时候,我们不能单独的配置,需要在config中来创建provider ,在angular中在config中只能注入提供服务的供应商,但是angular中提供了一个$provide。因此在使用provider的时候在config中注入的应该是$provide,通过$provide来创建provider。
语法为:
myApp.config(function($provide){
$provide.provider("providerName",function(){
this.$get = function(){
return { //返回函数和值
xxx:function(params){...return}
yyy:function(params){ ...}
}
}
})})
可以看出来this.$get函数其实和factory是一样的,这样就可以和上面说的相符合,provider是factory等的老大,而factory等是provider封装的。
由此可以看出来有的时候factory service provider 达到的效果是一样的那么他们的区别是什么呢,我们在什么情况下选择使用他们呢?
当使用factory
来创建服务的时候,相当于新创建了一个对象,然后在这个对象上新添属性,最后返回这个对象。当把这个服务注入控制器的时候,控制器就可以访问在那个对象上的属性了。
当使用service
创建服务的时候,相当于使用new
关键词进行了实例化。因此,你只需要在this
上添加属性和方法,然后,服务就会自动的返回this
。当把这个服务注入控制器的时候,控制器就可以访问在那个对象上的属性了。
provider
是唯一一种可以创建用来注入到config()
函数的服务的方式。想在你的服务启动之前,进行一些模块化的配置的话,就使用provider
。
如果你需要在你的应用在别处运行之前对你的服务对象进行一部分的配置,那么,这个就显得很重要了。
例子:
myApp.provider("MyProvider",function(){
this.number= "";
this.$get=function(){
var n =this.number;
return {
numbers:function(a){
return a*a;
},
num:n
}
}
});
你可以认为provider
有三个部分,第一部分是私有变量和私有函数,这些变量和函数会在以后被修改。第二部分是在myApp.config
函数里可以访问的变量和函数,所以,他们可以在在其他地方使用之前被修改。注意,这些变量和函数一定要添加到this
上面才行。在我们的例子中,myApp.config()
函数能够修改的只有number。第三部分是在控制器里可以访问的变量和函数。
当使用 provider
创建服务的时候,唯一可以让控制器访问的属性和方法是在$get()
函数里返回的属性和方法。上面的代码将$get
添加到了this
上面,最终这个函数会被返回。
现在,与之前的service
和factory
类似,只要我们把MyProvider
注入到控制器里面,对应的方法就可以使用了。下面是myCtrl
。
myApp.controller(“myCtrl”,function($scope, MyProvider){
$scope.result =MyProvider. numbers (5);
$scope.n= MyProvider. num;
});
正如之前提到的,使用provider
来创建服务的目的就是为了能够通过myApp.config()
函数修改一些变量来传递到最终的项目中。而上面也提到了myApp.config
中能修改的变量必须是在this
上的,因此我们能修改number的值。值得注意的是:config中只能够传递提供服务的供应商,而此时使用者中方法的话MyProvider是一个服务,因此我们要在后面使用驼峰命名法为期加上Provider,其变成了MyProviderProvider。myApp.config(function(MyProviderProvider){
MyProviderProvider. number = ”你好”
});
现在,你就能看到,在provider
里,number是空字符串,但是,当我们在DOM里显示的时候,他就会是我们上面所设置的字符串了。
输出的值 $scope.result为25 。$scope.n输出的值为 你好
注:
创建provider其实还有第二种方法,如上面的例子直接在config
中创建,但是需要注入$provide。然后使用$provide.provider来创建
总结:在实际的项目中,我们常常使用的最多的就是provider封装的这几个核心的组件。
11.脏检查的理解?
首先纠正误区,Angular并不是周期性触发藏检查。
只有当UI事件,ajax请求或者 timeout 延迟事件,才会触发脏检查。
为什么叫脏检查? 对脏数据的检查就是脏检查,比较UI和后台的数据是否一致!
- 简单理解,一次脏检查就是调用一次 $apply() 或者 $digest(),将数据中最新的值呈现在界面上。
- 而每次 UI 事件变更,ajax 还有 timeout 都会触发 $apply()。
- Angular 是一个 MVVM 前端框架,提供了双向数据绑定。所谓双向数据绑定(Two-way data binding)就是页面元素变化会触发 View-model 中对应数据改变, 反过来 View-model 中数据变化也会引发所绑定的 UI 元素数据更新。操作数据就等同于操作 UI
- 脏检查如何触发?
Angular 每一个绑定到UI的数据,就会有一个 $watch 对象。
所有的watch存储在$$watchList中,一次脏检查就是调用一次 $apply() 或者 $digest(),遍历检查所有watch,将数据中最新的值呈现在界面上。
实际上$apply 其实就是$digest的一个简单封装。
12。angular的双向绑定
Angular实现了双向绑定机制。所谓的双向绑定,无非是从界面的操作能实时反映到数据,数据的变更能实时展现到界面。
Angular主要通过scopes实现数据双向绑定。AngularJS的scopes包括以下四个主要部分:
digest循环以及dirty-checking,包括watch,watch,digest,和$apply。
Scope继承 - 这项机制使得我们可以创建scope继承来分享数据和事件。
对集合 – 数组和对象 – 的有效dirty-checking。
事件系统 - on,on,emit,以及$broadcast。
gularJS的scopes就是一般的JavaScript对象,在它上面你可以绑定你喜欢的属性和其他对象,然而,它们同时也被添加了一些功能用于观察数据结构上的变化。这些观察的功能都由dirty-checking来实现并且都在一个digest循环中被执行
我们主要讲解第一条Angular数据绑定是怎么实现的。
1.digest循环以及dirty-checking,包括watch,watch,digest,和$apply
①浏览器事件循环和Angular.js扩展
我们的浏览器一直在等待事件,比如用户交互。假如你点击一个按钮或者在输入框里输入东西,事件的回调函数就会在javascript解释器里执行,然后你就可以做任何DOM操作,等回调函数执行完毕时,浏览器就会相应地对DOM做出变化。 Angular拓展了这个事件循环,生成一个有时成为angular context的执行环境(这是个重要的概念)。
②watch队列(watch队列(watch list)
每次你绑定一些东西到你的UI上时你就会往$watch队列里插入一条$watch
。想象一下$watch
就是那个可以检测它监视的model里时候有变化的东西。
当我们的模版加载完毕时,也就是在linking阶段(Angular分为compile阶段和linking阶段---译者注),Angular解释器会寻找每个directive,然后生成每个需要的$watch
。
③$digest循环
还记得我前面提到的扩展的事件循环吗?当浏览器接收到可以被angular context处理的事件时,digest循环就会触发。这个循环是由两个更小的循环组合起来的。一个处理evalAsync队列,另一个处理digest循环就会触发。这个循环是由两个更小的循环组合起来的。一个处理evalAsync队列,另一个处理watch队列。 这个是处理什么的呢?digest将会遍历我们的digest将会遍历我们的watch,然后询问它是否有属性和值的变化,直$watch队列都检查过。
这就是所谓的dirty-checking
。既然所有的$watch
都检查完了,那就要问了:有没有$watch
更新过?如果有至少一个更新过,这个循环就会再次触发,直到所有的$watch
都没有变化。这样就能够保证每个model都已经不会再变化。记住如果循环超过10次的话,它将会抛出一个异常,防止无限循环。 当$digest
循环结束时,DOM相应地变化。
例如: controllers.js
app.controller('MainCtrl', function() {
$scope.name = "Foo";
$scope.changeFoo = function() {
$scope.name = "Bar";
}
});
index.html
{{ name }}
<button ng-click="changeFoo()">Change the name</button>
这里我们有一个$watch
因为ng-click不生成$watch
(函数是不会变的)。
-
- 我们按下按钮
- 浏览器接收到一个事件,进入
angular context
(后面会解释为什么)。 $digest
循环开始执行,查询每个$watch
是否变化。- 由于监视
$scope.name
的$watch
报告了变化,它会强制再执行一次$digest
循环。 - 新的
$digest
循环没有检测到变化。 - 浏览器拿回控制权,更新与
$scope.name
新值相应部分的DOM。
这里很重要的(也是许多人的很蛋疼的地方)是每一个进入angular context
的事件都会执行一个$digest
循环,也就是说每次我们输入一个字母循环都会检查整个页面的所有$watch
。
④通过$apply来进入angular context
谁决定什么事件进入angular context,而哪些又不进入呢?$apply!
如果当事件触发时,你调用apply,它会进入angularcontext,如果没有调用就不会进入。现在你可能会问:刚才的例子里我也没有调用apply,它会进入angularcontext,如果没有调用就不会进入。现在你可能会问:刚才的例子里我也没有调用apply,为什么?Angular为你做了!因此你点击带有ng-click的元素时,时间就会被封装到一个apply调用。如果你有一个ng−model="foo"的输入框,然后你敲一个f,事件就会这样调用apply调用。如果你有一个ng−model="foo"的输入框,然后你敲一个f,事件就会这样调用apply("foo = 'f';")。
Angular什么时候不会自动为我们apply呢?这是Angular新手共同的痛处。为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用apply呢?这是Angular新手共同的痛处。为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用apply,事件没有进入angular context,$digest循环永远没有执行。
2.具体实现
AngularJS的scopes就是一般的JavaScript对象,在它上面你可以绑定你喜欢的属性和其他对象,然而,它们同时也被添加了一些功能用于观察数据结构上的变化。这些观察的功能都由dirty-checking来实现并且都在一个digest循环中被执行。
①Scope 对象
创建一个test/scope_spec.js文件,并将下面的测试代码添加到其中:
test/scope_spec.js
-------
/* jshint globalstrict: true */
/* global Scope: false */
'use strict';
describe("Scope", function() {
it("can be constructed and used as an object", function() {
var scope = new Scope();
scope.aProperty = 1;
expect(scope.aProperty).toBe(1);
});
});
这个测试用来创建一个Scope,并在它上面赋一个任意值。我们可以轻松的让这个测试通过:创建src/scope.js文件然后在其中添加以下内容:
src/scope.js
------
/* jshint globalstrict: true */
'use strict'; function Scope() {
}
在这个测试中,我们将一个属性(aProperty)赋值给了这个scope。这正是Scope上的属性运行的方式。它们就是正常的JavaScript属性,并没有什么特别之处。这里你完全不需要去调用一个特别的setter,也不需要对你赋值的类型进行什么限制。真正的魔法在于两个特别的函数:watch和watch和digest。我们现在就来看看这两个函数。
②监视对象属性:watch和watch和digest
watch和watch和digest是同一个硬币的两面。它们二者同时形成了$digest循环的核心:对数据的变化做出反应。
为了实现这一块功能,我们首先来定义一个测试文件并断言你可以使用watch来注册一个监视器,并且当有人调用了watch来注册一个监视器,并且当有人调用了digest的时候监视器的监听函数会被调用。
在scope_spec.js文件中添加一个嵌套的describe块。并创建一个beforeEach函数来初始化这个scope,以便我们可以在进行每个测试时重复它:
test/scope_spec.js
------
describe("Scope", function() {
it("can be constructed and used as an object", function() { var scope = new Scope();
scope.aProperty = 1;
expect(scope.aProperty).toBe(1);
});
describe("digest", function() {
var scope;
beforeEach(function() { scope = new Scope();
});
it("calls the listener function of a watch on first $digest", function() { var watchFn = function() { return 'wat'; };
var listenerFn = jasmine.createSpy();
scope.$watch(watchFn, listenerFn);
scope.$digest();
expect(listenerFn).toHaveBeenCalled();
}); });
});
在上面的这个测试中我们调用了watch来在这个scope上注册一个监视器。我们现在对于监视函数本身并没有什么兴趣,因此我们随便提供了一个函数来返回一个常数值。作为监听函数,我们提供了一个JasmineSpy。接着我们调用了watch来在这个scope上注册一个监视器。我们现在对于监视函数本身并没有什么兴趣,因此我们随便提供了一个函数来返回一个常数值。作为监听函数,我们提供了一个JasmineSpy。接着我们调用了digest并检查这个监听器是否真正被调用。
首先,这个Scope需要有一些地方去存储所有被注册的监视器。我们现在就在Scope构造函数中添加一个数组存储它们:
src/scope.js
-----
function Scope(){
this.$$watchers = [];
}
上面代码中的$$前缀在AngularJS框架中被认为是私有变量,它们不应该在应用的外部被调用。
现在我们可以来定义watch函数了。它接收两个函数作为参数,并且将它们储存在watch函数了。它接收两个函数作为参数,并且将它们储存在$watchers数组中。我们想要每一个Scope对象都拥有这个函数,因此我们将它添加到Scope的原型中:
src/scope.js
-----
Scope.prototype.$watch = function(watchFn, listenerFn) {
var watcher = {
watchFn: watchFn,
listenerFn: listenerFn
};
this.$$watchers.unshift(watcher);
};
最后我们应该有一个digest函数。现在,我们来定义一个digest函数。现在,我们来定义一个digest函数的简化版本,它仅仅只是会迭代所有的注册监视器并调用它们的监听函数:
digest能够持续的迭代所有监视函数,直到被监视的值停止变化。多做几次digest是我们能够获得运用于监视器并依赖于其他监视器的变化。
首先,我们新建名为$$digestOnce,并且调整它以便它能够在所有监视器上运行一遍,然后返回一个布尔值来说明有没有任何变化:
src/scope.js
----
Scope.prototype.$$digestOnce = function(){
var length = this.$$watchers.length;
var watcher, newValue, oldValue, dirty;
while(length--){
watcher = this.$$watchers[length];
newValue = watcher.watchFn(this);
oldValue= watcher.last;
if(newValue !== oldValue){
watcher.last == newValue;
watcher.listenerFn(newValue, oldValue, this);
dirty = true;
}
}
return dirty;
};
接着,我们重定义digest以便它能够运行“外循环”,在变化发生时调用digest以便它能够运行“外循环”,在变化发生时调用$digestOnce:
src/scope.js
-----
Scope.prototype.$digest = function(){
var dirty;
do {
dirty = this.$$digestOnce();
} while (dirty);
};
很多人对Angular的脏检测机制感到不屑,推崇基于setter,getter的观测机制,在我看来,这只是同一个事情的不同实现方式,两者是各有优劣。
1.ng-show/ng-hide 与 ng-if的区别?
ng-show/ng-hide是通过display来进行隐藏和显示的。而ng-if实际上控制dom节点的增删来实现的。因此如果我们是根据不同的条件来进行dom节点的加载的话,那么ng-if的性能好过ng-show.
2.解释下什么是$rootScope以及和$scope的区别?
通俗的说$rootScope 页面所有$scope的父亲。
$rootScope和$scope是如何产生
①:Angular解析ng-app然后在内存中创建$rootScope。
②:Angular继续解析,找到{{}}表达式,并解析成变量。
③:解析带有ng-controller的div,然后指向到某个controller函数。这个时候在这个controller函数变成一个$scope对象实例。
3.表达式 {{yourModel}}是如何工作的?
它依赖于 $interpolation服务,在初始化页面html后,它会找到这些表达式,并且进行标记,于是每遇见一个{{}},则会设置一个$watch。而$interpolation会返回一个带有上下文参数的函数,最后该函数执行,则算是表达式$parse到那个作用域上。
4.Angular中的digest周期是什么?
每个digest周期中,angular总会对比scope上model的值,一般digest周期都是自动触发的,我们也可以使用$apply进行手动触发。
5.Angular Directive 中restrict中分别可以怎样设置?scope中@、==、&有什么区别?
restrict中可以分别设置:
A匹配属性
E匹配标签
C匹配class
M 匹配注释
当然你可以设置多个值比如AEC,进行多个匹配。
在scope中,@,=,&在进行值绑定时分别表示:
@获取一个设置的字符串,它可以自己设置的也可以使用{{yourModel}}进行绑定的;
= 双向绑定,绑定scope上的一些属性;
& 用于执行父级scope上的一些表达式,常见我们设置一些需要执行的函数
6. 列出至少三种实现不同模块之间通信方式?
1、Service
2、events,指定绑定的事件
3、使用 $rootScope
4、controller之间直接使用$parent, $$childHead等
5、directive 指定属性进行数据绑定
7.有哪些措施可以改善Angular 性能
1.关闭debug,$compileProvider;
2.使用一次绑定表达式即{{::yourModel}}
3.减少watcher数量
4.在无限滚动加载中避免使用ng-repeat
5.使用性能测试的小工具去挖掘你的angular性能问题,我们可以使用简单的console.time()也可以借助开发者工具以及Batarang
8.如何进行angular的单元测试
我们可以使用karam+jasmine 进行单元测试,我们通过ngMock引入angular app然后自行添加我们的测试用例。
9. angular的数据绑定采用什么机制?详述原理
AngularJS的双向数据绑定采用脏检查(dirty-checking)机制。ng只有在指定事件触发后,才进入 $digest cycle : - DOM事件,譬如用户输入文本,点击按钮等。( ng-click ) - XHR响应事件 ( $http ) - 浏览器Location变更事件 ( $location ) - Timer事件( $timeout , $interval ) - 执行 $digest() 或 $apply()
10. 依赖注入(DI)
让我们可以不用自己实例化就能创建依赖对象的方法. 简单的来说, 依赖是以注入的方式传递的. 在Web应用中, Angular让我们可以通过DI来创建像Controllers和Directives这样的对象. 我们还可以创建自己的依赖对象, 当我们要实例化它们时, Angular能自动实现注入.
11. compile和link的区别: 看到一个比较6的答案。性能力(性能和能力)
编译的时候,compile转换dom,碰到绑定监听器的地方就先存着,有几个存几个,到最后汇总成一个link函数,一并执行,提升了性能。
如果指令只进行DOM的修改,不进行数据绑定,那么配置在compile函数中,如果指令要进行数据绑定,那么配置在link函数中。
12. $apply()和 $digest()的区别
安全性:$apply()可以接收一个参数作为function(),这个 function 会被包装到一个 try … catch 块中,所以一旦有异常发生,该异常会被 $exceptionHandler service 处理。
- $apply会使ng进入 $digest cycle , 并从$rootScope开始遍历(深度优先)检查数据变更。
- $digest仅会检查该scope和它的子scope,当你确定当前操作仅影响它们时,用$digest可以稍微提升性能。
13.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 绑定给另一个显示区域,内层改变时,外层不会同步改变,因为此时已经是两个变量了。
ng-show不存在此问题,因为它不自带一级作用域。
避免这类问题出现的办法是,始终将页面中的元素绑定到对象的属性(data.x)而不是直接绑定到基本变量(x)上。
14.ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?
会提示 Duplicates in a repeater are not allowed.加 track by $index可解决。当然,也可以 trace by 任何一个普通的值,只要能唯一性标识数组中的每一项即可(建立 dom 和数据之间的关联)。
15.ng-click 中写的表达式,能使用 JS 原生对象上的方法吗?
不止是 ng-click 中的表达式,只要是在页面中,都不能直接调用原生的 JS 方法,因为这些并不存在于与页面对应的 Controller 的 $scope 中。
16.{{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 处理后的对象;
-
}
-
});
17.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。
18.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次后抛出一个异常,防止无限循环)。
19.两个平级界面块 a 和 b,如果 a 中触发一个事件,有哪些方式能让 b 知道?详述原理
这个问题换一种说法就是,如何在平级界面模块间进行通信。有两种方法,一种是共用服务,一种是基于事件。
共用服务
在 Angular 中,通过 factory 可以生成一个单例对象,在需要通信的模块 a 和 b 中注入这个对象即可。
基于事件
这个又分两种方式
第一种是借助父 controller。在子 controller 中向父 controller 触发( $emit)一个事件,然后在父 controller 中监听( $on)事件,再广播( $broadcast)给子 controller ,这样通过事件携带的参数,实现了数据经过父 controller,在同级 controller 之间传播。
第二种是借助 $rootScope。每个 Angular 应用默认有一个根作用域 $rootScope, 根作用域位于最顶层,从它往下挂着各级作用域。所以,如果子控制器直接使用 $rootScope广播和接收事件,那么就可实现同级之间的通信。
20.一个 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 层更薄了)。
21.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'
-
})
22.如果通过angular的directive规划一套全组件化体系,可能遇到哪些挑战?
没有自己用 directive 做过一全套组件,讲不出。
能想到的一点是,组件如何与外界进行数据的交互,以及如何通过简单的配置就能使用吧。
23.分属不同团队进行开发的 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 中并没有很好的解决办法,所以最好在前期进行统一规划,做好约定,严格按照约定开发,每个开发人员只写特定区块代码。
24.angular 的缺点有哪些?
强约束
导致学习成本较高,对前端不友好。
但遵守 AngularJS 的约定时,生产力会很高,对 Java 程序员友好。
不利于 SEO
因为所有内容都是动态获取并渲染生成的,搜索引擎没法爬取。
一种解决办法是,对于正常用户的访问,服务器响应 AngularJS 应用的内容;对于搜索引擎的访问,则响应专门针对 SEO 的HTML页面。
性能问题
作为 MVVM 框架,因为实现了数据的双向绑定,对于大数组、复杂对象会存在性能问题。
可以用来 优化 Angular 应用的性能的办法:
-
减少监控项(比如对不会变化的数据采用单向绑定)
-
主动设置索引(指定 track by,简单类型默认用自身当索引,对象默认使用 $$hashKey,比如改为 track by item.id)
-
降低渲染数据量(比如分页,或者每次取一小部分数据,根据需要再取)
-
数据扁平化(比如对于树状结构,使用扁平化结构,构建一个 map 和树状数据,对树操作时,由于跟扁平数据同一引用,树状数据变更会同步到原始的扁平数据)
另外,对于Angular1.x ,存在 脏检查 和 模块机制 的问题。
移动端
可尝试 Ionic,但并不完善。
25.如何看待 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,特殊对待。
26.详述 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){ ... }])
1. ng-show/ng-hide
与 ng-if
的区别?
我们都知道ng-show/ng-hide实际上是通过display
来进行隐藏和显示的。而ng-if实际上控制dom节点的增删除来实现的。因此如果我们是根据不同的条件来进行dom节点的加载的话,那么ng-if的性能好过ng-show.
2.解释下什么是$rootScrope
以及和$scope
的区别?
通俗的说$rootScrope
页面所有$scope
的父亲
。
我们来看下如何产生$rootScope
和$scope
吧。
step1:Angular解析ng-app
然后在内存中创建$rootScope
。
step2:angular回继续解析,找到{{}}
表达式,并解析成变量。
step3:接着会解析带有ng-controller
的div然后指向到某个controller函数。这个时候在这个controller函数变成一个$scope对象实例。
3. 表达式 {{yourModel}}
是如何工作的?
它依赖于 $interpolation服务,在初始化页面html后,它会找到这些表达式,并且进行标记,于是每遇见一个{{}}
,则会设置一个$watch
。而$interpolation
会返回一个带有上下文参数的函数,最后该函数执行,则算是表达式$parse
到那个作用域上。
4. Angular中的digest周期是什么?
每个digest周期中,angular总会对比scope上model的值,一般digest周期都是自动触发的,我们也可以使用$apply进行手动触发。更深层次的研究,可以移步The Digest Loop and apply。
5. 如何取消 $timeout
, 以及停止一个$watch()
?
停止 $timeout我们可以用cancel:
-
var customTimeout = $timeout(function () {
-
// your code
-
}, 1000);
-
$timeout.cancel(customTimeout);
停掉一个$watch
:
-
// .$watch() 会返回一个停止注册的函数
-
function that we store to a variable
-
var deregisterWatchFn = $rootScope.$watch(‘someGloballyAvailableProperty’, function (newVal) {
-
if (newVal) {
-
// we invoke that deregistration function, to disable the watch
-
deregisterWatchFn();
-
...
-
}
-
});
6. Angular Directive中restrict 中分别可以怎样设置?scope中@,=,&有什么区别?
restrict中可以分别设置:
A
匹配属性E
匹配标签C
匹配classM
匹配注释
当然你可以设置多个值比如AEC
,进行多个匹配。
在scope中,@,=,&在进行值绑定时分别表示
@
获取一个设置的字符串,它可以自己设置的也可以使用{{yourModel}}进行绑定的;=
双向绑定,绑定scope上的一些属性;&
用于执行父级scope上的一些表达式,常见我们设置一些需要执行的函数
-
angular.module('docsIsolationExample', [])
-
.controller('Controller', ['$scope', function($scope) {
-
$scope.alertName = function() {
-
alert('directive scope &');
-
}
-
}])
-
.directive('myCustomer', function() {
-
return {
-
restrict: 'E',
-
scope: {
-
clickHandle: '&'
-
},
-
template: '<button ng-click="testClick()">Click Me</button>',
-
controller: function($scope) {
-
$scope.testClick = function() {
-
$scope.clickHandle();
-
}
-
}
-
};
-
});
-
<div ng-app="docsIsolationExample">
-
<div ng-controller="Controller">
-
<my-customer click-handle="alertName()"></my-customer>
-
</div>
-
</div>
<
进行单向绑定。
7. 列出至少三种实现不同模块之间通信方式?
-
Service
-
events,指定绑定的事件
-
使用 $rootScope
-
controller之间直接使用
$parent
,$$childHead
等 -
directive 指定属性进行数据绑定
8. 有哪些措施可以改善Angular 性能
- 官方提倡的,关闭debug,
$compileProvider
-
myApp.config(function ($compileProvider) {
-
$compileProvider.debugInfoEnabled(false);
-
});
-
使用一次绑定表达式即{{::yourModel}}
-
减少watcher数量
-
在无限滚动加载中避免使用ng-repeat,关于解决方法可以参考这篇文章
-
使用性能测试的小工具去挖掘你的angular性能问题,我们可以使用简单的
console.time()
也可以借助开发者工具以及Batarang
-
console.time("TimerName");
-
//your code
-
console.timeEnd("TimerName");
9. 你认为在Angular中使用jQuery好么?
这是一个开放性的问题,尽管网上会有很多这样的争论,但是普遍还是认为这并不是一个特别好的尝试。其实当我们学习Angular的时候,我们应该做到从0去接受angular的思想,数据绑定,使用angular自带的一些api,合理的路由组织和,写相关指令和服务等等。angular自带了很多api可以完全替代掉jQuery中常用的api,我们可以使用angular.element
,$http
,$timeout
,ng-init
等。
我们不妨再换个角度,如果业务需求,而对于一个新人(比较熟悉jQuery)的话,或许你引入jQuery可以让它在解决问题,比如使用插件上有更多的选择,当然这是通过影响代码组织来提高工作效率,随着对于angular理解的深入,在重构时会逐渐摒弃掉当初引入jquery时的一些代码。
所以我觉得两种框架说完全不能一起用肯定是错的,但是我们还是应该尽力去遵循angular的设计。
10. 如何进行angular的单元测试
我们可以使用karam+jasmine 进行单元测试,我们通过ngMock引入angular app然后自行添加我们的测试用例。 一段简单的测试代码:
-
describe('calculator', function () {
-
beforeEach(module('calculatorApp'));
-
var $controller;
-
beforeEach(inject(function(_$controller_){
-
$controller = _$controller_;
-
}));
-
describe('sum', function () {
-
it('1 + 1 should equal 2', function () {
-
var $scope = {};
-
var controller = $controller('CalculatorController', { $scope: $scope });
-
$scope.x = 1;
-
$scope.y = 2;
-
$scope.sum();
-
expect($scope.z).toBe(3);
-
});
-
});
-
});
首先阐述下你对mvc和mvvm的理解
首先为什么我们会需要MVC?因为随着代码规模越来越大,切分职责是大势所趋,还有为了后期维护方便,修改一块功能不影响其他功能。还有为了复用,因为很多逻辑是一样的。而MVC只是手段,终极目标是模块化和复用。
mvvm的优点:
- 低耦合:View可以独立于Model变化和修改,同一个ViewModel可以被多个View复用;并且可以做到View和Model的变化互不影响
- 可重用性:可以把一些视图的逻辑放在ViewModel,让多个View复用
- 独立开发:开发人员可以专注与业务逻辑和数据的开发(ViewModemvvmdi计人员可以专注于UI(View)的设计
- 可测试性:清晰的View分层,使得针对表现层业务逻辑的测试更容易,更简单
在angular中MVVM模式主要分为四部分:
- View:它专注于界面的显示和渲染,在angular中则是包含一堆声明式Directive的视图模板。
- ViewModel:它是View和Model的粘合体,负责View和Model的交互和协作,它负责给View提供显示的数据,以及提供了View中Command事件操作Model的途径;在angular中
$scop
e对象充当了这个ViewModel的角色; - Model:它是与应用程序的业务逻辑相关的数据的封装载体,它是业务领域的对象,Model并不关心会被如何显示或操作,所以模型也不会包含任何界面显示相关的逻辑。在web页面中,大部分Model都是来自Ajax的服务端返回数据或者是全局的配置对象;而angular中的service则是封装和处理这些与Model相关的业务逻辑的场所,这类的业务服务是可以被多个Controller或者其他service复用的领域服务。
- Controller:这并不是MVVM模式的核心元素,但它负责ViewModel对象的初始化,它将组合一个或者多个service来获取业务领域Model放在ViewModel对象上,使得应用界面在启动加载的时候达到一种可用的状态。
MVVM.PNG
mvc的界面和逻辑关联紧密,数据直接从数据库读取。mvvm的界面与viewmode是松耦合,界面数据从viewmodel中获取。所以angularjs更倾向于mvvm
angular 核心?
AngularJS是为了克服HTML在构建应用上的不足而设计的。 AngularJS有着诸多特性,最为核心的是:
- MVC
- 模块化
- 自动化双向数据绑定
- 语义化标签、依赖注入等等
angularjs中 $scope
,controller,directive,sevice在 mvvm 中充当什么角色?
见上
factory 和 service,provider是什么关系?
factory 把 service 的方法和数据放在一个对象里,并返回这个对象;service 通过构造函数方式创建 service,返回一个实例化对象;provider 创建一个可通过 config 配置的 service。
从底层实现上来看,service 调用了 factory,返回其实例;factory 调用了 provider,将其定义的内容放在 $get
中返回。factory 和 service 功能类似,只不过 factory 是普通 function,可以返回任何东西(return 的都可以被访问,所以那些私有变量怎么写你懂的);service 是构造器,可以不返回(绑定到 this 的都可以被访问);provider 是加强版 factory,返回一个可配置的 factory。
ng-if 跟 ng-show/hide的区别有哪些?
- ng-if 在后面表达式为 true 的时候才创建这个 dom 节点,ng-show 是初始时就创建了,用 display:block 和 display:none 来控制显示和不显示。
- ng-if 会(隐式地)产生新作用域,ng-switch 、 ng-include 等会动态创建一块界面的也是如此。
ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?
会提示 Duplicates in a repeater are not allowed. 加 track by $index
可解决。当然,也可以 trace by 任何一个普通的值,只要能唯一性标识数组中的每一项即可(建立 dom 和数据之间的关联)。
在写controlloer逻辑的时候 你需要注意什么?
- 简化代码(这个是所有开发人员都要具备的)
- 坚决不能操作dom节点 这个时候可能会问 为什么不能啊
你的回答是:DOM操作只能出现在指令(directive)中。最不应该出现的位置就是服务(service)中。Angular倡导以测试驱动开发,在service或者controller中出现了DOM操作,那么也就意味着的测试是无法通过的。当然,这只是一点,重要的是使用Angular的其中一个好处是啥,那就是双向数据绑定,这样就能专注于处理业务逻辑,无需关系一堆堆的DOM操作。如果在Angular的代码中还到处充斥着各种DOM操作,那为什么不直接使用jquery去开发呢。
测试驱动开发是什么呢?普及一下:
测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。
angular 的数据绑定采用什么机制?详述原理。
1、每个双向绑定的元素都有一个watcher
2、在某些事件发生的时候,调用digest脏数据检测。
这些事件有:表单元素内容变化、Ajax请求响应、点击按钮执行的函数等。
3、脏数据检测会检测rootscope下所有被watcher的元素。$digest
函数就是脏数据监测
构建自己的AngularJS,第一部分:Scope和Digest
在使用angularjs项目开发中 你使用过那些第三方的插件?
AngularUi ui-router oclazyload等等
controller之间怎么通讯?
- event
这里可以有两种方式,一种是$scope.$emit
,然后通过监听$rootScope
的事件获取参数;另一种是$rootScope.$broadcast
,通过监听$scope
的事件获取参数。
这两种方法在最新版本的Angular中已经没有性能区别了,主要就是事件发送的方向不同,可以按实际情况选择。 - service
可以创建一个专用的事件Service,也可以按照业务逻辑切分,将数据存储在相应的Service中 $rootScope
这个方法可能会比较dirty一点,胜在方便,也就是把数据存在$rootScope
中,这样各个子$scope
都可以调用,不过需要注意一下生命周期- 直接使用
$scope.$$nextSibling
及类似的属性
类似的还有$scope.$parent
。这个方法的缺点就更多了,官方不推荐使用任何$$
开头的属性,既增加了耦合,又需要处理异步的问题,而且scope的顺序也不是固定的。不推荐
另外就是通过本地存储、全局变量或者现代浏览器的postMessage来传递参数了,除非特殊情况,请避免这类方式。
angular 中的 $http
$http
是 AngularJS 中的一个核心服务,用于读取远程服务器的数据。
我们可以使用内置的$http
服务直接同外部进行通信。$http
服务只是简单的封装了浏览器原生的XMLHttpRequest对象。
- 链式调用
$http
服务是只能接受一个参数的函数,这个参数是一个对象,包含了用来生成HTTP请求的
配置内容。这个函数返回一个promise对象,具有success和error两个方法。 - 返回一个promise对象
- 快捷的get请求
- 响应对象
1、angularjs 数据驱动,模块化
和vuejs的思想很像,都是数据驱动页面,数据变化,页面就跟着变化,我们不需要再去操作页面元素,只需要页面元素的属性绑定数据或者页面元素的事件绑定方法。我们需要改变页面就直接改变数据即可。这让我们抛弃了jquery。
同时也让我们可以把任意多个html元素组合在一起构成一个模块,来扩展属于我们自己的html元素。
2、typeScirp(ts) 面向对象编程
这个编程的风格更像java编程的书写风格。颠覆了js这个弱类型的语言编译书写时不报错,运行时就报错的缺点。又不失js语言编写的灵活性,只是外观和书写习惯让我们更像是在面向对象编程。
3、angular2(angular-cli)
angular2之后,就称之为angular,ng,可以通过angular-cli脚手架搭建项目。把angularjs和typescript融合起来。既是数据驱动,又是面向对象编程,还能模块化。所有的模块都是一个对象,模块可以是页面模块,页面绑定的数据就是这个对象的属性,页面绑定的方法就是这个这对象的方法,模块之间可以相互调用。
4、会涉及到哪些知识
数据绑定,
模块间通信(模块之间数据共享)
路由(实现不刷新切换部分页面)
http通信(实现不刷新更新数据类似ajax)
管道(数据筛选过滤)
5、有哪些缺陷
将所有模块打包在js里面,一次性加载。第一次加载会很慢。如果有几千个模块会怎么样。
构建单页应用,就只有一个页面,不容易别搜索引擎搜索到。
第三方支持的库不是很多。
1.angular的数据绑定采用什么机制?详述原理
脏检查机制。
双向数据绑定是 AngularJS 的核心机制之一。当 view 中有任何数据变化时,会更新到 model ,当 model 中数据有变化时,view 也会同步更新,显然,这需要一个监控。
原理就是,Angular 在 scope 模型上设置了一个监听队列,用来监听数据变化并更新 view 。每次绑定一个东西到 view 上时 AngularJS 就会往 $watch 队列里插入一条 $watch ,用来检测它监视的 model 里是否有变化的东西。当浏览器接收到可以被 angular context 处理的事件时, $digest 循环就会触发,遍历所有的 $watch ,最后更新 dom。
1、每个双向绑定的元素都有一个watcher
2、在某些事件发生的时候,调用digest脏数据检测。
这些事件有:表单元素内容变化、Ajax请求响应、点击按钮执行的函数等。
3、脏数据检测会检测rootscope下所有被watcher的元素。
$digest函数就是脏数据监测
3.在使用angularjs项目开发中 你使用过那些第三方的插件
AngularUi ui-router oclazyload等等 附上一篇文章仔细去看看 https://segmentfault.com/a/1190000003858219
4.ng-show/ng-hide 与 ng-if的区别?
我们都知道ng-show/ng-hide实际上是通过display来进行隐藏和显示的。而ng-if实际上控制dom节点的增删除来实现的。因此如果我们是根据不同的条件来进行dom节点的加载的话,那么ng-if的性能好过ng-show.
5 解释下什么是$rootScrope以及和$scope的区别?
通俗的说$rootScrope 页面所有$scope的父亲。
我们来看下如何产生$rootScope和$scope吧。
step1:Angular解析ng-app然后在内存中创建$rootScope。
step2:angular回继续解析,找到{{}}表达式,并解析成变量。
step3:接着会解析带有ng-controller的div然后指向到某个controller函数。 这个时候在这个controller函数变成一个$scope对象实例。
6 列出至少三种实现不同模块之间通信方式?
Service
events,指定绑定的事件
使用 $rootScope
controller之间直接使用$parent, $$childHead等
directive 指定属性进行数据绑定
7. 表达式 {{yourModel}} 是如何工作的?
它依赖于 $interpolation服务,在初始化页面html后,它会找到这些表达式,并且进行标记,于是每遇见一个 {{}} ,则会设置一个 $watch 。而 $interpolation 会返回一个带有上下文参数的函数,最后该函数执行,则算是表达式 $parse 到那个作用域上。
8.angular中的$http
$http 是 AngularJS 中的一个核心服务,用于读取远程服务器的数据。
我们可以使用内置的$http服务直接同外部进行通信。$http服务只是简单的封装了浏览器原生的XMLHttpRequest对象。
9.ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?
会提示 Duplicates in a repeater are not allowed. 加 track by $index 可解决。当然,也可以 trace by 任何一个普通的值,只要能唯一性标识数组中的每一项即可(建立 dom 和数据之间的关联)
10.angularjs 是mvc还是mvvm框架
首先阐述下你对mvc和mvvm的理解
首先为什么我们会需要MVC?因为随着代码规模越来越大,切分职责是大势所趋,还有为了后期维护方便,修改一块功能不影响其他功能。还有为了复用,因为很多逻辑是一样的。而MVC只是手段,终极目标是模块化和复用。
mvvm的优点
低耦合:View可以独立于Model变化和修改,同一个ViewModel可以被多个View复用;并且可以做到View和Model的变化互不影响;
可重用性:可以把一些视图的逻辑放在ViewModel,让多个View复用;
独立开发:开发人员可以专注与业务逻辑和数据的开发(ViewModemvvmdi计人员可以专注于UI(View)的设计;
可测试性:清晰的View分层,使得针对表现层业务逻辑的测试更容易,更简单。
在angular中MVVM模式主要分为四部分:
View:它专注于界面的显示和渲染,在angular中则是包含一堆声明式Directive的视图模板。
ViewModel:它是View和Model的粘合体,负责View和Model的交互和协作,它负责给View提供显示的数据,以及提供了View中Command事件操作Model的途径;在angular中$scope对象充当了这个ViewModel的角色;
Model:它是与应用程序的业务逻辑相关的数据的封装载体,它是业务领域的对象,Model并不关心会被如何显示或操作,所以模型也不会包含任何界面显示相关的逻辑。在web页面中,大部分Model都是来自Ajax的服务端返回数据或者是全局的配置对象;而angular中的service则是封装和处理这些与Model相关的业务逻辑的场所,这类的业务服务是可以被多个Controller或者其他service复用的领域服务。
Controller:这并不是MVVM模式的核心元素,但它负责ViewModel对象的初始化,它将组合一个或者多个service来获取业务领域Model放在ViewModel对象上,使得应用界面在启动加载的时候达到一种可用的状态。
mvc的界面和逻辑关联紧密,数据直接从数据库读取。mvvm的界面与viewmode是松耦合,界面数据从viewmodel中获取。所以angularjs更倾向于mvvm
11.angularjs中$scope,controller,directive,sevice在mvvm中充当什么角色
如果你不知道,第一题的分析以及很明确,仔细再仔细的看一遍
12.在使用angularjs项目开发中 你使用过那些第三方的插件
AngularUi ui-router oclazyload等等 附上一篇文章仔细去看看 https://segmentfault.com/a/1190000003858219
13.在angular项目中你如何控制静态资源的合理加载
懒加载
第三题提到了oclazyload这个插件,很好的一个懒加载静态资源的第三方插件
14.再写controlloer逻辑的时候 你需要注意什么?
1.简化代码(这个是所有开发人员都要具备的)
2.坚决不能操作dom节点 这个时候可能会问 为什么不能啊
你的回答是:DOM操作只能出现在指令(directive)中。最不应该出现的位置就是服务(service)中。Angular倡导以测试驱动开发,在service或者controller中出现了DOM操作,那么也就意味着的测试是无法通过的。当然,这只是一点,重要的是使用Angular的其中一个好处是啥,那就是双向数据绑定,这样就能专注于处理业务逻辑,无需关系一堆堆的DOM操作。如果在Angular的代码中还到处充斥着各种DOM操作,那为什么不直接使用jquery去开发呢。
测试驱动开发是什么呢?普及一下:
测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。
15.AngularJS的数据双向绑定是怎么实现的?
1、每个双向绑定的元素都有一个watcher
2、在某些事件发生的时候,调用digest脏数据检测。
这些事件有:表单元素内容变化、Ajax请求响应、点击按钮执行的函数等。
3、脏数据检测会检测rootscope下所有被watcher的元素。
$digest函数就是脏数据监测
又附上一篇原理性的文章 https://github.com/xufei/Make-Your-Own-AngularJS/blob/master/01.md
16.controller之间怎么通讯
1、event
这里可以有两种方式,一种是$scope.$emit,然后通过监听$rootScope的事件获取参数;另一种是$rootScope.$broadcast,通过监听$scope的事件获取参数。
这两种方法在最新版本的Angular中已经没有性能区别了,主要就是事件发送的方向不同,可以按实际情况选择。
2、service
可以创建一个专用的事件Service,也可以按照业务逻辑切分,将数据存储在相应的Service中
3、$rootScope
这个方法可能会比较dirty一点,胜在方便,也就是把数据存在$rootScope中,这样各个子$scope都可以调用,不过需要注意一下生命周期
4、直接使用$scope.$$nextSibling及类似的属性
类似的还有$scope.$parent。这个方法的缺点就更多了,官方不推荐使用任何$$开头的属性,既增加了耦合,又需要处理异步的问题,而且scope的顺序也不是固定的。不推荐
另外就是通过本地存储、全局变量或者现代浏览器的postMessage来传递参数了,除非特殊情况,请避免这类方式。
17.自定义指令的几个参数
说几个常用的如:
restrict:指令在dom中的声明形式 E(元素)A(属性)C(类名)M(注释)
template:两种形式,一种HTML文本;一个可以接受两个参数的函数,tElemetn和tAttrs,并返回一个代表模板的字符串。模板字符串必须存在一个根DOM元素
templateUrl:两种形式,一种代表外部HTML文件路径的字符串;一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个外部HTML文件路径的字符串
compile (对象或函数):compile 选项可以返回一个对象或函数。如果设置了 compile 函数,说明我们希望在指令和实时数据被放到DOM中之前进行DOM操作,在这个函数中进行诸如添加和删除节点等DOM操作是安全的。本质上,当我们设置了 link 选项,实际上是创建了一个 postLink() 链接函数,以便 compile() 函数可以定义链接函数。
然后又是传送门:http://www.cnblogs.com/mliudong/p/4180680.html
compile和link的区别:
编译的时候,compile转换dom,碰到绑定监听器的地方就先存着,有几个存几个,到最后汇总成一个link函数,一并执行,提升了性能。
18.angular中的$http
$http 是 AngularJS 中的一个核心服务,用于读取远程服务器的数据。
我们可以使用内置的$http服务直接同外部进行通信。$http服务只是简单的封装了浏览器原生的XMLHttpRequest对象。
1、链式调用
$http服务是只能接受一个参数的函数,这个参数是一个对象,包含了用来生成HTTP请求的
配置内容。这个函数返回一个promise对象,具有success和error两个方法。
2、返回一个promise对象
3、快捷的get请求
4、响应对象
传送门:http://www.2cto.com/kf/201506/405137.html
19.angular和jquery的区别
angular是基于数据驱动,所以angular适合做数据操作比较繁琐的项目(这里可以再提一下单页面应用,如果你不会福利又来了 http://www.zhihu.com/question/20792064)
jquery是基于dom驱动,jquery适合做dom操作多的项目
20.对angular中的form表单了解多少
Angular对input元素的type进行了扩展,一共提供了以下10种类型:
text
number
url
radio
checkbox
hidden
button
submit
reset
Angular为表单内置了4中CSS样式。
ng-valid 校验合法状态
ng-invalid 校验非法状态
ng-pristine 如果要使用原生的form,需要设置这个值
ng-dirty 表单处于脏数据状态
Angular在对表单进行自动校验的时候会校验Model上的属性,如果不设置ng-model,则Angular无法知道myForm.$invalid这个值是否为真。
校验的一下内容
required 表示是否输入内容
ng-maxlength 最大长度
ng-minlength 最小长度
例子:传送门https://github.com/18500047564/clutter
21. ng-show/ng-hide 与 ng-if 的区别?
我们都知道ng-show/ng-hide实际上是通过 display 来进行隐藏和显示的。而ng-if实际上控制dom节点的增删除来实现的。因此如果我们是根据不同的条件来进行dom节点的加载确认的话,那么ng-if的性能好过ng-show.
22.解释下什么是 $rootScrope 以及和 $scope 的区别?
$rootScrope是所有$scope的父对象
23. 表达式 {{yourModel}} 是如何工作的?
它依赖于 $interpolation服务,在初始化页面html后,它会找到这些表达式,并且进行标记,于是每遇见一个 {{}} ,则会设置一个 $watch 。而 $interpolation 会返回一个带有上下文参数的函数,最后该函数执行,则算是表达式 $parse 到那个作用域上。
24.fliter是什么你了解的有多少?实现一个自定义fliter
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’);
自定义一个去重数组的
app.filter('unique', function(){
return function(arr){
var n = [];
var obj = {};
for(var i = 0;i<arr.length;i++){
if(!obj[arr[i]]){
n.push(arr[i])
obj[arr[i]] = 1;
}
}
return n;
}
})