最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

angularjs 源碼解析之scope

 更新時(shí)間:2016年08月22日 09:22:10   作者:alexqdjay  
$scope 的使用貫穿整個(gè) Angular App 應(yīng)用,它與數(shù)據(jù)模型相關(guān)聯(lián),同時(shí)也是表達(dá)式執(zhí)行的上下文.有了 $scope 就在視圖和控制器之間建立了一個(gè)通道,基于作用域視圖在修改數(shù)據(jù)時(shí)會(huì)立刻更新 $scope,同樣的 $scope 發(fā)生改變時(shí)也會(huì)立刻重新渲染視圖.

簡(jiǎn)介

在ng的生態(tài)中scope處于一個(gè)核心的地位,ng對(duì)外宣稱的雙向綁定的底層其實(shí)就是scope實(shí)現(xiàn)的,本章主要對(duì)scope的watch機(jī)制、繼承性以及事件的實(shí)現(xiàn)作下分析。

監(jiān)聽(tīng)

1. $watch

1.1 使用

// $watch: function(watchExp, listener, objectEquality)

var unwatch = $scope.$watch('aa', function () {}, isEqual);

使用過(guò)angular的會(huì)經(jīng)常這上面這樣的代碼,俗稱“手動(dòng)”添加監(jiān)聽(tīng),其他的一些都是通過(guò)插值或者directive自動(dòng)地添加監(jiān)聽(tīng),但是原理上都一樣。

1.2 源碼分析

function(watchExp, listener, objectEquality) {
 var scope = this,
   // 將可能的字符串編譯成fn
   get = compileToFn(watchExp, 'watch'),
   array = scope.$$watchers,
   watcher = {
    fn: listener,
    last: initWatchVal,  // 上次值記錄,方便下次比較
    get: get,
    exp: watchExp,
    eq: !!objectEquality // 配置是引用比較還是值比較
   };

 lastDirtyWatch = null;

 if (!isFunction(listener)) {
  var listenFn = compileToFn(listener || noop, 'listener');
  watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
 }

 if (!array) {
  array = scope.$$watchers = [];
 }
 
 // 之所以使用unshift不是push是因?yàn)樵?$digest 中watchers循環(huán)是從后開(kāi)始
 // 為了使得新加入的watcher也能在當(dāng)次循環(huán)中執(zhí)行所以放到隊(duì)列最前
 array.unshift(watcher);

 // 返回unwatchFn, 取消監(jiān)聽(tīng)
 return function deregisterWatch() {
  arrayRemove(array, watcher);
  lastDirtyWatch = null;
 };
}

從代碼看 $watch 還是比較簡(jiǎn)單,主要就是將 watcher 保存到 $$watchers 數(shù)組中

2. $digest

當(dāng) scope 的值發(fā)生改變后,scope是不會(huì)自己去執(zhí)行每個(gè)watcher的listenerFn,必須要有個(gè)通知,而發(fā)送這個(gè)通知的就是 $digest

2.1 源碼分析

整個(gè) $digest 的源碼差不多100行,主體邏輯集中在【臟值檢查循環(huán)】(dirty check loop) 中, 循環(huán)后也有些次要的代碼,如 postDigestQueue 的處理等就不作詳細(xì)分析了。

臟值檢查循環(huán),意思就是說(shuō)只要還有一個(gè) watcher 的值存在更新那么就要運(yùn)行一輪檢查,直到?jīng)]有值更新為止,當(dāng)然為了減少不必要的檢查作了一些優(yōu)化。

代碼:

// 進(jìn)入$digest循環(huán)打上標(biāo)記,防止重復(fù)進(jìn)入
beginPhase('$digest');

lastDirtyWatch = null;

