标签:efficient each finally 简单 fun tip angular index new
$scope.aProperty = 1
$scope.$watch(‘aProperty‘, (newValue, oldValue) => {
// ...
})
$scope.$apply(function () {
$scope.bProperty = 1
})
$scope.$on(‘event‘, function () {})
作用:
- 监听数据的变化
- 广播并监听事件
- 绑定模板中的数据
其中“监听数据变化”是 Angular 中最具争议也是最大的卖点——脏检查。
将从以下几个功能点来介绍 scope 的实现。
scope 对象是一个普通 JS 对象。js function Scope () {} var scope = new Scope()
定义一个 $$watchers 数组,每调用 $watch 一次,就在数组中添加一项;
调用 $digest 时,遍历数组,就执行对应的 listener 函数。
// 定义部分
function Scope() {
this.$$watchers = []
}
Scope.prototype.$watch = function(watchFn, listenerFn) {
var watcher = {
watchFn,
listenerFn
}
this.$$watchers.push(watcher)
}
Scope.prototype.$digest = function() {
_.forEach(this.$$watchers, function(watcher) {
watcher.listenerFn()
})
}
// 使用部分
var scope = new Scope()
scope.$watch(
function (scope) { return scope.someValue },
function (newValue, oldValue, scope) { }
)
scope.$digest()
但是这个版本,没有判断 watch 的值是否发生变化了。我们应该在发生变化(脏)的情况下才执行 listener 函数。这就叫做脏检查。
/**
* 1. 对每个 watcher,比较 watch 函数的返回值和之前存储的 last 属性。
* 2. 如果值是不同的,就调用 listener 函数,传递新、旧值和 scope 对象本身。
* 3. 最后把 last 属性设为新返回的值,以便在下次比较中使用。
*/
Scope.prototype.$digest = function() {
var self = this
var newValue, oldValue
_.forEach(this.$$watchers, function(watcher) {
newValue = watcher.watchFn(self)
oldValue = watcher.last
if (newValue !== oldValue) {
watcher.last = newValue
watcher.listenerFn(newValue, oldValue, self)
}
})
}
【TIPS】对 last 属性赋初值。链接
function initWatchVal() { }
Scope.prototype.$watch = function(watchFn, listenerFn) {
var watcher = {
// ...
last: initWatchVal
}
}
Scope.prototype.$digest = function() {
_.forEach(this.$$watchers, function(watcher) {
// ...
if (newValue !== oldValue) {
watcher.last = newValue;
watcher.listenerFn(newValue,
(oldValue === initWatchVal ? newValue : oldValue),
self);
}
})
}
理由:若不赋初值,默认值会是 undefined
,当使用时如果第一个合法值也是 undefined
。listener 函数就不会执行。
做法:此处对 last 属性赋了一个空的函数。因为一个函数不会全等于除它自身外的任何值。(当 oldValue 是初始值函数时,listener 函数的第二个参数会传 newValue)
当一次 digest 循环结束,scope 仍然为“脏”的状态时,继续进行 digest 循环,直到所有 watch 值均不再更新。
理由:如果某一 listener 函数修改了 scope 属性,并且另一个 watcher 监听着这个属性,那么就会得到错误的值。js Scope.prototype.$digest = function () { var dirty do { dirty = false _.forEach(this.$$watchers, function (watcher) { // get value... if (newValue !== oldValue) { // listenerFn... dirty = true } }) } while(dirty) }
启示:watch 函数应该尽量没有副作用。特别是不要在这里调用 Ajax 请求,因为无法确保 Ajax 请求会被发送几次。
当一次 $digest 导致 10 次脏检查的循环时,抛出异常
理由:解决 watcher 循环依赖的问题。
Scope.prototype.$digest = function () {
var ttl = 10 // Time to Live
do {
// loop...
if (dirty && !(ttl--)) {
throw ‘10 $digest() iterations reached. Aborting!‘
}
} while(dirty)
}
【TIPS】记录一次循环中的最后一个 dirty watch,下次遍历到该 watch 时,如果没有发生变化,则不再继续向下遍历。(当新增 watch 时,需将记录清除)
function Scope() {
this.$$lastDirtyWatch = null
}
Scope.prototype.$digest = function () {
this.$$lastDirtyWatch = null
do {
// loop...
_.forEach(this.$$watchers, function(watcher) {
if (newValue !== oldValue) {
self.$$lastDirtyWatch = watcher
} else if (self.$$lastDirtyWatch === watcher) {
dirty = false
}
}
} while(dirty)
}
watch 第三个参数如果传 TRUE,则进行深比较。(此时,对 last 属性进行赋值时,应采用深拷贝,否则无法发现改动)
Scope.prototype.$watch = function(watchFn, listenerFn, objectEquality) {
var watcher = {
eq: !!objectEquality
}
}
判断 watch 的值是不是 NaN。
理由:NaN 与自身不相等,将永远是“脏”的。
【TIPS】通过调用定义的 $watch 函数,来删除这个 watcher。实际就是从 watchers 数组中移除了这一项。
Scope.prototype.$watch = function(watchFn, listenerFn, objectEquality) {
var self = this
// ...
return function deregisterWatch() {
var index = self.$$watchers.indexOf(watcher)
if (index >= 0) {
self.$$watchers.splice(index, 1)
}
}
}
$eval 函数提供了在 scope 上下文,执行 JS 代码的最简单的方法。
js Scope.prototype.$eval = function(expr, locals) { return expr(this, locals) }
$apply 函数利用 $eval 函数执行传入的函数,最后再触发一次 $digest 循环。
js Scope.prototype.$apply = function(expr) { try { return this.$eval(expr) } finally { this.$digest() } }
$apply 函数的目的是:我们可以运行一些 Angular 无法感知的却修改了 scope 中的属性的代码,以便 watcher 可以收集到数据的变化。
$evalAsync 把一个函数排在正在执行的 digest 最后执行。(在每一次循环的最后执行,而不是每个 digest 的最后执行)
$applyAsync 最初被设计处理 HTTP response。当请求密集时,可以做到合并 digest,避免资源浪费。
将多次 $apply 合并至一个队列中,在 digest 时统一执行。(通过 setTimeout、clearTimeout 实现)
【TIPS】当你预计你要在短时间调用多次 $apply 时,你可以用 $applyAsync 代替。
Angular 1.3 加入 $watchGroup。针对多个 watch 指定一个 listener。js // 基本原理 Scope.prototype.$watchGroup = function(watchFns, listenerFn) { var self = this _.forEach(watchFns, function(watchFn) { self.$watch(watchFn, function () { self.$evalAsync(listenerFn) }) }) }
// 这种方式是创建了一个 $rootScope。因为它没有 parent。
var scope = new Scope()
Angular 的 scope 继承是基于 JavaScript 原生对象的继承实现的。js Scope.prototype.$new = function () { var ChildScope = function () {} ChildScope.prototype = this var child = new ChildScope() return child }
当父级 digest 运行的时候,会运行子级的 watch,反之则不会。
Scope.prototype.$new = function() {
// prototype...
this.$$children.push(child)
child.$$watchers = []
child.$$children = []
return child
}
$apply 会执行 $root.$digest,这会导致执行每个 scope 上的每个 watch。为了节约性能,可以用 $digest 代替 $apply。
$new(true) 创建一个隔离的 scope,此时无法访问父级 scope 的属性。此时直接 new Scope(),而不是 new ChildScope()。但 $root 依然指向实际的 $rootScope。
$new 第二个参数是 parent。指定一个特定的 scope,而不是原型链上的父级。js Scope.prototype.$new = function(isolated, parent) { var child parent = parent || this if (isolated) { child = new Scope() child.$root = parent.$root } parent.$$children.push(child) return child }
$destory 函数删除全部的 watcher 并且从父级的 $$chiildren 删除 scope 自身。
Scope.prototype.$destroy = function() {
if (this.$parent) {
var siblings = this.$parent.$$children
var indexOfThis = siblings.indexOf(this)
if (indexOfThis >= 0) {
siblings.splice(indexOfThis, 1)
}
}
this.$$watchers = null;
}
$watchCollection 用来发现数组和对象的变化。当一项或者一个属性被添加、删除或覆盖。是对 $watch 值检查的优化,它仅做浅层的比较。
$scope.users = [{name: "Joe", age: 30},{name: "Jill", age: 29},{name: "Bob", age: 31} ];
Reference Watches
? $scope.users = newUsers;
? $scope.users.push(newUser);
? $scope.users[0].age = 31;
Collection Watches
? $scope.users = newUsers;
? $scope.users.push(newUser);
? $scope.users[0].age = 31;
Equality Watches
? $scope.users = newUsers;
? $scope.users.push(newUser);
? $scope.users[0].age = 31;
Angular 的 pub-sub 实现,与 jQuery 等常见实现大致相同,唯一的不同是与 scope 层级绑定。
事件向上层级传递时,当前 scope 和祖先 scope 会得到通知,这个操作叫 emit;
向下传递时,当前 scope 和子孙 scope 会得到通知,这个操作叫 broadcast。
$on 定义的事件,第一个参数是事件名;第二个参数是 listener 函数。
$$listeners 对象中会保存所有的事件, key 是事件名,value 是 listener 数组。
Scope.prototype.$on = function(eventName, listener) {
var listeners = this.$$listeners[eventName]
if (!listeners) {
this.$$listeners[eventName] = listeners = []
}
listeners.push(listener)
}
Scope.prototype.$emit|$broadcast = function(eventName) {
var listeners = this.$$listeners[eventName] || []
_.forEach(listeners, function(listener) {
listener()
})
return function() {
var index = listeners.indexOf(listener)
if (index >= 0) {
listeners.splice(index, 1)
}
}
}
Scope.prototype.$emit = function(eventName) {
var propagationStopped = false
var event = {
stopPropagation: function() {
propagationStopped = true
}
}
do {
// event...
} while (scope && !propagationStopped)
return event
}
// usage
child.$on(‘$destroy‘, listener)
scope.$destroy()
// define
Scope.prototype.$destroy = function() {
this.$broadcast(‘$destroy‘);
// destory...
}
标签:efficient each finally 简单 fun tip angular index new
原文地址:https://www.cnblogs.com/maopengyu/p/9190834.html