博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
angularjs源码分析之:angularjs执行流程
阅读量:6894 次
发布时间:2019-06-27

本文共 11385 字,大约阅读时间需要 37 分钟。

(看到一篇讲解angularjs执行流程分析的文章,收藏来看看)

angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题。其中涉及到很多概念,比如:directive,controller,service,compile,link,scope,isolate scope,双向绑定,mvvm等。最近准备把这些都慢慢搞懂,分析源码并贴到博客园,如有分析不对的地方,还望各位包容并指正。

angularjs源码分析之:angularjs执行流程

先上个大图,有个大概印象,注:angularjs的版本为:1.2.1,通过bower install angularjs安装的。

几个重要方法

bindJQuery();publishExternalAPI(angular);jqLite(document).ready(function() {    angularInit(document, bootstrap);});

20121行,bindJQuery,尝试绑定jQuery对象,如果没有则采用内置的jqLite

20123行,publishExternalAPI,初始化angular环境。

1820-1848行,把一些基础api挂载到angular上,如:extend,forEach,isFunction等。

1850行,angularModule = setupModuleLoader(window);此方法为模块加载器,在angular上添加了module方法,最后返回的实质上是:

angular.module = function module(name,require,configFn);

当我们angular.module('myApp'),只传一个参数,为getter操作,返回moduleInstance

当我们angular.module('myApp',[]) 时返回对象moduleInstance

var moduleInstance = {    // Private state    _invokeQueue: invokeQueue,    _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;    }}

因为这样,我们才可以链式操作 ,如: .factory().controller().

这里需要重点提到两个函数:ensure 和invokeLater。

通过ensure(obj,nam,factory)可以实现:先检索obj.name,如果有就是getter操作,否则为setter操作。

此机制完成了在windows对象上声明angular属性,在angular对象上声明module属性。

通过invokeLater可以返回一个闭包,当调用configprovider(即moduleInstance返回的那些api)调用时,实质上就是调用这个闭包,拿app.provider('myprovider',['$window',function($window){ //code}])举例,此api调用后,会将:

('$provide','provider',['$window',function($window){}]) push进invokeQueue数组中,注意此处只是入队操作,并未执行。在后面执行时,实际上市通过 第一个参数调用第二个参数方法名,把第三个参数当变量传入,即:args[0][args[1]].apply(args[0],args[2]);具体后面会分析到。

注意:上面提到api,run比较特殊,只把block放入到runBlock中,并没有放入invokeQueue中。

20125,domready后调用angularInit

function angularInit(element, bootstrap) {  var elements = [element],      appElement,      module,      names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],      NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;  function append(element) {    element && elements.push(element);  }  forEach(names, function(name) {    names[name] = true;    append(document.getElementById(name));    name = name.replace(':', '\\:');    if (element.querySelectorAll) {      forEach(element.querySelectorAll('.' + name), append);      forEach(element.querySelectorAll('.' + name + '\\:'), append);      forEach(element.querySelectorAll('[' + name + ']'), append);    }  });  forEach(elements, function(element) {    if (!appElement) {      var className = ' ' + element.className + ' ';      var match = NG_APP_CLASS_REGEXP.exec(className);      if (match) {        appElement = element;        module = (match[2] || '').replace(/\s+/g, ',');      } else {        forEach(element.attributes, function(attr) {          if (!appElement && names[attr.name]) {            appElement = element;            module = attr.value;          }        });      }    }  });  if (appElement) {    bootstrap(appElement, module ? [module] : []);  }}

遍历names,通过document.getElementById(name) 或者是querySelectorAll(name)检索到 element后存入 elements数组中,最后获取到appElement以及module。举个例子:我们一般会在文档开始的html标签上写 ng-app="myApp".通过以上方法,我们最后可以得到 名为myApp的module,后调用bootstrap(appElement,[module]);

bootstrap中需要重点关注 doBootstrap方法

var doBootstrap = function() {    element = jqLite(element);    if (element.injector()) {      var tag = (element[0] === document) ? 'document' : startingTag(element);      throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);    }    //通过上面分析我们知道此时 modules 暂时是这样的: modules = ['myApp'];    modules = modules || [];    //添加$provide这个数组    modules.unshift(['$provide', function($provide) {      $provide.value('$rootElement', element);    }]);    //添加 ng这个 module ,注意:1857行  我们注册过ng 这个module,并在1854行 我们注册过 它的依赖模块'ngLocale',    //angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 我们注册过ngLocale这个module    modules.unshift('ng');    //调用createInjector(module) 此时:module为:    //['ng',['$provide',function(){}],'myApp']  两个type为string,一个为array    var injector = createInjector(modules);    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;};

createInjector是重点,拿出来单独分析

function createInjector(modulesToLoad) {  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);          }));  forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });  return instanceInjector;  /**  ...省略若干  **/

主要是四个变量:

  • providerCache
  • providerInjector
  • instanceCache
  • instancheInjector

providerCache初始化只有一个对象 providerCache = { $provide:{}} ,紧接着调用createInternalInjector 方法返回一个若干重两级api(annotate,get等)赋值给providerCache.$injector以及provoderInjector.则结果就变成这样了:

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}

