AngularJS 源码剖析3

来源:http://www.sh-fengwen.com 作者:瘦身美容 人气:123 发布时间:2019-09-02
摘要:AngularJS简介 本文接着上一篇讲 上一篇地址 angularjs是google出品的一款MVVM前端框架,包含一个精简的类jquery库,创新的开发了以指令的方式来组件化前端开发,可以去它的官网看看,请戳这里

AngularJS简介

本文接着上一篇讲

上一篇地址


angularjs 是google出品的一款MVVM前端框架,包含一个精简的类jquery库,创新的开发了以指令的方式来组件化前端开发,可以去它的官网看看,请戳这里

回顾

上次说到了rootScope里的$watch方法中的解析监控表达式,即而引出了对parse的分析,今天我们接着这里继续挖代码.

 

$watch续

先上一块$watch代码

 $watch: function(watchExp, listener, objectEquality) {         var scope = this,             get = compileToFn(watchExp, 'watch'),             array = scope.$$watchers,             watcher = {               fn: listener,               last: initWatchVal,               get: get,               exp: watchExp,               eq: !!objectEquality             };          lastDirtyWatch = null;          // in the case user pass string, we need to compile it, do we really need this ?         if (!isFunction(listener)) {           var listenFn = compileToFn(listener || noop, 'listener');           watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};         }          if (typeof watchExp == 'string' && get.constant) {           var originalFn = watcher.fn;           watcher.fn = function(newVal, oldVal, scope) {             originalFn.call(this, newVal, oldVal, scope);             arrayRemove(array, watcher);           };         }          if (!array) {           array = scope.$$watchers = [];         }         // we use unshift since we use a while loop in $digest for speed.         // the while loop reads in reverse order.         array.unshift(watcher);          return function deregisterWatch() {           arrayRemove(array, watcher);           lastDirtyWatch = null;         };       } 

这里的get = compileToFn(watchExp, 'watch'),上篇已经分析完了,这里返回的是一个执行表达式的函数,接着往下看,这里初始化了一个watcher对象,用来保存一些监听相关的信息,简单的说明一下

  • fn, 代表监听函数,当监控表达式新旧不相等时会执行此函数
  • last, 保存最后一次发生变化的监控表达式的值
  • get, 保存一个监控表达式对应的函数,目的是用来获取表达式的值然后用来进行新旧对比的
  • exp, 保存一个原始的监控表达式
  • eq, 保存$watch函数的第三个参数,表示是否进行深度比较

然后会检查传递进来的监听参数是否为函数,如果是一个有效的字符串,则通过parse来解析生成一个函数,否则赋值为一个noop占位函数,最后生成一个包装函数,函数体的内容就是执行刚才生成的监听函数,默认传递当前作用域.

接着会检查监控表达式是否为字符串并且执行表达式的constant为true,代表这个字符串是一个常量,那么,系统在处理这种监听的时候,执行完一次监听函数之后就会删除这个$watch.最后往当前作用域里的$$watchers数组头中添加$watch信息,注意这里的返回值,利用JS的闭包保留了当前的watcher,然后返回一个函数,这个就是用来删除监听用的.

再贴上一个本文源码分析对应的angularjs源码合并版本1.2.4,精简版的,除掉了所有的注释, 请戳这里

$eval

这个$eval也是挺方便的函数,假如你想直接在程序里执行一个字符串的话,那么可以这么用

 $scope.name = '2'; $scope.$eval('1+name'); // ==> 会输出12  

大家来看看它的函数体

 return $parse(expr)(this, locals); 

其实就是通过parse来解析成一个执行表达式函数,然后传递当前作用域以及额外的参数,返回这个执行表达式函数的值

 

$evalAsync

evalAsync函数的作用就是延迟执行表达式,并且执行完不管是否异常,触发dirty check.

  if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {           $browser.defer(function() {             if ($rootScope.$$asyncQueue.length) {               $rootScope.$digest();             }           });         }  this.$$asyncQueue.push({scope: this, expression: expr}); 

可以看到当前作用域内部有一个$$asyncQueue异步队列,保存着所有需要延迟执行的表达式,此处的表达式可以是字符串或者函数,因为这个表达式最终会调用$eval方法,注意这里调用了$browser服务的defer方法,从ng->browser.js源码里可以看到,其实这里就是调用setTimeout来实现的.

 self.defer = function(fn, delay) {     var timeoutId;     outstandingRequestCount++;     timeoutId = setTimeout(function() {       delete pendingDeferIds[timeoutId];       completeOutstandingRequest(fn);     }, delay || 0);     pendingDeferIds[timeoutId] = true;     return timeoutId;   }; 

上面的代码主要是延迟执行函数,另外pendingDeferIds对象保存所有setTimeout返回的id,这个会在self.defer.cancel这里可以取消执行延迟执行.

说digest方法之前,还有一个方法要说说

从启动开始说起

$postDigest

这个方法跟evalAsync不同的时,它不会主动触发digest方法,只是往postDigestQueue队列中增加执行表达式,它会在digest体内最后执行,相当于在触发dirty check之后,可以执行别的一些逻辑.

 this.$$postDigestQueue.push(fn); 

下面我们来重点说说digest方法

定位到4939行,这里是angularjs开始执行初始化的地方,见代码

$digest

digest方法是dirty check的核心,主要思路是先执行$$asyncQueue队列中的表达式,然后开启一个loop来的执行所有的watch里的监听函数,前提是前后两次的值是否不相等,假如ttl超过系统默认值,则dirth check结束,最后执行$$postDigestQueue队列里的表达式.

 $digest: function() {         var watch, value, last,             watchers,             asyncQueue = this.$$asyncQueue,             postDigestQueue = this.$$postDigestQueue,             length,             dirty, ttl = TTL,             next, current, target = this,             watchLog = [],             logIdx, logMsg, asyncTask;          beginPhase('$digest');          lastDirtyWatch = null;          do { // "while dirty" loop           dirty = false;           current = target;            while(asyncQueue.length) {             try {               asyncTask = asyncQueue.shift();               asyncTask.scope.$eval(asyncTask.expression);             } catch (e) {               clearPhase();               $exceptionHandler(e);             }             lastDirtyWatch = null;           }            traverseScopesLoop:           do { // "traverse the scopes" loop             if ((watchers = current.$$watchers)) {               // process our watches               length = watchers.length;               while (length--) {                 try {                   watch = watchers[length];                   // Most common watches are on primitives, in which case we can short                   // circuit it with === operator, only when === fails do we use .equals                   if (watch) {                     if ((value = watch.get(current)) !== (last = watch.last) &&                         !(watch.eq                             ? equals(value, last)                             : (typeof value == 'number' && typeof last == 'number'                                && isNaN(value) && isNaN(last)))) {                       dirty = true;                       lastDirtyWatch = watch;                       watch.last = watch.eq ? copy(value) : value;                       watch.fn(value, ((last === initWatchVal) ? value : last), current);                       if (ttl < 5) {                         logIdx = 4 - ttl;                         if (!watchLog[logIdx]) watchLog[logIdx] = [];                         logMsg = (isFunction(watch.exp))                             ? 'fn: ' + (watch.exp.name || watch.exp.toString())                             : watch.exp;                         logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);                         watchLog[logIdx].push(logMsg);                       }                     } else if (watch === lastDirtyWatch) {                       // If the most recently dirty watcher is now clean, short circuit since the remaining watchers                       // have already been tested.                       dirty = false;                       break traverseScopesLoop;                     }                   }                 } catch (e) {                   clearPhase();                   $exceptionHandler(e);                 }               }             }              // Insanity Warning: scope depth-first traversal             // yes, this code is a bit crazy, but it works and we have tests to prove it!             // this piece should be kept in sync with the traversal in $broadcast             if (!(next = (current.$$childHead ||                 (current !== target && current.$$nextSibling)))) {               while(current !== target && !(next = current.$$nextSibling)) {                 current = current.$parent;               }             }           } while ((current = next));            // break traverseScopesLoop; takes us to here            if((dirty || asyncQueue.length) && !(ttl--)) {             clearPhase();             throw $rootScopeMinErr('infdig',                 '{0} $digest() iterations reached. Aborting!n' +                 'Watchers fired in the last 5 iterations: {1}',                 TTL, toJson(watchLog));           }          } while (dirty || asyncQueue.length);          clearPhase();          while(postDigestQueue.length) {           try {             postDigestQueue.shift()();           } catch (e) {             $exceptionHandler(e);           }         }       } 

通过上面的代码,可以看出,核心就是两个loop,外loop保证所有的model都能检测到,内loop则是真实的检测每个watch,watch.get就是计算监控表达式的值,这个用来跟旧值进行对比,假如不相等,则执行监听函数

注意这里的watch.eq这是是否深度检查的标识,equals方法是angular.js里的公共方法,用来深度对比两个对象,这里的不相等有一个例外,那就是NaN ===NaN,因为这个永远都是false,所以这里加了检查

 !(watch.eq     ? equals(value, last)     : (typeof value == 'number' && typeof last == 'number'        && isNaN(value) && isNaN(last))) 

 

比较完之后,把新值传给watch.last,然后执行watch.fn也就是监听函数,传递三个参数,分别是:最新计算的值,上次计算的值(假如是第一次的话,则传递新值),最后一个参数是当前作用域实例,这里有一个设置外loop的条件值,那就是dirty

true,也就是说只要内loop执行了一次watch,则外loop还要接着执行,这是为了保证所有的model都能监测一次,虽然这个有点浪费性能,不过超过ttl设置的值后,dirty check会强制关闭,并抛出异常

 if((dirty || asyncQueue.length) && !(ttl--)) {     clearPhase();     throw $rootScopeMinErr('infdig',         '{0} $digest() iterations reached. Aborting!n' +         'Watchers fired in the last 5 iterations: {1}',         TTL, toJson(watchLog)); } 

这里的watchLog日志对象是在内loop里,当ttl低于5的时候开始记录的

 if (ttl < 5) {     logIdx = 4 - ttl;     if (!watchLog[logIdx]) watchLog[logIdx] = [];     logMsg = (isFunction(watch.exp))         ? 'fn: ' + (watch.exp.name || watch.exp.toString())         : watch.exp;     logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);     watchLog[logIdx].push(logMsg); } 

当检查完一个作用域内的所有watch之后,则开始深度遍历当前作用域的子级或者父级,虽然这有些影响性能,就像这里的注释写的那样yes, this code is a bit crazy

 // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! // this piece should be kept in sync with the traversal in $broadcast if (!(next = (current.$$childHead ||       (current !== target && current.$$nextSibling)))) {     while(current !== target && !(next = current.$$nextSibling)) {       current = current.$parent;     } } 

上面的代码其实就是不断的查找当前作用域的子级,没有子级,则开始查找兄弟节点,最后查找它的父级节点,是一个深度遍历查找.只要next有值,则内loop则一直执行

 while ((current = next)) 

不过内loop也有跳出的情况,那就是当前watch跟最后一次检查的watch相等时就退出内loop.

 else if (watch === lastDirtyWatch) {     // If the most recently dirty watcher is now clean, short circuit since the remaining watchers     // have already been tested.     dirty = false;     break traverseScopesLoop; } 

注意这个内loop同时也是一个label(标签)语句,这个可以在loop中执行跳出操作就像上面的break

正常执行完两个loop之后,清除当前的阶段标识clearPhase();,然后开始执行postDigestQueue队列里的表达式.

 while(postDigestQueue.length) {     try {       postDigestQueue.shift()();     } catch (e) {       $exceptionHandler(e);     } } 

接下来说说,用的也比较多的$apply方法

 

$apply

这个方法一般用在,不在ng的上下文中执行js代码的情况,比如原生的DOM事件中执行想改变ng中某些model的值,这个时候就要使用$apply方法了

 $apply: function(expr) {     try {       beginPhase('$apply');       return this.$eval(expr);     } catch (e) {       $exceptionHandler(e);     } finally {       clearPhase();       try {         $rootScope.$digest();       } catch (e) {         $exceptionHandler(e);         throw e;       }     } } 

代码中,首先让当前阶段标识为$apply,这个可以防止使用$apply方法时检查是否已经在这个阶段了,然后就是执行$eval方法, 这个方法上面有讲到,最后执行$digest方法,来使ng中的M或者VM改变.

接下来说说scope中event模块,它的api跟一般的event事件模块比较像,提供有$on,$emit,$broadcast,这三个很实用的方法

bindJQuery(), publishExternalAPI(angular), jqLite(document).ready(function() {

$on

这个方法是用来定义事件的,这里用到了两个实例变量$$listeners, $$listenerCount,分别用来保存事件,以及事件数量计数

 $on: function(name, listener) {         var namedListeners = this.$$listeners[name];         if (!namedListeners) {           this.$$listeners[name] = namedListeners = [];         }         namedListeners.push(listener);          var current = this;         do {           if (!current.$$listenerCount[name]) {             current.$$listenerCount[name] = 0;           }           current.$$listenerCount[name]++;         } while ((current = current.$parent));          var self = this;         return function() {           namedListeners[indexOf(namedListeners, listener)] = null;           decrementListenerCount(self, 1, name);         };       } 

分析上面的代码,可以看出每当定义一个事件的时候,都会向$$listeners对象中添加以name为key的属性,值就是事件执行函数,注意这里有个事件计数,只要有父级,则也给父级的$$listenerCount添加以name为key的属性,并且值+1,这个$$listenerCount
会在广播事件的时候用到,最后这个方法返回一个取消事件的函数,先设置$$listeners中以name为key的值为null,然后调用decrementListenerCount来使该事件计数-1.

        angularInit(document, bootstrap)

$emit

这个方法是用来触发$on定义的事件,原理就是loop$$listeners属性,检查是否有值,有的话,则执行,然后依次往上检查父级,这个方法有点类似冒泡执行事件.

$emit: function(name, args) {
var empty = [],
namedListeners,
scope = this,
stopPropagation = false,
event = {
name: name,
targetScope: scope,
stopPropagation: function() {stopPropagation = true;},
preventDefault: function() {
event.defaultPrevented = true;
},
defaultPrevented: false
},
listenerArgs = concat([event], arguments, 1),
i, length;

    do {       namedListeners = scope.$$listeners[name] || empty;       event.currentScope = scope;       for (i=0, length=namedListeners.length; i<length; i++) {          // if listeners were deregistered, defragment the array         if (!namedListeners[i]) {           namedListeners.splice(i, 1);           i--;           length--;           continue;         }         try {           //allow all listeners attached to the current scope to run           namedListeners[i].apply(null, listenerArgs);         } catch (e) {           $exceptionHandler(e);         }       }       //if any listener on the current scope stops propagation, prevent bubbling       if (stopPropagation) return event;       //traverse upwards       scope = scope.$parent;     } while (scope);      return event;   }

上面的代码比较简单,首先定义一个事件参数,然后开启一个loop,只要scope有值,则一直执行,这个方法的事件链是一直向上传递的,不过当在事件函数执行stopPropagation方法,就会停止向上传递事件.

    })

$broadcast

这个是$emit的升级版,广播事件,即能向上传递,也能向下传递,还能平级传递,核心原理就是利用深度遍历当前作用域

$broadcast: function(name, args) {
var target = this,
current = target,
next = target,
event = {
name: name,
targetScope: target,
preventDefault: function() {
event.defaultPrevented = true;
},
defaultPrevented: false
},
listenerArgs = concat([event], arguments, 1),
listeners, i, length;

//down while you can, then up and next sibling or up and next sibling until back at root while ((current = next)) {   event.currentScope = current;   listeners = current.$$listeners[name] || [];   for (i=0, length = listeners.length; i<length; i++) {     // if listeners were deregistered, defragment the array     if (!listeners[i]) {       listeners.splice(i, 1);       i--;       length--;       continue;     }      try {       listeners[i].apply(null, listenerArgs);     } catch(e) {       $exceptionHandler(e);     }   }    // Insanity Warning: scope depth-first traversal   // yes, this code is a bit crazy, but it works and we have tests to prove it!   // this piece should be kept in sync with the traversal in $digest   // (though it differs due to having the extra check for $$listenerCount)   if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||       (current !== target && current.$$nextSibling)))) {     while(current !== target && !(next = current.$$nextSibling)) {       current = current.$parent;     }   } }  return event;

}

代码跟$emit差不多,只是跟它不同的时,这个是不断的取next值,而next的值则是通过深度遍历它的子级节点,兄弟节点,父级节点,依次查找可用的以name为key的事件.注意这里的注释,跟$digest里的差不多,都是通过深度遍历查找,所以$broadcast方法也不能常用,性能不是很理想

bindJQuery方法是检查是否引用jquery,没有的话jqlite就用本身自带的,否则切换到jquery中去.这个好理解

$destroy

这个方法是用来销毁当前作用域,代码主要是清空当前作用域内的一些实例属性,以免执行digest,$emit,$broadcast时会关联到

 $destroy: function() {     // we can't destroy the root scope or a scope that has been already destroyed     if (this.$$destroyed) return;     var parent = this.$parent;      this.$broadcast('$destroy');     this.$$destroyed = true;     if (this === $rootScope) return;      forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));      // sever all the references to parent scopes (after this cleanup, the current scope should     // not be retained by any of our references and should be eligible for garbage collection)     if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;     if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;     if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;     if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;       // All of the code below is bogus code that works around V8's memory leak via optimized code     // and inline caches.     //     // see:     // - https://code.google.com/p/v8/issues/detail?id=2073#c26     // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909     // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451      this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =         this.$$childTail = this.$root = null;      // don't reset these to null in case some async task tries to register a listener/watch/task     this.$$listeners = {};     this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];      // prevent NPEs since these methods have references to properties we nulled out     this.$destroy = this.$digest = this.$apply = noop;     this.$on = this.$watch = function() { return noop; }; } 

代码比较简单,先是通过foreach来清空$$listenerCount实例属性,然后再设置$parent,$$nextSibling,$$prevSibling,$$childHead,$$childTail,$root为null,清空$$listeners,$$watchers,$$asyncQueue,$$postDigestQueue,最后就是重罢方法为noop占位函数

 

总结

rootScope说完了,这是个使用比例非常高的核心provider,分析的比较简单,有啥错误的地方,希望大家能够指出来,大家一起学习学习,下次有空接着分析别的.


publishExternalAPI这个方法是绑定一些公共的方法到angular下面,这些是可以在网站中访问到的,像forEach,copy等公共方法,还有一个重要的任务就是初始化angular核心的模块,publishExternalAPI在465行,现在我们来分析里面的一些重要的代码

作者声明

作者: feenan

出处:

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


 

 

function publishExternalAPI(angular) {

        extend(angular, {

            bootstrap: bootstrap,

            copy: copy,

            extend: extend,

            equals: equals,

            element: jqLite,

            forEach: forEach,

            injector: createInjector,

            noop: noop,

            bind: bind,

            toJson: toJson,

            fromJson: fromJson,

            identity: identity,

            isUndefined: isUndefined,

            isDefined: isDefined,

            isString: isString,

            isFunction: isFunction,

            isObject: isObject,

            isNumber: isNumber,

            isElement: isElement,

            isArray: isArray,

            version: version,

            isDate: isDate,

            lowercase: lowercase,

            uppercase: uppercase,

            callbacks: {

                counter: 0

            },

            $$minErr: minErr,

            $$csp: csp

        }), angularModule = setupModuleLoader(window);

        try {

            angularModule("ngLocale")

        } catch (e) {

            angularModule("ngLocale", []).provider("$locale", $LocaleProvider)

        }

        angularModule("ng", ["ngLocale"], ["$provide",

            function($provide) {

                $provide.provider("$compile", $CompileProvider).directive({

                    a: htmlAnchorDirective,

                    input: inputDirective,

                    textarea: inputDirective,

                    form: formDirective,

                    script: scriptDirective,

                    select: selectDirective,

                    style: styleDirective,

                    option: optionDirective,

                    ngBind: ngBindDirective,

                    ngBindHtml: ngBindHtmlDirective,

                    ngBindTemplate: ngBindTemplateDirective,

                    ngClass: ngClassDirective,

                    ngClassEven: ngClassEvenDirective,

                    ngClassOdd: ngClassOddDirective,

                    ngCloak: ngCloakDirective,

                    ngController: ngControllerDirective,

                    ngForm: ngFormDirective,

                    ngHide: ngHideDirective,

                    ngIf: ngIfDirective,

                    ngInclude: ngIncludeDirective,

                    ngInit: ngInitDirective,

                    ngNonBindable: ngNonBindableDirective,

                    ngPluralize: ngPluralizeDirective,

                    ngRepeat: ngRepeatDirective,

                    ngShow: ngShowDirective,

                    ngStyle: ngStyleDirective,

                    ngSwitch: ngSwitchDirective,

                    ngSwitchWhen: ngSwitchWhenDirective,

                    ngSwitchDefault: ngSwitchDefaultDirective,

                    ngOptions: ngOptionsDirective,

                    ngTransclude: ngTranscludeDirective,

                    ngModel: ngModelDirective,

                    ngList: ngListDirective,

                    ngChange: ngChangeDirective,

                    required: requiredDirective,

                    ngRequired: requiredDirective,

                    ngValue: ngValueDirective

                }).directive(ngAttributeAliasDirectives).directive(ngEventDirectives), $provide.provider({

                    $anchorScroll: $AnchorScrollProvider,

                    $animate: $AnimateProvider,

                    $browser: $BrowserProvider,

                    $cacheFactory: $CacheFactoryProvider,

                    $controller: $ControllerProvider,

                    $document: $DocumentProvider,

                    $exceptionHandler: $ExceptionHandlerProvider,

                    $filter: $FilterProvider,

                    $interpolate: $InterpolateProvider,

                    $interval: $IntervalProvider,

                    $http: $HttpProvider,

                    $httpBackend: $HttpBackendProvider,

                    $location: $LocationProvider,

                    $log: $LogProvider,

                    $parse: $ParseProvider,

                    $rootScope: $RootScopeProvider,

                    $q: $QProvider,

                    $sce: $SceProvider,

                    $sceDelegate: $SceDelegateProvider,

                    $sniffer: $SnifferProvider,

                    $templateCache: $TemplateCacheProvider,

                    $timeout: $TimeoutProvider,

                    $window: $WindowProvider

                })

            }

        ])

    }

方法体中的setupModuleLoader方法是一个模块加载器,这也是一个关键方法, 主要作用是创建和获取模块,代码见417行.

 

 

 

function setupModuleLoader(window) {

 

        function ensure(obj, name, factory) {

            return obj[name] || (obj[name] = factory())

        }

 

        var $injectorMinErr = minErr("$injector"),

            ngMinErr = minErr("ng");

 

        return ensure(ensure(window, "angular", Object), "module", function() {

            var modules = {};

 

            return function(name, requires, configFn) {

                var assertNotHasOwnProperty = function(name, context) {

                    if ("hasOwnProperty" === name) throw ngMinErr("badname", "hasOwnProperty is not a valid {0} name", context)

                };

                return assertNotHasOwnProperty(name, "module"), requires && modules.hasOwnProperty(name) && (modules[name] = null), ensure(modules, name, function() {

                    function invokeLater(provider, method, insertMethod) {

                        return function() {

                            return invokeQueue[insertMethod || "push"]([provider, method, arguments]), moduleInstance

                        }

                    }

                    if (!requires) throw $injectorMinErr("nomod", "Module '{0}' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.", name);

                    var invokeQueue = [],

                        runBlocks = [],

                        config = invokeLater("$injector", "invoke"),

                        moduleInstance = {

                            _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) {

                                return runBlocks.push(block), this

                            }

                        };

                    return configFn && config(configFn), moduleInstance

                })

            }

        })

    }

上面publishExternalAPI 方法中的angularModule = setupModuleLoader(window);是在window下面创建全局的angular对象,并且返回一个高阶函数,赋值给了angular.module属性,所以一般我们创建模块都是用angular.module方法.这里的angularModule其实就是相当于angular.module

 

angular.module在创建模块的时候,传递一个参数的时候,是获取模块;传递一个以上的是创建新模块;该方法返回的是一个moduleInstance对象,它的任务就是来创建控制器,服务,指令,以及配置方法,全局运行方法,而且是链式调用,因为每个方法都会返回moduleInstance,看这里

 

 

function invokeLater(provider, method, insertMethod) {

    return function() {

        return invokeQueue[insertMethod || "push"]([provider, method, arguments]), moduleInstance

    }

}

此处的return invokeQueue[insertMethod || "push"]([provider, method, arguments]), moduleInstance,逗号表达式是返回最后一个值

 

再来一个angular.module在项目中运用的代码

 

 

angular.module('demoApp', [])

.factory()

.controller()

.directive()

.config()

.run();

接下来再看publishExternalAPI的代码,因为ngLocale默认没有创建,所以angularModule("ngLocale")这个直接异常,跳到catch里执行angularModule("ngLocale", []).provider("$locale", $LocaleProvider),记住这里的provider方法,默认是把它的参数都存到invokeQueue数组中,以便在后面用到.

 

接下来开始创建ng模块,它依赖上面的ngLocale模块,注意创建模块的时候传了第三个参数,当创建模块的时候传了三个参数,默认第三参数会执行config(configFn),这个方法也是把相应的参数放入invokeQueue数组中,只不过前两参数是$injector,invoke,这里先透露一下,其实所有invokeQueue数组项中,三个参数的意思:第一个参数调用第二个参数,然后传递第三个参数,这个后面会讲到.

 

这里说下ng模块中第三个参数里的函数体,这里主要做了两件事,初始了$compile服务,并且利用compile服务的directive方法,把一些常用的指令都保存到compile服务中的一个内部数组中.

 

这里先说下$provide.provider,这个在angular里用的比较多,其实就是提前把定义的provider放入DI函数内的providerCache内,看如下代码,在740行

 

function createInjector(modulesToLoad) {

        function supportObject(delegate) {

            return function(key, value) {

                return isObject(key) ? (forEach(key, reverseParams(delegate)), void 0) : delegate(key, value)

            }

        }

 

        function provider(name, provider_) {

            if (assertNotHasOwnProperty(name, "service"), (isFunction(provider_) || isArray(provider_)) && (provider_ = providerInjector.instantiate(provider_)), !provider_.$get) throw $injectorMinErr("pget", "Provider '{0}' must define $get factory method.", name);

            return providerCache[name + providerSuffix] = provider_

        }

 

        function factory(name, factoryFn) {

            return provider(name, {

                $get: factoryFn

            })

        }

 

        function service(name, constructor) {

            return factory(name, ["$injector",

                function($injector) {

                    return $injector.instantiate(constructor)

                }

            ])

        }

 

        function value(name, val) {

            return factory(name, valueFn(val))

        }

 

        function constant(name, value) {

            assertNotHasOwnProperty(name, "constant"), providerCache[name] = value, instanceCache[name] = value

        }

 

        function decorator(serviceName, decorFn) {

            var origProvider = providerInjector.get(serviceName + providerSuffix),

                orig$get = origProvider.$get;

            origProvider.$get = function() {

                var origInstance = instanceInjector.invoke(orig$get, origProvider);

                return instanceInjector.invoke(decorFn, null, {

                    $delegate: origInstance

                })

            }

        }

 

        function loadModules(modulesToLoad) {

            var moduleFn, invokeQueue, i, ii, runBlocks = [];

            return forEach(modulesToLoad, function(module) {

                if (!loadedModules.get(module)) {

                    loadedModules.put(module, !0);

                    try {

                        if (isString(module))

                            for (moduleFn = angularModule(module), runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks), invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; ii > i; i++) {

                                var invokeArgs = invokeQueue[i],

                                    provider = providerInjector.get(invokeArgs[0]);

                                provider[invokeArgs[1]].apply(provider, invokeArgs[2])

                            } else isFunction(module) ? runBlocks.push(providerInjector.invoke(module)) : isArray(module) ? runBlocks.push(providerInjector.invoke(module)) : assertArgFn(module, "module")

                    } catch (e) {

                        throw isArray(module) && (module = module[module.length - 1]), e.message && e.stack && -1 == e.stack.indexOf(e.message) && (e = e.message + "n" + e.stack), $injectorMinErr("modulerr", "Failed to instantiate module {0} due to:n{1}", module, e.stack || e.message || e)

                    }

                }

            }),runBlocks

        }

 

        function createInternalInjector(cache, factory) {

            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()

                }

            }

 

            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]);

本文由美高梅游戏平台网站发布于瘦身美容,转载请注明出处:AngularJS 源码剖析3

关键词:

上一篇:HTML无刷新提交表单

下一篇:没有了

频道精选

最火资讯