国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
(譯) Angular運(yùn)行原理揭秘 Part 1 | AngularJS中文社區(qū)

當(dāng)你用AngularJS寫的應(yīng)用越多, 你會(huì)越發(fā)的覺得它相當(dāng)神奇. 之前我用AngularJS實(shí)現(xiàn)了相當(dāng)多酷炫的效果, 所以我決定去看看它的源碼, 我想這樣也許我能知道它的原理. 下面是我從源碼中找到的一些可以了解AngularJS那些高級(jí)(和隱藏)功能如何實(shí)現(xiàn)的代碼.

1) 依賴注入的實(shí)現(xiàn)原理

依賴注入(DI)讓我們可以不用自己實(shí)例化就能創(chuàng)建依賴對(duì)象的方法. 簡(jiǎn)單的來說, 依賴是以注入的方式傳遞的. 在Web應(yīng)用中, Angular讓我們可以通過DI來創(chuàng)建像Controllers和Directives這樣的對(duì)象. 我們還可以創(chuàng)建自己的依賴對(duì)象, 當(dāng)我們要實(shí)例化它們時(shí), Angular能自動(dòng)實(shí)現(xiàn)注入.

最常見的被注入對(duì)象應(yīng)該是 $scope 對(duì)象. 它可以像下面這樣被注入的:

function MainCtrl ($scope) {  // access to $scope}angular  .module(‘a(chǎn)pp’)  .controller(‘MainCtrl’, MainCtrl);

對(duì)于從來沒有接觸過依賴注入的Javascript開發(fā)人員來說, 這樣看起來只是像傳遞了一個(gè)參數(shù). 而實(shí)際上, 他是一個(gè)依賴注入的占位符. Angular通過這些占位符, 把真正的對(duì)象實(shí)例化給我們, 讓來看看他是怎么實(shí)現(xiàn)的.

function的參數(shù)

當(dāng)你運(yùn)行你代碼的時(shí)候, 如果你把function聲明中的參數(shù)換成一個(gè)其它字母, 那么Angular就無法找到你真正想實(shí)例化的對(duì)象. 因?yàn)锳ngular在我們的function上使用了 toString() 方法, 他將把我們的整個(gè)function變成一個(gè)字符串, 然后解析function中聲明的每一個(gè)參數(shù). 它使用下面4個(gè)正則(RegExps)來完成這件事情.

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;var FN_ARG_SPLIT = /,/;var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

Angular做的第一件事情就是把我們的整個(gè)function轉(zhuǎn)換為字符串, 這確實(shí)是Javascript很強(qiáng)大的地方. 轉(zhuǎn)換后我們將得到如下字符串:

‘function MainCtrl ($scope) {...}’

然后, 他用正則移除了在 function() 中有可能的所有的注釋.

fnText = fn.toString().replace(STRIP_COMMENTS, '');

接著它提取其中的參數(shù)部分.

argDecl = fnText.match(FN_ARGS);

最后它使用 .split() 方法來移除參數(shù)中的所有空格, 完美! Angular使用一個(gè)內(nèi)部的 forEach 方法來遍歷這些參數(shù), 然后把他們放入一個(gè) $inject 數(shù)組中.

forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {  arg.replace(FN_ARG, function(all, underscore, name) {    $inject.push(name);  });});

正如你現(xiàn)在想的, 這是一個(gè)很大的性能開銷操作. 每個(gè)函數(shù)都要執(zhí)行4個(gè)正則表達(dá)式還有大量的轉(zhuǎn)換操作----這將給我們帶來性能損失. 不過我們可以通過直接添加需要注入的對(duì)象到 $inject 數(shù)組中的方式來避免這個(gè)開銷.

$inject

我們可以在function對(duì)象上添加一個(gè) $inject 屬性來告訴Angular我們的依賴對(duì)象. 如果對(duì)象是存在的, Angular將實(shí)例化它. 這樣的語法更具有可讀性, 因?yàn)槲覀兛梢赃@些對(duì)象是被注入的. 下面是一個(gè)例子:

function SomeCtrl ($scope) {}SomeCtrl.$inject = ['$scope'];angular  .module('app', [])  .controller('SomeCtrl', ['$scope', SomeCtrl]);

