1. 类 (Classes)
1.1 类的定义
类是一种自定义的数据类型,其中包含了数据和对数据的操作(方法)。类是对某种数据类型的定义,而不是一个具体的值。如果想获得一个具体的值,我们需要在程序中将类实例化(instantiation)
class Page {
constructor(text) {
this.text = text;
}
print() {
console.log(this.text)
}
}
class Notebook {
constructor() {
this.pages = [];
}
addPage(text) {
var page = new Page(text);
this.pages.push(page);
}
print() {
for (let page of this.pages) {
page.print();
}
}
}
var programNotes = new Notebook();
programNotes.addPage("String is a series of characters.")
programNotes.addPage("Array is a series of values.")
programNotes.print()
/*
String is a series of characters.
Array is a series of values.
*/
在Page类中,数据是存储在text变量中的字符串,对数据的操作是print(),它会把text的内容打印到控制台。
在Notebook类中,数据是由若干个Page的示例组成的数组。操作是addPage(),它可以实例化一个Page对象,然后把这个Page加到Page列表中;print() 会把所有页面的内容打印出来。
类让我们可以把数据和操作打包组织起来。虽然我们不使用类也可以实现上述的功能,但类显著地提高了程序的可读性和可维护性。
1.2 类的继承(Inheritance)
class Publication {
constructor(title,author,pubDate) {
this.title = title;
this.author = author;
this.pubDate = pubDate;
}
print() {
console.log(`
Title: ${ this.title }
By: ${ this.author }
${ this.pubDate }
`);
}
}
Publication 类定义了所有出版物普遍拥有的数据和操作。
接下来我们来定义几种更具体的出版物,比如书籍和博客。
class Book extends Publication {
constructor(bookDetails) {
super(
bookDetails.title,
bookDetails.author,
bookDetails.publishedOn
);
this.publisher = bookDetails.publisher;
this.ISBN = bookDetails.ISBN;
}
print() {
super.print();
console.log(`
Publisher: ${ this.publisher }
ISBN: ${ this.ISBN }
`);
}
}
class BlogPost extends Publication {
constructor(title,author,pubDate,URL) {
super(title,author,pubDate);
this.URL = URL;
}
print() {
super.print();
console.log(this.URL);
}
}
Book 类和 Publication 类都用了 extends 子句来扩充原有的出版物的定义。构造函数 (constructor) 中的 super() 引用了父类 Publication 的构造函数来进行初始化。在JS中子类如果定义了自己的构造函数,就必须引用 super() ,否则会报错。
var YDKJSY = new Book({
title: "You Don't Know JS yet",
author: "Kyle Simpson",
publishedOn: "June 2020",
publisher: "O'Reilly",
ISBN: "123456-789"
});
YDKJSY.print();
// Title: You Don't Know JS yet
// By: Kyle Simpson
// June 2020
// Publisher: O'Reilly
// ISBN: 123456-789
var OrganizeInJS = new BlogPost(
"How We Organize in IS",
"Kyle Simpson",
"October 27, 2020",
"https://www.inJS.org"
);
OrganizeInJS.print();
// Title: For and against let
// By: Kyle Simpson
// October 27, 2020
// https://www.inJS.org
注意到 Book类和 BlogPost类都有一个 print() 方法,它们覆盖(override) 了继承(inherit) 于父类 Publication的 print() 方法。这种同个方法可以有不同的行为的现象叫做多态(polymorphism)
2. 模块(Modules)
2.1 传统的函数模块定义法
传统的模块实际上就是定义一个外层函数。调用这个函数会返回一个模块的实例,里面包含了若干个可以操作实例内的数据的函数。
下面我们用传统的函数模块方法来再次定义 Publication, Book和 BlogPost
function Publication(title,author,pubDate) {
var publicAPI = {
print() {
console.log(`
Title: ${ title }
By: ${ author }
${ pubDate }
`);
}
};
return publicAPI;
}
function Book(bookDetails) {
var pub = Publication(
bookDetails.title,
bookDetails.author,
bookDetails.publishedOn
);
var publicAPI = {
print() {
pub.print();
console.log(`
Publisher: ${ bookDetails.publisher }
ISBN: ${ bookDetails.ISBN }
`);
}
};
return publicAPI;
}
function BlogPost(title,author,pubDate,URL) {
var pub = Publication(title,author,pubDate);
var publicAPI = {
print() {
pub.print();
console.log(URL);
}
};
return publicAPI;
}
我们来对比一下函数模块和类之间的异同。
类把方法和数据存储在一个对象的实例中,这些方法和数据都要用this.前缀来获取。而在上面这种方法中,数据和方法都可以直接在作用域中获取,不需要this.前缀。
在类中,所有的数据和方法都是公开(public) 的。而在函数定义的模块中,数据和未被引用的方法都是私有(private) 的。
接下来我们来看看如何使用函数定义的模块。 js
var YDKJSY = Book({
title: "You Don't Know JS yet",
author: "Kyle Simpson",
publishedOn: "June 2020",
publisher: "O'Reilly",
ISBN: "123456-789"
});
YDKJSY.print();
// Title: You Don't Know JS yet
// By: Kyle Simpson
// June 2020
// Publisher: O'Reilly
// ISBN: 123456-789
var OrganizeInJS = BlogPost(
"How We Organize in IS",
"Kyle Simpson",
"October 27, 2020",
"https://www.inJS.org"
);
OrganizeInJS.print();
// Title: For and against let
// By: Kyle Simpson
// October 27, 2020
// https://www.inJS.org
不难发现,实际使用中函数模块和类的唯一区别就是没有使用 new 关键字。
2.2 ES模块 (ES Modules)
这是ES6引入的新特性,其目的和上文介绍的函数模块定义方法是相同的。但它们的使用方式大为不同。
首先,ES模块中不需要定义一个外层函数,而是以文件的形式进行组织。一个文件代表一个模块。
其次,你不需要直接定义和引用一个模块的“API”打交道,而是通过 export 关键字来添加变量或方法到你想要给其他程序使用的接口。
最后,你要通过 import 关键字来引用ES模块,而不是像上面两种方法那样创建一个实例。这也就意味着你不能创建多个不同的实例,如果你需要这样,那就得在ES模块中使用传统的模块定义法,或者在其中引入类。
我们通过同样的实例来看看这如何实现
publication.js
function printDetails(title,author,pubDate) {
console.log(`
Title: ${ title }
By: ${ author }
${ pubDate }
`);
}
export function create(title,author,pubDate) {
var publicAPI = {
print() {
printDetails(title,author,pubDate);
}
};
return publicAPI;
}
blogpost.js 这个模块引用了上面的模块
import { create as createPub } from "publication.js";
function printDetails(pub,URL) {
pub.print();
console.log(URL);
}
export function create(title,author,pubDate,URL) {
var pub = createPub(title,author,pubDate);
var publicAPI = {
print() {
printDetails(pub,URL);
}
};
return publicAPI;
}
最后,我们在下面这个 main.js 中使用 blogpost 模块
import { create as newBlogPost } from "blogpost.js";
var forAgainstLet = newBlogPost(
"For and against let",
"Kyle Simpson",
"October 27, 2014",
"https://davidwalsh.name/for-and-against-let"
);
forAgainstLet.print();
// Title: For and against let
// By: Kyle Simpson
// October 27, 2014
// https://davidwalsh.name/for-and-against-let
第一行的 as newBlogPost 将引用的函数 create 进行了重命名,这样可以提高可读性。