内射老阿姨1区2区3区4区_久久精品人人做人人爽电影蜜月_久久国产精品亚洲77777_99精品又大又爽又粗少妇毛片

深入理解AngularJs-scope的臟檢查(一)

進(jìn)入正文前的說(shuō)明:本文中的示例代碼并非AngularJs源碼,而是來(lái)自書(shū)籍<<Build Your Own AngularJs>>, 這本書(shū)的作者僅依賴(lài)jquery和lodash一步一步構(gòu)建出AngularJs的各核心模塊,對(duì)全面理解AngularJs有非常巨大的幫助。若有正在使用AngulaJs攻城拔寨并且希望完全掌握手中武器的小伙伴,相信能對(duì)你理解AngularJs帶來(lái)莫大幫助,感謝作者。

創(chuàng)新互聯(lián)建站2013年至今,先為固安等服務(wù)建站,固安等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢(xún)服務(wù)。為固安企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。

在這篇文章中,希望能讓您理清楚以下幾項(xiàng)與scope相關(guān)的功能:

1.dirty-checking(臟檢測(cè))核心機(jī)制,主要包括:$watch 和 $digest;

2.幾種不同的觸發(fā)$digest循環(huán)的方式:$eval, $apply, $evalAsync, $applyAsync;

3.scope的繼承機(jī)制以及isolated scope;

4.依賴(lài)于scope的事件循環(huán):$on, $broadcast, $emit.

現(xiàn)在開(kāi)始我們的第一部分:scope和dirty-checking

dirty-checking(臟檢測(cè))原理簡(jiǎn)述:scope通過(guò)$watch方法向this.$$watchers數(shù)組中添加watcher對(duì)象(包含watchFn, listenerFn, valueEq, last 四個(gè)屬性)。每當(dāng)$digest循環(huán)被觸發(fā)時(shí),它會(huì)遍歷$$watchers數(shù)組,執(zhí)行watcher中的watchFn,獲取當(dāng)前scope上某屬性的值(一個(gè)watcher對(duì)應(yīng)scope上一個(gè)被監(jiān)聽(tīng)屬性),然后去同watcher中的last(上一次的值)做比較,若兩值不相等,就執(zhí)行l(wèi)istenerFn。

function Scope() {
  this.$$watchers = []; // 監(jiān)聽(tīng)器數(shù)組
  this.$$lastDirtyWatch = null; // 每次digest循環(huán)的最后一個(gè)臟的watcher, 用于優(yōu)化digest循環(huán)
  this.$$asyncQueue = []; // scope上的異步隊(duì)列
  this.$$applyAsyncQueue = []; // scope上的異步apply隊(duì)列
  this.$$applyAsyncId = null; //異步apply信息
  this.$$postDigestQueue = []; // postDigest執(zhí)行隊(duì)列
  this.$$phase = null; // 儲(chǔ)存scope上正在做什么,值有:digest/apply/null
  this.$root = this; // rootScope

  this.$$listeners = {}; // 存儲(chǔ)包含自定義事件鍵值對(duì)的對(duì)象

  this.$$children = []; // 存儲(chǔ)當(dāng)前scope的兒子Scope,以便$digest循環(huán)遞歸
}

實(shí)際上scope就是一個(gè)普通的javascript對(duì)象,一個(gè)類(lèi)構(gòu)造函數(shù),可以通過(guò)new進(jìn)行實(shí)例化。根據(jù)臟檢測(cè)的原理,接下來(lái),我們一起看看scope的$watch方法的實(shí)現(xiàn)。

/* $watch方法:向watchers數(shù)組中添加watcher對(duì)象,以便對(duì)應(yīng)調(diào)用 */
Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) {
  var self = this;

  watchFn = $parse(watchFn);

  // watchDelegate: 針對(duì)watch expression是常量和 one-time-binding的情況,進(jìn)行優(yōu)化。在第一次初始化之后刪除watch
  if(watchFn.$$watchDelegate) {
    return watchFn.$$watchDelegate(self, listenerFn, valueEq, watchFn);
  }
  var watcher = {
    watchFn: watchFn,
    listenerFn: listenerFn || function() {},
    valueEq: !!valueEq,
    last: initWatchVal
  };

  this.$$watchers.unshift(watcher);
  this.$root.$$lastDirtyWatch = null;

  return function() {
    var index = self.$$watchers.indexOf(watcher);
    if(index >= 0) {
      self.$$watchers.splice(index, 1);
      self.$root.$$lastDirtyWatch = null;
    }
  };
};