// 臟值檢查循環(huán)開(kāi)始
do {
 dirty = false;
 current = target;

 // asyncQueue 循環(huán)省略

 traverseScopesLoop:
 do {
  if ((watchers = current.$$watchers)) {
   length = watchers.length;
   while (length--) {
    try {
     watch = watchers[length];
     if (watch) {
      // 作更新判斷,是否有值更新,分解如下
      // value = watch.get(current), last = watch.last
      // value !== last 如果成立,則判斷是否需要作值判斷 watch.eq?equals(value, last)
      // 如果不是值相等判斷,則判斷 NaN的情況,即 NaN !== NaN
      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;
       // 記錄這個(gè)循環(huán)中哪個(gè)watch發(fā)生改變
       lastDirtyWatch = watch;
       // 緩存last值
       watch.last = watch.eq ? copy(value, null) : value;
       // 執(zhí)行l(wèi)istenerFn(newValue, lastValue, scope)
       // 如果第一次執(zhí)行,那么 lastValue 也設(shè)置為newValue
       watch.fn(value, ((last === initWatchVal) ? value : last), current);
       
       // ... watchLog 省略 
       
       if (watch.get.$$unwatch) stableWatchesCandidates.push({watch: watch, array: watchers});
      } 
      // 這邊就是減少watcher的優(yōu)化
      // 如果上個(gè)循環(huán)最后一個(gè)更新的watch沒(méi)有改變,即本輪也沒(méi)有新的有更新的watch
      // 那么說(shuō)明整個(gè)watches已經(jīng)穩(wěn)定不會(huì)有更新,本輪循環(huán)就此結(jié)束,剩下的watch就不用檢查了
      else if (watch === lastDirtyWatch) {
       dirty = false;
       break traverseScopesLoop;
      }
     }
    } catch (e) {
     clearPhase();
     $exceptionHandler(e);
    }
   }
  }

  // 這段有點(diǎn)繞,其實(shí)就是實(shí)現(xiàn)深度優(yōu)先遍歷
  // A->[B->D,C->E]
  // 執(zhí)行順序 A,B,D,C,E
  // 每次優(yōu)先獲取第一個(gè)child,如果沒(méi)有那么獲取nextSibling兄弟,如果連兄弟都沒(méi)了,那么后退到上一層并且判斷該層是否有兄弟,沒(méi)有的話繼續(xù)上退,直到退到開(kāi)始的scope,這時(shí)next==null,所以會(huì)退出scopes的循環(huán)
  if (!(next = (current.$$childHead ||
    (current !== target && current.$$nextSibling)))) {
   while(current !== target && !(next = current.$$nextSibling)) {
    current = current.$parent;
   }
  }
 } while ((current = next));

 // break traverseScopesLoop 直接到這邊

 // 判斷是不是還處在臟值循環(huán)中,并且已經(jīng)超過(guò)最大檢查次數(shù) ttl默認(rèn)10
 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); // 循環(huán)結(jié)束

// 標(biāo)記退出digest循環(huán)
clearPhase();

上述代碼中存在3層循環(huán)

第一層判斷 dirty,如果有臟值那么繼續(xù)循環(huán)

do {

  // ...

} while (dirty)

第二層判斷 scope 是否遍歷完畢,代碼翻譯了下,雖然還是繞但是能看懂

do {

    // ....

    if (current.$$childHead) {
      next =  current.$$childHead;
    } else if (current !== target && current.$$nextSibling) {
      next = current.$$nextSibling;
    }
    while (!next && current !== target && !(next = current.$$nextSibling)) {
      current = current.$parent;
    }
} while (current = next);

第三層循環(huán)scope的 watchers

length = watchers.length;
while (length--) {
  try {
    watch = watchers[length];
   
    // ... 省略

  } catch (e) {
    clearPhase();
    $exceptionHandler(e);
  }
}

3. $evalAsync

3.1 源碼分析

$evalAsync用于延遲執(zhí)行,源碼如下:

function(expr) {
 if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
  $browser.defer(function() {
   if ($rootScope.$$asyncQueue.length) {
    $rootScope.$digest();
   }
  });
 }

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

通過(guò)判斷是否已經(jīng)有 dirty check 在運(yùn)行,或者已經(jīng)有人觸發(fā)過(guò)$evalAsync

if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length)
$browser.defer 就是通過(guò)調(diào)用 setTimeout 來(lái)達(dá)到改變執(zhí)行順序 

$browser.defer(function() {
 //...   
});

如果不是使用defer,那么

function (exp) {
 queue.push({scope: this, expression: exp});

 this.$digest();
}

scope.$evalAsync(fn1);
scope.$evalAsync(fn2);

