Angular源码解读publishExternalAPI函数
Wscats opened this issue · comments
function publishExternalAPI(angular){}
publishExternalAPI就是将一些通用的方法挂载到全局变量angular上,然后我们就可以以API的形式进行调用
函数首先传入一个angular的全局变量,下面将会遇到在前面定义的好的一个extend函数
function extend(dst) {
var h = dst.$$hashKey;
return forEach(arguments, function(obj) {
obj !== dst && forEach(obj, function(value, key) {
dst[key] = value
})
}), setHashKey(dst, h), dst
}
这里的方法就是将某些通用的方法挂到一个对象上面,extend传入和返回都是dst形参对象,里面经历了两个forEach,forEach也是angular前面定义好的函数,extend根据传入的arguments个数进行判断,dst是需要挂载的对象,arguments中除了dst外都是需要被挂载到dst中的对象
obj !== dst && forEach(obj, function(value, key) {})
上面这句就是将arguments中的dst对象排除出去,然后对dst进行对象挂载
然后就转化成
angular['boostrap'] = boostrap;
angular['copy'] = copy;
angular['extend'] = extend;
//...省略若干个通用的方法
上面就不一一列出所有方法,当然当我们对加载了angular.js的页面进入控制台进行打印就会很清晰的看出这里的全部方法
console.log(angular);
如图所示
这些通用的工具函数在实际的项目中会经常使用到,挺方便的例如forEach,toJson和fromJson这些常用函数
当然就如上面的extend也可以是做API调用,也写个简单的例子,同样在控制器里面输入一段代码,然后打印,这里创建一个空对象,并把该对象后面的对象拷贝并挂载到该空对象中,并赋值给wscat,那么wscat对象就有对应的这些属性
var wscat = angular.extend({}, {name:"wscats",skill:"none"})
console.log(wscat)
咱们继续往下看
angularModule = setupModuleLoader(window)
这里为angular对象创建和加载module()函数,这里我在Angular源码解读setupModuleLoader函数有详细的分析,这里就不多说了,有需要的的可以回头看一下
继续下面这句
try {
angularModule("ngLocale")
} catch (e) {
angularModule("ngLocale", []).provider("$locale", $LocaleProvider)
}
try代码块里面首先判断是否获取到获取ngLocale模块,如果我们获取不到,则就报个异常(ngLocale默认没有创建的)
catch代码块接受上面的异常(也就是获取不到ngLocale模块走catch分支),然后在这个位置注册ngLocale模块
简单的说这里angular.module在创建模块的时候,传递一个参数的时候,是获取模块;传递一个以上的是创建新模块;所以try里面是获取,catch里面就是创建了
那么这里就成功的创建了一个ngLocale模块,其实angularModule实际上就是angular.module
angularModule = angular.module
angularModule("ngLocale", []) = angular.module("ngLocale", [])
所以我们就可以链式去调用对象moduleInstance中的directive和provider
annotate用来分析一个函数的签名,也就是函数的参数
function annotate(fn) {
var $inject, fnText, argDecl, last;
return "function" == typeof fn ? ($inject = fn.$inject) || ($inject = [], fn.length && (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)
})
})), fn.$inject = $inject) : isArray(fn) ? (last = fn.length - 1, assertArgFn(fn[last], "fn"), $inject = fn.slice(0, last)) : assertArgFn(fn, "fn", !0), $inject
}
注入器$injector的annotate方法的作用为分析函数的参数签名
上面代码把它展开如下
function annotate(fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
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);
});
});
}
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;
}
annotate传参的第一种情况(传函数)
首先判断传入的值是否函数
typeof fn == 'function'
再判断函数有没有$inject属性,如果没有的话
!($inject = fn.$inject)
首先经过toString()序列化 处理转换为字符串,
fnText = fn.toString().replace(STRIP_COMMENTS, '');
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
STRIP_COMMENTS和FN_ARG在前面定义好的正则表达式
STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm
FN_ARG = /^\s*(_?)(\S+?)\1\s*$/
以上正则获取了依赖模块的名称并存入$inject数组中返回
简单演示这部分函数,走了了个类似的流程代码方便理解,如下
var
FN_ARG_SPLIT = /,/,
FN_ARG = /^\s*(_?)(\S+?)\1\s*$/,
FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m,
STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
function annotate(fn) {
var $inject,
fnText,
argDecl;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
argDeclSplit = argDecl[1].split(FN_ARG_SPLIT);
argDeclSplit[0].replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
argDeclSplit[1].replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
argDeclSplit[2].replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
console.log(fn)
console.log(fnText);
console.log(argDecl);
console.log(argDeclSplit);
console.log($inject);
}
fn.$inject = $inject;
}
}
return $inject;
}
annotate(function(a , $scope ,$window) {});
打印的结果如下图
里面省略了forEach等angular方法,不过思路和目的是相同的,可以看到一些诸如空格等被正则正确的处理掉,然后最后返回一个数组
annotate传参的第二种情况(传数组)
那就是走了else if (isArray(fn)) {}
的分支
这里用了angular自定义的函数assertArgFn
function assertArg(arg, name, reason) {
if (!arg) throw ngMinErr("areq", "Argument '{0}' is {1}", name || "?", reason || "required");
return arg
}
function assertArgFn(arg, name, acceptArrayAnnotation) {
return acceptArrayAnnotation && isArray(arg) && (arg = arg[arg.length - 1]), assertArg(isFunction(arg), name, "not a function, got " + (arg && "object" == typeof arg ? arg.constructor.name || "Object" : typeof arg)), arg
}
其实就是处理数组中最后的元素,而最后的元素是个函数
var fn = ["a", "$scope", "$window", function(){}];
function annotate(fn){
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
console.log($inject)
}
function assertArgFn(arg, name, acceptArrayAnnotation) {
return acceptArrayAnnotation && (arg = arg[arg.length - 1]), arg
}
annotate(fn);
createInternalInjector返回一个对象如下
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name)
}
}
里面有五个方法invoke,instantiate,get,annotate,has
annotate已经详细介绍过了,就是获取函数的参数
instantiate则是用来实例化一个对象
get用来获得一个服务的实例对象
invoke用来调用一个函数
首先分析invoke函数
function invoke(fn, self, locals) {
var length, i, key, args = [],
$inject = annotate(fn);
for (i = 0, length = $inject.length; length > i; i++) {
if (key = $inject[i], "string" != typeof key) throw $injectorMinErr("itkn", "Incorrect injection token! Expected service name as string, got {0}", key);
args.push(locals && locals.hasOwnProperty(key) ? locals[key] : getService(key))
}
switch (fn.$inject || (fn = fn[length]), self ? -1 : args.length) {
case 0:
return fn();
case 1:
return fn(args[0]);
case 2:
return fn(args[0], args[1]);
case 3:
return fn(args[0], args[1], args[2]);
case 4:
return fn(args[0], args[1], args[2], args[3]);
case 5:
return fn(args[0], args[1], args[2], args[3], args[4]);
case 6:
return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
case 7:
return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
case 8:
return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
case 9:
return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
case 10:
return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
default:
return fn.apply(self, args)
}
}
首先把函数的fn的$inject = annotate(fn);
参数用annotate以数组形式保存在$inject里面
再验证函数的参数
for (i = 0, length = $inject.length; length > i; i++) {
if (key = $inject[i], "string" != typeof key) throw $injectorMinErr("itkn", "Incorrect injection token! Expected service name as string, got {0}", key);
args.push(locals && locals.hasOwnProperty(key) ? locals[key] : getService(key))
}
不正确注册签名,服务名称应该为字符串Incorrect injection token! Expected service name as string
然后判断是否有传入locals,如果有则判断它的属性是否含有fn函数的参数,如果有并且包含这些参数然后把他们保存到args数组里面
例如angular在bootstrap方法中调用invoke函数
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
function(scope, element, compile, injector, animate) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
return injector;
开始args.push(getService($rootScope)),args.push(getService($rootElement))...
然后判断fn.$inject || (fn = fn[length]),如果是数组的话就会拿数组最后一个,也就是传入最后的那个函数赋给fn
然后进入因为没有传入self所以self ? -1 : args.length
self就是-1,就是走default分支return fn.apply(self, args)
,如果self有值得话其实很好理解就是把$inject = annotate(fn),key = $inject[i]
annotate函数拿到的参数一个一个的传进去function(){}里面,然后后面的injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',function(scope, element, compile, injector, animate) {})'])的回调函数就可以在执行的时候调用里面的依赖
我写了个简单的流程来模仿invoke函数实际上做了什么如下
var fn = ['wsccat','wscats',function fn(){
console.log(arguments);
}];
function invoke(fn, self, locals) {
var args = ['wsccat','wscats'];//annotate(fn)处理后放入args
var fn = fn[fn.length-1];
var self = self ? -1 : fn.length-1;//switch处理
switch (self) {
case 0:
return fn();
break;
default:
return fn.apply(self, args);
}
}
//invoke(fn);
invoke(['wsccat','wscats',function fn(){
console.log(arguments);
}]);
最后执行的打印结果是运行了闭包函数并输出apply后'wsccat','wscats'
的两个参数
实际上apply会执行匿名函数一次,此时的匿名函数fn(){}已经有了wsccat,wscats两个参数了
get就是createInternalInjector里面的getService闭包函数
后面是这样调用的var provider = providerInjector.get(servicename + providerSuffix);
也就是相当于var provider = providerInjector.get(servicename + ‘Provider’);
function getService(serviceName) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) throw $injectorMinErr("cdep", "Circular dependency found: {0}", path.join(" <- "));
return cache[serviceName]
}
try {
return path.unshift(serviceName), cache[serviceName] = INSTANTIATING, cache[serviceName] = factory(serviceName)
} finally {
path.shift()
}
}
先看看在执行getService时候,createInternalInjector函数体里有个path = [],path的数组
首先执行这个函数前要用到createInternalInjector函数传入的参数createInternalInjector(cache, factory),cache和factory,比如下面的这句
createInternalInjector(providerCache, function() {
throw $injectorMinErr("unpr", "Unknown provider: {0}", path.join(" <- "))
}),
providerCache对象是下面这个
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
}
这些参数再放到闭包函数getService里面执行
createInjector方法里面,其实是通过createInternalInjector方法来创建注入器的。
var INSTANTIATING = {}, providerSuffix = "Provider",
path = [],
loadedModules = new HashMap,
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
}, providerInjector = providerCache.$injector = createInternalInjector(providerCache, function() {
throw $injectorMinErr("unpr", "Unknown provider: {0}", path.join(" <- "))
}),
instanceCache = {}, instanceInjector = instanceCache.$injector = createInternalInjector(instanceCache, function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider)
});
这里会用createInternalInjector方法创建两个$injector对象
providerInjector是用来创建provider的,instanceInjector是用来创建一个对象实例的。
第一个创建providerInjector对象
providerInjector = providerCache.$injector = createInternalInjector(providerCache, function() {
throw $injectorMinErr("unpr", "Unknown provider: {0}", path.join(" <- "))
})
实际上providerCache就变成了这样
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
$injector:{
get:getService,
annotate:annotate,
instantiate:instantiate,
invoke:invoke,
has:has
}
}
而providerInjector变成了这样
providerInjector:{
nvoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
第二个创建instanceInjector对象
instanceInjector = instanceCache.$injector = createInternalInjector(instanceCache, function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider)
})
实际上instanceCache就变成了这样
instanceCache:{
$injector:{
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
}
而instanceInjector变成了这样
instanceInjector = {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
从angular源码可以知道,创建provider的这几种方式:provider/factory/service/constant/value,左往右灵活性越低,provider方法是基础,其他都是调用provider方法实现的,只是的参数不一样而已
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_;
}