這將節(jié)省框架的大量操作, 它不用再解析function的參數(shù), 也不用去操作數(shù)組(查看下一節(jié)數(shù)組參數(shù)), 它可以直接獲取我們已經(jīng)傳遞給他的 $inject 屬性. 簡(jiǎn)單, 高效.

理想情況下我們應(yīng)該使用構(gòu)建工具, 比如 Grunt.js 或者 Gulp.js 來做這個(gè)事情: 讓他們?cè)诰幾g時(shí)生成相應(yīng)的 $injext 屬性, 這樣能讓W(xué)eb應(yīng)用運(yùn)行的更快.

注: 實(shí)際上上面介紹的內(nèi)容并不涉如何實(shí)例化那些需要被注入的對(duì)象. 整個(gè)操作只是標(biāo)記出需要的名字----實(shí)例化的操作將由框架的另一部分來完成.

數(shù)組參數(shù)

最后要提到的是數(shù)組參數(shù). 數(shù)組的前面每個(gè)元素的名字和順序, 剛是數(shù)組最后一個(gè)元素function的參數(shù)名字和順序. 比如: [‘$scope’, function ($scope) {}].

這個(gè)順序是非常重要的, 因?yàn)锳ngular是以這個(gè)順序來實(shí)例化對(duì)象. 如果順序不正確, 那么它可能將其它對(duì)象錯(cuò)誤的實(shí)例化到你真正需要的對(duì)象上.

function SomeCtrl ($scope, $rootScope) {}angular  .module('app', [])  .controller('SomeCtrl', ['$scope', ‘$rootScope’, SomeCtrl]);

像上面一樣, 我們需要做的就是把函數(shù)最為數(shù)組的最后一個(gè)元素. 然后Angular會(huì)遍歷前面的每一個(gè)元素, 把它們添加到 $inject 數(shù)組中. 當(dāng)Angular開始解析一個(gè)函數(shù)的時(shí)候, 它會(huì)先檢查目標(biāo)對(duì)象是不是一個(gè)數(shù)組類型, 如果是的話, 他將把最后一個(gè)元素作為真正的function, 其它的元素都作為依賴對(duì)象添加到 $inject 中.

} else if (isArray(fn)) {  last = fn.length - 1;  assertArgFn(fn[last], 'fn');  $inject = fn.slice(0, last);}

2) Factory和Service

Factory和Service看起來非常相似, 以至于很多開發(fā)人員都無法理解它們有什么不同.

當(dāng)實(shí)例化一個(gè) .service() 的時(shí)候, 其實(shí)他將通過調(diào)用 new Service() 的形式來給我們創(chuàng)建一個(gè)新的實(shí)例, .service() 的方法像是一個(gè)構(gòu)造函數(shù).

服務(wù)(service)實(shí)際上來說是一個(gè)最基本的工廠(factory), 但是它是通過 new 來創(chuàng)建的, 你需要使用 this 來添加你需要的變量和函數(shù), 最后返回這個(gè)對(duì)象.

工廠(factory)實(shí)際上是非常接近面向?qū)ο笾械?工廠模式(factory pattern)". 當(dāng)你調(diào)用時(shí), 它會(huì)創(chuàng)建新的實(shí)例. 本質(zhì)上來說, 那個(gè)實(shí)例是一個(gè)全新的對(duì)象.

下面是Angular內(nèi)部實(shí)際執(zhí)行的源碼:

  function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }  function service(name, constructor) {    return factory(name, ['$injector', function($injector) {      return $injector.instantiate(constructor);    }]);  }

3) 從 $rootScope 中創(chuàng)建新的 $scope

所有的scope對(duì)象都繼承于 $rootScope, $rootScope 又是通過 new Scope() 來創(chuàng)建的. 所有的子scope都是用過調(diào)用 $scope.$new() 來創(chuàng)建的.

var $rootScope = new Scope();

它內(nèi)部有一個(gè) $new 方法, 讓新的scope可以從原型鏈上引用它們的父scope, 子scope(為了digest cycle), 以及前后的scope.

從下面的代碼可以看出, 如果你想創(chuàng)建一個(gè)獨(dú)立的scope, 那么你應(yīng)該使用 new Scope(), 否則它將以繼承的方式來創(chuàng)建.

