this 绑定规则

系统梳理 JavaScript 中 this 的四条绑定规则(默认、隐式、显式、new)及箭头函数的词法 this,结合面试高频场景深入讲解优先级与陷阱。

为什么 this 是 JavaScript 面试的永恒考点

this 是 JavaScript 中最令人困惑的特性之一。不同于大多数语言中 this 指向定义时的对象,JavaScript 的 this调用时动态决定(箭头函数除外)。理解 this 的绑定规则,是理解对象方法、构造函数、事件处理、React 类组件等所有场景的基础。


一、四条绑定规则

"To understand this, you have to understand the call-site: the location in code where a function is called (not where it's declared)."

— Kyle Simpson, You Don't Know JS: this & Object Prototypes

1.1 默认绑定(Default Binding)

当函数以独立调用(plain, undecorated function call)方式执行时,this 指向全局对象(非严格模式)或 undefined(严格模式)。

function showThis() {
  console.log(this);
}

showThis(); // 非严格模式:window(浏览器)/ global(Node)
            // 严格模式:undefined

面试要点:严格模式下默认绑定为 undefined,这是很多"意外 TypeError"的来源。

1.2 隐式绑定(Implicit Binding)

当函数作为对象的方法调用时,this 指向调用该方法的对象。

const user = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  }
};

user.greet(); // 'Alice' — this → user

隐式丢失(Implicit Loss)——最常见的陷阱:

const fn = user.greet;  // 将方法赋值给变量
fn();                    // undefined — 隐式绑定丢失,退回默认绑定

// 回调中也会丢失
setTimeout(user.greet, 100); // undefined
[1].forEach(user.greet);     // undefined

原理:赋值或传参只是传递了函数引用,调用时不再通过 user. 触发,因此隐式绑定不生效。

1.3 显式绑定(Explicit Binding)

使用 callapplybind 明确指定 this

function greet(greeting) {
  console.log(`${greeting}, ${this.name}`);
}

const user = { name: 'Alice' };

greet.call(user, 'Hello');    // "Hello, Alice"
greet.apply(user, ['Hello']); // "Hello, Alice"

const bound = greet.bind(user);
bound('Hello');               // "Hello, Alice"
方法 调用时机 参数传递方式
call 立即调用 逐个传参
apply 立即调用 数组传参
bind 返回新函数,延迟调用 逐个传参(支持偏函数)

硬绑定(Hard Binding)bind 创建的函数无法被再次隐式或显式覆盖。

const hardBound = greet.bind(user);
const otherUser = { name: 'Bob' };
hardBound.call(otherUser, 'Hi'); // "Hi, Alice" — 仍然是 Alice

1.4 new 绑定

使用 new 调用构造函数时,this 指向新创建的实例对象

function Person(name) {
  this.name = name;
  // 隐式 return this
}

const alice = new Person('Alice');
alice.name; // 'Alice'

new 绑定的完整流程见 prototype-chain 专题中 new 运算符的四步过程。


二、优先级

当多条规则同时适用时,优先级从高到低为:

new 绑定  >  显式绑定(call/apply/bind)  >  隐式绑定  >  默认绑定
function foo(val) {
  this.a = val;
}

const obj = {};
const bar = foo.bind(obj);

bar(2);
console.log(obj.a); // 2 — 显式绑定生效

const baz = new bar(3);
console.log(obj.a); // 2 — 没有变
console.log(baz.a); // 3 — new 绑定优先级高于 bind

面试要点new 可以覆盖 bindthis 绑定。这是因为 bind 的内部实现检测到 new 调用时会让步。


三、箭头函数的词法 this

箭头函数没有自己的 this,它从定义时的外层词法作用域继承 this

const user = {
  name: 'Alice',
  greetLater() {
    setTimeout(() => {
      console.log(this.name); // 'Alice' — 继承 greetLater 的 this
    }, 100);
  }
};

箭头函数的 this 不可改变

const arrow = () => console.log(this);

