在 JavaScript 开发中,
this是一个既核心又容易让人困惑的概念。它像一个 “代词”,在不同场景下指向不同的对象,为代码提供了优雅的隐式对象引用方式,让代码更简洁、更易于复用。本文将带你系统梳理
this的使用场景、绑定规则及核心原理,帮助你彻底掌握this。
一、为什么要有 this ?
this的存在,本质是为了更优雅地传递对象引用。
想象一个场景:我们有一个方法,需要操作某个对象的属性。如果没有this,我们可能需要将对象作为参数显式传入,代码会变得冗余。有了this以后,我们可以通过this隐式获取其执行时的上下文对象,让代码变得更简洁,更具有复用性。
代码示例:
const user = {
name: '张三',
greet: function() {
console.log(`Hello, I'm ${this.name}`); // this 隐式指向 user 对象
}
};
user.greet(); // 输出: Hello, I'm 张三
在上述示例中,greet方法通过this.name访问对象属性,无需显式传递user对象,更加简洁直观。
二、this 在哪里生效?
this 并非在所有代码作用域中都有意义,它只在全局作用域和函数作用域中生效:
- 全局作用域:在浏览器环境中,全局作用域的
this指向window全局对象(严格模式下为undefined)。 - 函数作用域:函数内部的
this指向完全由调用方式决定,这也是this最复杂、最核心的部分。
注意:在 ES6+ 的块级作用域(如
if/for大括号内)中,this继承自外层作用域,并不会产生新的绑定。
三、this 的五大绑定规则
this 的指向并非由定义位置决定,而是由函数的调用方式决定。
1. 默认绑定:独立调用时的 this
当函数独立调用(没有任何上下文对象)时,会触发默认绑定:
- 非严格模式下:
this指向全局对象(浏览器中为window)。 - 严格模式下:
this为undefined。
代码示例:
function sayHi() {
console.log(this);
}
sayHi(); // 非严格模式下输出 window,严格模式下输出 undefined
2. 隐式绑定:作为对象方法调用
当函数被某个对象调用时,this 会隐式绑定到该对象上,即指向调用它的对象。
代码示例:
const obj = {
name: '张三',
getName: function() {
return this.name;
}
};
obj.getName(); // this 指向 obj,返回 '张三'
- 隐式丢失:当多层对象嵌套调用时,
this只绑定到最近一层的调用对象;若将对象方法赋值给变量再调用,会丢失绑定,触发默认绑定。
代码示例:
const obj2 = {
level: 2,
nested: {
level: 1,
getLevel: function() {
return this.level;
}
}
};
obj2.nested.getLevel(); // this 指向 nested,返回 1
const getLevel = obj2.nested.getLevel;
getLevel(); // 独立调用,触发默认绑定,非严格模式下返回 undefined
3. 显式绑定:手动指定 this
通过 call()、apply()、bind() 三个方法,可以手动强制指定函数执行时的 this 指向,这就是显式绑定。
fn.call(obj, arg1, arg2, ...):将this绑定到obj,call负责帮fn接受参数,参数逐个传入。fn.apply(obj, [arg1, arg2, ...]):将this绑定到obj,apply负责帮fn以数组的形式接受参数。fn.bind(obj, arg1, arg2, ...):不立即执行,返回一个新函数,新函数的this永久绑定到obj,bind负责帮fn接受参数,返回一个新的函数,需要调用新的函数才能实现调用的效果。
代码示例:
function greet(greeting) {
console.log(`${greeting}, I'm ${this.name}`);
}
const user = { name: '张三' };
greet.call(user, 'Hi'); // 输出: Hi, I'm 张三
greet.apply(user, ['Hello']); // 输出: Hello, I'm 张三
const boundGreet = greet.bind(user, 'Hey');
boundGreet(); // 输出: Hey, I'm 张三
4.new 绑定:构造函数中的 this
当使用 new 关键字调用函数(构造函数)时,会触发 new 绑定:
- 创建一个全新的空对象。
- 将构造函数的
this绑定到这个新对象。 - 执行构造函数内部代码。
- 将这个新对象的
__proto__指向构造函数的prototype。 - 返回这个新对象。
代码示例:
Person.prototype.sayName = 'hello'
function Person() {
// var obj={} --1
//Person.call(obj) --2
this.name = '张三' //--3
//obj.__proto__=Person.prototype //--4
//return obj --5
}
let p = new Person()
console.log(p.name) //张三
注:如果构造函数中 return 了一个引用类型(对象 / 数组 / 函数),则 new 表达式会返回这个 return 的值,而非新创建的对象。
5.箭头函数:继承外层作用域的 this
ES6 引入的箭头函数是一个特殊存在:它没有自己的 this。
箭头函数中的 this 完全继承自外层作用域(定义时的作用域)的 this,且在定义时就确定了,不会随调用方式改变。
代码示例:
const obj = {
name: '张三',
greet: function() {
// 箭头函数的 this 继承自外层 greet 方法的 this(即 obj)
setTimeout(() => {
console.log(`Hello, ${this.name}`); // 输出: Hello, 张三
}, 100);
}
};
obj.greet();
对比普通函数:如果 setTimeout 中是普通函数,独立调用时 this 会指向全局对象,导致 this.name 为 undefined。
四、绑定优先级与常见陷阱
绑定优先级
当多种绑定规则同时存在时,优先级如下:箭头函数 > new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定 > 默认绑定
- 箭头函数的
this一旦确定,无法被call/apply/bind/new改变。 new绑定无法与call/apply同时使用,但优先级高于显式绑定。
常见陷阱
-
隐式丢失:将对象方法赋值给变量后调用,
this丢失绑定,指向全局。const getName = obj.getName; getName(); // this 不再指向 obj
解决:使用 bind() 永久绑定 this,或使用箭头函数。
-
回调函数中的 this:事件监听、
setTimeout等回调函数若为普通函数,this会指向全局或事件目标。解决:使用箭头函数继承外层this,或用bind()预先绑定。 -
严格模式的影响:严格模式下默认绑定的
this为undefined,容易引发错误。
五、总结
this 是 JavaScript 中实现面向对象编程的核心机制,理解它的关键在于:this 的指向由函数的调用方式决定。
- 记住五大绑定规则:默认绑定、隐式绑定、显式绑定、new 绑定、箭头函数。
- 关注函数的调用位置而非定义位置,这是判断
this指向的核心。 - 利用箭头函数或
bind()可以稳定this指向,避免常见陷阱。
理解 this 的本质,能让你写出更健壮、更易维护的 JavaScript 代码,也能在排查 this 相关问题时游刃有余。