// 這樣的結(jié)果是
// $digest() > fn1 > $digest() > fn2
// 但是實(shí)際需要達(dá)到的效果:$digest() > fn1 > fn2

上節(jié) $digest 中省略了了async 的內(nèi)容,位于第一層循環(huán)中

while(asyncQueue.length) {
 try {
  asyncTask = asyncQueue.shift();
  asyncTask.scope.$eval(asyncTask.expression);
 } catch (e) {
  clearPhase();
  $exceptionHandler(e);
 }
 lastDirtyWatch = null;
}

簡(jiǎn)單易懂,彈出asyncTask進(jìn)行執(zhí)行。

不過(guò)這邊有個(gè)細(xì)節(jié),為什么這么設(shè)置呢?原因如下,假如在某次循環(huán)中執(zhí)行到watchX時(shí)新加入1個(gè)asyncTask,此時(shí)會(huì)設(shè)置 lastDirtyWatch=watchX,恰好該task執(zhí)行會(huì)導(dǎo)致watchX后續(xù)的一個(gè)watch執(zhí)行出新值,如果沒(méi)有下面的代碼,那么下個(gè)循環(huán)到 lastDirtyWatch (watchX)時(shí)便跳出循環(huán),并且此時(shí)dirty==false。

lastDirtyWatch = null;

還有這邊還有一個(gè)細(xì)節(jié),為什么在第一層循環(huán)呢?因?yàn)榫哂欣^承關(guān)系的scope其 $$asyncQueue 是公用的,都是掛載在root上,故不需要在下一層的scope層中執(zhí)行。

2. 繼承性

scope具有繼承性,如 $parentScope, $childScope 兩個(gè)scope,當(dāng)調(diào)用 $childScope.fn 時(shí)如果 $childScope 中沒(méi)有 fn 這個(gè)方法,那么就是去 $parentScope上查找該方法。

這樣一層層往上查找直到找到需要的屬性。這個(gè)特性是利用 javascirpt 的原型繼承的特點(diǎn)實(shí)現(xiàn)。

源碼:

function(isolate) {
 var ChildScope,
   child;

 if (isolate) {
  child = new Scope();
  child.$root = this.$root;
  // isolate 的 asyncQueue 及 postDigestQueue 也都是公用root的,其他獨(dú)立
  child.$$asyncQueue = this.$$asyncQueue;
  child.$$postDigestQueue = this.$$postDigestQueue;
 } else {
  if (!this.$$childScopeClass) {
   this.$$childScopeClass = function() {
    // 這里可以看出哪些屬性是隔離獨(dú)有的,如$$watchers, 這樣就獨(dú)立監(jiān)聽(tīng)了,
    this.$$watchers = this.$$nextSibling =
      this.$$childHead = this.$$childTail = null;
    this.$$listeners = {};
    this.$$listenerCount = {};
    this.$id = nextUid();
    this.$$childScopeClass = null;
   };
   this.$$childScopeClass.prototype = this;
  }
  child = new this.$$childScopeClass();
 }
 // 設(shè)置各種父子,兄弟關(guān)系,很亂!
 child['this'] = child;
 child.$parent = this;
 child.$$prevSibling = this.$$childTail;
 if (this.$$childHead) {
  this.$$childTail.$$nextSibling = child;
  this.$$childTail = child;
 } else {
  this.$$childHead = this.$$childTail = child;
 }
 return child;
}

代碼還算清楚,主要的細(xì)節(jié)是哪些屬性需要獨(dú)立,哪些需要基礎(chǔ)下來(lái)。

最重要的代碼:

this.$$childScopeClass.prototype = this;

就這樣實(shí)現(xiàn)了繼承。

3. 事件機(jī)制

3.1 $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);
 };
}

跟 $wathc 類似,也是存放到數(shù)組 -- namedListeners。

還有不一樣的地方就是該scope和所有parent都保存了一個(gè)事件的統(tǒng)計(jì)數(shù),廣播事件時(shí)有用,后續(xù)分析。

var current = this;
do {
 if (!current.$$listenerCount[name]) {
  current.$$listenerCount[name] = 0;
 }
 current.$$listenerCount[name]++;
} while ((current = current.$parent));

