人人都会AI编程

5.3 绑定优先级排序与特殊场景判定

更新时间:2026-07-11

上一节我们梳理了 this 的四大绑定规则,但在实际代码中,往往多条规则会同时作用于一个函数调用。此时就需要一个明确的“优先级排序”来决定 this 最终指向哪里,这也是面试和实际排坑中的高频考点。

5.3.1 优先级排序:记忆口诀与实战验证

四大规则的优先级如下(从高到低):

  1. new 绑定 —— 构造函数调用,this 指向新创建的对象。
  2. 显式绑定 —— call、apply、bind 强制指定 this。
  3. 隐式绑定 —— 对象方法调用,this 指向调用对象。
  4. 默认绑定 —— 独立函数调用,非严格模式指向全局对象,严格模式为 undefined。

我们可以通过几段代码,一步步验证这条优先级链。

验证:显式绑定 vs 隐式绑定

const obj1 = {
  name: 'obj1',
  show() {
    console.log(this.name);
  }
};

const obj2 = {
  name: 'obj2'
};

obj1.show();              // obj1 —— 隐式绑定
obj1.show.call(obj2);     // obj2 —— 显式绑定优先级 > 隐式绑定

验证:new 绑定 vs 显式绑定

function Person(name) {
  this.name = name;
}

const obj = {};
const BoundPerson = Person.bind(obj); // 硬绑定到 obj
BoundPerson('尝试修改');              // 非 new 调用,this 为 obj

const instance = new BoundPerson('new出来的');
console.log(obj.name);      // "尝试修改"   —— 没有 new 时生效
console.log(instance.name); // "new出来的"  —— new 绑定优先级 > 显式绑定

new 操作符在调用 new BoundPerson(...) 时,引擎会创建一个新对象,并将该对象作为构造函数的 this。即便函数已经通过 bind 硬绑定了另一个对象,new 也会覆盖该绑定。new 绑定是排在显式绑定之上的

验证:new 绑定 vs 隐式绑定

const obj = {
  create(name) {
    this.name = name;
  }
};

const a = new obj.create('a');
console.log(a.name); // "a" —— new 绑定优先级 > 隐式绑定

综合来看,优先级顺序确实为:new > 显式 > 隐式 > 默认。记住这条链,绝大多数 this 指向问题都能迎刃而解。

5.3.2 特殊场景 1:显式绑定传入 null / undefined

在使用 callapplybind 时,如果第一个参数传入 nullundefined,实际效果会退化为默认绑定

const a = '全局';
function show() {
  console.log(this.a);
}

show.call(null);       // 非严格模式输出 "全局";严格模式 this 为 null,报错
show.apply(undefined); // 同上
const fn = show.bind(null);
fn();                  // 同上

为什么会有这种设计? 早期代码中经常用 fn.apply(null, args) 来展开数组传参(现在可用展开运算符),这种写法不关心 this 指向,只是借用了 apply 的参数列表能力。它会导致 this 意外指向全局对象,容易引发全局变量污染,应尽量使用空对象或严格模式规避。

5.3.3 特殊场景 2:箭头函数的 “词法 this” 不参与排序

箭头函数不存在自身 this,它通过作用域链从外层(定义时所处的执行上下文)中捕获 this。因此,箭头函数不适用任何绑定规则,call、apply、bind、new 对它的 this 都无效。

const outer = {
  name: 'outer',
  inner: () => {
    console.log(this.name); // 这里 this 指向全局,因为箭头函数定义在全局作用域
  }
};
outer.inner(); // 非严格模式:undefined 或全局对象的 name

const other = { name: 'other' };
outer.inner.call(other); // 无效,仍为全局 this

箭头函数的 this 在定义时就已经“锁死”,后续无论如何调用都无法更改。这常被用于在回调函数或闭包中“保持”外层 this,但一旦将箭头函数作为对象方法使用,就会失去预期的 this。只有普通函数才能享受四大绑定规则。

5.3.4 特殊场景 3:硬绑定(bind)之后的 “铁板一块”

通过 bind 返回的函数,其 this 被永久锁定为绑定的对象(除非使用 new 调用,new 优先级更高)。之后再用 call 或 apply 尝试修改 this 是徒劳的。

function say() {
  console.log(this.name);
}
const bindSay = say.bind({ name: 'locked' });
bindSay();                    // locked

bindSay.call({ name: 'new' }); // 仍然是 locked,call 改不动

需要特别指出,如果对已经 bind 过的函数再次 bind,只有第一次 bind 设定的 this 对象有效

const bindTwice = bindSay.bind({ name: 'second' });
bindTwice(); // locked,而不是 second

本质上是因为 bind 返回的是一个内部包装函数,它的 this 已经被固定为最初传入的值,后续的 bind 只能继续包装而不能替换最初的绑定对象。

5.3.5 小结:快速判定 this 的万能提问

遇到一个函数调用,心里按顺序问自己三个问题:

  1. 函数是否通过 new 调用? → 是:this 指向新创建的对象。
  2. 函数是否通过 call / apply / bind 调用? → 是:this 指向显式指定的对象(注意 null/undefined 会退化为默认绑定)。
  3. 函数是否作为对象的方法调用? → 是:this 指向调用该方法的对象。

如果以上都不是,那就是默认绑定。同时,箭头函数永远跳过上述问题,直接从外层作用域抓取 this。

掌握了这层优先级与特殊场景,this 指向的问题就从“玄学”变成了有章可循的推理过程。