ES6 中的 class 是一种面向对象编程的语法糖,提供了一种简洁的方式来定义对象的结构和行为。
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1, 2);
基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的 class 改写,就是下面这样:
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }
ES6 的类,完全可以看作构造函数的另一种写法:
class Point { // ... } typeof Point // "function" Point === Point.prototype.constructor // true ---------------------------------------------------------------------------- class Point { constructor() { // ... } toString() { // ... } toValue() { // ... } } // 等同于 Point.prototype = { constructor() {}, toString() {}, toValue() {}, };
主要特性:
1. 声明式语法:使用 class 关键字声明类。
2. 构造函数:使用 constructor 方法初始化类实例。
3. 实例方法:定义在类内部的普通方法,使用 this 访问实例属性。
4. 静态方法:使用 static 关键字定义,不依赖于类的实例。
5. 实例属性:在构造函数中初始化,或使用字段声明语法(目前是 Stage 3 proposal)。
6. 继承:使用 extends 关键字实现。
7. super 关键字:在子类的构造函数中调用父类的构造函数或方法。
8. getter 和 setter:使用 get 和 set定义属性的访问器。
9. 私有属性和方法:使用 # 定义私有属性和方法(目前是 Stage 3 proposal)。
1. 基本类定义和实例化
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return `Point(${this.x}, ${this.y})`; } } let point = new Point(10, 20); console.log(point.toString()); // 输出: Point(10, 20)
2. 静态方法、属性
class MathUtils { constructor() { console.log(MyClass.myStaticProp); // 42 } static add(a, b) { return a + b; } static myStaticProp = 42; } console.log(MathUtils.add(1, 2)); // 输出: 3
3. 继承和 super
class Rectangle { constructor(width, height) { this.width = width; this.height = height; } area() { return this.width * this.height; } } class Square extends Rectangle { constructor(sideLength) { super(sideLength, sideLength); } } let square = new Square(5); console.log(square.area()); // 输出: 25
4. getter 和 setter
class Rectangle { constructor(width, height) { this.width = width; this.height = height; } get area() { return this.width * this.height; } set width(newWidth) { if (newWidth > 0) { this.width = newWidth; } else { console.log("Width must be positive."); } } } let rect = new Rectangle(4, 5); console.log(rect.area); // 输出: 20 rect.width = -10; // 输出: Width must be positive.
class的注意点
(1)严格模式
类和模块的内部,默认就是严格模式,所以不需要使用 use strict 指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
(2)不存在提升
类不存在变量提升(hoist),这一点与 ES5 完全不同。
new Foo(); // ReferenceError class Foo {} //不会报错 //因为 Bar 继承 Foo 的时候, Foo 已经有定义了。 //但是,如果存在 class 的提升,上面代码就会报错, //因为 class 会被提升到代码头部,而 let 命令是不提升的, //所以导致 Bar 继承 Foo 的时候, Foo 还没有定义。 { let Foo = class {}; class Bar extends Foo { } }
(3)name 属性
由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括 name 属性。
class Point {} Point.name // "Point" //name 属性总是返回紧跟在 class 关键字后面的类名。
(4)Generator 方法
如果某个方法之前加上星号( * ),就表示该方法是一个 Generator 函数。
class Foo { constructor(...args) { this.args = args; } * [Symbol.iterator]() { for (let arg of this.args) { yield arg; } } } for (let x of new Foo('hello', 'world')) { console.log(x); } // hello // world //Foo 类的 Symbol.iterator 方法前有一个星号,表示该方法是一个 Generator 函数。 //Symbol.iterator 方法返回一个 Foo 类的默认遍历器, for...of 循环会自动调用这个遍历器。
(5)this 的指向
类的方法内部如果含有 this ,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
class Logger { printName(name = 'there') { this.print(`Hello ${name}`); } print(text) { console.log(text); } } const logger = new Logger(); const { printName } = logger; printName(); // TypeError: Cannot read property 'print' of undefined
避免使用this,在构造方法中绑定 this:
class Logger { constructor() { this.printName = this.printName.bind(this); } // ... }
避免使用this,使用箭头函数:
class Obj { constructor() { this.getThis = () => this; } } const myObj = new Obj(); myObj.getThis() === myObj // true
避免使用this,使用 Proxy
function selfish (target) { const cache = new WeakMap(); const handler = { get (target, key) { const value = Reflect.get(target, key); if (typeof value !== 'function') { return value; } if (!cache.has(value)) { cache.set(value, value.bind(target)); } return cache.get(value); } }; const proxy = new Proxy(target, handler); return proxy; } const logger = selfish(new Logger());