YUI自定义事件订阅者的作用域问题
Time: 10-01-2 Comments: 0
前几天, 同事在使用YAHOO.util.Event.onDOMReady时, 回调函数得不到执行, 浏览器提示回调函数”is not a function”(可以确定回调函数是存在且有效的), 经检查, 发现是作用域’惹的祸’, 这里重现一下场景, 大伙在使用时注意一下.
一. 示例代码
// 基于YUI 2.7或2.8
YAHOO.namespace('Demo');
YAHOO.Demo = function() {
var age = 24;
return {
init: function() {
var curAge = this.showAge(); // 浏览器会提示this.showAge is not a function
alert(curAge);
},
showAge: function(curAge) {
return YAHOO.lang.isNumber(curAge) ? curAge : age;
}
};
}();
// 会出错的调用方式
YAHOO.util.Event.onDOMReady(YAHOO.Demo.init);
// 用匿名函数将回调包起来, 则可以成功执行
YAHOO.util.Event.onDOMReady(function() {YAHOO.Demo.init();});
二. 分析
1. 首先, 我看了一下onDOMReady的代码:
onDOMReady: function(fn, obj, overrideContext) {
if (this.DOMReady) {
setTimeout(function() {
var s = window;
if (overrideContext) {
if (overrideContext === true) {
s = obj;
} else {
s = overrideContext;
}
}
fn.call(s, "DOMReady", [], obj);
}, 0);
} else {
this.DOMReadyEvent.subscribe(fn, obj, overrideContext);
}
}
我在调用YAHOO.util.Event.onDOMReady(YAHOO.Demo.init);时, DOM还不可用, 看来还得继续看一下DOMReadyEvent事件的定义代码.
2. DOMReadyEvent事件的定义代码:
if (!YAHOO.util.Event) {
YAHOO.util.Event = function() {
...
return {
...
DOMReadyEvent: new YAHOO.util.CustomEvent("DOMReady", this),
...
}
}();
}
YAHOO.util.CustomEvent共接受4个参数, 第2个参数即为回调函数的默认作用域, 此时, this指向的是window, 看到这里, 我推测回调被执行时, 作用域被改变了, 于是看了一下YAHOO.util.CustomEvent中fire方法的源码.
3. fire方法的定义代码:
// 这里列出一些关键部分的代码
fire: function() {
...
var len=this.subscribers.length;
...
var subs = this.subscribers.slice(); // this.subscribers用于存储当前事件(如DOMReadyEvent)的所有事件订阅者(即回调函数)
for (i=0; i<len; ++i) {
var s = subs[i];
...
var scope = s.getScope(this.scope); // this.scope表示回调函数的默认作用域
if (this.signature == YAHOO.util.CustomEvent.FLAT) {
try {
ret = s.fn.call(scope, param, s.obj);
} catch(e) { ... }
} else {
try {
ret = s.fn.call(scope, this.type, args, s.obj);
} catch(e) { ... }
}
}
}
看到这里, 可以确定回调被执行时, 作用域被改成了scope, 而这个scope的具体值是什么, 则需要看一下getScope方法的源码咯.
4. getScope方法的定义代码:
YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
if (this.overrideContext) {
if (this.overrideContext === true) {
return this.obj;
} else {
return this.overrideContext;
}
}
return defaultScope;
};
从这里可以看出, scope的值取决于obj和overrideContext.
三. 总结
DOMReadyEvent在定义时, 指定回调函数的默认作用域为window, 而同事在添加回调时, 采用YAHOO.util.Event.onDOMReady(YAHOO.Demo.init);的形式, 没有传递后两个参数(obj, overrideContext), 使得回调函数执行时的作用域变成了window, 这样, this.showAge变成了window.showAge(即this指向了window), 而根本不存在名为showAge的全局函数, 故而浏览器会提示”this.showAge is not a function”.
使用YAHOO.util.Event.onDOMReady(function() {YAHOO.Demo.init();})的形式, init方法的作用域没有被改变(被改变作用域的是那个匿名函数), 故而能成功执行.
所以, 大家在给自定义事件添加回调时, 要注意指定正确的作用域, 如果不能确定作用域, 我建议使用匿名函数包起来(推荐这种方式, 这样不需要管作用域的问题).
附: YUI 2.8中, 不知道出于什么样的考虑, 将DOMReadyEvent的定义改成了这样: DOMReadyEvent: new YAHOO.util.CustomEvent(“DOMReady”, YAHOO, 0, 0, 1), 但即使是这样, 作用域的问题依然存在.
CustomEvent, scope, YUI, 作用域, 自定义事件
