this指向的四种绑定规则深度解析
深入解析this的四种绑定规则:默认绑定、隐式绑定、显式绑定、new绑定,以及它们的优先级关系
this指向的四种绑定规则深度解析
一句话概括
this 的指向由调用方式决定,而非函数定义的位置——理解四种绑定规则,就掌握了 JavaScript 中 this 的全部奥秘。
背景
this 是 JavaScript 中最让人困惑的概念之一,也是面试高频考点。很多初学者会误以为 this 指向函数自身或作用域,但实际上:
- Java:
this永远指向当前对象 - Python:显式传递
self - JavaScript:
this取决于如何调用(由调用栈决定),而非定义位置
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)
通过 call、apply、bind 强制指定 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 代码执行分为两个阶段:
- 创建阶段:创建执行上下文,设置
this绑定 - 执行阶段:执行代码,
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) 执行步骤:
- 创建空对象
{} - 设置原型链:
obj.__proto__ = Fn.prototype - 绑定 this:
Fn.call(obj, args) - 返回对象(若构造函数返回对象则返回该对象,否则返回新对象)
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
答: undefined、obj、obj。注意严格模式下默认绑定 this 为 undefined。
总结与扩展
核心要点
this由调用方式决定,不是定义位置- 四种绑定规则:默认 → 隐式 → 显式 → new,优先级递增
- 箭头函数没有自己的
this,继承外层作用域的 this - 绑定丢失是最常见的 bug 源头,用
bind/ 箭头函数 / 保存引用解决 new绑定的优先级最高,但实际面试中显式绑定的bind更常考
扩展思考
this在异步回调中的行为:Promise 的.then回调中this的值取决于回调函数的类型(普通函数 → undefined;箭头函数 → 继承外层)- 类字段语法(
class fields):handleClick = () => {}相当于在构造函数中this.handleClick = () => {},常用于 React 类组件的事件处理 - 装饰器模式中的
this:Function.prototype.decorate等高级用法
本文由作者按照 CC BY 4.0 进行授权