$watch方法的參數(shù):

watchFn-監(jiān)視表達(dá)式,在使用$watch時(shí),通常是傳入一個(gè)expression, 經(jīng)過(guò)$parse服務(wù)處理后返回一個(gè)監(jiān)視函數(shù),提供動(dòng)態(tài)訪(fǎng)問(wèn)scope上屬性值的功能,可以看作 function() { return scope.someValue; }。

listenerFn-監(jiān)聽(tīng)函數(shù),當(dāng)$digest循環(huán)dirty時(shí)(即scope上$$watchers數(shù)組中有watcher監(jiān)測(cè)到屬性值變化時(shí)),執(zhí)行的回調(diào)函數(shù)。

valueEq-是否全等監(jiān)視,布爾值,valueEq默認(rèn)為false,此時(shí)$watch對(duì)監(jiān)視對(duì)象進(jìn)行“引用監(jiān)視”,如果被監(jiān)視的表達(dá)式是原始數(shù)據(jù)類(lèi)型,$watch能夠發(fā)現(xiàn)改變。如果被監(jiān)視的表達(dá)式是引用類(lèi)型,由于引用類(lèi)型的賦值只是將被賦值變量指向當(dāng)前引用,故$watch認(rèn)為沒(méi)有改變。若需要對(duì)引用類(lèi)型進(jìn)行監(jiān)視,則需要將valueEq設(shè)置為true,這是$watch會(huì)對(duì)被監(jiān)視對(duì)象進(jìn)行“全等監(jiān)視”,在每次比較前會(huì)用angular.copy()對(duì)被監(jiān)視對(duì)象進(jìn)行深拷貝,然后用angular.equal()進(jìn)行比對(duì)。雖然“全等監(jiān)視”能夠監(jiān)視到所有改變,但如果被監(jiān)視對(duì)象很大,性能肯定會(huì)大打折扣。所以應(yīng)該根據(jù)實(shí)際情況來(lái)使用valueEq。

從代碼中能夠看出,$watch的功能其實(shí)非常簡(jiǎn)單,就是構(gòu)造watcher對(duì)象,并將watcher對(duì)象插入到scope.$$watchers數(shù)組中,然后返回一個(gè)銷(xiāo)毀當(dāng)前watcher的函數(shù)。

接下來(lái)進(jìn)入到臟檢測(cè)最核心的部分:$digest循環(huán)

《Build your own AngularJs》的作者將$digest分成了兩個(gè)函數(shù):$digestOnce 和 $digest。這雖然不用與框架源碼,但能夠使代碼更易理解。兩個(gè)函數(shù)實(shí)際上分別對(duì)應(yīng)了$digest的內(nèi)層循環(huán)和外層循環(huán)。代碼如下:

內(nèi)層循環(huán)

Scope.prototype.$$digestOnce = function() {
      var dirty;
      var continueLoop = true;
      var self = this;

      this.$$everyScope(function(scope) {
        var newValue, oldValue;

        _.forEachRight(scope.$$watchers, function(watcher) {
          try {
            if(watcher) {
              newValue = watcher.watchFn(scope);
              oldValue = watcher.last;

              if(!scope.$$areEqual(newValue, oldValue, watcher.valueEq)) {
                scope.$root.$$lastDirtyWatch = watcher;

                watcher.last = (watcher.valueEq ? _.cloneDeep(newValue) : newValue);
                
                watcher.listenerFn(newValue,
                  (oldValue === initWatchVal? newValue : oldValue), scope);
                dirty = true;
              } else if(scope.$root.$$lastDirtyWatch === watcher) {
                continueLoop = false;
                return false;
              }
            }
          } catch(e) {
            console.error(e);
          }
        });
        return continueLoop;
      });

      return dirty;
    };

