跳到主要内容

this 是函数执行上下文(Execution Context)的核心属性,指向调用该函数的对象。但它的指向并非固定,而是由函数的调用方式动态决定

注意

以下内容完全是自(copy)主(from)研(腾讯)发(元宝)

一、this 的绑定规则(核心)

this 的最终指向由函数的调用位置绑定方式共同决定,主要分为以下 4 种规则:

1. 默认绑定(Default Binding)

当函数独立调用(非对象方法、非构造函数、非显式绑定)时,this 指向全局对象(浏览器为 window,Node.js 为 global)。

严格模式("use strict" 下,thisundefined

示例:

function show() {
// 浏览器:window;
// Node.js:global;
// 严格模式:undefined
console.log(this);
}
// 独立调用 → 默认绑定
show();

2. 隐式绑定(Implicit Binding)

当函数作为对象的方法调用时,this 指向调用该方法的对象(即“所属对象”)。

示例:

const obj = {
name: 'Alice',
greet() {
// this 指向 obj
console.log(`Hello, ${this.name}`);
},
};
// "Hello, Alice"(obj 调用 greet,this 绑定 obj)
obj.greet();

注意:若对象属性被赋值给其他变量,可能丢失隐式绑定:

示例:

const greet = obj.greet;

// 独立调用 → 默认绑定(this 指向全局或 undefined)
greet();

3. 显式绑定(Explicit Binding)

通过 callapplybind 方法强制指定 this 的指向。

  • call:立即调用函数,this 指向第一个参数,后续参数为函数参数。
  • apply:立即调用函数,this 指向第一个参数,第二个参数为参数数组。
  • bind:返回一个新函数,永久绑定 this 和部分参数(不立即调用)。

示例:

function say(name, age) {
console.log(`我是${name},今年${age}岁,this是${this.title}`);
}

const person = { title: '工程师' };

// call 显式绑定 this
// "我是张三,今年25岁,this是工程师"
say.call(person, '张三', 25);

// apply 显式绑定 this(参数用数组)
// "我是李四,今年30岁,this是工程师"
say.apply(person, ['李四', 30]);

// bind 永久绑定 this(返回新函数)
const boundSay = say.bind(person);
// "我是王五,今年35岁,this是工程师"
boundSay('王五', 35);

链式绑定:多次 bind 仅第一次生效:

信息

当对一个函数使用 bind 后,返回的是一个 exotic function object (特殊函数对象),它内部有 [[BoundThis]][[BoundArgs]] 等内部插槽,用于保存绑定的 this 和参数。

示例

const obj1 = { a: 1 };
const obj2 = { b: 2 };
function foo() {
console.log(this);
}
const boundFoo = foo.bind(obj1).bind(obj2);
// 输出 obj1(仅第一次 bind 生效)
boundFoo();

4. new 绑定(构造函数绑定)

当函数作为构造函数调用(通过 new 关键字)时,this 指向新创建的实例对象

示例

function Person(name) {
this.name = name; // this 指向新创建的 Person 实例
}
const alice = new Person('Alice');
console.log(alice.name); // "Alice"

注意:若构造函数返回一个对象(非原始值),this 会被覆盖为返回的对象:

function Dog() {
this.name = '旺财';
// 返回对象 → this 被覆盖
return { name: '小白' };
}
const dog = new Dog();

// "小白"(而非 "旺财")
console.log(dog.name);

二、特殊场景下的 this 指向

1. 箭头函数(Arrow Function)

箭头函数没有自己的 this,其 this 继承自定义时的外层作用域(词法作用域),且无法通过 call/apply/bind 修改。

示例

const obj = {
name: 'Alice',
greet: () => {
// this 继承自外层(此处可能是全局或 obj 的外层)
console.log(this.name);
},
};

// 若外层是全局,输出 undefined(严格模式)或 window.name
obj.greet();

// 对比普通函数
const obj2 = {
name: 'Bob',
greet() {
// 普通函数,this 指向 obj2
console.log(this.name); // "Bob"
},
};
obj2.greet();

常见用途:在事件回调或嵌套函数中固定 this

class Counter {
count = 0;
increment() {
setInterval(() => {
// 箭头函数继承 increment 的 this(指向 Counter 实例)
this.count++;
}, 1000);
}
}

2. 事件处理函数中的 this

  • DOM 事件监听:回调函数的 this 默认指向触发事件的 DOM 元素(除非显式绑定)。

示例

document.querySelector('button').addEventListener('click', function () {
console.log(this); // 指向被点击的 button 元素
});
  • 箭头函数作为事件回调this 继承自定义时的外层作用域(可能不是 DOM 元素)。

3. 定时器/异步回调中的 this

  • setTimeout/setInterval:回调函数的 this 默认指向全局对象(或严格模式的 undefined),除非显式绑定。

示例

const obj = {
name: 'Alice',
greet() {
setTimeout(() => {
// 箭头函数继承 greet 的 this(指向 obj)→ "Alice"
console.log(this.name);
}, 100);
// 普通函数的话:
setTimeout(function () {
// this 指向全局 → undefined(严格模式)
console.log(this.name);
}, 100);
},
};
obj.greet();

4. 嵌套函数中的 this

嵌套函数(如普通函数内部的函数)的 this 通常指向全局对象(或严格模式的 undefined),除非通过闭包保存外层 this

示例

const obj = {
name: 'Bob',
outer() {
function inner() {
// 全局对象(非严格模式)或 undefined(严格模式)
console.log(this);
}
inner();
},
};

// 输出非预期的 this
obj.outer();

解决:通过闭包保存外层 thisconst that = this)或使用箭头函数:

