angularjs-1.3代码学习 模块
花了点时间,阅读了下angularjs的源码。本次先从模块化开始。
angular可以通过module的api来实现前端代码的模块化管理。跟define类似。但不具备异步加载脚本的功能。先从最基本的module开始。查看代码发现angular对module的定义是在setupModuleLoader方法中定义的
function setupModuleLoader(window) {
var $injectorMinErr = minErr('$injector');
var ngMinErr = minErr('ng');
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
var angular = ensure(window, 'angular', Object);
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
return function module(name, requires, configFn) {
//省略.....
return ensure(modules, name, function() {
//省略...
return moduleInstance;
});
};
});
}
可以看到源码中是通过ensure函数,将module定义到angular,并且在函数内返回函数,这就使用到了闭包,angular的目的是为了存储对应的module信息。在整个angular模块系统中,源码通过modules来存储所有用户定义的模块。当用户对自己定义的模块注册服务时(factory,service,provider,value时),这些服务会被存在各自模块内部的invokeQueue数组中维护。这同样是一个闭包。
当setupModuleLoader方法执行结束之后,返回的值是一个moduleInstance。这也是我们在外部调用angular.module时返回的值,看一下内部结构:
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
_configBlocks: configBlocks,
_runBlocks: runBlocks,
requires: requires,
name: name,
provider: invokeLater('$provide', 'provider'),
factory: invokeLater('$provide', 'factory'),
service: invokeLater('$provide', 'service'),
value: invokeLater('$provide', 'value'),
constant: invokeLater('$provide', 'constant', 'unshift'),
animation: invokeLater('$animateProvider', 'register'),
filter: invokeLater('$filterProvider', 'register'),
controller: invokeLater('$controllerProvider', 'register'),
directive: invokeLater('$compileProvider', 'directive'),
config: config,
run: function(block) {
runBlocks.push(block);
return this;
}
};
我们可以使用_invokeQueue属性查看,当前module下面注册了哪些服务。另外moduleInstance也向外部公开了诸如provider,factory.service这些接口,供用户去注册自定义服务。至于如何注册的,是通过其内部的invokeLater方法,统统加入到invokeQueue数组中
function invokeLater(provider, method, insertMethod, queue) {
if (!queue) queue = invokeQueue;
return function() {
queue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
目前,我们大致了解了模块的定义和一般服务的注册。大致调用代码如下:
var app = angular.module("A", []); //定义一个模块A
app.service("A1", function(){
console.log(1);
}); //在模块A上定义一个服务A1
ok,万事俱备,只欠业炎。我们如何使用它?angular提供了一个injector的方法,我们来看一下。从内部函数publishExternalAPI中看到injector的定义,它来源于angular的一个叫做createInInjector的函数。
function createInjector(modulesToLoad, strictDi) {
strictDi = (strictDi === true);
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap([], true),
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
};
var providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function(serviceName, caller) {
if (angular.isString(caller)) {
path.push(caller);
}
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
}))
var instanceCache = {};
var instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
return instanceInjector;
// 省略.....
}
可以看出,内部定义了两个核心对象providerCache和instanceCache。它们都是由createInternalInjector函数生成。那进入createInternalInjector函数里看一下
function createInternalInjector(cache, factory) {
//....
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
//对象本身是否有该属性
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
}
因为createInjector函数中最终返回的是instanceInjector,而此君指向的则是instanceCache的$injector属性,而这个属性则是createInternalInjector函数的返回值。换言之,用户可以在外部调用这些方法
var injector = angular.injector(["A"]);
//其中injector就是createInternalInjector函数的返回的对象
在我们调用这些接口之前,需要告诉angular去执行哪个模块里的服务,让我们回到createInjector函数中,看一下函数最后的for循环
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
可以猜想这个loadModules函数的作用应该是获取模块。简单看一下loadModules
function loadModules(modulesToLoad) {
var runBlocks = [], moduleFn;
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return; //先尝试去loadedModules查找
loadedModules.put(module, true);
function runInvokeQueue(queue) {
var i, ii;
for (i = 0, ii = queue.length; i < ii; i++) {
//获取用户之前需要注册的服务
var invokeArgs = queue[i],
provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
}
try {
if (isString(module)) {
moduleFn = angularModule(module); //angularModule其实就是外部用户调用的module接口,获取到对应模块
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);//如果模块有依赖则事先获取依赖模块
runInvokeQueue(moduleFn._invokeQueue);//之前所有的声明的服务都被存在放这个模块的invokeQueue数组中
runInvokeQueue(moduleFn._configBlocks);
} else if (isFunction(module)) {
runBlocks.push(providerInjector.invoke(module));
} else if (isArray(module)) {
runBlocks.push(providerInjector.invoke(module));
} else {
assertArgFn(module, 'module');
}
} catch (e) {
if (isArray(module)) {
module = module[module.length - 1];
}
if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
// Safari & FF's stack traces don't contain error.message content
// unlike those of Chrome and IE
// So if stack doesn't contain message, we create a new string that contains both.
// Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
/* jshint -W022 */
e = e.message + '\n' + e.stack;
}
throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
module, e.stack || e.message || e);
}
});
return runBlocks;
}
在runInvokeQueue中,我们看到源码获取了之前用户存在放invokeQueue中的服务对象,并且通过get方法及getService方法去获取服务。这里有个细节地方,源码是通过providerInjector的get方法来获取的,那我们看getService源码的时候,其中的cache则应该是在之前createInjector函数中的providerCache。
function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
}
throw err;
} finally {
path.shift();
}
}
}
基本还是先尝试在cache中找一次,没有则创建并保存在cache中。因为之前providerInjector中就已经有了$provider何$inject这样的方法。所以在
var invokeArgs = queue[i],
provider = providerInjector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
中,provider就是:
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
};
然后,再有后面的一步provider[invokeArgs[1].apply(provider, invokeArgs[2])]。看到这里我们突然恍然大悟,开始angular为什么要使用
provider: invokeLater('$provide', 'provider'),
factory: invokeLater('$provide', 'factory'),
service: invokeLater('$provide', 'service'),
value: invokeLater('$provide', 'value'),
constant: invokeLater('$provide', 'constant', 'unshift'),
animation: invokeLater('$animateProvider', 'register'),
filter: invokeLater('$filterProvider', 'register'),
controller: invokeLater('$controllerProvider', 'register'),
directive: invokeLater('$compileProvider', 'directive'),
这类写法注册的原因了,也就是现在我们才真正调用具体service,factory或者是value等api去执行或者讲要初始化我们自定义的服务。这里我们顺便看一下factory,service和provider的内部实现,这些API以后我们会经常用到:
function provider(name, provider_) {
assertNotHasOwnProperty(name, 'service');
if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_); //实例化
}
if (!provider_.$get) {
throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
}
return providerCache[name + providerSuffix] = provider_; //并存储在本地缓存中
}
function factory(name, factoryFn, enforce) {
return provider(name, { //底层调用的是provider
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) { //底层调用factory
return $injector.instantiate(constructor);
}]);
}
function value(name, val) { return factory(name, valueFn(val), false); } //底层调用factory
factory,service和value基本底层都是使用的provider方法。可以看到provider方法需要$get的这种键值对的方式将函数传入。所以我们在外部使用这一系列API的方式,大致
var app = angular.module("A", []);
app.service("A1", function(){
console.log(1);
});
app.factory("B1", [function(){
console.log(2);
return {};
}]);
app.provider("C1", {
$get: function(){
console.log(3);
}
});
app.value("D1", 4);
app.constant("E1", 5);
有兴趣的朋友可以试下,service使用factory的方法定义可以不可以^_^。至此,我们之前使用injector()方法实际上获取到了instanceInjector。目前我们还是没有触发或者执行我们定义的服务,实际上instanceInjector上提供了如下几个方法:
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
//对象本身是否有该属性
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
当我们执行get方法的时候,我们注册的服务的以执行!
function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
}
throw err;
} finally {
path.shift();
}
}
}
这里你可能会奇怪这个factory到底是哪个?是来自于providerInjector的还是来自instanceInjector的?实际上factory是由instanceInjector中定义的回调函数。为什么呢?因为上文我们说了外部调用injector的时候angular内部返回的是instanceInjector。所以在instanceInjector上调用get时,这个factory应该就是instanceInjector上注册的回调函数,来看一下这个回调:
function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}
在instanceInjector的回调里面调用providerInjector的get,其实这个很清楚,之前angular将所有服务存储都存放在了providerCache里面,而外部对用户而言,他无法直接修改providerCache里面的东西。重复的get我们就不看,来看一下invoke方法:
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;
for (i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
'Incorrect injection token! Expected service name as string, got {0}', key);
}
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
if (isArray(fn)) {
fn = fn[length];
}
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args); //执行服务
}
千呼万唤始出来啊。服务的执行在invoke方法得到执行,那么我们算是基本的将module和服务注册和执行的流程简单的过了一遍。
简单回顾一下,angular的module实际上就是用了两层闭包来管理对象,没有动态异步加载模块,这个功能有点类似namespace。比较鸡肋。另外返回的moduleInstance中有factory,service,provider,value等一些可以注册服务的接口,其内部使用了providerInjector何instanceInjector来存储相关服务。通过invoke来执行相应模块里的相应服务。目前我们还没有看angular的bootstrap部分,其内部也是调用invoke方法,达到内置注册的服务得到执行。
时间不多,内容刚好,以上是个人阅读源码的一些理解,有不对或者偏差的地方,还希望园友们斧正。共同进步。
angularjs-1.3代码学习 模块的更多相关文章
- NEC学习 ---- 模块 - 上图下文图文列表
上图下文图文列表的效果如下图: 可以看到三个红色框中的三中"上图下文的图文列表"; 这里的代码其实没什么问题, 对于这种布局, 其实可以参考我上一篇介绍: NEC学习 ---- 模 ...
- AngularJS高级程序设计读书笔记 -- 模块篇
一. 模块基础 1. 创建模块 <!DOCTYPE html> <html ng-app="exampleApp"> <head> <ti ...
- AngularJS进阶(四十)创建模块、服务
AngularJS进阶(四十)创建模块.服务 学习要点 使用模块构架应用 创建和使用服务 为什么要使用和创建服务与模块? 服务允许你打包可重用的功能,使之能在此应用中使用. 模块允许你打包可重用的功能 ...
- Learning Memory-guided Normality代码学习笔记
Learning Memory-guided Normality代码学习笔记 记忆模块核心 Memory部分的核心在于以下定义Memory类的部分. class Memory(nn.Module): ...
- u-boot代码学习内容
前言 u-boot代码庞大,不可能全部细读,只能有选择的读部分代码.在读代码之前,根据韦东山教材,关于代码学习内容和深度做以下预先划定. 一.Makefile.mkconfig.config.mk等 ...
- AngularJS进阶(十四)AngularJS灵异代码事件
AngularJS灵异代码事件 注:请点击此处进行充电! 事情原委 router_sys.js源代码如下: 自己在html路由跳转的代码如下: 但是在实际路由过程中,却路由到了下面的状态,相应的页面中 ...
- Objective-C代码学习大纲(3)
Objective-C代码学习大纲(3) 2011-05-11 14:06 佚名 otierney 字号:T | T 本文为台湾出版的<Objective-C学习大纲>的翻译文档,系统介绍 ...
- ORB-SLAM2 论文&代码学习 ——Tracking 线程
本文要点: ORB-SLAM2 Tracking 线程 论文内容介绍 ORB-SLAM2 Tracking 线程 代码结构介绍 写在前面 上一篇文章中我们已经对 ORB-SLAM2 系统有了一个概览性 ...
- ORB-SLAM2 论文&代码学习 —— 单目初始化
转载请注明出处,谢谢 原创作者:Mingrui 原创链接:https://www.cnblogs.com/MingruiYu/p/12358458.html 本文要点: ORB-SLAM2 单目初始化 ...
随机推荐
- 一天搞定CSS:css选择器--07
选择器:是指选择标签的方法 1.选择器类型 2.id选择器 代码演示 <!DOCTYPE html> <html> <head> <meta charset= ...
- 转自52 梦回凉亭的她 Java常见问题,面试题
收集整理分享# 相关概念## 面向对象的三个特征封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象.## 多态的好处允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不 ...
- java中调用本地动态链接库(*.DLL)的两种方式详解和not found library、打包成jar,war包dll无法加载等等问题解决办法
我们经常会遇到需要java调用c++的案例,这里就java调用DLL本地动态链接库两种方式,和加载过程中遇到的问题进行详细介绍 1.通过System.loadLibrary("dll名称,不 ...
- 隐马尔科夫模型HMM(四)维特比算法解码隐藏状态序列
隐马尔科夫模型HMM(一)HMM模型 隐马尔科夫模型HMM(二)前向后向算法评估观察序列概率 隐马尔科夫模型HMM(三)鲍姆-韦尔奇算法求解HMM参数 隐马尔科夫模型HMM(四)维特比算法解码隐藏状态 ...
- ssh代理上网
背景: 公司开发机没有外网,但可以通过ssh连接到另一台可以上公网的机器,所以想通过ssh代理的方式上网,简单又方便,而且需要的时候上,不需要的时候也可以不上 配置: 超级简单 在开发机上建立ssh隧 ...
- js移动端/H5同时选择多张图片上传并使用canvas压缩图片
最近在做一个H5的项目,里边涉及到拍照上传图片的功能以及识别图片的功能,这里对识别图片的功能不做赘述,不属本文范畴.我在做完并上线项目后,同事跟我提了一个要求是可不可以同时选择多张图片上传,我做的时候 ...
- 支付宝即时到账DEMO配置与使用
支付宝网页即时到账功能,可让用户在线向开发者的支付宝账号支付资金,交易资金即时到账,帮助开发者快速回笼资金. 当用户进行支付操作时候可以直接跳转到支付宝支付页面进行支付 1. 准备 关于支付宝签约即时 ...
- curl的使用
curl -v -0 -T 123.wav "127.0.0.1:80/saveSound?filename=18696770041_1379903830_xxx.wav&du ...
- noip借教室 题解
题目描述 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样. 面对海量租借教室的信息,我们自然 ...
- Chapter 4. The MPEG-4 and H.264 Standards
本章节介绍一些关于MPEG-4标准与H.264标准的基本知识 比较重要的是第95页关于两种标准的对比表格.其他部分没有什么特别重要的细节.