前端必看:this 不是玄学!5 大绑定规则帮你永久告别 this 困惑

0 阅读2分钟

在 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)。
  • 严格模式下:thisundefined

代码示例:

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 绑定:

  1. 创建一个全新的空对象。
  2. 将构造函数的 this 绑定到这个新对象。
  3. 执行构造函数内部代码。
  4. 将这个新对象的 __proto__ 指向构造函数的 prototype
  5. 返回这个新对象。

代码示例:

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.nameundefined

四、绑定优先级与常见陷阱

绑定优先级

当多种绑定规则同时存在时,优先级如下:箭头函数 > new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定 > 默认绑定

  • 箭头函数的 this 一旦确定,无法被 call/apply/bind/new 改变。
  • new 绑定无法与 call/apply 同时使用,但优先级高于显式绑定。

常见陷阱

  1. 隐式丢失:将对象方法赋值给变量后调用,this 丢失绑定,指向全局。

    const getName = obj.getName;
    getName(); // this 不再指向 obj
    

解决:使用 bind() 永久绑定 this,或使用箭头函数。

  1. 回调函数中的 this:事件监听、setTimeout 等回调函数若为普通函数,this 会指向全局或事件目标。解决:使用箭头函数继承外层 this,或用 bind() 预先绑定。

  2. 严格模式的影响:严格模式下默认绑定的 thisundefined,容易引发错误。

五、总结

this 是 JavaScript 中实现面向对象编程的核心机制,理解它的关键在于:this 的指向由函数的调用方式决定

  • 记住五大绑定规则:默认绑定、隐式绑定、显式绑定、new 绑定、箭头函数。
  • 关注函数的调用位置而非定义位置,这是判断 this 指向的核心。
  • 利用箭头函数或 bind() 可以稳定 this 指向,避免常见陷阱。

理解 this 的本质,能让你写出更健壮、更易维护的 JavaScript 代码,也能在排查 this 相关问题时游刃有余。