3.2 $emit

$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++) {
   // 當(dāng)監(jiān)聽(tīng)remove以后,不會(huì)從數(shù)組中刪除,而是設(shè)置為null,所以需要判斷
   if (!namedListeners[i]) {
    namedListeners.splice(i, 1);
    i--;
    length--;
    continue;
   }
   try {
    namedListeners[i].apply(null, listenerArgs);
   } catch (e) {
    $exceptionHandler(e);
   }
  }
  // 停止傳播時(shí)return
  if (stopPropagation) {
   event.currentScope = null;
   return event;
  }

  // emit是向上的傳播方式
  scope = scope.$parent;
 } while (scope);

 event.currentScope = null;

 return event;
}

3.3 $broadcast

$broadcast 是向內(nèi)傳播,即向child傳播,源碼:

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;

 while ((current = next)) {
  event.currentScope = current;
  listeners = current.$$listeners[name] || [];
  for (i=0, length = listeners.length; i<length; i++) {
   
   // 檢查是否已經(jīng)取消監(jiān)聽(tīng)了
   if (!listeners[i]) {
    listeners.splice(i, 1);
    i--;
    length--;
    continue;
   }

   try {
    listeners[i].apply(null, listenerArgs);
   } catch(e) {
    $exceptionHandler(e);
   }
  }
  
  // 在digest中已經(jīng)有過(guò)了
  if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
    (current !== target && current.$$nextSibling)))) {
   while(current !== target && !(next = current.$$nextSibling)) {
    current = current.$parent;
   }
  }
 }

 event.currentScope = null;
 return event;
}

其他邏輯比較簡(jiǎn)單,就是在深度遍歷的那段代碼比較繞,其實(shí)跟digest中的一樣,就是多了在路徑上判斷是否有監(jiān)聽(tīng),current.$$listenerCount[name],從上面$on的代碼可知,只要路徑上存在child有監(jiān)聽(tīng),那么該路徑頭也是有數(shù)字的,相反如果沒(méi)有說(shuō)明該路徑上所有child都沒(méi)有監(jiān)聽(tīng)事件。

if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
    (current !== target && current.$$nextSibling)))) {
 while(current !== target && !(next = current.$$nextSibling)) {
  current = current.$parent;
 }
}

傳播路徑:

Root>[A>[a1,a2], B>[b1,b2>[c1,c2],b3]]

Root > A > a1 > a2 > B > b1 > b2 > c1 > c2 > b3

4. $watchCollection

4.1 使用示例

$scope.names = ['igor', 'matias', 'misko', 'james'];
$scope.dataCount = 4;

$scope.$watchCollection('names', function(newNames, oldNames) {
 $scope.dataCount = newNames.length;
});

expect($scope.dataCount).toEqual(4);
$scope.$digest();

expect($scope.dataCount).toEqual(4);

$scope.names.pop();
$scope.$digest();

expect($scope.dataCount).toEqual(3);

4.2 源碼分析

function(obj, listener) {
 $watchCollectionInterceptor.$stateful = true;
 var self = this;
 var newValue;
 var oldValue;
 var veryOldValue;
 var trackVeryOldValue = (listener.length > 1);
 var changeDetected = 0;
 var changeDetector = $parse(obj, $watchCollectionInterceptor); 
 var internalArray = [];
 var internalObject = {};
 var initRun = true;
 var oldLength = 0;

 // 根據(jù)返回的changeDetected判斷是否變化
 function $watchCollectionInterceptor(_value) {
  // ...
  return changeDetected;
 }

 // 通過(guò)此方法調(diào)用真正的listener,作為代理
 function $watchCollectionAction() {
  
 }

 return this.$watch(changeDetector, $watchCollectionAction);
}

主脈絡(luò)就是上面截取的部分代碼,下面主要分析 $watchCollectionInterceptor 和 $watchCollectionAction

4.3 $watchCollectionInterceptor

