1. 程式人生 > >AngularJS 原始碼分析3

AngularJS 原始碼分析3

本文接著上一篇講

上一篇地址

回顧

上次說到了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,然後返回一個函式,這個就是用來刪除監聽用的.

$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方法

$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,這三個很實用的方法

$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.

$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方法也不能常用,效能不是很理想

$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,分析的比較簡單,有啥錯誤的地方,希望大家能夠指出來,大家一起學習學習,下次有空接著分析別的.

作者宣告

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

相關推薦

AngularJS 原始碼分析3

本文接著上一篇講 上一篇地址 回顧 上次說到了rootScope裡的$watch方法中的解析監控表示式,即而引出了對parse的分析,今天我們接著這裡繼續挖程式碼. $watch續 先上一塊$watch程式碼 $watch: function(watchExp, listener, objectEquali

lucene原始碼分析(3)facet例項

簡單的facet例項 public class SimpleFacetsExample { private final Directory indexDir = new RAMDirectory(); private final Directory taxoDir = new RAMD

Shiro原始碼分析(3) - 認證器(Authenticator)

本文在於分析Shiro原始碼,對於新學習的朋友可以參考 [開濤部落格](http://jinnianshilongnian.iteye.com/blog/2018398)進行學習。 Authenticator就是認證器,在Shiro中負責認證使用者提交的資訊,

CocosCreator物理引擎Demo原始碼分析(3)-stick-arrow

stick-arrow示例展示瞭如何動態發射剛體飛往目標點。 技術點 1、觸控式螢幕幕發射剛體,計算起點和目標點的夾角,設定剛體的線性速度。 2、在Update中不斷施加一個作用力到剛體尾部,使它能一直往目標點飛去。 3、在碰撞上後,動態計算並設定WeldJoin

記一次 JVM 原始碼分析(3.記憶體管理與GC)

簡介 miniJVM 的記憶體管理的實現較為簡單 記憶體分配使用了開源的 ltalloc 庫 GC就是經典的 Mark-Sweep GC 物件分配 物件分配要關注的就兩個過程 New 一個 Java 物件的過程 記憶體塊在堆上分配的過程 物件在 JVM

NodeJS原始碼分析(3)

Node Stream模組 Stream在平時業務開發時很少用到, 但是很多模組都是基於stream實現的,引用官方文件的解釋: 流(stream)在 Node.js 中是處理流資料的抽象介面(abstract interface)。 stream 模組提

OKHttp原始碼分析3

1 概述 上篇文章,我們詳細分析了OKHttp中Request的建立和傳送過程。其中sendRequest(), readResponse(), followUpRequest()三個關鍵方法在底層HttpEngine中實現。革命尚未成功,我們接下來在這篇文章

coreutils4.5.1 dirname.c原始碼分析3

老調重彈,每次先按程式碼量排序,從行數少的程式開始讀,總能有所收穫。比如,在dirname.c中,我發現幾條: 第一、函式和括號可以用空格隔開,很奇怪。如 void usage (int status) 在usage與(中有一個空格,我寫了一個測試程式,也驗證了猜想。 第二、對字元取地址,真怪異!

coreutils4.5.1 basename.c原始碼分析3

coreutils4.5.1 basename.c原始碼分析2 前幾天又重新讀了basename.c對其中去掉字尾的那段,終於理解了。現總結如下; static void remove_suffix (char *name, const char *suffix) {   char *

Erlang:RabbitMQ原始碼分析 3. supervisor和supervisor2深入分析

supervisor也是Erlang/OTP裡一個常用的behavior,用於構建supervisor tree實現程序監控,故障恢復。 而RabbitMQ實現了一個supervisor2,我們從原始碼角度分析二者的實現和區別。 先介紹一些supervisor的基本概念,

LAV Filter 原始碼分析 3: LAV Video (1)

LAV Video 是使用很廣泛的DirectShow Filter。它封裝了FFMPEG中的libavcodec,支援十分廣泛的視訊格式的解碼。在這裡對其原始碼進行詳細的分析。LAV Video 工程程式碼的結構如下圖所示直接看LAV Video最主要的類CLAVVideo

AngularJS 原始碼分析2

上一篇地址 本文主要分析RootScopeProvider和ParseProvider RootScopeProvider簡介 今天這個rootscope可是angularjs裡面比較活躍的一個provider,大家可以理解為一個模型M或者VM,它主要負責與控制器或者指令進行資料互動. 今天使用的原始碼跟上次

AngularJS 原始碼分析1

AngularJS簡介 angularjs 是google出品的一款MVVM前端框架,包含一個精簡的類jquery庫,創新的開發了以指令的方式來元件化前端開發,可以去它的官網看看,請戳這裡 再貼上一個本文原始碼分析對應的angularjs原始碼合併版本1.2.4,精簡版的,除掉了所有的註釋, 請戳這裡 從啟動

vivi原始碼分析3

繼續分析vivi原始碼。 step 5:     MTD裝置初始化。     關於什麼是MTD,為什麼要使用MTD,MTD技術的架構是什麼,等等,可以參考《Linux MTD原始碼分析》(作者:Jim Zeus,2002-04-29)。這份文件的參考價值比較大,猜想作者

Spring原始碼分析3 — spring XML配置檔案的解析流程

1 介紹 建立並初始化spring容器中,關鍵一步就是讀取並解析spring XML配置檔案。這個過程比較複雜,本文將詳細分析整個流程。先看涉及到的關鍵類。 XmlWebApplicationContext:web應用的預設Spring容器 XmlBean

Redis網路庫原始碼分析(3)之ae.c

一、aeCreateEventLoop & aeCreateFileEvent 上一篇文章中,我們已經將伺服器啟動,只是其中有些細節我們跳過了,比如aeCreateEventLoop函式到底做了什麼? 接下來我們要分析ae.c檔案,它是整個Redis

Django原始碼分析3:處理請求wsgi分析與檢視View

django原始碼分析 本文環境python3.5.2,django1.10.x系列 根據前上一篇runserver的博文,已經分析了本地除錯伺服器的大致流程,現在我們來分析一下當runserver執行起來後,django框架是如何處理一個請求的,djan

malloc原始碼分析---3

malloc原始碼分析—_int_malloc 上一章分析了_int_malloc的前面一小部分,本章繼續往下看, _int_malloc — fastbin static void * _int_malloc(mstate av, size_t by

AngularJS 原始碼分析4

angularjs之$compile 今天主要說說ng裡的$compile,這是一個非常關鍵的服務,頁面上的雙向繫結,各個監聽基本上都是在這裡執行的. 原始碼部分還是引用angular1.2.4,連結在這裡下載 compile的源頭 ng裡最開始引用$compile

Monkey原始碼分析3—Monkey原始碼的整體設計結構

Monkey原始碼地址,點選檢視 Monkey自動化測試的本質就是隨機生成一個事件,然後向Android設備註入一個事件。通俗的來說就是,monkey隨機生成一個點選螢幕事件,然後選取Android螢幕的一個座標,對此座標進行點選操作。來實現自動化測試的。當然產生的事件不