// 取ngRepeat表达式
var expression = $attr.ngRepeat;
var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression);
// 匹配该表达式 注意 in as track by
var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
if (!match) {
throw ngRepeatMinErr('iexp', 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got \'{0}\'.',
expression);
}
var lhs = match[1]; // 存储临时变量即in前面的括号部分,例如`item` in items 或者`(item, index)` in items
var rhs = match[2]; // 存储被循环的collection名,即上面的items
var aliasAs = match[3];// 存储别名
var trackByExp = match[4]; // 存储可选的track by字符串
// 具体的临时变量部分解析
match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);
if (!match) {
throw ngRepeatMinErr('iidexp', '\'_item_\' in \'_item_ in _collection_\' should be an identifier or \'(_key_, _value_)\' expression, but got \'{0}\'.',
lhs);
}
var valueIdentifier = match[3] || match[1]; // value
var keyIdentifier = match[2]; // key
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
throw ngRepeatMinErr('badident', 'alias \'{0}\' is invalid --- must be a valid JS identifier which is not a reserved name.',
aliasAs);
}
var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
var hashFnLocals = {$id: hashKey};
// 有track by语句则处理,否则如果colletion为对象则使用key作为id,如果数组使用hashKey
if (trackByExp) {
trackByExpGetter = $parse(trackByExp);
} else {
trackByIdArrayFn = function(key, value) {
return hashKey(value);
};
trackByIdObjFn = function(key) {
return key;
};
}
match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/); 中正则这样划分(\s*[$\w]+)和\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\)两部分,分别对应item in items和(key, value) in items的情形。其他见中文注释
下面是link函数的内容
// 如果有track by语句则使用track by语句
if (trackByExpGetter) {
trackByIdExpFn = function(key, value, index) {
// assign key, value, and $index to the locals so that they can be used in hash functions
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
hashFnLocals[valueIdentifier] = value;
hashFnLocals.$index = index;
return trackByExpGetter($scope, hashFnLocals);
};
}
//lastBlockMap是一个用来保存最近一次view更新完后各个循环item状态的hash。该hash的键是各个被循环的item,值则是一个保存了该item相应属性的对象:
//scope 与该item绑定的scope
//element view中在该元素之前的元素
//index item对应的元素在页面中的出现顺序,也即最后一次循环时,该item被处理的先后顺序。
var lastBlockMap = createMap();
//核心部分,监听collection
$scope.$watchCollection(rhs, function ngRepeatAction(collection) {
var index, length,
previousNode = $element[0], // node that cloned nodes should be inserted after
// initialized to the comment node anchor
nextNode,
// Same as lastBlockMap but it has the current state. It will become the
// lastBlockMap on the next iteration.
nextBlockMap = createMap(),
collectionLength,
key, value, // key/value of iteration
trackById,
trackByIdFn,
collectionKeys,
block, // last object information {scope, element, id}
nextBlockOrder,
elementsToRemove;
if (aliasAs) {
$scope[aliasAs] = collection;
}
// 类数组collection直接赋值,对象则按枚举顺序提取key加入collectionKeys
// 结果是如果collection为[1,2,3],collectionKeys为[1,2,3]
// 如果collection为{a: 1, b: 2, c: 3} ,collectionKeys为['a', 'b', 'c']
if (isArrayLike(collection)) {
collectionKeys = collection;
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
} else {
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
// if object, extract keys, in enumeration order, unsorted
collectionKeys = [];
for (var itemKey in collection) {
if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
collectionKeys.push(itemKey);
}
}
}
collectionLength = collectionKeys.length;
nextBlockOrder = new Array(collectionLength);
// locate existing items
for (index = 0; index < collectionLength; index++) {
// 获取key ,value
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
//按照collectionKeys中保存的key依次取出要被循环处理的value。trackById是使用trackByIdFn计算出来的每个item唯一的标识,用来建立item与页面中元素间的关联。
//如果lastBlockMap中有trackById这个属性,则说明该item在上次循环中已经存在,则将相应的属性/值设置到nextBlockMap对象中,同时在nextBlockOrder数组中保存顺序。
//如果在lastBlockMap中找不到trackById但在nextBlockMap中找到了,则说明在collection中有两个item的trackById是相同的,这时会抛出异常,因为不可能两个item对应页面中的同一个element。
//如果在两个map对象中都没有找到,则说明这个item是首次出现,那么则在nextBlockMap中将对应的值设置为false,表明没有scope与之对应。
trackById = trackByIdFn(key, value, index);
if (lastBlockMap[trackById]) {
// found previously seen block
block = lastBlockMap[trackById];
delete lastBlockMap[trackById];
nextBlockMap[trackById] = block;
nextBlockOrder[index] = block;
} else if (nextBlockMap[trackById]) {
// if collision detected. restore lastBlockMap and throw an error
forEach(nextBlockOrder, function(block) {
if (block && block.scope) lastBlockMap[block.id] = block;
});
throw ngRepeatMinErr('dupes',
'Duplicates in a repeater are not allowed. Use \'track by\' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}',
expression, trackById, value);
} else {
// new never before seen block
nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
nextBlockMap[trackById] = true;
}
}
// 在经历前面一次检查后,现在还留在lastBlockMap中的item就是被从collection中移除的item。要做的就是将对应的element从DOM中移除并销毁item对应的scope。
for (var blockKey in lastBlockMap) {
block = lastBlockMap[blockKey];
elementsToRemove = getBlockNodes(block.clone);
$animate.leave(elementsToRemove);
if (elementsToRemove[0].parentNode) {
// if the element was not removed yet because of pending animation, mark it as deleted
// so that we can ignore it later
for (index = 0, length = elementsToRemove.length; index < length; index++) {
elementsToRemove[index][NG_REMOVED] = true;
}
}
block.scope.$destroy();
}
// 这个循环用来处理已有item的DOM移动以及新item对应的DOM插入。在这个循环中previousNode代表了上一次循环item元素在DOM中的位置,angular会顺次将各个block插入到前一个block的后面(对于已经存在的元素则是移动)。
for (index = 0; index < collectionLength; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
block = nextBlockOrder[index];
// 已有的block移动位置
if (block.scope) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
nextNode = previousNode;
// skip nodes that are already pending removal via leave animation
do {
nextNode = nextNode.nextSibling;
} while (nextNode && nextNode[NG_REMOVED]);
if (getBlockStart(block) !== nextNode) {
// existing item which got moved
$animate.move(getBlockNodes(block.clone), null, previousNode);
}
previousNode = getBlockEnd(block);
// 跟新scope
updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
} else {
// 新的block则有新的scope,插入dom
$transclude(function ngRepeatTransclude(clone, scope) {
block.scope = scope;
var endNode = ngRepeatEndComment.cloneNode(false);
clone[clone.length++] = endNode;
$animate.enter(clone, null, previousNode);
previousNode = endNode;
block.clone = clone;
nextBlockMap[block.id] = block;
updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
});
}
}
// 为下一次做准备
lastBlockMap = nextBlockMap;
});
$scope
$scope,作用域,即控制范围,表示某个范围数据和视图都由它管。比如
表示id为ctrl的div被exampleCtrl控制,这个范围下的数据都在其scope控制下。
作用
作用域基本功能
作用域包含了渲染视图时所需的功能和数据,它是所有视图的唯一源头。可以将作用域理解成视图模型(view model)。
生命周期
创建:在创建控制器或指令时,AngularJS会用$injector创建一个新的作用域,并在这个新建的控制器或指令运行时将作用域传递进去。
链接:当Angular开始运行时,所有的$scope对象都会附加或者链接到视图中。所有创建$scope对象的函数也会将自身附加到视图中。这些作用域将会注册当Angular应用上下文中发生变化时需要运行的函数。
更新:当事件循环运行时,它通常执行在顶层$scope对象上(被称作$rootScope),每个子作用域都执行自己的脏值检测。每个监控函数都会检查变化。如果检测到任意变化,$scope对象就会触发指定的回调函数。
销毁:当一个$scope在视图中不再需要时,这个作用域将会清理和销毁自己。
scope的继承类似于js的原型继承.即先在自己的scope中查找属性,如果没找到则到父级scope中查找,直到rootScope。没有就报错
下面几种情况会产生scope:
ng-repeat
ng-switch
ng-view
ng-controller
带有 scope: true 的指令
带有 transclude: true 的指令 以下指令创建新的scope, 且在原型上 不继承 父scope:
带有 scope: { ... } 的指令, 这会创建一个 独立的scope (isolate scope) 注意: 默认指令并不会创建 scope, 默认是 scope: false, 通常称之为 共享scope.
ng-repeat
源码在这里
JS:
HTML:
这个指令有点特殊,每次repeat都会新建一个新的scope,每个scope都会在原型上继承父级的scope.所以如果迭代对象是一个primitive,则每个值会复制到每个scope属性上。如果迭代的对象为object,则其引用会被赋值给scope属性,具体如图:
primitive:
object:
ng-include、ng-switch、ng-view 、ng-controller类似
指令
其中的关系如图:
参考
源码看scope 作用域与事件
ngRepeat解读
整体
主要来看compile和link中的内容:
compile中在postLink之前:
match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);
中正则这样划分(\s*[$\w]+)
和\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\)
两部分,分别对应item in items
和(key, value) in items
的情形。其他见中文注释下面是link函数的内容