AngularJS 用法总结






3.60/5 (7投票s)
我对 AngularJS 的理解。
引言
介绍 AngularJS 的一些基础和重要知识。
背景
我使用 AngularJS 一段时间了,我之所以使用它,是因为我们的客户要求我们在我们的项目中使用这项技术。 从完全空白到对 AngularJS 有一些了解,我认为有必要写一些关于我对 AngularJS 理解的东西。
1.双向数据绑定
有很多不同种类的 MV** 框架非常流行,AngularJS 就是其中之一(MVVM)。
MV** 框架最重要的事情是它们可以分离 View 层和 Model 层,解耦你的代码。 MVC、MVP、MVVM 都有相同的目标,它们之间的区别在于如何将模型层与视图层关联起来。
数据如何在模型层和视图层之间流动是关键问题。 双向数据绑定是视图上的更改可以反映在模型层中,反之亦然。 那么 AngularJS 如何进行双向绑定以及它如何变成脏检查呢? 让我们从一个前端问题开始
html:
<input type="button" value="increase 1" id="J-increase" />
<span id="J-count"></span>
js:
<script>
var bindDate = {
count: 1,
appy: function () {
document.querySelector('#J-count').innerHTML = this.count;
},
increase: function () {
var _this = this;
document.querySelector('#J-increase').addEventListener('click', function () {
_this.count++;
appy();
}, true);
},
initialize: function () {
this.appy();
this.increase();
}
};
bindDate.initialize();
</script>
如上例,有两个过程
a. 视图层影响模型层:点击页面上的按钮会增加计数。
b. 模型层反映视图层:在 'count' 改变后,它会通过 'apply' 函数在视图上表示出来。
这是我们使用 jQuery 或其他库的方式,有三个明显的缺陷
a. 它涉及太多 DOM 操作。
b. 过程很复杂。
c. 代码耦合度太高,不易编写单元测试。
让我们看看 AngularJS 如何处理数据
第 1 步,添加 Watcher:当数据更改定义需要检查哪个对象时,需要先注册。
$watch: function(watchExp, listener, objectEquality) {
var scope = this,
array = scope.$$watchers,
watcher = {
fn: listener,
last: initWatchVal,
get: get,
exp: watchExp,
eq: !!objectEquality
};
if (!array) {
array = scope.$$watchers = [];
}
array.unshift(watcher);
}
第 2 步,脏检查:当指定范围下的数据发生变化时,需要循环注册的 $$watchers = [...]
$digest: function() {
while (length--) {
watch = watchers[length];
watch.fn(value, lastValue, scope);
}
}
这实现了双向数据绑定,你可以看到类似于自定义事件,并使用了观察者设计模式或发布者-订阅者模式。
2.依赖注入
让我们看看如果我们不使用 DI(依赖注入)如何解决对象之间的相互依赖关系。
function Car() {
...
}
Car.prototype = {
run: function () {...}
}
function Benz() {
var cat = new Car();
}
Benz.prototype = {
...
}
如上所示,Benz 类依赖于 Car 类,它通过使用内部 New 来解决依赖关系。 这样做显然不好,代码耦合度高,不利于维护。
我们知道 javascript 语言没有注释机制,让我们看看 AngularJS 如何实现它。
第 1 步,模拟注释
function annotate(fn, strictDi, name) {
var $inject;
if (!($inject = fn.$inject)) {
$inject = [];
$inject.push(name);
}else if (isArray(fn)) {
$inject = fn.slice(0, last);
}
return $inject;
}
createInjector.$$annotate = annotate;
第 2 步,创建注入对象
function createInjector(modulesToLoad, strictDi) {
var providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
return instanceInjector;
}
第 3 步,获取注入对象
function invoke(fn, self, locals, serviceName) {
var args = [],
$inject = annotate(fn, strictDi, serviceName);
for (...) {
key = $inject[i];
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
if (isArray(fn)) {
fn = fn[length];
}
return fn.apply(self, args);
}
所以如果没有注解,那么模拟一个,这就是 PPK 说 angular 是“非前端开发人员为非前端开发人员开发的框架”的原因。
3.控制器通信
在实际的开发工作中,应用程序系统会变得非常庞大,一个应用程序不可能只有一个控制器,如何处理不同控制器之间的通信? 有两种主要方式
1. 事件机制:在 $rootScope 上注册事件,这样会在 $rootScope 上注册太多事件,这会引起很多问题。
//controller1
app.controller('controller1', function ($rootScope) {
$rootScope.$on('eventType', function (arg) {
......
})
})
// controller2
app.controller('controller2', function ($rootScope) {
$rootScope.$emit('eventType',arg);
or
$rootScope.$broadcast('eventType',arg);
})
2. 使用服务:充分利用 angular DI 的特性,使用 angular 服务是一个单例特性,它将充当不同控制器之间的桥梁。
// register service
app.service('Message', function () {
return {
count: void(0);
}
})
// controller1
app.controller('controller1', function ($scope, Message) {
$scope.count = 1;
Message.count = $scope.count;
});
// controller2
app.controller('controller2', function ($scope, Message) {
$scope.num = Message.count;
});
4.服务特性
1. 单例:在 AngularJS 中,只有服务才能进行依赖注入,类似控制器、指令等都没有这个特性,angular 服务只提供一些基本的服务,它不会与特定的业务相关联,但控制器、指令都与特定的业务紧密相关,所以我们需要保持服务的唯一性。
2. 延迟新建:angular 会先生成服务的提供者,但它不会立即生成对应的服务,它只会在我们需要使用它时才会被实例化。
3. 提供者的类别:provider()、factory、service、value、constant。 哪个提供者是底层实现,其他方式都是基于它。 请注意,所有这些服务最终都需要添加 $get 方法,因为所有特定的服务都是通过执行 $get 方法生成的。
5. 指令的实现
指令的编译器有两个阶段:编译、链接。 简而言之,编译阶段主要处理模板 DOM,在此阶段不涉及范围问题,即没有数据渲染。 例如,ng-Repeat 指令只是使用 compile 来修改模板,在执行 compile 后它将返回 link 函数,覆盖后续的 link 函数; 另一方面,link 主要负责数据渲染,它分为两个步骤。 这两个步骤的编译顺序是相反的,post-link 将先编译内部,然后编译外部。 这对于 directie 编译是安全的,因为指令可以嵌入指令,与此同时 link 将处理实际的 DOM,这将涉及 DOM 操作性能问题。