arrow.call({ name: 'Bob' });   // 仍然是外层 this,无法覆盖
arrow.bind({ name: 'Bob' })(); // 仍然是外层 this
new arrow();                    // TypeError: arrow is not a constructor

箭头函数不适用的场景

// ❌ 对象方法 — this 不会指向 obj
const obj = {
  name: 'Alice',
  greet: () => console.log(this.name) // this → 外层(可能是 window)
};

// ❌ 原型方法
Person.prototype.getName = () => this.name; // ❌

// ❌ DOM 事件处理器(期望 this 指向元素)
button.addEventListener('click', () => {
  console.log(this); // ❌ 不是 button
});

// ❌ 需要 arguments 对象的场景
const fn = () => console.log(arguments); // ReferenceError

四、特殊场景

4.1 DOM 事件处理器

button.addEventListener('click', function () {
  console.log(this); // button 元素 — 隐式绑定由浏览器设置
});

button.addEventListener('click', () => {
  console.log(this); // 外层 this(通常是 window)
});

4.2 class 中的 this

class Timer {
  count = 0;

  // 方法定义 — this 取决于调用方式
  tick() {
    this.count++;
  }

  // 箭头函数属性 — this 始终指向实例
  tickArrow = () => {
    this.count++;
  };
}

const timer = new Timer();
const { tick, tickArrow } = timer;

tick();      // ❌ TypeError(严格模式下 this 为 undefined)
tickArrow(); // ✅ this 仍指向 timer 实例

React 类组件中的经典问题

class App extends React.Component {
  handleClick() {
    console.log(this); // undefined(如果不 bind)
  }

  render() {
    // 三种修复方案:
    return (
      <>
        <button onClick={this.handleClick.bind(this)}>Bind</button>
        <button onClick={() => this.handleClick()}>Arrow wrapper</button>
        {/* 或在 constructor 中 this.handleClick = this.handleClick.bind(this) */}
        {/* 或使用 class field 箭头函数:handleClick = () => { ... } */}
      </>
    );
  }
}

4.3 globalThis

ES2020 引入 globalThis,统一了不同环境中获取全局 this 的方式:

// 浏览器:globalThis === window
// Node.js:globalThis === global
// Web Worker:globalThis === self

五、手写 callapplybind

手写 call

Function.prototype.myCall = function (context, ...args) {
  context = context == null ? globalThis : Object(context);
  const key = Symbol('temp');
  context[key] = this;
  const result = context[key](...args);
  delete context[key];
  return result;
};

手写 apply

Function.prototype.myApply = function (context, args = []) {
  context = context == null ? globalThis : Object(context);
  const key = Symbol('temp');
  context[key] = this;
  const result = context[key](...args);
  delete context[key];
  return result;
};

手写 bind

Function.prototype.myBind = function (context, ...boundArgs) {
  const fn = this;

  return function bound(...args) {
    // 支持 new 调用
    if (new.target) {
      return new fn(...boundArgs, ...args);
    }
    return fn.apply(context, [...boundArgs, ...args]);
  };
};

六、面试高频题型

题型 1:说出输出结果

const obj = {
  name: 'obj',
  getName() { return this.name; },
  getNameArrow: () => this.name
};

console.log(obj.getName());      // 'obj'
console.log(obj.getNameArrow()); // undefined(或 window.name)
const fn = obj.getName;
console.log(fn());               // undefined(严格模式)

题型 2:修复 this 丢失

"给你一个回调函数,如何确保 this 指向正确的对象?"

答案要点:bind、箭头函数、class field 箭头函数、闭包变量 const self = this

题型 3:this 优先级排序

默认 < 隐式 < 显式 < new。能给出代码证明 new 覆盖 bind 的候选人会加分。


七、与其他主题的关联

关联主题 关系
scope-closure this 不走词法作用域链(箭头函数除外)
prototype-chain new 绑定是原型继承的核心步骤
async-programming async 方法中 this 丢失是常见 bug
design-patterns-js 观察者、策略等模式常需处理 this 上下文

参考资料

延展阅读