我省略了一些不必要的代碼, 下面是他的核心實(shí)現(xiàn)

  $new: function(isolate) {    var child;    if (isolate) {      child = new Scope();      child.$root = this.$root;    } else {      // Only create a child scope class if somebody asks for one,      // but cache it to allow the VM to optimize lookups.      if (!this.$$ChildScope) {        this.$$ChildScope = function ChildScope() {          this.$$watchers = null;        };        this.$$ChildScope.prototype = this;      }      child = new this.$$ChildScope();    }    child['this'] = child;    child.$parent = this;    return child;  }

理解這一點(diǎn)對(duì)寫測(cè)試非常重要, 如果你想測(cè)試你的Controller, 那么你應(yīng)該使用 $scope.$new() 來創(chuàng)建$scope對(duì)象. 明白scope是如何創(chuàng)建的在測(cè)試驅(qū)動(dòng)開發(fā)(TDD)中是十分重要的, 這將更加有助于你mock module.

4) Digest Cycle

digest cycle的實(shí)現(xiàn)其實(shí)就是我們經(jīng)常看到的 $digest 關(guān)鍵字, Angular強(qiáng)大的雙向綁定功能依賴于它. 每當(dāng)一個(gè)model被更新時(shí)他都會(huì)運(yùn)行, 檢查當(dāng)前值, 如果和以前的不同, 將觸發(fā)listener. 這些都是臟檢查(dirty checking)的基礎(chǔ)內(nèi)容. 他會(huì)檢查所有的model, 與它們?cè)瓉淼闹颠M(jìn)行比較, 如果不同, 觸發(fā)listener, 循環(huán), 直到不在有變化為止.

$scope.name = 'Todd';$scope.$watch(function() {    return $scope.name;}, function (newValue, oldValue) {    console.log('$scope.name was updated!');} );

當(dāng)你調(diào)用 $scope.$watch 的時(shí)候, 實(shí)際上干了2件事情. watch的第一個(gè)參數(shù)是一個(gè)function, 這個(gè)function的返回你想監(jiān)控的對(duì)象(如果你傳遞的是一個(gè)string, Angular會(huì)把他轉(zhuǎn)換為一個(gè)function). digest cycle 運(yùn)行的時(shí)候, 它會(huì)調(diào)用這個(gè)function. 第二個(gè)參數(shù)也是一個(gè)function, 當(dāng)?shù)谝粋€(gè)function的值發(fā)生變化的時(shí)候它會(huì)被調(diào)用. 讓我們看看他是怎么實(shí)現(xiàn)監(jiān)控的:

$watch: function(watchExp, listener, objectEquality) {    var get = $parse(watchExp);    if (get.$$watchDelegate) {      return get.$$watchDelegate(this, listener, objectEquality, get);    }    var scope = this,        array = scope.$$watchers,        watcher = {          fn: listener,          last: initWatchVal,          get: get,          exp: watchExp,          eq: !!objectEquality        };    lastDirtyWatch = null;    if (!isFunction(listener)) {      watcher.fn = noop;    }    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;    };  }

這個(gè)方法將會(huì)把參數(shù)添加到scope中的 $$watchers 數(shù)組中, 并且它會(huì)返回一個(gè)function, 以便于你想結(jié)束這個(gè)監(jiān)控操作.

然后digest cycle會(huì)在每次調(diào)用 $scope.$apply 或者 $scope.$digest 的時(shí)候運(yùn)行. $scope.$apply 實(shí)際上是一個(gè)rootScope的包裝, 他會(huì)從根$rootScope向下廣播. 而 $scope.$digest 只會(huì)在當(dāng)前scope中運(yùn)行(并向下級(jí)scope廣播).

$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;      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, null) : 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) {              $exceptionHandler(e);            }          }        }      } 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);      }    }  }

