您将要创建的内容
你熟悉“意大利面条代码”这个术语吗?这是你可能从非javascript开发人员那里听到的批评语言的比喻。它是没有结构的代码。它将由一行又一行的语句组成。有些可能包含在函数中,有些则根本没有。如果幸运的话,所有 9,000 行代码都将在一个文件中。这种“意大利面条”结构可能是过程编程的结果。
在过程编程中,函数用于执行任务。我们需要功能,但我们也需要可以使用的设计。虽然意大利面很适合晚餐,但它不适合代码。解药是面向对象的编程。为了理解面向对象编程,我们将介绍设计程序、定义类和创建对象。
设计程序
假设您被分配了为书店制作应用程序的任务。只是为了好玩,让我们打电话给我们的书店亚马逊。亚马逊将有书籍。会有书评。我们将要按作者查找书籍。我们将希望在我们的应用程序中实现更多功能,但现在这已经足够了。
面向对象的编程以创建对象为中心。那么我们如何着手将这些需求转化为对象呢?一种技术是从我们的描述中列出名词,然后将列表细化为与问题相关的名词。我们在问题中提到了以下名词:
应用
书店
图书
评论
作者
应用程序是一个不相关的名词,所以它可以被丢弃。我们也可以去掉书店,因为它类似于应用程序。如果我们需要与多家书店一起做某事,那么我们可以保留它。我们留下了书籍、评论和作者。(作者已被复数化,因为我们将在此应用程序中拥有多个作者。)
现在让我们看看我们将如何设计每个类。类是创建对象的蓝图。我们创建的书本类将为我们提供创建书本对象的蓝图。
这类似于建筑师如何使用蓝图来建造房屋。蓝图将显示卧室、浴室、厨房和客厅。许多房子可以用这个蓝图建造。但是,它们不必都相同。例如,每个房子都可以通过改变油漆、地板或固定装置来定制。
任务
编写您将用于购物车程序的类。购物车应该能够执行以下操作:
持有物品清单。
在购物车中添加和删除项目。
计算购物车的总数。
获取客户信息。
为购买创建收据。
课程
为了设计我们的书类,我们需要考虑类负责知道什么以及负责做什么。对于一本书,我们需要知道书名、作者和 ISBN。这些是我们的数据属性。
类需要能够做的一些事情是获取和设置标题,获取和设置作者,以及获取和设置 ISBN。这些将是类的方法。考虑到我们的要求,这就是我们的 book 类的样子:
class Book { constructor(title, author, isbn){ this.title = title; this.author = author; this.isbn = isbn; } getTitle(){ return this.title; } setTitle(newTitle){ this.title = newTitle; } getAuthor(){ return this.author; } setAuthor(newAuthor){ this.author = newAuthor; } getIsbn(){ return this.isbn; } setIsbn(newIsbn){ this.isbn = newIsbn; } }
按照惯例,类名是 c api化的。构造函数是声明和初始化数据属性的特殊函数。this
在构造函数内部,使用关键字添加属性。接下来,列出该类的任何方法,不带任何分隔符。
以 开头的方法get
称为访问器方法,因为它们返回一个值。以 mutator开头set
的方法是因为它们存储值或更改属性的值。
这是定义类的一般形式:
class ClassName { constructor(...args){ this.attr = arg1; this.attr2 = arg2; ... } methodOne(){...} methodTwo(){...} }
也可以使用以下语法声明一个类:
const ClassName = class { ... }
类也可以有静态方法。静态方法是类的属性,而不是对象的方法。假设我们想为我们的 book 类创建一个静态方法来生成 id。这是语法:
class Book { constructor(){ ... } static generateId(){ ... } }
调用方法:
Book.generateId();
一个自然的问题是何时以及为什么要使用静态方法?我不能说我知道使用静态方法的充分理由。这取决于你如何设计你的班级。静态方法可以用作对象的辅助方法,但这样的函数可以存储在它们自己的类中。如果您知道一个好的用例,请在评论中留下您的想法。
最后,为了组织,你应该将一个类存储为一个模块。模块只是一个包含您的代码的文件。为了使我们的 book 类成为一个模块,我们export
在它之前添加一个语句。
export class Book { ... }
要在另一个文件中使用 Book 类,我们导入它。
import { Book } from Book
其中{ }
包含从模块导出的值,并且from Book
是对文件 Book.js 的引用。
任务
为作者和评论定义一个类。
对象
除非我们对它做点什么,否则类本身对我们毫无用处。我们想创作书籍。为此,我们必须实例化该类。实例化是创建新对象的技术术语。我们称从类创建的对象为实例。这就是我们如何创建一本书的新实例:
let book = new Book("Great expectations", "Charles Dickens", 1234); book.getTitle() //Great Expectations
对象必须用new
操作符实例化。传递给对象的数据是我们在构造函数中定义的参数。这是实例化类的一般形式:
variableName = new ClassName(..args);
假设我们要向图书类添加属性,例如 ID、价格和库存数量。现在我们的构造函数中有六个参数,这并不漂亮。不仅不好看。它为使用该类的开发人员带来了额外的努力,因为他们必须知道参数的顺序。更好的解决方案是将对象作为参数传递。例子:
class Book { constructor(data){ this.id = data.id; this.title = data.title; this.author = data.author; this.isbn = data.isbn; this.units = data.units; this.price = data.price; } getTitle(){ return this.title; } ... }
实例化对象:
let data = { id: 1, title: "Great Expectations", author: "Charles Dickens", isbn: 1234, units: 10, price: 29.95 } let book = new Book(data);
在我们的示例中,我们还可以使用语句访问标题, book.title
因为类中的所有属性都是公共的。现在您可能想知道如果我们可以直接访问属性,为什么我会创建所有这些方法。只是为了向您展示语法吗?是的。另外,我想展示以这种方式组织代码的好处。
将相关代码捆绑到对象中称为封装。封装的好处之一是数据隐藏。数据隐藏意味着不能在类之外访问对象的属性。
在其他语言如 java 和python中,我们可以有私有属性和私有方法。因为我们所有的数据在 JavaScript 类中默认是公开的,所以我们不能利用这个特性。尽管如此,我们还是应该使用 getter 和 setter 访问我们的数据。一种约定是在属性前加上下划线_
来表示它是私有的。
任务
创建一个使用作者对象设置作者属性的书籍对象。
最后的想法
我们了解到,类是创建对象的蓝图,对象是类的实例。将软件构建到对象中的好处是它提供了程序结构并使其更易于管理。
当我们有一个大型程序时,将其分解为对象允许独立于其他部分进行开发和维护。这种模块化带来了可重用性。因为我们的代码是封装的,所以对象可以在程序的其他部分反复使用。另外,我们有一个可测试的代码单元。我们的代码测试得越好,它就越安全。
- 任务
- 任务
- 任务