this 指向绑定规则

1. 核心规则

在 JS 中,this 是在函数被调用时才确定的,而不是在定义时。判断 this 指向,只需看函数从哪里被调用

(1) 默认绑定

独立函数调用。

  • 非严格模式:window (浏览器) 或 global (Node)。
  • 严格模式:undefined
function foo() {
  console.log(this.a);
}
var a = 2; // window.a
foo(); // 2 (非严格模式)

(2) 隐式绑定

函数作为对象的方法被调用。this 指向调用它的那个对象(只看最后一层)。

function foo() {
  console.log(this.a);
}
var obj = { a: 2, foo: foo };
obj.foo(); // 2

// 链式调用
obj2.obj1.foo(); // obj1 (最后一层)

陷阱 (隐式丢失) 如果把对象方法赋值给变量,再调用变量,this 会丢失并退化为默认绑定。

var bar = obj.foo;
var a = "global";
bar(); // "global"

(3) 显式绑定

通过 call, apply, bind 强制指定 this

var obj = { a: 2 };
foo.call(obj); // 2

硬绑定 bind 返回一个新函数,该函数的 this永久绑定,无法再被 call/apply 修改。

var bar = foo.bind(obj);
bar.call(window); // 2 (仍然是 obj)

(4) new 绑定

使用 new 关键字调用构造函数时,会创建一个新对象,并将 this 指向它。 (如果构造函数返回特定对象,则 this 失效,以返回对象为准)

function Foo(a) {
  this.a = a;
}
var bar = new Foo(2);
console.log(bar.a); // 2

优先级new > 显式绑定 (bind) > 隐式绑定 > 默认绑定

2. 箭头函数 —— 规则之外

箭头函数根本没有自己的 this。它只会捕获其定义时外层作用域的 this 值。 一旦捕获,永久不变,甚至无法被 call/apply/bind/new 修改。

function foo() {
  return (a) => {
    // this 继承自 foo() 调用时的 this
    console.log(this.a);
  };
}

var obj1 = { a: 2 };
var obj2 = { a: 3 };

var bar = foo.call(obj1);
bar.call(obj2); // 2 (不会变为 3,因为箭头函数的 this 已经定死在 obj1 了)

3. 面试陷阱题

(1) 为什么 React 类组件方法需要 bind?

因为类方法默认没有绑定实例。当作为事件回调传递给 JSX 时 (onClick={this.handleClick}),实际执行时并不是 instance.handleClick() 而是直接调用引用,导致 this 丢失 (变为 undefined)。

(2) 手写 bind (核心考点)

需要处理 new 调用的优先级。如果被 bind 的函数后来被 new 调用了,那么 this 必须指向新实例,而不是 bind 绑定的对象。

Function.prototype.myBind = function (context, ...args) {
  const fn = this;
  return function F(...newArgs) {
    if (this instanceof F) {
      return new fn(...args, ...newArgs);
    }
    return fn.apply(context, [...args, ...newArgs]);
  };
};

(3) setTimeout 中的 this

setTimeout 回调函数的执行环境是全局对象 (window),除非使用箭头函数或 bind。

var obj = {
  fn: function () {
    setTimeout(function () {
      console.log(this);
    }, 100);
  },
};
obj.fn(); // window