一、基础概念

ES6 引入 class 语法,是构造函数的语法糖,本质还是基于原型继承,只是写法更贴近传统面向对象语言(Java/C#)。 作用:封装属性、方法,统一创建同类对象。

1. 基础定义与实例化

js

运行

// 定义类
class Person {
  // 构造函数:创建实例时自动执行,接收参数
  constructor(name, age) {
    // 实例属性,绑定到每个实例
    this.name = name;
    this.age = age;
  }

  // 实例方法:存在原型上,所有实例共享,节省内存
  sayHi() {
    console.log(`我是${this.name},今年${this.age}`);
  }
}

// new 实例化
const p1 = new Person("张三", 22);
p1.sayHi(); // 我是张三,今年22
console.log(p1.name); // 张三

关键点:

  1. constructor 固定构造方法,一个类只能有一个 constructor,不写会自动生成空构造;
  2. 必须用 new 调用类,直接 Person() 会报错;
  3. this 指向当前 new 出来的实例。

二、实例属性、原型方法、静态属性 / 静态方法

1. 实例属性

两种写法: 1)构造函数内 this.xxx(常用) 2)类内部直接声明(ES2022 实例字段)

js

运行

class User {
  // 直接声明实例属性,所有实例默认拥有
  gender = "男";
  constructor(name) {
    this.name = name;
  }
}
const u = new User("李四");
console.log(u.gender, u.name); // 男 李四

2. 实例方法(原型方法)

写在 class 大括号内,不加 function,自动挂载到 类.prototype 所有实例共用一份函数,不会每次 new 新建函数:

js

运行

class Car {
  run() {
    console.log("车在跑");
  }
}
const c1 = new Car();
const c2 = new Car();
console.log(c1.run === c2.run); // true

3. 静态 static(属于类本身,不属于实例)

static 关键字,只能类名。方法调用,实例访问不到。 常用于工具方法、全局常量。

js

运行

class MathUtil {
  // 静态属性
  static PI = 3.1415;
  // 静态方法
  static sum(a, b) {
    return a + b;
  }
}

console.log(MathUtil.PI);
console.log(MathUtil.sum(1,2)); // 3

const util = new MathUtil();
console.log(util.sum); // undefined 实例拿不到静态方法

静态方法内的 this 指向当前类,不是实例。

三、访问器 Getter / Setter(拦截属性读写)

监听属性读取、修改,常用于数据校验、格式化:

js

运行

class Student {
  constructor(realAge) {
    this._realAge = realAge;
  }

  // 读取时触发 stu.age
  get age() {
    return this._realAge + "岁";
  }

  // 赋值时触发 stu.age = xx
  set age(val) {
    if (typeof val !== "number") {
      throw new Error("年龄必须是数字");
    }
    this._realAge = val;
  }
}

const s = new Student(18);
console.log(s.age); // 18岁 get执行
s.age = 20; // set执行
s.age = "二十"; // 报错

约定:内部真实数据用 _xxx 命名,表示私有(早期无真正私有)。

四、私有属性 / 私有方法 #(ES2022)

# 开头代表私有,只能在类内部访问,外部、子类都拿不到。

1. 私有字段

js

运行

class Book {
  #price; // 私有属性声明

  constructor(name, price) {
    this.name = name;
    this.#price = price;
  }

  showPrice() {
    console.log(this.#price); // 内部可访问
  }
}

const b = new Book("JS高级", 59);
console.log(b.#price); // 语法报错,外部禁止访问
b.showPrice(); // 59

2. 私有方法

js

运行

class Demo {
  #log() {
    console.log("私有方法");
  }
  run() {
    this.#log(); // 内部调用正常
  }
}
const d = new Demo();
d.#log(); // 报错

五、继承 extends + super(核心面向对象)

extends 实现类继承,子类拥有父类所有属性、方法; super() 代表调用父类构造函数,子类 constructor 第一行必须写 super ()

基础继承

js

运行

// 父类
class Animal {
  constructor(name) {
    this.name = name;
  }
  eat() {
    console.log(`${this.name}吃东西`);
  }
}

// 子类 Dog 继承 Animal
class Dog extends Animal {
  constructor(name, color) {
    // 调用父类构造,给父类属性赋值
    super(name);
    // 子类独有属性
    this.color = color;
  }
  // 重写父类方法(覆盖)
  eat() {
    // super.方法() 调用父类原有逻辑
    super.eat();
    console.log(`${this.color}小狗啃骨头`);
  }
}

const dog = new Dog("旺财", "黄色");
dog.eat();
/*
旺财吃东西
黄色小狗啃骨头
*/

super 两种用法:

  1. super(参数):构造器中调用父类构造;
  2. super.xxx():方法内调用父类同名方法。

继承规则

  1. 子类不写 constructor,会自动补全 constructor(...args) { super(...args) }
  2. 子类构造函数在 super() 执行前,不能使用 this
  3. 静态方法也能继承,子类可通过 super.静态方法() 调用父类静态。

六、类的 this 指向大坑

1. 普通实例方法:this 默认指向实例

js

运行

class Test {
  fn() {
    console.log(this);
  }
}
const t = new Test();
t.fn(); // Test 实例

2. 方法单独提取调用,this 丢失(变成 undefined)

严格模式下 class 内代码默认严格模式,this 不会指向 window:

js

运行

const fn = t.fn;
fn(); // undefined

解决三种方案:

  1. constructor 内 bind 绑定 this

js

运行

constructor() {
  this.fn = this.fn.bind(this);
}
  1. 箭头函数实例字段(推荐 React 组件使用)

js

运行

class Test {
  fn = () => {
    console.log(this);
  }
}
  1. 调用时 fn.call(t) 临时绑定

七、类与传统构造函数对比

class 是语法糖,底层原型逻辑完全一致:

js

运行

// ES5 构造函数
function Person(name) {
  this.name = name;
}
Person.prototype.say = function(){}

// ES6 class 等价于上面
class Person {
  constructor(name) { this.name = name }
  say(){}
}

区别:

  1. class 不能直接调用,必须 new;构造函数可直接执行;
  2. class 内部默认严格模式;
  3. class 方法不可枚举,ES5 prototype 方法可枚举;
  4. class 有私有字段、static、extends 更简洁。

八、常用实操案例:封装工具类

js

运行

// 日期工具类
class DateUtil {
  static format(timestamp) {
    const date = new Date(timestamp);
    const y = date.getFullYear();
    const m = date.getMonth() + 1;
    const d = date.getDate();
    return `${y}-${m}-${d}`;
  }
}
console.log(DateUtil.format(Date.now()));

九、高频面试考点总结

  1. class 是构造函数语法糖,底层基于原型;
  2. constructor 只能一个,new 触发;
  3. static 属于类本身,实例无法访问;
  4. 私有属性只能类内访问,子类也无法直接读取;

  5. extends 继承,子类构造必须先 super ();
  6. super () 调用父构造,super. 方法调用父类方法;
  7. 类方法单独取出执行 this 为 undefined,箭头字段可修复;
  8. getter/setter 拦截属性读写,用于数据校验;
  9. 所有类方法不存在变量提升,不能先使用再定义。

更多推荐