文章

this指向的四种绑定规则深度解析

深入解析this的四种绑定规则:默认绑定、隐式绑定、显式绑定、new绑定,以及它们的优先级关系

this指向的四种绑定规则深度解析

一句话概括

this 的指向由调用方式决定,而非函数定义的位置——理解四种绑定规则,就掌握了 JavaScript 中 this 的全部奥秘。

背景

this 是 JavaScript 中最让人困惑的概念之一,也是面试高频考点。很多初学者会误以为 this 指向函数自身或作用域,但实际上:

  • Javathis 永远指向当前对象
  • Python:显式传递 self
  • JavaScriptthis 取决于如何调用(由调用栈决定),而非定义位置
1
2
3
4
5
6
7
8
9
function foo() {
  console.log(this);  // this 是什么?取决于如何调用!
}

// 四种不同的调用方式,this 各不相同
foo();                    // 1. 默认绑定 → undefined(非严格模式为 window)
const obj = { foo };     // 2. 隐式绑定 → obj
foo.call({ x: 1 });      // 3. 显式绑定 → { x: 1 }
new foo();               // 4. new绑定 → 新创建的对象

概念与定义

四种绑定规则

JavaScript 中 this 的绑定规则只有以下四种,按优先级从低到高排列:

优先级规则判断依据this 指向
1(最低)默认绑定独立函数调用undefined(严格模式)或 window(非严格模式)
2隐式绑定通过对象调用调用上下文对象(.[] 前的对象)
3显式绑定call/apply/bind第一个参数
4(最高)new 绑定new 运算符新创建的对象

最小示例

1. 默认绑定(Default Binding)

