-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
angular.js 1.x $scope #11
Comments
ngRepeat解读整体var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var NG_REMOVED = '$$NG_REMOVED';
var ngRepeatMinErr = minErr('ngRepeat');
return {
restrict: 'A',
multiElement: true,
transclude: 'element',
priority: 1000,
terminal: true,
$$tlb: true,
compile: function ngRepeatCompile($element, $attr) {
......
return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
......
}
}
};
var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
scope[valueIdentifier] = value;
if (keyIdentifier) scope[keyIdentifier] = key;
scope.$index = index;
scope.$first = (index === 0);
scope.$last = (index === (arrayLength - 1));
scope.$middle = !(scope.$first || scope.$last);
scope.$odd = !(scope.$even = (index & 1) === 0);
};
var getBlockStart = function(block) {
return block.clone[0];
};
var getBlockEnd = function(block) {
return block.clone[block.clone.length - 1];
};]; 主要来看compile和link中的内容: compile中在postLink之前: // 取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;
};
}
下面是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;
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
$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
作用域与事件
The text was updated successfully, but these errors were encountered: