JavaScript this 关键字详解
简介
this 是 JavaScript 中最容易被误解的关键字之一。与其它语言不同,JavaScript 的 this 不是在定义时决定的,而是在调用时决定的。
核心规则:
this的值取决于函数的调用方式,而不是函数的定义位置。
function showThis() {
console.log(this);
}
// 同样的 showThis 函数,不同的调用方式,this 完全不同!
一、默认绑定(Default Binding)
独立函数调用时,this 使用默认绑定。
非严格模式
// 浏览器环境
function foo() {
console.log(this); // window(浏览器全局对象)
console.log(this === window); // true
}
foo();
// Node.js 环境
function bar() {
console.log(this); // global(Node.js 全局对象)
console.log(this === global); // true
}
bar();
严格模式(推荐)
'use strict';
function foo() {
console.log(this); // undefined(严格模式下默认绑定为 undefined)
}
foo();
// 在严格模式下,全局函数中 this 不再是全局对象
重要区别
| 模式 | 独立函数中的 this | 说明 |
|---|---|---|
| 非严格模式 | 全局对象(window / global) | 历史遗留行为 |
| 严格模式 | undefined |
现代 JavaScript 推荐 |
// 非严格模式
function test() {
console.log(this); // window 或 global
}
test();
// 严格模式
'use strict';
function testStrict() {
console.log(this); // undefined
}
testStrict();
二、隐式绑定(Implicit Binding)
当函数作为对象的方法调用时,this 指向该对象。
基本示例
let person = {
name: 'Alice',
greet: function() {
console.log('Hello, ' + this.name);
console.log(this); // person 对象
}
};
person.greet(); // this 指向 person → "Hello, Alice"
只有最后一层影响 this
let obj = {
name: 'Alice',
inner: {
name: 'Bob',
greet: function() {
console.log(this.name);
}
}
};
obj.inner.greet(); // this 指向 inner → "Bob"(只看最后一层)
隐式丢失(常见陷阱!)
let person = {
name: 'Alice',
greet: function() {
console.log('Hello, ' + this.name);
}
};
// 正常调用
person.greet(); // "Hello, Alice"(this = person)
// 陷阱:赋值给变量
let greetFn = person.greet; // 只是把函数本身赋值了
greetFn(); // "Hello, "(this = window 或 undefined!)
// 原因:greetFn 是独立调用的,使用默认绑定
另一个隐式丢失的例子
let person = {
name: 'Alice',
greet: function() {
console.log('Hello, ' + this.name);
}
};
// 将方法作为参数传递(常见在回调中)
setTimeout(person.greet, 100); // "Hello, "(this 丢失!)
// 原因:相当于 setTimeout(function() { ... }, 100)
// person.greet 只是传递了函数引用,调用时与 person 无关
链式调用中的 this
let calculator = {
value: 0,
add(n) {
this.value += n;
return this; // 返回 this 实现链式调用
},
subtract(n) {
this.value -= n;
return this;
},
getResult() {
return this.value;
}
};
let result = calculator.add(5).subtract(2).add(10).getResult();
console.log(result); // 13
三、显式绑定(Explicit Binding)
使用 call()、apply() 或 bind() 手动指定 this 的值。
call() — 逐个传参
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
let person = { name: 'Alice' };
// call 的第一个参数是 this,后面是函数的参数(逐个传)
greet.call(person, 'Hello', '!'); // "Hello, Alice!"
greet.call(person, 'Hi', '.'); // "Hi, Alice."
apply() — 数组传参
function add(a, b) {
console.log(this.prefix + (a + b));
}
let obj = { prefix: '结果: ' };
// apply 的第一个参数是 this,第二个是参数数组
add.apply(obj, [3, 5]); // "结果: 8"
call 和 apply 的区别
| 方法 | 参数传递方式 | 适用场景 |
|---|---|---|
call(this, arg1, arg2, ...) |
逐个传递参数 | 参数已知 |
apply(this, [arg1, arg2, ...]) |
数组传递参数 | 参数在数组中,或参数数量不定 |
function sum() {
let args = Array.from(arguments);
let total = args.reduce((a, b) => a + b, 0);
console.log(this.prefix + total);
}
let data = { prefix: '总和: ' };
// call:需要知道参数数量
sum.call(data, 1, 2, 3); // "总和: 6"
// apply:适合参数数组
sum.apply(data, [1, 2, 3, 4]); // "总和: 10"
bind() — 永久绑定 this
bind() 返回一个新函数,其 this 被永久绑定到指定值。
function greet() {
console.log('Hello, ' + this.name);
}
let person = { name: 'Alice' };
let greetAlice = greet.bind(person); // 创建一个 this 永久绑定到 person 的新函数
greetAlice(); // "Hello, Alice"
// 即使尝试用 call/apply 改变 this,也不生效(硬绑定)
greetAlice.call({ name: 'Bob' }); // 仍然是 "Hello, Alice"(已被 bind 锁定)
硬绑定(Hard Binding)
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments); // 强制 this 指向 obj
};
}
function greet() {
console.log('Hello, ' + this.name);
}
let person = { name: 'Alice' };
let boundGreet = bind(greet, person);
boundGreet(); // "Hello, Alice"
boundGreet.call({ name: 'Bob' }); // 仍然是 "Hello, Alice"(硬绑定)
四、new 绑定(New Binding)
使用 new 调用函数时,该函数成为构造函数,this 指向新创建的对象。
new 操作符的执行过程
function Person(name) {
// 1. 创建一个新空对象
// 2. 将新对象的原型指向 Person.prototype
// 3. 将 this 绑定到新对象(这一步!)
// 4. 执行函数体
// 5. 如果没返回其他对象,自动返回 this
this.name = name;
this.greet = function() {
console.log('Hello, ' + this.name);
};
// 没有 return,隐式返回 this
}
let alice = new Person('Alice');
alice.greet(); // "Hello, Alice"
console.log(alice.name); // "Alice"
// alice 是新对象,this 在构造函数中指向它
构造函数返回值的特殊情况
function Person(name) {
this.name = name;
// 如果返回的是对象(非原始值),会覆盖 new 创建的 this
// return { name: 'Bob' }; // 取消注释后,alice.name 会变成 "Bob"
}
let alice = new Person('Alice');
console.log(alice.name); // "Alice"(如果没有返回对象)
如果没有 new 会怎样?
function Person(name) {
this.name = name;
}
// 正确使用 new
let p1 = new Person('Alice');
console.log(p1.name); // "Alice"
// 忘记使用 new(非严格模式)
let p2 = Person('Bob'); // 没有 new!
console.log(p2); // undefined(构造函数没返回)
console.log(name); // "Bob"(this 是 window,污染了全局!)
// 严格模式下会更安全
'use strict';
function PersonStrict(name) {
// this 是 undefined,赋值会报错
// this.name = name; // TypeError: Cannot set property 'name' of undefined
}
PersonStrict('Charlie');
五、箭头函数(Arrow Functions)
箭头函数没有自己的 this。它继承外层词法作用域中的 this。
基本特性
let person = {
name: 'Alice',
// 普通函数:this 动态绑定
greetRegular: function() {
console.log('Regular: ' + this.name);
},
// 箭头函数:this 继承外层
greetArrow: () => {
console.log('Arrow: ' + this.name);
}
};
person.greetRegular(); // "Regular: Alice"(this = person)
// 问题:箭头函数的 this 继承自外层(这里是全局或 undefined)
person.greetArrow(); // "Arrow: " 或报错(this 不是 person!)
箭头函数适合的场景
let counter = {
count: 0,
start() {
// 箭头函数继承 start() 的 this(即 counter)
setInterval(() => {
this.count++;
console.log(this.count);
}, 1000);
}
};
counter.start(); // 每秒输出 1, 2, 3...(this 正确指向 counter)
箭头函数不适合的场景
// 不适合作为对象方法(无法获取对象 this)
let person = {
name: 'Alice',
// ❌ 箭头函数作为方法时,this 不是 person
greet: () => {
console.log('Hello, ' + this.name); // this.name 可能不是 "Alice"
}
};
// ✅ 应该使用普通函数
let person2 = {
name: 'Bob',
greet() {
console.log('Hello, ' + this.name); // this = person2
}
};
对比表
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
有自己的 this |
✅ 是 | ❌ 否(继承外层) |
this 绑定时机 |
调用时决定 | 定义时决定(继承外层) |
| 适合作为方法 | ✅ 适合 | ❌ 不适合 |
| 适合作为回调 | 需手动绑定 this |
✅ 自动继承外层 this |
有 arguments |
✅ 是 | ❌ 否 |
可用 new |
✅ 可以 | ❌ 不可以(没有 [[Construct]]) |
六、事件监听器中的 this
DOM 事件(浏览器环境)
在 DOM 事件监听器中,this 通常指向触发事件的元素。
// 浏览器环境
let button = document.getElementById('my-btn');
// 普通函数:this 指向触发事件的元素
button.addEventListener('click', function() {
console.log(this); // button 元素
console.log(this.tagName); // "BUTTON"
});
// 箭头函数:this 继承外层(可能不是 button!)
button.addEventListener('click', () => {
console.log(this); // 继承自外层作用域(可能是 window 或 undefined)
});
Node.js 事件(EventEmitter)
const EventEmitter = require('events');
let emitter = new EventEmitter();
emitter.on('message', function(data) {
console.log(this); // emitter 对象(EventEmitter)
console.log(data);
});
emitter.emit('message', 'Hello');
七、定时器中的 this
setTimeout / setInterval
在 setTimeout 和 setInterval 中,回调函数中的 this 默认指向全局对象(非严格)或 undefined(严格)。
// 非严格模式(浏览器)
let obj = {
count: 0,
start() {
setTimeout(function() {
this.count++; // this 是 window,不是 obj!
console.log(this.count); // NaN(window.count 是 undefined)
}, 100);
}
};
obj.start();
// 严格模式
'use strict';
let obj2 = {
count: 0,
start() {
setTimeout(function() {
console.log(this); // undefined(严格模式)
}, 100);
}
};
obj2.start();
解决方法 1:使用箭头函数(推荐)
let obj = {
count: 0,
start() {
// 箭头函数继承 start() 的 this(即 obj)
setTimeout(() => {
this.count++;
console.log(this.count); // 1, 2, 3...(this = obj)
}, 100);
}
};
obj.start();
解决方法 2:保存 this 引用
let obj = {
count: 0,
start() {
let self = this; // 保存 this 引用
setTimeout(function() {
self.count++; // 使用 self
console.log(self.count);
}, 100);
}
};
obj.start();
解决方法 3:使用 bind
let obj = {
count: 0,
start() {
setTimeout(function() {
this.count++;
console.log(this.count);
}.bind(this), 100); // 手动绑定 this
}
};
obj.start();
八、优先级规则
当多种绑定规则同时适用时,按以下优先级决定 this:
优先级顺序(从高到低)
1. new 绑定 → 最高优先级
new Foo() → this = 新创建的对象
2. 显式绑定 → call / apply / bind
fn.call(obj) → this = obj
3. 隐式绑定 → 作为对象方法调用
obj.fn() → this = obj
4. 默认绑定 → 独立函数调用
fn() → this = 全局对象 或 undefined
判断决策树
函数是如何调用的?
│
├─ 使用了 new 吗?
│ └─ 是 → this = 新对象(优先级最高)
│
├─ 使用了 call / apply / bind 吗?
│ └─ 是 → this = 指定的对象(优先级第二)
│
├─ 是作为对象的方法调用的吗?(obj.fn())
│ └─ 是 → this = 该对象(优先级第三)
│
└─ 都不是
└─ this = 全局对象(非严格)或 undefined(严格)(优先级最低)
示例验证
function foo() {
console.log(this);
}
let obj1 = { name: 'obj1' };
let obj2 = { name: 'obj2' };
// 默认绑定
foo(); // window 或 undefined
// 隐式绑定(优先级高于默认)
obj1.foo = foo;
obj1.foo(); // obj1(this = obj1)
// 显式绑定(优先级高于隐式)
foo.call(obj2); // obj2(this = obj2,覆盖了隐式)
// new 绑定(优先级最高)
let instance = new foo(); // foo {}(this = 新对象,即使 foo 上有 call/apply)
九、特殊情况和陷阱
null / undefined 作为 call / apply 的第一个参数
function foo() {
console.log(this);
}
// null 或 undefined 作为第一个参数时,被忽略,使用默认绑定
foo.call(null); // window(非严格)或 undefined(严格)
foo.apply(undefined); // window(非严格)或 undefined(严格)
// 严格模式下
'use strict';
function bar() {
console.log(this);
}
bar.call(null); // null(严格模式下,this 就是 null)
间接引用导致的 this 丢失
function foo() {
console.log(this);
}
let obj1 = {
name: 'obj1',
foo: foo
};
let obj2 = {
name: 'obj2'
};
// 正常
obj1.foo(); // obj1
// 间接引用:赋值表达式返回的是函数本身,不是方法调用
(obj2.foo = obj1.foo)(); // window 或 undefined(this 丢失!)
回调函数中的 this 丢失
let person = {
name: 'Alice',
friends: ['Bob', 'Charlie'],
listFriends() {
this.friends.forEach(function(friend) {
// forEach 的回调中,this 是 window 或 undefined!
console.log(this.name + ' 的朋友: ' + friend); // " 的朋友: Bob"(this.name 丢失)
});
}
};
person.listFriends(); // 输出不正确
// 解决方法 1:保存 this
listFriends() {
let self = this;
this.friends.forEach(function(friend) {
console.log(self.name + ' 的朋友: ' + friend); // "Alice 的朋友: Bob"
});
}
// 解决方法 2:forEach 的第二个参数(thisArg)
listFriends() {
this.friends.forEach(function(friend) {
console.log(this.name + ' 的朋友: ' + friend); // "Alice 的朋友: Bob"
}, this); // 传入 thisArg
}
// 解决方法 3:箭头函数(推荐)
listFriends() {
this.friends.forEach(friend => {
console.log(this.name + ' 的朋友: ' + friend); // "Alice 的朋友: Bob"
});
}
嵌套函数中的 this 问题
let obj = {
name: 'Alice',
outer() {
console.log(this); // obj(outer 作为方法调用)
function inner() {
console.log(this); // window 或 undefined!(inner 是独立调用)
}
inner();
}
};
obj.outer();
// 解决方法:箭头函数
let obj2 = {
name: 'Bob',
outer() {
console.log(this); // obj2
let inner = () => {
console.log(this); // obj2(箭头函数继承 outer 的 this)
};
inner();
}
};
obj2.outer();
十、综合示例
示例 1:方法借用(Method Borrowing)
let person1 = {
name: 'Alice',
greet() {
console.log('Hello, ' + this.name);
}
};
let person2 = {
name: 'Bob'
};
// 借用 person1 的 greet 方法,但 this 指向 person2
person1.greet.call(person2); // "Hello, Bob"
示例 2:柯里化和偏函数中的 this
function multiply(a, b) {
return a * b;
}
// 使用 bind 固定第一个参数
let double = multiply.bind(null, 2); // this 无关紧要,因为函数没用 this
console.log(double(5)); // 10(2 * 5)
console.log(double(10)); // 20(2 * 10)
// 如果函数中使用了 this
function greet(greeting) {
return greeting + ', ' + this.name;
}
let alice = { name: 'Alice' };
let greetAlice = greet.bind(alice, 'Hello');
console.log(greetAlice()); // "Hello, Alice"
示例 3:安全地使用事件监听器
// 浏览器环境
let button = document.getElementById('my-btn');
let counter = {
count: 0,
handleClick() {
this.count++;
console.log('点击次数: ' + this.count);
}
};
// ❌ 直接传递:this 丢失
// button.addEventListener('click', counter.handleClick);
// ✅ 方法 1:bind
button.addEventListener('click', counter.handleClick.bind(counter));
// ✅ 方法 2:箭头函数
button.addEventListener('click', () => counter.handleClick());
// ✅ 方法 3:包装函数
button.addEventListener('click', function() {
counter.handleClick(); // 通过 counter 调用,this 正确
});
示例 4:修复定时器中的 this
let timer = {
count: 0,
start() {
// ❌ 错误方式
// setInterval(function() {
// this.count++; // this 不是 timer!
// }, 1000);
// ✅ 方法 1:箭头函数(推荐)
setInterval(() => {
this.count++;
console.log('计数: ' + this.count);
}, 1000);
},
// ✅ 方法 2:bind
start2() {
setInterval(function() {
this.count++;
console.log('计数: ' + this.count);
}.bind(this), 1000);
}
};
timer.start();
示例 5:构造函数的安全模式
function Person(name) {
// 安全模式:检查是否使用了 new
if (!(this instanceof Person)) {
return new Person(name); // 没使用 new,自动补上
}
this.name = name;
}
let p1 = new Person('Alice');
console.log(p1.name); // "Alice"
let p2 = Person('Bob'); // 忘记使用 new
console.log(p2.name); // "Bob"(仍然正确,因为函数内部处理了)
总结:this 速查表
四种绑定规则
| 绑定类型 | 调用方式 | this 指向 | 优先级 |
|---|---|---|---|
| new 绑定 | new Foo() |
新创建的对象 | 1(最高) |
| 显式绑定 | fn.call(obj) / fn.apply(obj) / fn.bind(obj) |
obj |
2 |
| 隐式绑定 | obj.fn() |
obj |
3 |
| 默认绑定 | fn() |
全局对象(非严格)或 undefined(严格) |
4(最低) |
箭头函数的 this
| 特性 | 说明 |
|---|---|
没有自己的 this |
继承外层词法作用域的 this |
| 定义时决定 | this 在定义箭头函数时就确定了 |
| 无法改变 | call / apply / bind 对箭头函数无效 |
this 判断清单
每次遇到 this 时,按顺序问自己:
- 是否使用了
new?(是 → 新对象) - 是否使用了
call/apply/bind?(是 → 指定的对象) - 是否作为对象的方法调用?(是 → 该对象)
- 是否是箭头函数?(是 → 外层作用域的
this) - 以上都不是 → 全局对象(非严格)或
undefined(严格)
常见陷阱速查
| 陷阱 | 现象 | 解决方法 |
|---|---|---|
| 方法赋值给变量 | this 变为默认绑定 |
使用 bind() 或箭头函数 |
| 回调函数传参 | this 丢失 |
传入 thisArg 或用箭头函数 |
| 定时器回调 | this 不是期望的对象 |
箭头函数或 bind() |
forEach 回调 |
this 是全局或 undefined |
传 thisArg 或用箭头函数 |
| 嵌套函数 | 内层函数 this 丢失 |
保存 self 或用箭头函数 |
忘记 new |
污染全局或报错 | 使用安全模式或 class |
终极记忆:JavaScript 的
this不关心函数定义在哪,只关心函数怎么被调用。箭头函数是个例外——它关心定义在哪(继承外层的this)。