Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular源码解读publishExternalAPI函数 #26

Closed
Wscats opened this issue May 26, 2016 · 3 comments
Closed

Angular源码解读publishExternalAPI函数 #26

Wscats opened this issue May 26, 2016 · 3 comments
Labels

Comments

@Wscats
Copy link
Owner

Wscats commented May 26, 2016

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

@Wscats
Copy link
Owner Author

Wscats commented May 26, 2016

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

@Wscats
Copy link
Owner Author

Wscats commented May 26, 2016

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
}

@Wscats
Copy link
Owner Author

Wscats commented May 27, 2016

从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_;
}

@Wscats Wscats added the notes label Jun 3, 2016
@Wscats Wscats closed this as completed Aug 22, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant