JavaScript_this
1. 引言
this 是什么:this 是 JavaScript 中的一个关键字,它指向当前执行上下文中的一个对象。与其他语言(如 Java、C++)不同,JavaScript 中的 this 不是在函数定义时确定的,而是在函数调用时动态绑定的。
2. 四大绑定规则
根据函数的调用方式不同,this 会绑定到不同的对象。主要有以下四种规则。
2.1 默认绑定
独立函数调用时,默认绑定规则生效。
- 在非严格模式下,
this指向全局对象(浏览器中是window,Node.js 中是global)。 - 在严格模式(
'use strict')下,this为undefined。
function showThis() {
console.log(this);
}
showThis(); // 非严格: window,严格: undefined
// 函数内部嵌套的函数也是独立调用
function outer() {
function inner() {
console.log(this);
}
inner(); // 独立调用,仍然是默认绑定
}
outer();2.2 隐式绑定
当函数作为对象的方法被调用时,this 指向调用该方法的对象。
const person = {
name: 'Alice',
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
person.greet(); // Hello, I'm Alice常见陷阱:隐式丢失
如果把方法赋值给一个变量,然后单独调用,就会丢失隐式绑定,回退到默认绑定:
const greetFn = person.greet;
greetFn(); // Hello, I'm undefined(非严格下输出 Hello, I'm undefined,因为 this 指向 window,window.name 通常为空)同样,作为回调函数传递时也会丢失,回退到默认绑定:
setTimeout(person.greet, 100); // 回调执行时是独立调用,丢失 this2.3 显式绑定
通过 call、apply、bind 可以强制指定函数执行时的 this。
call(thisArg, arg1, arg2, ...):立即执行,参数逐个传递。apply(thisArg, [arg1, arg2, ...]):立即执行,参数以数组形式传递。bind(thisArg, arg1, arg2, ...):返回一个新函数,this 被永久绑定,可延迟执行。
function introduce(age, city) {
console.log(`${this.name}, ${age}岁, 来自${city}`);
}
const user = { name: 'Bob' };
introduce.call(user, 25, '北京'); // Bob, 25岁, 来自北京
introduce.apply(user, [25, '北京']); // Bob, 25岁, 来自北京
const bound = introduce.bind(user, 25);
bound('上海'); // Bob, 25岁, 来自上海2.4 new 绑定
使用 new 关键字调用构造函数时,会执行以下步骤:
- 创建一个全新的空对象。
- 将这个空对象的原型指向构造函数的
prototype属性。 - 将这个空对象绑定为构造函数内部的
this。 - 如果构造函数返回非原始值,则返回该值;否则返回新创建的对象。
因此,new 调用后,this 指向新创建的对象实例。
function Car(model) {
this.model = model;
this.drive = function() {
console.log(`Driving ${this.model}`);
};
}
const tesla = new Car('Model 3');
tesla.drive(); // Driving Model 33. 箭头函数:无自己的 this
箭头函数(=>)不绑定自己的 this,它捕获其所在(定义时)上下文的 this 值作为自己的 this。并且,箭头函数的 this 无法通过 call、apply、bind 改变(但可以传入参数)。
const obj = {
name: 'Arrow',
normal: function() {
console.log('normal:', this.name);
},
arrow: () => {
console.log('arrow:', this.name);
}
};
obj.normal(); // normal: Arrow
obj.arrow(); // arrow: undefined(因为箭头函数定义时 this 指向全局)注意:在对象字面量中定义箭头函数,其 this 通常指向全局(浏览器中为 window),而不是对象本身。因此,对象方法一般不建议使用箭头函数。
箭头函数最适合用于回调函数,避免丢失外层 this:
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 箭头函数中的 this 指向 Timer 实例
console.log(this.seconds);
}, 1000);
}
new Timer();4. 绑定优先级与例外情况
4.1 优先级
当多种绑定规则同时适用时,优先级从高到低为:
new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定 > 默认绑定
function foo(something) {
this.a = something;
}
const obj1 = {};
const bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
const obj2 = new bar(3);
console.log(obj1.a); // 2 (没有被修改)
console.log(obj2.a); // 3 (new 绑定的优先级高于显式绑定)4.2 例外情况
忽略
this:如果显式绑定传入null或undefined,this会被忽略,使用默认绑定。这种做法不推荐(可能意外修改全局对象),可使用Object.create(null)代替。javascriptfunction test() { console.log(this); } test.call(null); // 非严格: window,严格: null间接引用:
(0, obj.func)()这种写法会使函数独立调用,导致默认绑定。javascriptvar name = 'global'; const obj = { name: 'local', getName() { console.log(this.name); } }; (obj.getName)(); // local(括号不影响) (0, obj.getName)(); // global(间接引用,相当于独立调用)
5. 特殊场景下的 this
5.1 事件处理函数
- 传统函数(非箭头函数)作为 DOM 事件监听器时,
this指向触发事件的元素。 - 箭头函数中的
this保持定义时的外层this(通常是window或所在组件)。
<button id="btn">Click</button>
<script>
const btn = document.getElementById('btn');
btn.addEventListener('click', function() {
console.log(this); // <button id="btn">...
});
btn.addEventListener('click', () => {
console.log(this); // window(严格模式下 undefined)
});
</script>5.2 定时器 setTimeout / setInterval
回调函数默认情况下是独立调用,因此 this 指向全局对象(严格模式为 undefined)。
const obj = {
name: 'Timer',
say() {
setTimeout(function() {
console.log(this.name); // undefined 或 ''
}, 100);
}
};
obj.say();解决方案:使用箭头函数、bind 或者保存 this 到变量(const self = this)。
5.3 类(class)中的 this
ES6 类的方法默认采用严格模式。将类方法作为回调传递时,会丢失 this 指向。
class Logger {
constructor(name) {
this.name = name;
}
log() {
console.log(this.name);
}
}
const logger = new Logger('App');
setTimeout(logger.log, 100); // 丢失 this → undefined三种修复方式:
// 1. 在构造函数中 bind
class Logger {
constructor(name) {
this.name = name;
this.log = this.log.bind(this);
}
log() { console.log(this.name); }
}
// 2. 使用箭头函数作为类字段(需要 Babel 或现代浏览器)
class Logger {
log = () => {
console.log(this.name);
}
}
// 3. 调用时显式绑定
setTimeout(() => logger.log(), 100);5.4 严格模式的影响
严格模式下,默认绑定的 this 变成 undefined,而不是全局对象。这可以避免很多意外错误。
'use strict';
function test() {
console.log(this);
}
test(); // undefined