解决JS(jQuery)中blur触发函数this指针指向问题(函数作用域问题)

2011年6月18日 · 14 years ago

昨晚在写JS的时候(基于jQuery)一直纠结这么一个问题,不太好表述清楚,所以这篇文章的标题也怪怪的。大致状况如下:

/*
*定义一个表单类,里面成员变量有各个inputs的对象集合
*各个inputs绑定blur事件用来触发表单输入验证
*验证用的函数封装在prototype中,属于成员函数
*/
function UserForm(){
//...这里获取表单和inputs
this.inputs.blur(this.validate);
}
UserForm.prototype = {
setStatus: function(){
//用来设置表单状态,表示填写进度用
},
validate: function(){
//...省略无关内容
//这里获取当前触发了blur事件的input对象
$input = $(this);
//这里用来调用成员函数setStatus
this.setStatus(1);
}
}

代码如上,问题就出在this指针上面。当我在构造函数UserForm()里面使用this.elements定义各个成员变量的时候,这里的this指针会指向我们的UserForm对象,在成员函数validate()里面正常情况下也可以使用this.fun()的方式来调用其他公有函数。但是,我在构造函数中调用validate()函数的时候使用了jQuery的blur()函数来调用,那么这时候的this指针就指向了当前失去焦点触发blur的input元素,则无法再使用this.fun()的方式来调用其他公有函数。

解决方法1:
既然是this指针的问题,那么我们不在类里面封装使用blur方法调用,我到类外先获取inputs然后给input绑定一个blur函数,再把当前input对象传给validate就能解决。

function UserForm(){
//...这里获取表单和inputs
this.inputs.blur(this.validate);
}
UserForm.prototype = {
setStatus: function(){
//用来设置表单状态,表示填写进度用
},
//修改validate方法,改为把当前input对象作为参数传进来
validate: function($input){
//...省略无关内容
//这里获取当前触发了blur事件的input对象
$input = $(this);
//这里用来调用成员函数setStatus
this.setStatus(1);
}
}
$(document).ready(function(){
var loginForm = new UserForm('#UserLoginForm','#signUp');
var inputs = $('#UserLoginForm input');
$inputs.blur(function(){
loginForm.validate($(this));
});
});

但这是非常不可取的,我封装成类本来就是想把逻辑处理的内容都在类里面完成,这样做就变成我把处理过程都放到类外去做了,那我的类不就成废物一个了么?
于是……

解决方法二:
纠结好久我到推上发了发牢骚,结果 @rainux 大大粗线鸟,告诉我一个神奇的$.proxy(this.handler, this)函数,是jQuery的函数,用来改变函数作用域,把handler的作用域绑定在this上面。如果写在blur函数里面,那么就是element.blur($.proxy(this.handler, this)),如此,validate函数中的this指针就仍然是指向UserForm类,就可以顺利调用成员函数了。

function UserForm(){
//...这里获取表单和inputs
this.inputs.blur($.proxy(this.validate, this));
}
UserForm.prototype = {
setStatus: function(){
//用来设置表单状态,表示填写进度用
},
validate: function(){
//...省略无关内容
//这里用来调用成员函数setStatus
this.setStatus(1);
}
}
$(document).ready(function(){
var loginForm = new UserForm('#UserLoginForm','#signUp');
});

但是还有一个问题,我改变了this指针使得我的成员函数可以使用this.foo的方式调用没错但是我要怎么确定当前哪个input触发了该函数呢?
再试了半天纠结了好久之后给出最终解决方法:使用event.currentTarget。(依然感谢 @rainux 大大的帮助)
最终解决方法:

function UserForm(){
//...这里获取表单和inputs
this.inputs.blur($.proxy(this.validate, this));
}
UserForm.prototype = {
setStatus: function(){
//用来设置表单状态,表示填写进度用
},
//这里把event作为参数传入
validate: function(event){
//...省略无关内容
//使用event.currentTarget获取当前DOM对象,为了方便我把它转化为jQuery对象
$input = $(document.getElementById(event.currentTarget.id));
//这里用来调用成员函数setStatus
this.setStatus(1);
}
}
$(document).ready(function(){
var loginForm = new UserForm('#UserLoginForm','#signUp');
});

总结
在解决这些问题的时候走了挺多弯路,说明之前写的代码不够多,看的文档不够仔细,其实click()、blur()等这些事件方法都是可以直接传参数什么的,而不是我一直以为的必须使用匿名函数的方法,这些技巧都挺有用的,所以,多看官方文档有好处的T_T