function $watchCollectionInterceptor(_value) {
 newValue = _value;
 var newLength, key, bothNaN, newItem, oldItem;

 if (isUndefined(newValue)) return;

 if (!isObject(newValue)) {
  if (oldValue !== newValue) {
   oldValue = newValue;
   changeDetected++;
  }
 } else if (isArrayLike(newValue)) {
  if (oldValue !== internalArray) {
   oldValue = internalArray;
   oldLength = oldValue.length = 0;
   changeDetected++;
  }

  newLength = newValue.length;

  if (oldLength !== newLength) {
   changeDetected++;
   oldValue.length = oldLength = newLength;
  }
  for (var i = 0; i < newLength; i++) {
   oldItem = oldValue[i];
   newItem = newValue[i];

   bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
   if (!bothNaN && (oldItem !== newItem)) {
    changeDetected++;
    oldValue[i] = newItem;
   }
  }
 } else {
  if (oldValue !== internalObject) {
   oldValue = internalObject = {};
   oldLength = 0;
   changeDetected++;
  }
  newLength = 0;
  for (key in newValue) {
   if (hasOwnProperty.call(newValue, key)) {
    newLength++;
    newItem = newValue[key];
    oldItem = oldValue[key];

    if (key in oldValue) {
     bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
     if (!bothNaN && (oldItem !== newItem)) {
      changeDetected++;
      oldValue[key] = newItem;
     }
    } else {
     oldLength++;
     oldValue[key] = newItem;
     changeDetected++;
    }
   }
  }
  if (oldLength > newLength) {
   changeDetected++;
   for (key in oldValue) {
    if (!hasOwnProperty.call(newValue, key)) {
     oldLength--;
     delete oldValue[key];
    }
   }
  }
 }
 return changeDetected;
}

1). 當(dāng)值為undefined時(shí)直接返回。

2). 當(dāng)值為普通基本類型時(shí) 直接判斷是否相等。

3). 當(dāng)值為類數(shù)組 (即存在 length 屬性,并且 value[i] 也成立稱為類數(shù)組),先沒(méi)有初始化先初始化oldValue

if (oldValue !== internalArray) {
 oldValue = internalArray;
 oldLength = oldValue.length = 0;
 changeDetected++;
}

然后比較數(shù)組長(zhǎng)度,不等的話記為已變化 changeDetected++

if (oldLength !== newLength) {
 changeDetected++;
 oldValue.length = oldLength = newLength;
}

再進(jìn)行逐個(gè)比較

for (var i = 0; i < newLength; i++) {
 oldItem = oldValue[i];
 newItem = newValue[i];

 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
 if (!bothNaN && (oldItem !== newItem)) {
  changeDetected++;
  oldValue[i] = newItem;
 }
}

4). 當(dāng)值為object時(shí),類似上面進(jìn)行初始化處理

if (oldValue !== internalObject) {
 oldValue = internalObject = {};
 oldLength = 0;
 changeDetected++;
}

接下來(lái)的處理比較有技巧,但凡發(fā)現(xiàn) newValue 多的新字段,就在oldLength 加1,這樣 oldLength 只加不減,很容易發(fā)現(xiàn) newValue 中是否有新字段出現(xiàn),最后把 oldValue中多出來(lái)的字段也就是 newValue 中刪除的字段給移除就結(jié)束了。

newLength = 0;
for (key in newValue) {
 if (hasOwnProperty.call(newValue, key)) {
  newLength++;
  newItem = newValue[key];
  oldItem = oldValue[key];

  if (key in oldValue) {
   bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
   if (!bothNaN && (oldItem !== newItem)) {
    changeDetected++;
    oldValue[key] = newItem;
   }
  } else {
   oldLength++;
   oldValue[key] = newItem;
   changeDetected++;
  }
 }
}
if (oldLength > newLength) {
 changeDetected++;
 for (key in oldValue) {
  if (!hasOwnProperty.call(newValue, key)) {
   oldLength--;
   delete oldValue[key];
  }
 }
}

4.4 $watchCollectionAction

function $watchCollectionAction() {
 if (initRun) {
  initRun = false;
  listener(newValue, newValue, self);
 } else {
  listener(newValue, veryOldValue, self);
 }

 // trackVeryOldValue = (listener.length > 1) 查看listener方法是否需要oldValue
 // 如果需要就進(jìn)行復(fù)制
 if (trackVeryOldValue) {
  if (!isObject(newValue)) {
   veryOldValue = newValue;
  } else if (isArrayLike(newValue)) {
   veryOldValue = new Array(newValue.length);
   for (var i = 0; i < newValue.length; i++) {
    veryOldValue[i] = newValue[i];
   }
  } else { 
   veryOldValue = {};
   for (var key in newValue) {
    if (hasOwnProperty.call(newValue, key)) {
     veryOldValue[key] = newValue[key];
    }
   }
  }
 }
}

