ES6 Class
ES6(ECMAScript 2015)引入了类(Class)的概念,这是 JavaScript 编程中一个重要的特性,使得 JavaScript 可以更加面向对象地编程。ES6 Class 构建在 JavaScript 原型继承的基础上,提供了更清晰、更易于理解和使用的语法,使得创建对象和实现继承变得更加简洁和可读。
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。
基本上,ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
constructor()
方法
class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
console.log("Animal sound");
}
}
上面代码定义了一个“类”,可以看到里面有一个constructor()
方法,这就是构造方法,而this
关键字则代表实例对象。这种新的 Class 写法,本质上与 ES5 的构造函数是一致的。
下面是一个类使用 extends
关键字实现继承,一个类可以继承另一个类的属性和方法。
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类的构造函数
this.breed = breed;
}
makeSound() {
console.log("Woof woof");
}
}
ES6 的类,完全可以看作构造函数的另一种写法。
class Animal{
// ...
}
typeof Animal// "function"
Animal=== Animal.prototype.constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
使用的时候,也是直接对类使用new
命令,跟构造函数的用法完全一致。
类的实例
class Bar {
doStuff() {
console.log('stuff');
}
}
const b = new Bar();
b.doStuff() // "stuff"
由于类的方法都定义在prototype
对象上面,所以类的新方法可以添加在prototype
对象上面。Object.assign()
方法可以很方便地一次向类添加多个方法。
class Animal{
constructor(){
// ...
}
}
Object.assign(Animal.prototype, {
toString(){},
toValue(){}
});
prototype
对象的constructor
属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
Animal.prototype.constructor === Point // true
与 ES5 一样,类的所有实例共享一个原型对象。
var p1 = new Animal(2,3);
var p2 = new Animal(3,2);
p1.__proto__ === p2.__proto__
//true
实例属性的一种新写法
ES2022 为类的实例属性,又规定了一种新写法。实例属性现在除了可以定义在constructor()
方法里面的this
上面,也可以定义在类内部的最顶层。
class IncreasingCounter {
count = 0;
get value() {
console.log('Getting the current value!');
return this.count;
}
increment() {
this.count++;
}
}
使用get
和set
关键字
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
// Getter 方法将摄氏温度转换为华氏温度
get fahrenheit() {
return (this.celsius * 9/5) + 32;
}
// Setter 方法用于设置摄氏温度,同时更新华氏温度
set celsius(value) {
if (value < -273.15) {
throw new Error("Temperature is too low.");
}
this._celsius = value;
}
get celsius() {
return this._celsius;
}
}
const temp = new Temperature(25);
console.log(temp.celsius); // 输出:25
console.log(temp.fahrenheit); // 输出:77
temp.celsius = 30; // 设置摄氏温度,同时更新华氏温度
console.log(temp.celsius); // 输出:30
console.log(temp.fahrenheit); // 输出:86
// 尝试设置超低温度,会触发错误
try {
temp.celsius = -300;
} catch (error) {
console.log(error.message); // 输出:Temperature is too low.
}
与 ES5 一样,在“类”的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
属性名采用表达式
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
上面代码中,Square
类的方法名getArea
,是从表达式得到的。
类表达式
与函数一样,类也可以使用表达式的形式定义
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是Me
,但是Me
只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass
引用。
静态方法和静态属性
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”
通过在方法前面使用 static
关键字,你可以创建一个静态方法。静态方法不会被实例继承,而是直接属于类本身。这意味着它们不能在类的实例上调用,而是通过类本身调用。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
上面的写法为Foo
类定义了一个静态属性prop
。
私有方法和私有属性
在传统的 JavaScript 中,没有像其他编程语言中那样的严格的私有方法和私有属性的概念。然而,可以使用一些约定和技巧来模拟私有性。ES6(ECMAScript 2015)引入的一些功能也使得实现更接近私有性成为可能。
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但早期的 ES6 不提供,只能通过变通方法模拟实现。
一种做法是在命名上加以区别。
class Widget {
// 公有方法
foo (baz) {
this._bar(baz);
}
// 私有方法
_bar(baz) {
return this.snaf = baz;
}
// ...
}
另一种方法就是索性将私有方法移出类,因为类内部的所有方法都是对外可见的。
class Widget {
foo (baz) {
bar.call(this, baz);
}
// ...
}
function bar(baz) {
return this.snaf = baz;
}
还有一种方法是利用Symbol
值的唯一性,将私有方法的名字命名为一个Symbol
值。
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
// ...
};
ES2022正式为class
添加了私有属性,方法是在属性名之前使用#
表示。
class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}
上面代码中,#count
就是私有属性,只能在类的内部使用(this.#count
)。如果在类的外部使用,就会报错。
const counter = new IncreasingCounter();
counter.#count // 报错
当然使用闭包可以模拟真正的私有性,因为闭包中的变量只在函数内部可见。
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1
console.log(counter.count); // undefined,count 是私有的
使用 ES6 引入的 Symbol 数据类型可以用于创建唯一的键,从而模拟私有属性。
const _privateKey = Symbol("privateKey");
class MyClass {
constructor() {
this[_privateKey] = "This is a private value.";
}
getPrivateValue() {
return this[_privateKey];
}
}
const instance = new MyClass();
console.log(instance.getPrivateValue()); // 可以获取私有值
console.log(instance[_privateKey]); // 但是无法直接访问,因为私有键是 Symbol 类型