在jQuery的实际编码过程中,this的指向问题一直是困扰许多前端人员的技术难点。尤其是在使用ajax、each以及事件绑定的回调函数时,同样写成this,运行起来却可能指向完全不一样的对象。要彻底弄懂这种现象,必须从JavaScript函数执行时的上下文规则以及jQuery源码内部如何调用这些回调说起。JavaScript里函数中的this并不是在定义时绑定,而是在被调用那一刻由调用者通过call、apply或者作为对象方法的形式来决定。jQuery在设计各类遍历与异步接口时,为了操作方便,特意把不同的上下文注入到回调中,从而造成了三种典型场景下的指向差异。

ajax回调中this的指向与请求配置对象
当我们使用jQuery的ajax方法发起请求时,如果在success、error或者complete等回调函数中直接使用this,它指向的并不是触发请求的DOM元素,也不是全局window,而是当前这次ajax调用的配置对象,也就是传入$.ajax(settings)里的那个settings,或者是$.get等简写方法内部生成的请求选项对象。jQuery在底层实现中,通过jquery的ajax传输层将回调以callback.call(options, data, textStatus, jqXHR)类似的形式执行,这里options就是本次请求的配置,因此this自然绑定到它。
这种设计的好处是,在回调内部可以方便地通过this.url、this.type获取当前请求的参数,而不需要在外层闭包中额外保存变量。但很多开发者误以为this还是原先绑定事件的元素,于是写出this.html(data)这样的代码,结果报错。下面的例子展示了正确的用法:在success回调中通过this.type确认请求类型,同时用传参的data处理返回结果。
$.ajax({
url: 'https://ipipp.com/api/user',
type: 'GET',
success: function(data, status, xhr) {
// 此处的this指向本次ajax的配置对象
console.log(this.type); // 输出 GET
console.log(this.url); // 输出 https://ipipp.com/api/user
$('#result').html(data.name);
}
});
如果确实需要在ajax回调里使用触发请求的DOM元素,应当采用外部变量保存或者使用bind、箭头函数固定上下文。例如把按钮对象在闭包中保存为var btn = this,再在回调中操作btn。另一种方式是使用$.ajax的context配置项,jQuery专门提供了context参数来指定回调执行时的this值,这是官方推荐的解决手段。
$('#btn').on('click', function() {
$.ajax({
url: 'https://ipipp.com/api/user',
context: this, // 将回调中的this绑定到按钮DOM
success: function(data) {
// 此时this指向按钮
$(this).text(data.name);
}
});
});
each遍历回调中this指向当前DOM元素
jQuery的each方法分为两种:一种是全局的$.each用于遍历数组或对象,另一种是企业开发中更常用的jQuery实例方法$('selector').each,用来遍历匹配到的DOM集合。在后者中,回调函数的this被jQuery显式设置为当前正在遍历的那个DOM元素。源码层面,jQuery在each内部通过callback.call(obj[i], i, obj[i])把DOM节点作为call的第一个参数传入,从而覆盖默认指向。
由于this是原生DOM元素而非jQuery对象,如果直接调用this.addClass就会报错,因为DOM元素没有addClass方法。正确写法是用$(this)将其包装为jQuery对象后再调用jQuery API。这一点与事件回调类似,但和ajax回调截然不同,因为each场景下this是节点,而不是配置。下面的代码演示了如何为每个列表项添加索引提示。
$('li').each(function(index) {
// this指向当前li的DOM元素
var $li = $(this);
$li.attr('data-index', index);
$li.text('第' + (index + 1) + '项:' + $li.text());
});
对于$.each(arr, function(i, item))遍历普通数组的情况,this的指向则取决于数组元素的值,如果元素是DOM节点则this为该节点,如果是普通对象则this为该对象。但实践中为了避免混淆,建议始终使用回调的第二个参数item来取值,而不是依赖this。这样代码可读性更高,也不会在数组元素是基本类型时让this指向window或undefined。
var list = ['a', 'b', 'c'];
$.each(list, function(i, item) {
// 此处this为字符串'a'等,不建议使用
console.log(i, item);
});
事件回调函数里this指向触发事件的DOM节点
在jQuery中用on、click、bind等方法注册事件处理器时,回调中的this固定指向触发该事件的DOM元素。这与原生JavaScript的addEventListener行为保持一致,jQuery并没有改变这一规则,只是在内部把原生事件包装成jQuery事件对象作为第一个参数传入。因此,在事件回调中写this,拿到的就是用户点击、输入或悬停的那个真实节点。
这种指向让我们可以很方便地在事件里操作当前元素,例如高亮被点击的按钮、获取输入框的值等。但必须注意,this是DOM元素,要调用jQuery方法必须先包装。同时,如果事件是通过事件委托绑定在父级容器上,this依然指向实际触发事件的子元素,而event.currentTarget才指向绑定事件的父容器。下面的示例展示了点击列表中某项时改变自身背景色。
$('#list').on('click', 'li', function(event) {
// this指向被点击的li DOM节点
$(this).css('background', 'yellow');
// event.currentTarget指向#list
console.log(event.currentTarget.id);
});
当事件回调中使用箭头函数时,由于箭头函数没有自己的this,它会继承外层词法作用域的this,此时回调里的this就不再指向DOM元素,而指向定义时的外层对象,通常是undefined或window,这会导致$(this)失效。因此,在需要依赖jQuery事件this指向的地方,应避免使用箭头函数,或者改用event.currentTarget来替代this获取元素。通过理解这三种场景的差异,配合$(this)包装与context配置,就能稳定控制jQuery回调中的上下文。