outer() {
// 保存外层 this
const that = this;
function inner() {
// "Bob"
console.log(that.name);
}
inner();
}
// 或用箭头函数:
outer() {
const inner = () => {
// "Bob"(箭头函数继承 outer 的 this)
console.log(this.name);
};
inner();
}

三、需要避免的 this 陷阱

1. 对象方法赋值给变量后丢失 this

const obj = {
name: 'Alice',
greet() {
console.log(this.name);
},
};
const greet = obj.greet;
greet(); // this 指向全局 → 输出 undefined(非严格模式)

解决:用 bind 绑定 this 或使用箭头函数:

const greet = obj.greet.bind(obj);
// 或定义时用箭头函数:

const objA = {
name: "Tom",
greet: ()=> console.log(this.name)
}

const obj = {
name: "Alice",
// 但更推荐显式绑定或闭包
greet: function() { ... }.bind(this)
};

2. 构造函数中忘记 new 关键字

若构造函数未通过 new 调用,this 会指向全局对象(污染全局):

function Person(name) {
this.name = name; // 未 new 时,this 指向全局 → 全局被污染
}
const p = Person('Alice'); // 全局出现 name: "Alice"

解决:构造函数首字母大写,或在内部检查 this 是否为实例:

function Person(name) {
if (!(this instanceof Person)) {
throw new Error('必须通过 new 调用');
}
this.name = name;
}

3. 箭头函数误用导致 this 错误

箭头函数的 this 不可修改,若在需要动态 this 的场景使用会出错:

const obj = {
name: 'Alice',
greet: () => console.log(this.name),
clickHandler: function () {
// 箭头函数继承 clickHandler 的 this(指向 obj),但如果外层是全局,就会错误
document.querySelector('button').onclick = () => {
console.log(this.name); // 可能不是预期的 obj
};
},
};

解决:在需要动态 this 的场景(如事件回调)使用普通函数,或通过闭包传递 this

四、总结:this 指向的判断步骤

  1. 是否通过 new 调用? → 是 → this 指向新实例。
  2. 是否通过 call/apply/bind 显式绑定? → 是 → this 指向绑定的对象。
  3. 是否作为对象的方法调用? → 是 → this 指向调用该方法的对象。
  4. 否则 → 默认绑定(全局对象或 undefined,取决于严格模式)。

掌握 this 的绑定规则和常见陷阱,能帮助你写出更健壮、可维护的 JavaScript 代码。关键是根据调用方式和场景,准确判断 this 的指向,必要时通过显式绑定或箭头函数控制其行为。