1. 程式人生 > >AngularJS-1.啟動流程

AngularJS-1.啟動流程

整體結構

AngularJS的原始碼在整體上,與其它很多庫和框架一樣,是一個自執行函式,其整體結構簡化如下:

(function(window, document, undefined) {
    if (window.angular.bootstrap) {//判斷是否已經啟動
        console.log('WARNING: Tried to load angular more than once.');
        return;
    }
    bindJQuery();//繫結JQuery
    publishExternalAPI(angular);//對外公佈AngularJS的函式
jqLite(document).ready(function() { angularInit(document, bootstrap);//啟動AngularJS }); })
(window, document);

整體思路

  • 首先是一些全域性變數和方法的定義,以及一些其它操作;
  • 通過window.angular.bootstrap判斷是否已經載入angular,如果已經載入,則直接退出;
  • 執行bindJQuery(),如果已經載入了jQuery,則AngularJS會使用已經載入的jQuery,否則使用內部實現的JQLite,其相當於是一個簡化版的jQuery;
  • 執行publishExternalAPI(angular)來為全域性變數angular增加屬性和方法,並建立起模組機制,註冊核心模組;
    在文件載入完成後執行angularInit()。

bindJQuery

該方法主要是繫結jQuery,簡化後的程式碼如下:

var bindJQueryFired = false;
function bindJQuery() {
    if (bindJQueryFired) {
        return;
    }
    var jqName = jq();
    jQuery = window.jQuery;
    if (isDefined(jqName)) {
        jQuery = jqName === null ? undefined : window[jqName];
    }
    if
(jQuery && jQuery.fn.on) { jqLite = jQuery; // ... ... } else { jqLite = JQLite; } angular.element = jqLite;//具體意義看腳註 bindJQueryFired = true; }

檢視angular.element1

  • bindJQueryFired相當於是一個標誌符,初始值為false。在執行bindJQuery的時候,先判斷bindJQueryFired的值,如果其為true,則說明已經執行過jQuery繫結,直接返回;否則執行繫結過程,並將bindJQueryFired的值設定為true;
  • jqName是呼叫jq()的返回值,jq()的主要作用是遍歷文件,找出第一個包含屬性ng-jq的節點,然後取其屬性值;
  • 變數jQuery取值為window.jQuery,如果載入了jQuery函式庫,則其值非空;
  • 在應用了ng-jq指令的情況下,如果jQName的值不為null,則設定變數jQuery的值為window[jqName],否則設定為undefined
    如果jQuery變數有效,則使用jQuery變數指定的庫;否則使用內建實現的JQLite。

總結起來,繫結的jQuery可以的來源有三個:ng-jq指定、引入的jQuery庫、內建實現的JQLite,其使用流程為:

  • 如果有ng-jq
    – 如果ng-jq的值不為空,則使用它指定的庫
    – 如果ng-jq指定的值為空,則變數jQuery的值為undefined,此時強制使用JQLite,無論是否引入了jQuery庫
  • 如果沒有ng-jq
    – 如果引入了jQuery庫,則使用它
    – 如果沒有引入jQuery庫,則使用JQLite

publishExternalAPI

該方法的程式碼簡化如下:

function publishExternalAPI(angular) {
    //拓展angular物件
    extend(angular, {
        ...... 
    });
    //定義angular.module方法
    angularModule = setupModuleLoader(window);
    //嘗試獲取ngLocal模組,如果沒有,則需要註冊一個
    try {
        angularModule('ngLocale');
    } catch (e) {
        angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
    }
    //註冊ng模組,也就是angularJS的核心模組
    angularModule('ng', ['ngLocale'], ['$provide',
        function ngModule($provide) {
            // ... ...
        }
    ]);
}

主要功能為:

  • 對angular物件進行擴充套件;
  • 執行setupModuleLoader(window),該方法主要是定義了angular.module方法,用於註冊及獲取模組;angular.module只有一個引數的時候為獲取模組,否則為註冊模組;
  • 如果沒有註冊ngLocal模組,則對其進行註冊;
  • 註冊ng模組,也就是AngularJS的核心模組。

angularInit

function angularInit(element, bootstrap) {
    var appElement,
        module,
        config = {};

    //先判斷element元素是否包含ng-app屬性
    forEach(ngAttrPrefixes, function(prefix) {
        var name = prefix + 'app';

        if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
            appElement = element;
            module = element.getAttribute(name);
        }
    });
    //再判斷element元素的子元素中是否包含ng-app屬性
    forEach(ngAttrPrefixes, function(prefix) {
        var name = prefix + 'app';
        var candidate;

        if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
            appElement = candidate;
            module = candidate.getAttribute(name);
        }
    });
    if (appElement) {
        config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
        bootstrap(appElement, module ? [module] : [], config);
    }
}

需要說明的是,AngularJS支援的屬性字首有多種,包括ng-、data-ng-、ng:和x-ng-,具體請看ngAttrPrefixes變數。

  • 首先對element進行檢測,看它是否有ng-app等屬性,如果有則設定appElement和module;
  • 如果element元素沒有ng-app等屬性,則對其子元素進行查詢,找到第一個有ng-app等屬性的元素,從而設定appElement和module;
  • 如果appElement不為空,即找到了應用的入口元素,則執行bootstrap。

需要注意的是,如果有多個元素都有ng-app屬性,則只會找到第一個並啟動它,而後面的應用則不會自動啟動。因為這個函式主動執行一次。

應用啟動

應用的啟動方式主要包括自動啟動和手動啟動。

自動啟動

<div ng-app="MyModule">
    <div ng-controller="ctrl">

    </div>
</div>
<script>
    var myModule = angular.module('MyModule', []);
    myModule.controller('ctrl', ['$scope', function($scope) {
        $scope.name = 'alex';
    }]);
</script>

手動啟動

<div>
    <div ng-controller="ctrl">

    </div>
</div>
<script>
    var myModule = angular.module('MyModule', []);
    myModule.controller('ctrl', ['$scope', function($scope) {
        $scope.name = 'alex';
    }]);

    angular.element(document).ready(function() {
        angular.bootstrap(document, ['MyModule']);
    });
</script>

注意 :需要在ready函式中呼叫啟動函式,因為如果文件沒有載入完成,則angular無法掃描到含有ng-app標籤的元素。

  1. 引用jQuery的前提下,和$用法基本相同:angular.element(‘#id’).hide(),不引入jQuery,則功能較弱angular.element(document).find(‘xx’).hide()