ES6之【this】的使用要点

184 阅读4分钟

第一部分: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 的管理,但同时也需要开发者理解其底层原理。