JS进阶-this指向
通常,对象方法需要访问对象中存储的信息才能完成其工作。例如,user.sayHi() 中的代码可能需要用到 user 的 name 属性。为了访问该对象,方法中可以使用 this 关键字。this 的值就是在点之前的这个对象,即调用该方法的对象。
let user = {
name: "John",
age: 30,
sayHi() {
// "this" 指的是“当前的对象”
alert(this.name);
}
};
user.sayHi(); // John
四种绑定规则
JavaScript 中的 this 可以用于任何函数,即使它不是对象的方法。判断普通函数中的this指向,可以通过这4个问题:
-
函数是否是通过
new调用的?场景:
new Func()
规则:this指向新创建的实例对象。这是优先级最高的规则。function Person(name) { this.name = name; } const p = new Person('Bob'); console.log(p.name); // Bob -
函数是否通过
call/apply/bind显式指定了上下文?场景:
call,apply,bind
规则: 传入什么对象,this就是什么对象(若传null/undefined则转为默认绑定)。function greet() { console.log(this.name); } const person = { name: 'Alice' }; greet.call(person); // Alice greet.apply(person); // Alice const boundGreet = greet.bind(person); boundGreet(); // Alice -
函数是否是通过对象打点调用的 (e.g.
obj.fn())?场景:
obj.func()
规则: 指向调用时最后一级的那个对象。const obj = { name: 'obj', inner: { name: 'inner', fn: function() { console.log(this.name); } } }; obj.inner.fn(); // 输出 'inner' —— 最近一级调用者是 inner⚠️ 进阶陷阱:隐式丢失
const obj = { name: 'obj', fn: function(){ console.log(this); } }; const reference = obj.fn; // 仅仅是函数引用的传递 reference(); // 输出 window —— 回归到规则1:默认绑定 -
是否独立调用?
场景:
func()
规则: 非严格模式下指向window(浏览器)或global(Node);严格模式下指向undefined。function show(){ console.log(this); } show(); // window 或 undefined (严格模式)
上述规则针对普通函数,箭头函数不遵守上述四种规则,它没有自己的this。
箭头函数(没有自己的this)
箭头函数的this在定义时就确定了,永远指向外层作用域的this(静态作用域/词法作用域)。
const obj = {
name: 'obj',
// 普通函数:this 取决于调用者
normalFn: function() {
console.log('Normal:', this.name);
},
// 箭头函数:this 取决于定义时的外层(此处外层是全局/模块作用域)
arrowFn: () => {
console.log('Arrow:', this.name);
}
};
obj.normalFn(); // Normal: obj
obj.arrowFn(); // Arrow: undefined (因为定义时外层this是window/module)
经典用法:解决 setTimeout 回调丢失 this 的问题
const timer = {
count: 0,
start: function() {
// 箭头函数让 this 锁定为 start 函数中的 this(即 timer 对象)
setTimeout(() => {
console.log(this.count++); // 正确指向 timer
}, 1000);
}
};
实践应用
-
数组方法中的
thisArgforEach,map,filter等方法的第二个参数可以显式绑定this。const obj = { multiplier: 10 }; // ❌ 箭头函数:thisArg 被忽略 [1, 2, 3].map((item) => { return item * this.multiplier; // this 指向外层作用域,不是 obj }, obj); // 报错或 NaN // ✅ 普通函数:thisArg 生效 [1, 2, 3].map(function(item) { return item * this.multiplier; }, obj); // [10, 20, 30] -
事件处理函数
在原生 DOM 规范中,事件处理函数内部的
this被强制绑定为当前触发事件的元素(即event.currentTarget)。浏览器在调用事件回调时,本质上执行了类似这样的操作(伪代码):
// 浏览器事件中心内部逻辑 element.addEventListener('click', handler); // 触发时,浏览器底层做了一次 call 调用: handler.call(element, event); // 所以 this === element可以通过代码验证:
const btn = document.createElement('button'); btn.textContent = 'Click'; btn.addEventListener('click', function(e) { console.log(this === btn); // true console.log(this === e.currentTarget); // true console.log(this === e.target); // 不一定,取决于点击的具体子元素 });如果监听器使用箭头函数,浏览器的
call(element)将无效,this回归外层词法作用域。btn.addEventListener('click', (e) => { console.log(this); // 指向定义时外层的作用域(可能是 window 或 类实例) // 此时想获取元素必须用 e.currentTarget }); -
优先级冲突测试
问题:
new和bind谁更优先?function foo() { console.log(this.a); } const obj = { a: 2 }; const Bar = foo.bind(obj); // 强行把 foo 的 this 绑定为 obj const b = new Bar(); // 使用 new 调用 // 输出 undefined,因为 new 创建的新对象 { a: undefined } 覆盖了 bind 的 obj // 结论:new > 显式绑定 > 隐式绑定 > 默认绑定
为什么提倡使用箭头函数
提倡使用箭头函数,本质上是为了锁定 this 的指向,彻底终结传统函数中 this 动态变化带来的不确定性。
简单来说:普通函数关注"谁调用了它",箭头函数关注"谁定义了它"。
当把一个普通函数作为回调传递时,只传递了函数体,并没有传递调用者。到了回调真正执行的时候,调用者已经变成了全局环境(window/undefined)或别的对象。
const obj = {
name: 'Alice',
greet: function() {
console.log(`Hello, ${this.name}`);
}
};
// 我们希望 1 秒后打招呼
setTimeout(obj.greet, 1000);
// 输出:Hello, undefined (或报错)
// 因为 setTimeout 内部是独立调用 fn(),this 指向了 window
如果不用箭头函数,传统解法非常繁琐:
setTimeout(function() { obj.greet(); }, 1000);(多一层包装,不够优雅)setTimeout(obj.greet.bind(obj), 1000);(显式绑定,心智负担重)
使用箭头函数重构:
箭头函数没有自己的 this。它在定义时就把外层的 this "快照"进了闭包。无论之后函数被如何调用、被传递给谁、被哪个第三方库调用,里面的 this 永远指向当初定义它的那个对象。
const obj = {
name: 'Alice',
// 使用箭头函数定义方法 (如果是对象字面量,注意外层的 this)
// 或者更常见的场景:
init: function() {
// 这里的 this 是 obj
setTimeout(() => {
// 箭头函数没有 this,它直接从 init 作用域借用 this
console.log(`Hello, ${this.name}`);
}, 1000);
}
};
obj.init(); // 1秒后正确输出 "Hello, Alice"
除了定时器外,异步请求也是箭头函数最经典的阵地。异步回调脱离了原本的调用栈,普通函数的 this 必然丢失。
class Loader {
constructor() {
this.data = null;
}
// ✅ 推荐:箭头函数
fetchDataArrow() {
fetch('/api').then(res => res.json()).then(data => {
this.data = data; // this 指向 Loader 实例
});
}
// ❌ 反模式:普通函数 (除非你 bind 了它)
fetchDataNormal() {
fetch('/api').then(function(res) {
return res.json();
}).then(function(data) {
this.data = data; // TypeError: Cannot set property 'data' of undefined
});
}
}
⚠️唯一的"不建议"场景:不要用箭头函数定义对象方法
const person = {
name: 'Bob',
// ❌ 这里的箭头函数定义在了全局作用域,this 指向 window
sayHi: () => {
console.log(`Hi ${this.name}`); // Hi undefined
}
};
原因:对象字面量 {} 本身不构成作用域。箭头函数在这里会捕获更外层的 this(通常是 window),导致无法访问对象属性。
只要函数离开了当前对象去执行(即:不是 obj.fn() 这种即时调用),可以使用箭头函数来保持 this 的正确性。

浙公网安备 33010602011771号