独立函数调用(没有任何上下文引用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 非严格模式
function foo() {
  console.log(this);  // window
}
foo();

// 严格模式
function bar() {
  'use strict';
  console.log(this);  // undefined
}
bar();

// 间接调用(也是默认绑定)
const obj = { fn: foo };
const fn2 = obj.fn;
fn2();  // this → undefined(严格模式)

2. 隐式绑定(Implicit Binding)

通过对象属性引用调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const person = {
  name: 'Alice',
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

person.greet();  // this → person,"Hello, I'm Alice"

// 丢失隐式绑定
const greet = person.greet;
greet();  // this → undefined(严格模式),this.name 为 undefined

// 回调函数中丢失绑定
function doSomething(fn) {
  fn();  // 独立调用,丢失 this
}
doSomething(person.greet);  // this → undefined

3. 显式绑定(Explicit Binding)

通过 callapplybind 强制指定 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function greet(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const person = { name: 'Bob' };

// call:立即调用,参数逐个传递
greet.call(person, 'Hi', '!');  // "Hi, I'm Bob!"

// apply:立即调用,参数以数组传递
greet.apply(person, ['Hello', '~']);  // "Hello, I'm Bob~"

// bind:返回绑定了 this 的新函数(不立即调用)
const boundGreet = greet.bind(person, 'Hey');
boundGreet('?');  // "Hey, I'm Bob?"

4. new 绑定(new Binding)

通过 new 调用构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name) {
  // this 指向新创建的对象
  this.name = name;
  console.log(this);  // Person { name: 'Alice' }
}

const p = new Person('Alice');

// new 的执行步骤:
// 1. 创建空对象
// 2. 原型链接:p.__proto__ = Person.prototype
// 3. 绑定 this:Person.call(p, 'Alice')
// 4. 返回对象

核心知识点拆解

优先级判断

当多种规则同时存在时,按优先级高者决定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function foo() {
  console.log(this);
}

const obj1 = { name: 'obj1', foo };
const obj2 = { name: 'obj2', foo };

// new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

// 场景1:隐式 + 默认
obj1.foo();  // this → obj1(隐式)

// 场景2:显式 + 隐式 → 显式胜出
obj1.foo.call(obj2);  // this → obj2(显式覆盖隐式)

// 场景3:new + 显式 → new 胜出
function Bar() {
  this.x = 1;
}
const b = new Bar();  // this → b,new 绑定覆盖显式

箭头函数的特殊性

箭头函数没有自己的 this,它继承外层作用域的 this(定义时确定,不可修改):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 普通函数:this 由调用方式决定
function normal() {
  console.log(this);
}
normal.call({ x: 1 });  // { x: 1 }

// 箭头函数:this 继承外层
const arrow = () => {
  console.log(this);
};
arrow.call({ x: 1 });  // window(忽略 call 的 this,继承定义时的 this)

// 在对象方法中使用箭头函数的坑
const counter = {
  count: 0,
  // 箭头函数 → this 指向 window(不是 counter!)
  increase: () => this.count++,
  // 普通函数 → this 指向 counter
  decrease: function() { this.count--; }
};

counter.increase.call(counter);  // window.count++(不是 counter.count!)

绑定丢失的经典场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 场景1:赋值给变量
const getName = user.getName;
getName();  // this → undefined(严格模式)

// 场景2:作为回调传递
setTimeout(user.getName, 1000);  // this → undefined

// 场景3:解决方案:bind / 箭头函数 / that = this
setTimeout(user.getName.bind(user), 1000);  // ✅ 绑定正确

// 场景4:Vue / React 中的 this 问题
class Counter extends React.Component {
  handleClick() {
    this.setState({ count: this.state.count + 1 });  // this 丢失!
  }

  render() {
    // ❌ 直接传 handleClick → this 为 undefined
    // ✅ 方式1:bind
    // ✅ 方式2:箭头函数
    // ✅ 方式3:class fields 语法
    return <button onClick={this.handleClick.bind(this)}>Click</button>;
  }
}

实战案例

案例1:实现一个 bind 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 手写 bind:核心是返回新函数,在新函数内部用 apply 执行原函数
Function.prototype.myBind = function(context, ...args) {
  // this 是原函数(foo)
  const fn = this;

  // bind 返回的函数可能是普通调用或 new 调用
  return function(...innerArgs) {
    // new 调用时,this 应该指向新创建的对象,而非 context
    if (this instanceof fn) {
      return fn.apply(this, [...args, ...innerArgs]);
    }
    return fn.apply(context, [...args, ...innerArgs]);
  };
};

// 测试
function greet(greeting, name) {
  console.log(`${greeting}, ${name}! My name is ${this.name}`);
}

const person = { name: 'Charlie' };
const boundGreet = greet.myBind(person, 'Hello');
boundGreet('World');  // "Hello, World! My name is Charlie"

// new 调用时,bind 指定的 this 被忽略
const p = new boundGreet('Test');  // this → 新创建的对象

案例2:实现一个简易的事件绑定函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 常见场景:封装 DOM 事件绑定,自动维护 this
function addEvent(el, event, handler) {
  // 方式1:使用 bind
  el.addEventListener(event, handler.bind(el));

  // 方式2:使用箭头函数包装
  el.addEventListener(event, (e) => handler.call(el, e));

  // 方式3:显式保存 this(兼容性更好)
  const boundHandler = function(e) {
    handler.call(el, e);
  };
  el.addEventListener(event, boundHandler);
  return () => el.removeEventListener(event, boundHandler);  // 返回移除函数
}

// 使用
const button = document.querySelector('button');
const remove = addEvent(button, 'click', function() {
  console.log(this);  // this 始终指向 button
});

案例3:类数组转数组的多种实现对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function toArray() {
  // ❌ 错误:Array.prototype.slice 调用时 this 为 arguments(类数组)
  // return Array.prototype.slice();  // 需要传 context

  // ✅ 显式绑定 this
  return Array.prototype.slice.call(arguments);

  // ✅ 借用数组方法
  return [].slice.call(arguments);

  // ✅ ES6+
  return Array.from(arguments);
  return [...arguments];
}

底层原理

执行上下文与 this 绑定

JavaScript 代码执行分为两个阶段:

  1. 创建阶段:创建执行上下文,设置 this 绑定
  2. 执行阶段:执行代码,this 的值已经在创建阶段确定
1
2
3
4
5
6
// V8 引擎中 this 的绑定时机
function executeCode() {
  // 创建执行上下文时,this 已经被绑定
  // 此时根据调用方式确定 this 值
  console.log(this);
}

call 的底层实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 简化版 call 实现
Function.prototype.myCall = function(context, ...args) {
  // context 为 null/undefined 时,指向全局对象
  context = context || globalThis;

  // 在 context 上创建临时属性,存储原函数
  const key = Symbol('fn');
  context[key] = this;

  // 通过 context 调用,this 自动指向 context
  const result = context[key](...args);

  // 清理临时属性
  delete context[key];

  return result;
};

V8 引擎的 this 优化

V8 使用 Hidden Class(隐藏类)和 Inline Cache(内联缓存)优化属性访问,this 的类型在 JIT 编译时如果保持稳定,可以获得更好的优化。

高频面试题解析

Q1:this 的四种绑定规则是什么?优先级如何?

答: 四种规则:

  • 默认绑定:独立函数调用 → window/undefined
  • 隐式绑定obj.method()obj
  • 显式绑定fn.call(obj) / fn.apply(obj) / fn.bind(obj)obj
  • new 绑定new Fn() → 新建对象

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

Q2:箭头函数的 this 与普通函数有何不同?

答: 箭头函数没有自己的 this,它继承外层作用域的 this,且不可被修改call/apply/bind 无效)。普通函数的 this 由调用方式决定,可被显式绑定修改。

Q3:什么是 this 绑定丢失?如何解决?

答: 将对象方法赋值给变量或作为回调传递时,隐式绑定丢失。

解决方式:

1
2
3
4
5
6
7
8
9
10
// 方式1:bind
const fn = obj.method.bind(obj);
setTimeout(fn, 1000);

// 方式2:箭头函数包裹
setTimeout(() => obj.method(), 1000);

// 方式3:保存 this 引用
const that = this;
setTimeout(function() { that.method(); }, 1000);

Q4:new 操作符做了什么?

答: new Fn(args) 执行步骤:

  1. 创建空对象 {}
  2. 设置原型链:obj.__proto__ = Fn.prototype
  3. 绑定 thisFn.call(obj, args)
  4. 返回对象(若构造函数返回对象则返回该对象,否则返回新对象)

Q5:以下代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
var name = 'window';

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

const greet = obj.greet;
greet();              // 输出什么? → undefined(严格模式)或 window
obj.greet();          // 输出什么? → obj
greet.call(obj);      // 输出什么? → obj

答: undefinedobjobj。注意严格模式下默认绑定 thisundefined

总结与扩展

核心要点

  1. this 由调用方式决定,不是定义位置
  2. 四种绑定规则:默认 → 隐式 → 显式 → new,优先级递增
  3. 箭头函数没有自己的 this,继承外层作用域的 this
  4. 绑定丢失是最常见的 bug 源头,用 bind / 箭头函数 / 保存引用解决
  5. new 绑定的优先级最高,但实际面试中显式绑定的 bind 更常考

扩展思考

  • this 在异步回调中的行为:Promise 的 .then 回调中 this 的值取决于回调函数的类型(普通函数 → undefined;箭头函数 → 继承外层)
  • 类字段语法class fields):handleClick = () => {} 相当于在构造函数中 this.handleClick = () => {},常用于 React 类组件的事件处理
  • 装饰器模式中的 thisFunction.prototype.decorate 等高级用法
本文由作者按照 CC BY 4.0 进行授权