代碼中,$$everyScope是遞歸childScope執(zhí)行回調(diào)函數(shù)的工具方法,后面會(huì)貼出。

$digestOnce的核心邏輯就在$$everyScope方法的循環(huán)體內(nèi),即遍歷scope.$$watchers, 比對(duì)新舊值,根據(jù)比對(duì)結(jié)果確定是否執(zhí)行l(wèi)istenerFn,并向listenerFn中傳入newValue, oldValue, scope供開(kāi)發(fā)者獲取。

示例代碼第18行,watcher.last的賦值證實(shí)了上文提到的$watch的第三個(gè)參數(shù)valueEq的作用。

示例代碼第23行,由于$digest循環(huán)會(huì)一直運(yùn)行直到?jīng)]有dirty watcher時(shí),故單次$digest循環(huán)通過(guò)緩存最后一個(gè)dirty的watcher,在下一次$digest循環(huán)時(shí)如果遇到$$lastDirtyWatcher就停止當(dāng)前循環(huán)。這樣做減少了遍歷watcher的數(shù)量,優(yōu)化了性能。

 外層循環(huán)

在我們的示例中,外層循環(huán)即由 $digest來(lái)控制。$digest函數(shù)主要由do while循環(huán)體內(nèi)調(diào)用$digestOnce進(jìn)行臟檢測(cè) 以及 對(duì)其他一些異步操作的處理組成。代碼如下:

// digest循環(huán)的外循環(huán),保持循環(huán)直到?jīng)]有臟值為止
    Scope.prototype.$digest = function() {
      var ttl = TTL;
      var dirty;
      this.$root.$$lastDirtyWatch = null;

      this.$beginPhase('$digest');

      if(this.$root.$$applyAsyncId) {
        clearTimeout(this.$root.$$applyAsyncId);
        this.$$flushApplyAsync();
      }

      do {
        while (this.$$asyncQueue.length) {
          try {
            var asyncTask = this.$$asyncQueue.shift();
            asyncTask.scope.$eval(asyncTask.expression);
          } catch(e) {
            console.error(e);
          }
        }

        dirty = this.$$digestOnce();

        if((dirty || this.$$asyncQueue.length) && !(ttl--)) {
          this.$clearPhase();
          throw TTL + ' digest iterations reached';
        }
      } while (dirty || this.$$asyncQueue.length);
      this.$clearPhase();

      while(this.$$postDigestQueue.length) {
        try {
          this.$$postDigestQueue.shift()();
        } catch(e) {
          console.error(e);
        }
      }
    };

在這一節(jié)中我們的主要關(guān)注點(diǎn)是臟檢測(cè),異步任務(wù)相關(guān)的$$applyAsync,$$flushApplyAsync,$$asyncQueue,$$postDigestQueue之后再做分析。

示例代碼第24行,調(diào)用$$digestOnce,并把返回值賦值給dirty。在do while循環(huán)中,只要dirty為true,那么循環(huán)就會(huì)一直執(zhí)行下去,直到dirty的值為 false。這就是臟檢測(cè)機(jī)制的外層循環(huán)的實(shí)現(xiàn),是不是覺(jué)得其實(shí)很簡(jiǎn)單呢,嘿嘿。

設(shè)想一下,某些值可能會(huì)在listenerFn中持續(xù)被改變并且,無(wú)法穩(wěn)定下來(lái),那勢(shì)必會(huì)出現(xiàn)死循環(huán)。為了解決這個(gè)問(wèn)題,AngularJs使用 TTL(time to live)來(lái)對(duì)循環(huán)次數(shù)進(jìn)行控制,超過(guò)最大次數(shù),就會(huì)throw錯(cuò)誤 并 告訴開(kāi)發(fā)者循環(huán)可能永遠(yuǎn)不會(huì)穩(wěn)定。

現(xiàn)在我們把注意力移到代碼第26行的 if 代碼塊上,不難看出,這里是對(duì)最大$digest循環(huán)次數(shù)進(jìn)行了限制,每執(zhí)行一次do while循環(huán)的循環(huán)體,TTL就會(huì)自減1。當(dāng)TTL值為0,再進(jìn)行循環(huán)就會(huì)報(bào)錯(cuò)。當(dāng)然咯,這個(gè)TTL的值也是能夠進(jìn)行配置的。