這個(gè)實(shí)現(xiàn)非常有才, 雖然我沒有進(jìn)去看它是如何向下級(jí)廣播的, 但這里的關(guān)鍵是循環(huán)遍歷 $$watchers, 執(zhí)行里面的函數(shù)(就是那個(gè)你通過 $scope.$watch 注冊(cè)的第一個(gè)function), 然后如果得到和之前不同的值, 他又將調(diào)用listener(那個(gè)你傳遞的第二個(gè)function). 然后, 砰! 我們得到了一個(gè)變量發(fā)生改變的通知. 關(guān)鍵是我們是如何知道一個(gè)值發(fā)生變化了的? 當(dāng)一個(gè)值被更新的時(shí)候digest cycle會(huì)運(yùn)行(盡管它可能不是必須的). 比如在 ng-model 上, 每一個(gè)keydown事件都會(huì)觸發(fā)digest cycle.

$scope.$apply

當(dāng)你想在Angular框架之外做點(diǎn)什么的時(shí)候, 比如在 setTimeout 的方法里面你想讓Angular知道你可能改變了某個(gè)model的值. 那么你需要使用 $scope.$apply, 你把一個(gè)function放在它的參數(shù)之中, 那么他會(huì)在Angular的作用域運(yùn)行它, 然后在 $rootScope 上調(diào)用 $digest. 它將向它下面所有的scope進(jìn)行廣播, 這將觸發(fā)你注冊(cè)的所有l(wèi)isteners和watchers. 這一點(diǎn)意味著Angular可以知道你更新了任何作用域的變量.

通過特征檢查和閉包實(shí)現(xiàn)Polyfilling

Angular實(shí)現(xiàn)polyfilling的方式非常巧妙, 它不是用像 Function.prototype.bind 一樣的方式直接綁定在一個(gè)對(duì)象的原型鏈上. Angular會(huì)調(diào)用一個(gè)function來判定瀏覽器是否支持這個(gè)方法(基礎(chǔ)特征檢查), 如果存在它會(huì)直接返回這個(gè)方法. 如果不存在, 他將使用一段簡(jiǎn)短的代碼來實(shí)現(xiàn)它.

這樣是比較安全的方式. 如果直接在原型鏈上綁定方法, 那么它可能會(huì)覆蓋其它類庫或者框架的代碼(甚至是我們自己的代碼). 閉包也讓我們可以更安全的儲(chǔ)存和計(jì)算那些臨時(shí)變量, 如果存在這個(gè)方法, Angular將直接調(diào)用. 原生方法通常會(huì)帶來極大的性能提升.

函數(shù)功能檢查

Angular支持IE8+的瀏覽器(撰寫本文時(shí)Angular版本是1.2.x), 這意味著它還是要兼容老的瀏覽器, 為它們提供那些沒有的功能. 讓我們來用 indexOf 來舉例.

function indexOf(array, obj) {  if (array.indexOf) return array.indexOf(obj);  for (var i = 0; i < array.length; i++) {    if (obj === array[i]) return i;  }  return -1;}

它直接取代了原來的 array.indexOf 方法, 它自己實(shí)現(xiàn)了indexOf方法. 但如果瀏覽器支持這個(gè)函數(shù), 他將直接調(diào)用原生方法. 十分簡(jiǎn)單.

閉包

實(shí)現(xiàn)閉包可以用一個(gè)立即執(zhí)行函數(shù)(IIFE). 比如下面這個(gè) isArray 方法, 如果瀏覽器不支持這個(gè)功能, 它將使用閉包返回一個(gè) Array.isArray 的實(shí)現(xiàn). 如果 Array.isArray 是一個(gè)函數(shù), 那么它將直接使用原生方法----又一個(gè)提高性能的方法. IIFE可以讓我們十分的方便來封裝一些東西, 然后只返回我們需要的內(nèi)容.

 var isArray = (function() {  if (!isFunction(Array.isArray)) {    return function(value) {      return toString.call(value) === '[object Array]';    };  }  return Array.isArray;})();

這就是我看的第一部分Angular源碼, 第二部分將在下周發(fā)布.

原文: AngularJS: Looking under the hood [Part 1]

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
scope中的兩個(gè)異步方法
(二十) 理解和解決angularJS報(bào)錯(cuò)$apply already in progress
Angularjs十大經(jīng)典面試題
再談angularJS數(shù)據(jù)綁定機(jī)制及背后原理
使用超動(dòng)感HTML & JS開發(fā)WEB應(yīng)用! | AngularJS中文社區(qū)
項(xiàng)目演化系列
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服