同样,instanceCacheinstanceInjector变成:

instanceCache:{      $injector:{          invoke: invoke,          instantiate: instantiate,          get: getService,          annotate: annotate,          has: has      }}instanceInjector = {      invoke: invoke,      instantiate: instantiate,      get: getService,      annotate: annotate,      has: has}

两个重要方法:

  • loadModules
  • createInternalInjector
function loadModules(modulesToLoad){    //刚才说了,modulesToLoad长这样:['ng',['$provider',function(){}], 'myApp']    forEach(modulesToLoad, function(module) {        if (isString(module)) {             // module为字符串时进入此判断。       moduleFn = angularModule(module);            //迭代,把所有依赖模块的runBlocks都取出            runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);            //前面已经提到,每个module下有个_invokeQueue存了一堆controller,service之类东西,现在就可以遍历拿出来运行啦            for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {                //还记得我刚才说了具体怎么调用的把:第一个参数调用名为第二个参数的方法,传入第三个参数                var invokeArgs = invokeQueue[i],                    provider = providerInjector.get(invokeArgs[0]);                provider[invokeArgs[1]].apply(provider, invokeArgs[2]);            }        }else if(isFunction(module)){            //这个我还没找到…………        }else if(isArray(module)){             //这里就是第二参数的情形了,用invoke方法执行后将结果存到runBlocks            runBlocks.push(providerInjector.invoke(module));        }    }    return runBlocks;}

重量级函数createInternalInjector 闪亮登场,正是因为有这个函数,才能实现那么优雅的DI。

function createInternalInjector (){    function getService(serviceName) {};    function invoke(fn, self, locals){};    function instantiate(Type, locals) {};    return {      invoke: invoke,      instantiate: instantiate,      get: getService,      annotate: annotate,      has: function(name) {        //      }    };}

这么重要的函数实际上是一个工厂,最后返回五个方法。下面一一分析:

*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;}

传参有两种形式,1.annotate(fn(injectName)) 以及 2.annotate([injectName,function(){}]).分别进入代码中的两个判断,如果是只传了fn,且fn有参数,则利用fn.toString返回函数体本身,再通过正则取出injectName数组添加到fn.$inject上。 如果通过第二方式调用,取出所有$inject数组

  • invoke,通过annoate取出依赖注入,将依赖注入为参数调用函数体。如:xx.invoke(function($window){});

    function invoke(fn, self, locals){      var args = [],          $inject = annotate(fn),          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)        );    //省略若干    //switch做了优化处理,罗列了一些常见的参数个数   switch (self ? -1 : args.length) {        case  0: return fn();        //省略若干}
  • instantiate 此函数比较特殊,也比较有用,但一般不直接用,隐藏较深。

我们考虑这样一种情况,一般较创建的写法。

app.provider('myProvider',function(){     //do something      this.$get = function(){        return obj;    }});

假如我们想要在angular中用一些设计模式,我们换一种写法:

app.provider('myProvider',myProvider);    function myProvider(){       BaseClass.apply(this,arguments);    }    unit.inherits(BaseClass,myProvider);    extend(myProvider,{       $get:function(){            return something       }    });

这样也可以实现我们需要的,而且可扩展。但如果我们没有了解过instantiate这个方法,你看到此写法会觉得疑惑不解。

instantiate(Type,locals)方法创建了一个空的构造函数,并继承自传入的Type,然后实例化得到instance,最后在调用invoke函数。

万事俱备,只欠东风

做了这么多准备,各种provider也有了,我们需要在页面上展示效果了

injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',   function(scope, element, compile, injector, animate) {    scope.$apply(function() {      element.data('$injector', injector);      compile(element)(scope);    });  }]);

通过$apply将作用域转入angular作用域,所谓angular作用域是指:angular采用dirity-check方式进行检测,达到双向绑定。

再利用compile函数编译整个页面文档,识别出directive,按照优先级排序,执行他们的compilie函数,最后返回link function的结合。通过scope与模板连接起来,形成一个即时,双向绑定。这个过程后续再分析。

到此,执行流程也就都出来了。

收藏自

转载地址:http://ctkdl.baihongyu.com/

你可能感兴趣的文章
面向对象分析笔记
查看>>
在OEL6.1下安装ORACLE 11G R2
查看>>
windows平台下gvim的一些配置
查看>>
DPM 2010 Error Code Catalog
查看>>
SAP打开界面黑色问题处理方法
查看>>
ubuntu 安装mysql
查看>>
SQL查询下级节点
查看>>
编码的原理讲解
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
主机屋使用感受
查看>>
卫星大锅上网链接调试
查看>>
我的友情链接
查看>>
Lync Server 2013企业版部署系列之二:准备DNS
查看>>
实战免费公网证书申请
查看>>
TCP/IP建立连接(三次握手)和关闭连接(四次挥手)
查看>>
Android dex分包方案
查看>>
CAS sso配置
查看>>
【转载】持久层框架 Apache Cayenne 推介
查看>>
我还是个旱鸭子
查看>>