現(xiàn)在,相信小伙伴們對(duì)$digest循環(huán)已經(jīng)比較清楚了吧~簡(jiǎn)單來(lái)說(shuō),dirty-checking就是依賴(lài)緩存在scope上的$$watchers和$digest循環(huán)來(lái)對(duì)值進(jìn)行監(jiān)聽(tīng)的。有了$digest,當(dāng)然還需要有手段去觸發(fā)它咯。

接下來(lái),我們將進(jìn)入第二部分:觸發(fā)$digest循環(huán) 和 異步任務(wù)處理 

$eval

說(shuō)到觸發(fā)$digest循環(huán),大部分同學(xué)都會(huì)想到$apply。要說(shuō)$apply就需要先說(shuō)說(shuō)$eval。

$eval使我們能夠在scope的context中執(zhí)行一段表達(dá)式,并允許傳入locals object對(duì)當(dāng)前scope context進(jìn)行修改。

tip:$parse服務(wù)能夠接受一個(gè)表達(dá)式或者函數(shù)作為參數(shù),經(jīng)過(guò)處理返回一個(gè)函數(shù)供開(kāi)發(fā)者調(diào)用。這個(gè)函數(shù)有兩個(gè)參數(shù)context object(通常就是scope),locals object(本地對(duì)象,常用來(lái)覆蓋context中的屬性)。

 Scope.prototype.$eval = function(expr, locals) {
   return $parse(expr)(this, locals);
 };

$apply

$apply 方法接收一個(gè)expression或者function作為參數(shù),$apply通過(guò)$eval函數(shù)執(zhí)行傳入的expression 或 function。最終從$rootScope上觸發(fā)$digest循環(huán)。

$apply 被認(rèn)為是 使AngularJs與第三方庫(kù)混合使用最標(biāo)準(zhǔn)的方式。初學(xué)者朋友剛開(kāi)始都會(huì)遇到用第三方庫(kù)修改了scope上的屬性或者被watch的屬性,但并沒(méi)有觸發(fā)$digest循環(huán),導(dǎo)致雙向綁定失效的問(wèn)題。此時(shí),$apply就是解決這種情況的良藥!

Scope.prototype.$apply = function(expr) {
  try {
    this.$beginPhase('$apply');
    return this.$eval(expr);
  } finally {
    this.$clearPhase();
    this.$root.$digest();
  }
};

$apply本質(zhì)上,就是用$eval執(zhí)行了一段表達(dá)式,再調(diào)用rootScope的$digest方法。

有時(shí)候,當(dāng)我們能夠確定我們不需要從rootScope開(kāi)始進(jìn)行$digest循環(huán)時(shí),我可以調(diào)用scope.digest() 來(lái)代替 $apply,這樣能夠帶來(lái)性能的提升。

 $evalAsync

$evalAsync 用于延遲執(zhí)行一段表達(dá)式。通常我們更習(xí)慣使用$timeout服務(wù)來(lái)進(jìn)行代碼的延遲執(zhí)行,但$timeout會(huì)將執(zhí)行控制權(quán)交給瀏覽器,如果瀏覽器同時(shí)還需要執(zhí)行諸如 ui渲染/事件控制/ajax 等任務(wù)時(shí),我們代碼延遲執(zhí)行的時(shí)機(jī)就會(huì)變得非常不可控。

我們來(lái)看看$evalAsync是如何讓代碼延遲執(zhí)行的時(shí)機(jī)變得嚴(yán)格,可控的。

Scope.prototype.$evalAsync = function(expr) {
  var self = this;
  if(!self.$$phase && !self.$$asyncQueue.length) {
    setTimeout(function() {
      if(self.$$asyncQueue.length) {
        self.$root.$digest();
      }
    }, 0);
  }

  this.$$asyncQueue.push({
    scope: this,
    expression: expr
  });
};

$evalAsync方法的主要功能是從代碼第11行開(kāi)始,向$$asyncQueeu中添加對(duì)象。$$asyncQueue隊(duì)列的執(zhí)行是在$digest的do while循環(huán)中進(jìn)行的。

