es6之js的类

 阅读约 7 分钟

起源

JS 从创建之初就不支持类,也没有把类继承作为定义相似对象以及关联对象的主要方式,这让不少开发者感到困惑。而从 ES1 诞生之前直到ES5 时期,很多库都创建了一些工具,让 JS 显得貌似能支持类。尽管一些 JS 开发者强烈认为这门语言不需要类,但为处理类而创建的代码库如此之多,导致 ES6 最终引入了类。

ES5 中的仿类结构

JS 在 ES5 及更早版本中都不存在类。与类最接近的是:创建一个构造器,然后将方法指派到
该构造器的原型上。这种方式通常被称为创建一个自定义类型。例如:

function PersonType(name) {
this.name = name;
}
PersonType.prototype.sayName = function() {
console.log(this.name);
};
let person = new PersonType("Nicholas");
person.sayName(); // 输出 "Nicholas"
console.log(person instanceof PersonType); // true
console.log(person instanceof Object); // true

此代码中的 PersonType 是一个构造器函数,并创建了单个属性 name 。 sayName() 方法被
指派到原型上,因此在 PersonType 对象的所有实例上都共享了此方法。接下来,使用 new
运算符创建了 PersonType 的一个新实例 person ,此对象会被认为是一个通过原型继承了
PersonType 与 Object 的实例。
这种基本模式在许多对类进行模拟的 JS 库中都存在,而这也是 ES6 类的出发点。

es6基本的类的声明

类声明以 class 关键字开始,其后是类的名称;剩余部分的语法看起来就像对象字面量中的
方法简写,并且在方法之间不需要使用逗号。作为范例,此处有个简单的类声明:

class PersonClass {
// 等价于 PersonType 构造器
constructor(name) {
this.name = name;
}
// 等价于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Nicholas");
person.sayName(); // 输出 "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"

es6中类关键字class本质是一种语法糖,而使用类实现的继承其本质上就是原型的继承.

为何要使用类的语法

  • 类声明不会被提升,这与函数定义不同。类声明的行为与 let 相似,因此在程序的执行到达声明处之前,类会存在于暂时性死区内。
  • 类声明中的所有代码会自动运行在严格模式下,并且也无法退出严格模式。
  • 调用类构造器时不使用 new ,会抛出错误。
  • 试图在类的方法内部重写类名,会抛出错误。

作为一级公民的类

在编程中,能被当作值来使用的就称为一级公民( first-class citizen ),意味着它能作为参
数传给函数、能作为函数返回值、能用来给变量赋值。 JS的函数就是一级公民(它们有时又
被称为一级函数),此特性让 JS 独一无二
ES6 延续了传统,让类同样成为一级公民。这就使得类可以被多种方式所使用。例如,它能
作为参数传入函数:

function createObject(classDef) {
return new classDef();
}
let obj = createObject(class {
sayHi() {
console.log("Hi!");
}
});
obj.sayHi(); // "Hi!

使用派生类进行继承

ES6 之前,实现自定义类型的继承是个繁琐的过程。严格的继承要求有多个步骤。例如,研
究以下范例:

function Rectangle(length, width) {
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function() {
return this.length * this.width;
};
function Square(length) {
Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value:Square,
enumerable: true,
writable: true,
configurable: true
}
});
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true

Square 继承了 Rectangle ,为此它必须使用 Rectangle.prototype 所创建的一个新对象来
重写 Square.prototype ,并且还要调用 Rectangle.call() 方法。这些步骤常常会搞晕 JS
的新手,并会成为有经验开发者出错的根源之一。

类让继承工作变得更轻易,使用熟悉的 extends 关键字来指定当前类所需要继承的函数,即
可。生成的类的原型会被自动调整,而你还能调用 super() 方法来访问基类的构造器。此处
是与上个例子等价的 ES6 代码:

class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
class Square extends Rectangle {
constructor(length) {
// 与 Rectangle.call(this, length, length) 相同
super(length, length);
}
}
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true

使用 super() 时需牢记以下几点:

  1. 你只能在派生类中使用 super() 。若尝试在非派生的类(即:没有使用 extends关键字的类)或函数中使用它,就会抛出错误。
  2. 在构造器中,你必须在访问 this 之前调用 super() 。由于 super() 负责初始化this ,因此试图先访问 this 自然就会造成错误。
  3. 唯一能避免调用 super() 的办法,是从类构造器中返回一个对象。

总结

ES6 的类让 JS 中的继承变得更简单,因此对于你已从其他语言学习到的类知识,你无须将其
丢弃。 ES6 的类起初是作为 ES5 传统继承模型的语法糖,但添加了许多特性来减少错误。

阅读 209发布于 4月1日
推荐阅读
目录