今天在开发1050的时候,遇到了这么一个有意思的问题:
我有类SongModel和SongView:
var SongModel = Backbone.Model.extend({
...
});
var SongView = Backbone.View.extend({
...
render: function() {
// render to Dom
},
events: {
'click xxx': 'delete'
},
delete: function(e) {
var tThis = this;
this.model.destroy({
wait: true,
$btn: $(e.currentTarget),
success: function() {
tThis.remove();
}
});
}
});
然后:
var songModel = new SongModel(someObj);
var songView = new SongView({model: songModel});
页面进来的时候,执行代码,然后songView正常的显示在页面上。
当我点击删除的时候,songView的delete事件被触发,然后发送一个Http Delete异步请求,后台执行删除,成功以后执行tThis.remove();。
有意思的是,我为了统一代码的交互,用到了一个utils.js来执行一些同样的工作,如,每次发起异步请求before的时候,显示加载中...的进度条。
具体的实现方式,是通过jQuery的ajaxSetup以及ajaxSend, ajaxComplete, ajaxSuccess, ajaxError来实现的:
var utils = {
setGlobalAjaxSettings: function() {
var tThis = this;
...
$(document).ajaxSuccess(function(event, jqxhr, settings) {
if(jqxhr.responseJSON&&!jqxhr.responseJSON.error) {
tThis.success(event, jqxhr, settings);
} else {
tThis.error(event, jqxhr, settings);
}
});
},
success: function(event, jqxhr, settings) {
if(settings.$btn) {
var $alert = $(this.getAlertHtml('alert-success', '操作成功'));
var $target = settings.$btn.closest('.row').find('*[tag="alert"]');
this.renderAlert($target, $alert);
}
}
}
所以,当我发起的Http Delete请求成功之后,会先执行songView里面的success回调的代码,然后执行utils里面通用的success回调的代码。
然后,有意思的事情就来了,我们注意到,前面发请求的时候,传入了一个$btn: $(e.currentTarget),当回调成功以后,会执行tThis.remove();;
这时,按我之前错误的理解,在通用的回调里面,settings.$btn应该是undefined,而当我单步调试的时候发现,他竟然还是当前点击按钮的那个jQuery元素!
而且,它能一直parent()追溯到songView的el,再往上就是undefined了。
我顿时就来劲了,仔细想了一下,原来是这样:

如图所示,当我们执行var songView = new SongView({model: songModel});的时候,内存1被创建,它的内容存我们实例化的jQuery对象;
同时,内存2被创建,它的内容存1的地址,如图红色线所示。
而当我们render的时候,实际上是把2的地址存到DOM的内容中,如图绿色线所示。
接着,让我们执行请求的时候,传入了一个$btn: $(e.currentTarget),我们知道,在JavaScript中,参数的传递,不管是基本类型还是引用类型,都是传值;
所以,这时,内存4被创建,它的内容也存1的地址,如图蓝色线所示。
然而,当请求成功之后,执行:tThis.remove();,这时,内存2被销毁。红色线和绿色线同时也不存在了。
但此时,蓝色线还是存在的,所以,这就解释了我之前单步调试的时候遇到的现象。
那如果我们不执行tThis.remove();呢?,这时,我们执行var $target = settings.$btn.closest('.row').find('*[tag="alert"]');就能搜索到DOM树中其它的父元素。
因为内存2的地址被存在DOM树中,同时内存2的内容又指向内存1的地址,所以DOM树中就会显示内存1的内容,当内存4不断执行parent()的时候,实际上也是在DOM树中往上查找,最终也会找到window元素。
由此可见,DOM, View, e.target之间真是纠缠不清,也难怪我会亲切的称呼它们为三贱客了。