代碼還是比較簡(jiǎn)單,就是調(diào)用 listenerFn,初次調(diào)用時(shí) oldValue == newValue,為了效率和內(nèi)存判斷了下 listener是否需要oldValue參數(shù)

5. $eval & $apply

$eval: function(expr, locals) {
 return $parse(expr)(this, locals);
},
$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 最后調(diào)用 $rootScope.$digest(),所以很多書(shū)上建議使用 $digest() ,而不是調(diào)用 $apply(),效率要高點(diǎn)。

主要邏輯都在$parse 屬于語(yǔ)法解析功能,后續(xù)單獨(dú)分析。

相關(guān)文章

  • 使用angularjs.foreach時(shí)return的問(wèn)題解決

    使用angularjs.foreach時(shí)return的問(wèn)題解決

    這篇文章主要介紹了使用angularjs.foreach時(shí)return的問(wèn)題解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • Angular整合zTree的示例代碼

    Angular整合zTree的示例代碼

    本篇文章主要介紹了Angular整合zTree的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • angular ng-repeat數(shù)組中的數(shù)組實(shí)例

    angular ng-repeat數(shù)組中的數(shù)組實(shí)例

    下面小編就為大家?guī)?lái)一篇angular ng-repeat數(shù)組中的數(shù)組實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-02-02
  • 詳解angular應(yīng)用容器化部署

    詳解angular應(yīng)用容器化部署

    這篇文章主要介紹了詳解angular應(yīng)用容器化部署,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • Angular directive遞歸實(shí)現(xiàn)目錄樹(shù)結(jié)構(gòu)代碼實(shí)例

    Angular directive遞歸實(shí)現(xiàn)目錄樹(shù)結(jié)構(gòu)代碼實(shí)例

    本篇文章主要介紹了Angular directive遞歸實(shí)現(xiàn)目錄樹(shù)結(jié)構(gòu)代碼實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Angular2環(huán)境搭建具體操作步驟(推薦)

    Angular2環(huán)境搭建具體操作步驟(推薦)

    下面小編就為大家?guī)?lái)一篇Angular2環(huán)境搭建具體操作步驟(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • Angular.js與node.js項(xiàng)目里用cookie校驗(yàn)賬戶登錄詳解

    Angular.js與node.js項(xiàng)目里用cookie校驗(yàn)賬戶登錄詳解

    這篇文章主要介紹了Angular.js與node.js項(xiàng)目里用cookie校驗(yàn)賬戶登錄的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-02-02
  • Angular4集成ng2-file-upload的上傳組件

    Angular4集成ng2-file-upload的上傳組件

    本篇文章主要介紹了Angular4集成ng2-file-upload的上傳組件,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • Angular中的ActivatedRoute和Router原理解釋

    Angular中的ActivatedRoute和Router原理解釋

    這篇文章主要為大家介紹了Angular中的ActivatedRoute和Router原理解釋,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • angular中使用路由和$location切換視圖

    angular中使用路由和$location切換視圖

    這篇文章主要介紹了angular中使用路由和$location切換視圖,需要的朋友可以參考下
    2015-01-01

最新評(píng)論

舟曲县| 宝鸡市| 沈丘县| 太保市| 宁德市| 赤峰市| 铜山县| 凉城县| 武山县| 浦江县| 买车| 黑山县| 二手房| 六盘水市| 和顺县| 石泉县| 唐河县| 佛坪县| 镇原县| 红桥区| 本溪市| 阿勒泰市| 鹰潭市| 扬州市| 许昌市| 马鞍山市| 治县。| 南京市| 桐柏县| 柳林县| 和平区| 酒泉市| 德安县| 武邑县| 广安市| 全椒县| 赞皇县| 三门峡市| 洞头县| 黄大仙区| 石首市|