第一部分:ES5 中的 this 机制
1. 默认绑定(全局绑定)
规则:独立函数调用时,this 指向全局对象(浏览器中为 window,严格模式为 undefined)
function show() {
console.log(this); // window (非严格模式)
}
show();
2. 隐式绑定(对象方法调用)
规则:通过对象调用的方法,this 指向调用者
const obj = {
value: 42,
getValue: function() {
console.log(this.value); // 42
}
};
obj.getValue();
3. 显式绑定(call/apply/bind)
规则:强制改变 this 指向
function logValue() {
console.log(this.value);
}
const ctx = { value: 99 };
logValue.call(ctx); // 99
Tips: call/apply/bind 三者的使用区别
call : 调用方法时修改方法中的this指向,第一个参数为修改后this指向,入参顺序传入
function logValue(name,age) {
console.log(this.value,name,age);
}
obj1 = {
value:2
}
logValue.call(obj1,2,1)
args[0] = obj1 // 目标对象
args[1] = 姓名 // 目标对象
args[2] = 年龄 // 目标对象
apply : 调用方法时修改方法中的this指向,第一个参数为修改后this指向,其余入参为数组对象[]
function logValue(name,age) {
console.log(this.value,name,age);
}
obj1 = {
value:2
}
logValue.apply(obj1,[2,1])
args[0] = obj1 // 目标对象
args[1][0] = 姓名 // 目标对象
args[1][1] = 年龄 // 目标对象
bind : 修改方法中的this指向且不允许再次修改,第一个参数为修改后this指向,入参顺序传入,返回的是方法,多次调用会将后续的参数整合
function logValue(name,age) {
console.log(this.value,name,age);
}
obj1 = {
value:2
}
obj2 = {
value:3
}
const func1 = logValue.bind(obj1,1,2)
const func2 = logValue.bind(obj2,3,4)
func2(5,6) // 3,1,2,3,4,5,6
4. new 绑定(构造函数)
规则:new 创建的实例会绑定到构造函数内部的 this
function Person(name) {
this.name = name;
}
const p = new Person("John");
console.log(p.name); // John
5. 回调函数的 this 丢失
经典问题:回调函数中的 this 会丢失绑定
const obj = {
value: 42,
handleClick: function() {
console.log(this.value); // undefined
}
};
setTimeout(obj.handleClick, 100); // this 指向 window
第二部分:ES6 新增特性
1. 箭头函数(Lexical this)
规则:继承定义时的外层 this,且不可修改, 故class中箭头函数永远指向构造函数本身
const obj = {
value: 42,
getValue: () => console.log(this.value) // 箭头函数的外层 this 是全局对象
};
obj.getValue(); // undefined
2. 类(Class)中的 this
基础规则
class MyClass {
constructor() {
this.value = 10;
}
print() {
console.log(this.value);
}
}
const obj = new MyClass();
const print = obj.print;
print(); // TypeError: Cannot read property 'value' of undefined
原因
const print = obj.print; 进行了复制操作,所以print成为了一个普通函数,方法中的this指向指向了全局
解决方案
// 方案1:构造函数中绑定
class MyClass {
constructor() {
this.print = this.print.bind(this);
}
}
// 方案2:使用箭头函数方法(实例属性)
class MyClass {
print = () => {
console.log(this.value);
}
}
// 方案3:直接调用属性方法
obj.print()
第三部分:继承中的 this 与 super
ES5 经典继承模式
// 父类
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + " makes a noise");
};
// 子类
function Dog(name) {
Animal.call(this, name); // 关键:绑定父类构造函数中的 this
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const d = new Dog("Buddy");
d.speak(); // "Buddy makes a noise"
ES6 Class 继承与 super
构造函数中的 super()
规则:子类构造函数必须调用 super() 后才能使用 this
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 相当于 Animal.call(this, name)
this.breed = breed; // 必须 super() 之后才能使用 this
}
}
方法中的 super.method()
规则:super.method() 会保留当前 this 指向;在 JavaScript 中,当子类通过 super.speak() 调用父类方法时,方法中的 this 仍然指向子类的实例(即当前调用方法的对象)。super 只是提供了一种访问父类方法的途径,但不会改变 this 的指向。
关键验证
以下代码可以验证 this 的指向:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this === d); // 验证 this 是否指向 Dog 实例
console.log(this.name + " makes noise");
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
speak() {
super.speak(); // 调用父类方法,但 this 仍然是 Dog 实例
console.log("Also barks!");
}
}
const d = new Dog("Buddy");
d.speak();
输出结果:
true // this === d 为 true,说明父类方法中的 this 指向 Dog 实例
Buddy makes noise
Also barks!
第四部分:this 的对比表格
| 场景 | ES5 行为 | ES6 新增特性 |
|---|---|---|
| 独立函数调用 | this 指向全局对象 | 严格模式默认 undefined |
| 箭头函数 | 无 | 继承外层 this,不可绑定 |
| 类方法 | 无类语法,通过原型链实现 | 方法默认绑定实例,但需注意提取后的绑定 |
| 继承中的构造函数 | 需显式调用父构造函数(Parent.call) | 必须通过 super() 初始化父类 |
| 回调函数中的 this | 需要闭包或 bind 绑定 | 优先使用箭头函数自动绑定 |
| 对象方法简写 | 无 | 方法简写与传统函数 this 行为一致 |
第五部分:关键细节
1. super 的双重身份
- 构造函数中:
super()等价于父类构造函数的call调用 - 普通方法中:
super.method()保持当前this指向,相当于Parent.prototype.method.call(this)
2. 箭头函数的特殊场景
class Counter {
count = 0;
// 箭头函数作为类字段方法
increment = () => {
this.count++; // 永远绑定实例
};
}
const c = new Counter();
const inc = c.increment;
inc(); // 正常执行
3. 严格模式的影响
"use strict";
function test() {
console.log(this); // undefined
}
test();
4. 多层继承的 this 传递
class A {
constructor() {
this.tag = "A";
}
}
class B extends A {
constructor() {
super();
this.tag += "->B";
}
}
class C extends B {
constructor() {
super();
this.tag += "->C"
console.log(this.tag); // "A->B->C"
}
}
new C()
通过理解这些规则,可以精确控制 JavaScript 中 this 的指向,特别是在复杂继承结构和异步场景中。ES6 的 class 和箭头函数大大简化了 this 的管理,但同时也需要开发者理解其底层原理。