while (this.$$asyncQueue.length) {
  try {
    var asyncTask = this.$$asyncQueue.shift();
    asyncTask.scope.$eval(asyncTask.expression);
  } catch(e) {
    console.error(e);
  }
}

$evalAsync的代碼會(huì)在正在運(yùn)行的$digest循環(huán)中被執(zhí)行,如果當(dāng)前沒(méi)有正在運(yùn)行的$digest循環(huán),會(huì)自己延遲觸發(fā)一個(gè)$digest循環(huán)來(lái)執(zhí)行延遲代碼。

 $applyAsync

$applyAsync用于合并短時(shí)間內(nèi)多次$digest循環(huán),優(yōu)化應(yīng)用性能。

在日常開(kāi)發(fā)工作中,常常會(huì)遇到要短時(shí)間內(nèi)接收若干http響應(yīng),同時(shí)觸發(fā)多次$digest循環(huán)的情況。使用$applyAsync可合并若干次$digest,優(yōu)化性能。

/* 這個(gè)方法用于 知道需要在短時(shí)間內(nèi)多次使用$apply的情況,
  能夠?qū)Χ虝r(shí)間內(nèi)多次$digest循環(huán)進(jìn)行合并,
  是針對(duì)$digest循環(huán)的優(yōu)化策略
  */
Scope.prototype.$applyAsync = function(expr) {
  var self = this;
  self.$$applyAsyncQueue.push(function() {
    self.$eval(expr);
  });

  if(self.$root.$$applyAsyncId === null) {
    self.$root.$$applyAsyncId = setTimeout(function() {
      self.$apply(_.bind(self.$$flushApplyAsync, self));
    }, 0);
  }
};

$$postDigest

$$postDigest方法提供了在下一次digest循環(huán)后執(zhí)行代碼的方式,這個(gè)方法的前綴是"$$",是一個(gè)AngularJs內(nèi)部方法,應(yīng)用開(kāi)發(fā)極少用到。

此方法不自主觸發(fā)$digest循環(huán),而是在別處產(chǎn)生$digest循環(huán)之后執(zhí)行。

/* $$postDigest 用于在下一次digest循環(huán)后執(zhí)行函數(shù)隊(duì)列 
   不同于applyAsync 和 evalAsync, 它不觸發(fā)digest循環(huán)
   */
 Scope.prototype.$$postDigest = function(fn) {
   this.$$postDigestQueue.push(fn);
 };

到這里,我們對(duì)臟檢測(cè)的原理,即它的工作機(jī)制就了解的差不多了。希望這些知識(shí)能夠幫助你更好的應(yīng)用AngularJs來(lái)開(kāi)發(fā),能夠更輕松地定位錯(cuò)誤。

下一章,我會(huì)繼續(xù)為大家介紹文章開(kāi)頭提到的另外兩處scope相關(guān)的特性。篇幅較長(zhǎng),感謝您的耐心閱讀~也希望大家多多支持創(chuàng)新互聯(lián)。

網(wǎng)站題目:深入理解AngularJs-scope的臟檢查(一)
文章鏈接:http://www.rwnh.cn/article40/ipcsho.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營(yíng)銷(xiāo)型網(wǎng)站建設(shè)定制開(kāi)發(fā)、云服務(wù)器、動(dòng)態(tài)網(wǎng)站、網(wǎng)站營(yíng)銷(xiāo)、搜索引擎優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

營(yíng)銷(xiāo)型網(wǎng)站建設(shè)
平罗县| 岑溪市| 菏泽市| 峡江县| 彭山县| 都匀市| 湛江市| 讷河市| 庆安县| 双流县| 延安市| 宁海县| 东海县| 武隆县| 读书| 甘谷县| 铜鼓县| 永德县| 柏乡县| 丁青县| 维西| 苗栗市| 富平县| 安陆市| 康平县| 泉州市| 溧水县| 贵溪市| 牟定县| 和平区| 永修县| 砀山县| 宁明县| 夹江县| 广宗县| 青浦区| 洛浦县| 炎陵县| 和硕县| 东兴市| 诏安县|