咱們在這裏不討論Angular2和Angular4,由於其徹底重寫,其實已經不叫AngularJS了。javascript
AngularJS的缺陷:html
angularJS定義是一個當即執行的匿名函數,那麼其執行時機爲引入angularJS的位置決定。前端
(function(window) { ... })(window);
基於1.6.9java
源碼的上部分基本在定義方法,其主要執行開始在源碼的最後,主要函數以下: git
bindJQuery(); publishExternalAPI(angular); jqLite(function() { angularInit(window.document, bootstrap); });
嘗試綁定jQuery對象,若是沒有則採用內置的jqLiteangularjs
function publishExternalAPI(angular) { extend(angular, { 'bootstrap': bootstrap, 'copy': copy, 'extend': extend, 'bind': bind, ... }); angularModule = setupModuleLoader(window); angularModule('ng', ['ngLocale'], ['$provide', function ngModule($provide) { ... } ... }
angular.module
方法。找到帶angular項目標識的元素,而後調用bootstrap方法。 github
function angularInit(element, bootstrap) { var appElement, module, config = {}; forEach(ngAttrPrefixes, function(prefix) { var name = prefix + 'app'; if (!appElement && element.hasAttribute && element.hasAttribute(name)) { appElement = element; module = element.getAttribute(name); } }); ... if (appElement) { ... bootstrap(appElement, module ? [module] : [], config); } }
bootstrap函數主要功能:建立注入器和用注入器進行調用web
function bootstrap(element, modules, config) { ... var injector = createInjector(modules, config.strictDi); injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', function bootstrapApply(scope, element, compile, injector) { scope.$apply(function() { element.data('$injector', injector); compile(element)(scope); }); }] ); ... }
createInjector:其內部代碼複雜,DI及provider在此不作展開。在這裏模塊加載完成後,運行模塊,生成實例。編程
injector.invoke:生成編譯實例,經過編譯實例去編譯項目起始頁,編譯的核心是生成指令對應的link函數,有點相似後端的編譯,先詞法分析,用lex,而後語法分析,用parse,最後連接,生成link函數。具體分析以下bootstrap
在publishExternalAPI函數中咱們定義了Provider,這個provider實現了AngularJS的核心方法。
$rootScope: $RootScopeProvider
接下來看看該provider的源碼,下一章咱們將分析其核心實現細節。
function $RootScopeProvider() { var TTL = 10; this.digestTtl = function(value) { ... }; this.$get = ['$exceptionHandler', '$parse', '$browser', function($exceptionHandler, $parse, $browser) { function Scope() { ... } Scope.prototype = { constructor: Scope, $new: function, $watch: function, $watchGroup: function, $watchCollection: function, $digest: function, $destroy: function, $eval: function, $evalAsync: function, $$postDigest: function, $apply: function, $applyAsync: function, $on: function, $emit: function, $broadcast: function, }; var $rootScope = new Scope(); } ]; }
$watch和$digest是相輔相成的。二者一塊兒,構成了Angular做用域的核心:數據變化的響應。
做用爲在Scope上添加一個監聽器。當Scope上發生變動時,監聽器會收到提示。監聽器包括下面二個函數:
一般來講這裏的監聽器是監控一個表達式。監控表達式是一個字符串,好比說「{{user.firstName}}」,一般在數據綁定,指令的屬性,或者JavaScript代碼中指定,它被Angular解析和編譯成一個監控函數。
Angular框架中,雙美圓符前綴$$表示這個變量被看成私有的來考慮,不該當在外部代碼中調用。
$watch源碼以下:
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
array = scope.$$watchers, watcher = { fn: fn, last: initWatchVal, get: get, exp: prettyPrintExpression || watchExp, eq: !!objectEquality }; array.unshift(watcher); return function deregisterWatch() { var index = arrayRemove(array, watcher); if (index >= 0) { incrementWatchersCount(scope, -1); if (index < array.$$digestWatchIndex) { array.$$digestWatchIndex--; } } lastDirtyWatch = null; }; },
該函數定義在原型上,爲全部scope實例共用。
該方法主要接受兩個函數做參數(表達式和監聽函數),把它們存儲在$$watchers數組中 - array.unshift(watcher);
Wather數組中的監聽器將會被下面的digest函數調用。
Digest函數是angularjs的核心方法之一,進行髒值檢查,代碼以下:
do { // "traverse the scopes" loop if ((watchers = current.$$watchers)) { watchers.$$digestWatchIndex = watchers.length; while (watchers.$$digestWatchIndex--) { try { watch = watchers[watchers.$$digestWatchIndex]; // 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) { get = watch.get; if ((value = get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (isNumberNaN(value) && isNumberNaN(last)))) { dirty = true; lastDirtyWatch = watch; watch.last = watch.eq ? copy(value, null) : value; fn = watch.fn; fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; watchLog[logIdx].push({ msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, newVal: value, oldVal: last }); } } 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) { $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.$$watchersCount && current.$$childHead) || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } } while ((current = next));
Loop全部的watcher,經過get方法取得當前值,與 記錄的上次值(watch.last)比較,若是不一樣則值爲髒值。
更新watch.last爲當前新值,調用watcher上的監聽函數,並把新值做爲參數。
注意:在監聽函數中有可能會再次更改scope中的值,在最後一部分有個瘋狂的深度優先遍歷。這裏會有個問題,若是一直有髒值怎麼辦,注意代碼中有個TTL最大try的次數。
以上實現了實現了Angular做用域的本質:添加監聽器,在digest裏運行它們。
特性:
在做用域上添加數據自己並不會有性能折扣。若是沒有監聽器在監控某個屬性,它在不在做用域上都無所謂。Angular並不會遍歷做用域的屬性,它遍歷的是監聽器。
$digest裏會調用每一個監控函數,所以,最好關注監聽器的數量,還有每一個獨立的監控函數或者表達式的性能。
做用:集成外部代碼與digest循環
$apply使用函數做參數,它用$eval執行這個函數,而後經過$digest觸發digest循環。
$apply: function(expr) { try { beginPhase('$apply'); try { return this.$eval(expr); } finally { clearPhase(); } } catch (e) { $exceptionHandler(e); } finally { try { $rootScope.$digest(); } catch (e) { $exceptionHandler(e); // eslint-disable-next-line no-unsafe-finally throw e; } } },
apply的源碼很是簡單,如上所示,經過eval調用指定的方法。而且保證執行後調用一次digest以進行髒值檢查。
apply使用時機:DOM事件、setTimeout、XHR或其餘第三方的庫,特別是異步方法對scope中數據的改變不會被watcher監控到,須要顯示調用apply告訴angularjs。
以上就是angularJS核心思想及其源碼實現,有點懶沒有展開的說,也有可能理解不夠透徹,畢竟是過期的框架了,做爲前端編程思想進行學習而已。
refers:
https://www.cnblogs.com/leo_wl/p/3446075.html
https://www.cnblogs.com/wuya16/p/3769032.html
https://blog.csdn.net/u013510614/article/details/50703811
https://blog.csdn.net/u013510614/article/details/50703813
https://blog.csdn.net/u013510614/article/details/50703815
https://blog.csdn.net/cteng/article/details/72823190
https://github.com/xufei/Make-Your-Own-AngularJS/blob/master/01.md