Wscats / articles

🔖My Learning Notes and Memories - 分享我的学习片段和与你的回忆

Home Page:https://github.com/Wscats/articles

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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);
如图所示
image
这些通用的工具函数在实际的项目中会经常使用到,挺方便的例如forEach,toJson和fromJson这些常用函数
当然就如上面的extend也可以是做API调用,也写个简单的例子,同样在控制器里面输入一段代码,然后打印,这里创建一个空对象,并把该对象后面的对象拷贝并挂载到该空对象中,并赋值给wscat,那么wscat对象就有对应的这些属性
var wscat = angular.extend({}, {name:"wscats",skill:"none"})
console.log(wscat)
image

咱们继续往下看
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) {});

打印的结果如下图
image
里面省略了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);

image

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.lengthself就是-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'的两个参数
image
实际上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_;
}