深入理解 this 绑定
五种绑定规则、优先级链、箭头函数特殊性、所有边界 case 一网打尽
什么是 this?
定义:this 是函数执行时的上下文对象引用。与大多数语言不同,JavaScript 的 this 不是在定义时确定的(箭头函数除外),而是在调用时根据调用方式动态绑定的。它的值取决于函数"怎么被调用",而非"在哪里定义"。
涉及场景:
- 对象方法调用:
obj.method()中this指向obj - 构造函数:
new Constructor()中this指向新创建的实例 - 事件处理器:DOM 事件回调中
this指向触发事件的元素 - 定时器/回调:
setTimeout中this丢失是经典陷阱 - React 类组件:方法中
this丢失需要手动bind或用箭头函数 - call/apply/bind:手动指定函数执行时的
this值
作用:
- 对象行为绑定:让方法能够访问所属对象的属性和其他方法
- 代码复用:同一个函数通过不同的
this在不同对象上工作 - 面向对象基础:构造函数和原型方法都依赖
this访问实例 - 高频面试考点:
this指向判断题是面试必考项
五种绑定规则
1. 默认绑定(独立调用)
函数独立调用时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。严格模式下为 undefined。
javascript
function showThis() {
console.log(this);
}
showThis(); // window(非严格模式)
// 严格模式
'use strict';
function showThisStrict() {
console.log(this);
}
showThisStrict(); // undefined
// 常见陷阱:嵌套函数的独立调用
const obj = {
name: '张三',
greet() {
console.log(this.name); // '张三'(隐式绑定)
function inner() {
console.log(this.name); // undefined(默认绑定!)
// inner 是独立调用的,不是 obj.inner()
}
inner();
}
};
obj.greet();
// IIFE 也是独立调用
const obj2 = {
name: '李四',
method() {
(function() {
console.log(this); // window(默认绑定)
})();
}
};2. 隐式绑定(对象方法调用)
函数作为对象的方法调用时,this 指向调用它的对象。
javascript
const obj = {
name: '张三',
greet() {
return `Hello, ${this.name}`;
}
};
console.log(obj.greet()); // 'Hello, 张三'
// 链式调用:this 指向最近的调用者
const a = {
name: 'a',
b: {
name: 'b',
getName() {
return this.name;
}
}
};
console.log(a.b.getName()); // 'b'(this 指向 b,不是 a)
// ⚠️ 隐式丢失:将方法赋值给变量
const fn = obj.greet;
console.log(fn()); // undefined(变成了默认绑定!)
// ⚠️ 隐式丢失:作为回调传递
function execute(callback) {
callback(); // 独立调用
}
execute(obj.greet); // undefined
// ⚠️ 隐式丢失:setTimeout
setTimeout(obj.greet, 100); // undefined
// ⚠️ 隐式丢失:数组方法
const handlers = [obj.greet];
handlers[0](); // undefined(虽然是 handlers[0]() 调用,但函数引用已经丢失了obj上下文)
// 实际上 handlers[0]() 的 this 指向 handlers 数组
// 解决方案
setTimeout(() => obj.greet(), 100); // ✅ 箭头函数保持上下文
setTimeout(obj.greet.bind(obj), 100); // ✅ bind 显式绑定3. 显式绑定(call / apply / bind)
使用 call、apply、bind 明确指定 this。
javascript
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: '张三' };
// call - 逐个传参
greet.call(user, 'Hello', '!'); // 'Hello, 张三!'
// apply - 数组传参
greet.apply(user, ['Hello', '!']); // 'Hello, 张三!'
// bind - 返回新函数,永久绑定this
const boundGreet = greet.bind(user, 'Hello');
boundGreet('!'); // 'Hello, 张三!'
boundGreet('?'); // 'Hello, 张三?'call vs apply vs bind 对比:
| 方法 | 执行时机 | 参数形式 | 返回值 |
|---|---|---|---|
call | 立即执行 | 逐个传参 fn.call(ctx, a, b) | 函数返回值 |
apply | 立即执行 | 数组传参 fn.apply(ctx, [a, b]) | 函数返回值 |
bind | 不执行,返回新函数 | 逐个传参(可柯里化) | 新函数 |
javascript
// 显式绑定的特殊情况:传入 null/undefined
function foo() {
console.log(this);
}
foo.call(null); // window(非严格模式,忽略null)
foo.call(undefined); // window
// 严格模式下保持 null/undefined
'use strict';
foo.call(null); // null
foo.call(undefined); // undefined
// 安全做法:使用空对象
const empty = Object.create(null);
foo.call(empty); // {}(不会意外修改全局对象)4. new 绑定(构造函数调用)
使用 new 调用函数时,this 指向新创建的对象。
javascript
function Person(name, age) {
// this = {} (新创建的对象)
this.name = name;
this.age = age;
// return this; (隐式返回)
}
const p = new Person('张三', 25);
console.log(p.name); // '张三'
// 特殊情况:构造函数显式返回对象
function Strange(name) {
this.name = name;
return { custom: true }; // 返回对象 → 替代 this
}
const s = new Strange('张三');
console.log(s.name); // undefined
console.log(s.custom); // true
// 返回非对象 → 忽略返回值,使用 this
function Normal(name) {
this.name = name;
return 42; // 返回原始值 → 被忽略
}
const n = new Normal('张三');
console.log(n.name); // '张三'5. 箭头函数绑定(词法 this)
箭头函数没有自己的 this,它继承定义时外层函数的 this。
javascript
const obj = {
name: '张三',
// 普通方法
greetNormal() {
setTimeout(function() {
console.log(this.name); // undefined(默认绑定)
}, 100);
},
// 箭头函数
greetArrow() {
setTimeout(() => {
console.log(this.name); // '张三'(继承 greetArrow 的 this)
}, 100);
}
};
// 箭头函数不能用 call/apply/bind 改变 this
const arrow = () => console.log(this);
arrow.call({ name: '张三' }); // window(call 无效!)
arrow.bind({ name: '张三' })(); // window(bind 也无效!)
// 箭头函数不能用作构造函数
const Arrow = () => {};
// new Arrow(); // ❌ TypeError: Arrow is not a constructor
// ⚠️ 箭头函数不适合做对象方法
const bad = {
name: '张三',
greet: () => {
console.log(this.name); // undefined
// 箭头函数的 this 是定义时的外层(全局),不是 bad
}
};
// ✅ 正确做法:用普通函数做对象方法
const good = {
name: '张三',
greet() {
console.log(this.name); // '张三'
}
};绑定优先级
new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定(obj.fn) > 默认绑定(fn())javascript
// 验证优先级
// 1. 隐式 vs 显式:显式胜
const obj = {
name: 'obj',
greet() { return this.name; }
};
console.log(obj.greet.call({ name: 'call' })); // 'call'(显式 > 隐式)
// 2. 显式 vs new:new 胜
function Foo(name) {
this.name = name;
}
const bound = Foo.bind({ name: 'bind' });
const instance = new bound('new');
console.log(instance.name); // 'new'(new > 显式)
// 3. 箭头函数无视所有规则
const arrowObj = {
name: 'obj',
getArrow() {
return () => this.name; // this 固定为 getArrow 的 this
}
};
const arrow = arrowObj.getArrow();
console.log(arrow.call({ name: 'call' })); // 'obj'(箭头函数无视 call)特殊场景
class 中的 this
javascript
class MyClass {
name = '张三';
// 普通方法 - this 取决于调用方式
greet() {
return this.name;
}
// 箭头函数属性 - this 永远指向实例
greetArrow = () => {
return this.name;
};
// 静态方法 - this 指向类本身
static create() {
console.log(this === MyClass); // true
return new this('created');
}
}
const obj = new MyClass();
// 普通方法:隐式丢失
const fn = obj.greet;
// fn(); // ❌ TypeError(class 默认严格模式,this 是 undefined)
// 箭头函数属性:不会丢失
const fn2 = obj.greetArrow;
console.log(fn2()); // '张三' ✅
// React 中的经典问题
class Button {
text = 'Click me';
// ❌ 传给事件处理器时 this 丢失
handleClick() {
console.log(this.text);
}
// ✅ 方案1:箭头函数属性
handleClick2 = () => {
console.log(this.text);
};
render() {
// ✅ 方案2:bind
element.addEventListener('click', this.handleClick.bind(this));
// ✅ 方案3:箭头函数包装
element.addEventListener('click', () => this.handleClick());
// ✅ 方案4:使用箭头函数属性
element.addEventListener('click', this.handleClick2);
}
}DOM 事件中的 this
javascript
const button = document.querySelector('#btn');
// addEventListener - this 指向绑定元素
button.addEventListener('click', function(e) {
console.log(this === button); // true
console.log(this === e.target); // true(如果直接点击该按钮)
console.log(this === e.currentTarget); // true
});
// 箭头函数 - this 是外层作用域
button.addEventListener('click', (e) => {
console.log(this === window); // true(箭头函数不绑定this)
console.log(e.target); // button(通过 e.target 访问)
});
// onclick 属性 - this 指向元素
button.onclick = function() {
console.log(this); // button
};
// HTML内联事件 - this 指向元素
// <button onclick="console.log(this)">Click</button>
// this → button 元素回调函数中的 this
javascript
// 数组方法的 thisArg 参数
const obj = { factor: 2 };
const arr = [1, 2, 3];
// forEach/map/filter/find/some/every 都支持第二个参数指定 this
arr.map(function(item) {
return item * this.factor;
}, obj); // [2, 4, 6]
// 注意:箭头函数作为回调时,thisArg 参数无效
arr.map((item) => {
return item * this.factor; // this 是外层,不受 thisArg 影响
}, obj);
// JSON.parse reviver 中的 this
JSON.parse('{"a": 1, "b": 2}', function(key, value) {
// this 指向当前解析的对象
console.log(key, value, this);
return value;
});globalThis(ES2020)
javascript
// 统一访问全局对象
console.log(globalThis); // 在任何环境中都指向全局对象
// 之前需要根据环境判断:
// 浏览器:window / self / frames
// Node.js:global
// Web Worker:self
// globalThis 统一了这些差异
globalThis.myGlobal = 'accessible everywhere';手写 call / apply / bind
手写 call
javascript
Function.prototype.myCall = function(context, ...args) {
// null/undefined → 全局对象
context = context == null ? globalThis : Object(context);
// 用 Symbol 避免属性名冲突
const key = Symbol('fn');
context[key] = this;
// 通过对象方法调用,实现隐式绑定
const result = context[key](...args);
delete context[key];
return result;
};
// 测试
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
console.log(greet.myCall({ name: '张三' }, 'Hello')); // 'Hello, 张三'手写 apply
javascript
Function.prototype.myApply = function(context, argsArray = []) {
context = context == null ? globalThis : Object(context);
const key = Symbol('fn');
context[key] = this;
const result = context[key](...argsArray);
delete context[key];
return result;
};手写 bind
javascript
Function.prototype.myBind = function(context, ...outerArgs) {
if (typeof this !== 'function') {
throw new TypeError('must be a function');
}
const fn = this;
const bound = function(...innerArgs) {
// new 调用时,this 指向新实例,忽略绑定的 context
return fn.apply(
this instanceof bound ? this : context,
[...outerArgs, ...innerArgs]
);
};
// 维护原型链
if (fn.prototype) {
bound.prototype = Object.create(fn.prototype);
}
return bound;
};经典面试题
题目1:综合判断
javascript
var name = 'window';
const obj = {
name: 'obj',
fn1: function() {
console.log(this.name);
},
fn2: () => {
console.log(this.name);
},
fn3: function() {
return function() {
console.log(this.name);
};
},
fn4: function() {
return () => {
console.log(this.name);
};
}
};
obj.fn1(); // 'obj'(隐式绑定)
obj.fn2(); // 'window'(箭头函数,外层是全局)
obj.fn3()(); // 'window'(返回的函数独立调用 → 默认绑定)
obj.fn4()(); // 'obj'(箭头函数继承 fn4 的 this)
const fn1 = obj.fn1;
fn1(); // 'window'(隐式丢失 → 默认绑定)
obj.fn3.call({ name: 'call' })(); // 'window'(call 改变 fn3 的 this,但返回的函数独立调用)
obj.fn4.call({ name: 'call' })(); // 'call'(箭头函数继承 fn4 的 this,fn4 被 call 绑定为 { name: 'call' })题目2:new vs bind
javascript
function Foo() {
this.a = 1;
return this;
}
Foo.prototype.getA = function() {
return this.a;
};
// 场景1:普通调用
const result1 = Foo();
console.log(result1.a); // 1(this 是 window,window.a = 1)
// 场景2:new 调用
const result2 = new Foo();
console.log(result2.a); // 1(this 是新对象)
// 场景3:bind 后 new
const BoundFoo = Foo.bind({ a: 100 });
const result3 = new BoundFoo();
console.log(result3.a); // 1(new 优先级高于 bind)
console.log(result3.getA()); // 1题目3:setTimeout 中的 this
javascript
const obj = {
x: 42,
getX() {
return this.x;
}
};
// 各种调用方式
console.log(obj.getX()); // 42(隐式绑定)
console.log(obj.getX.call({ x: 100 })); // 100(显式绑定)
setTimeout(obj.getX, 0); // undefined(隐式丢失)
setTimeout(obj.getX.bind(obj), 0); // 42(bind 修复)
setTimeout(() => console.log(obj.getX()), 0); // 42(箭头函数保持)
const arr = [obj.getX];
console.log(arr[0]()); // undefined(隐式丢失)判断 this 的流程图
调用 fn() 时,判断 this:
1. fn 是箭头函数吗?
→ 是:this = 定义时外层函数的 this(无法修改)
→ 否:继续 ↓
2. fn 是通过 new 调用的吗?
→ 是:this = 新创建的对象
→ 否:继续 ↓
3. fn 是通过 call/apply/bind 调用的吗?
→ 是:this = 传入的第一个参数(null/undefined 在非严格模式下转为全局对象)
→ 否:继续 ↓
4. fn 是通过 obj.fn() 调用的吗?
→ 是:this = obj
→ 否:继续 ↓
5. 默认绑定
→ 非严格模式:this = 全局对象(window/global)
→ 严格模式:this = undefined总结
this 绑定核心规则:
┌──────────────────────────────────────────────────────────┐
│ 规则 │ 调用方式 │ this 指向 │
├───────────────┼────────────────┼────────────────────────┤
│ 默认绑定 │ fn() │ window / undefined │
│ 隐式绑定 │ obj.fn() │ obj │
│ 显式绑定 │ fn.call(ctx) │ ctx │
│ new 绑定 │ new Fn() │ 新对象 │
│ 箭头函数 │ () => {} │ 外层 this(不可变) │
├──────────────────────────────────────────────────────────┤
│ 优先级:new > 显式 > 隐式 > 默认 │
│ 箭头函数:无视所有规则,永远继承外层 │
├──────────────────────────────────────────────────────────┤
│ 常见陷阱: │
│ • 方法赋值给变量 → 隐式丢失 │
│ • setTimeout 回调 → 隐式丢失 │
│ • 箭头函数做对象方法 → this 不指向对象 │
│ • class 方法作为回调 → 需要 bind 或箭头函数属性 │
└──────────────────────────────────────────────────────────┘