主要内容
复习:面向对象审计
以下复习这个有关面向对象设计的教程所包括的内容。
当我们编程时,我们经常发现要创建很多具有相似的属性的对象 - 例如许多猫,有稍微不同的毛色和大小,或者许多按钮,有不同的标签和位置。我们想要说 “猫大概是这样子的”,然后说 “咱门做这个具体的猫,以及另外一只猫,它们会在某些方面相似而在某些方面不同。” 在此情况中,我们要用 面向对象设计 来定义各种类型的对象并创建对象的实例。
为了在 Javascript 中定义对象类型,我们先要定义一个 “构造函数”。这是我们随时想要创建这种对象类型的实例时要用的函数。以下是
Book
(书)对象类型的构造函数:var Book = function(title, author, numPages) {
this.title = title;
this.author = author;
this.numPages = numPages;
this.currentPage = 0;
};
此函数接受形容每本书的不同特点的参数 - 书名,作者,页数。接着,它根据这些参数设定对象原本的属性,用
this
特殊词语。当我们在对象中用 this
时,我们指的是当前的对象实例,指它自己。我们必须在 this
中存储属性,来保证以后还能记得。为了创建
Book
对象的一个实例,我们要声明一个新的变量来存它,然后用 new
关键字,接着写构造函数的名字,并将构造函数接受的参数传入函数:var book = new Book("Robot Dreams", "Isaac Asimov", 320);
我们接下来可以用点号记法调用任何在对象中存储的属性:
println("我喜欢读 " + book.title); // 我喜欢读 Robot Dreams
println(book.author + " 是我最喜爱的作者"); // Isaac Asimov 是我最喜爱的作者
让我们比较一下,看看如果我们没有正确地创建构造函数会怎么样:
var Book = function(title, author, numPages) {
};
var book = new Book("Little Brother", "Cory Doctorow", 380);
println("我喜欢读 " + book.title); // 我喜欢读 undefined
println(book.author + " 是我最喜欢的作者"); // undefined 是我最喜欢的作者
如果我们将参数代入构造函数但不明确地存储在
this
中,那么我们以后就 不能 调用它们!那个对象将忘记它们。当我们定义对象类型时,我们经常想要使它们既包含属性 又包含方法(对象可以执行的行为) - 全部的猫对象都应该能 meow() (叫)和 eat() (吃)。因此我们必须能为我们的对象的定义加上函数,为了实现这个功能,我们可以用 对象原型 定义它们。
Book.prototype.readItAll = function() {
this.currentPage = this.numPages;
println("你读了 " + this.numPages + " 页!");
};
就像我们平常定义函数,但是我们是用
Book
原型定义,而不是全局地定义。就是这样 Javascript 才知道这是个能在任何 Book
对象调用的函数,而且此函数能调用正在被使用的书的 this
。接着,我们可以调用此函数(我们还叫他 方法,因为它连着对象),例如:
var book = new Book("Animal Farm", "George Orwell", 112);
book.readItAll(); // 你读了 112 页!
请记住,面向对象设计的目的就是让我们更容易地创建多个相似的对象(对象实例)。让我们在代码中看看:
var pirate = new Book("Pirate Cinema", "Cory Doctorow", 384);
var giver = new Book("The Giver", "Lois Lowry", 179);
var tuck = new Book("Tuck Everlasting", "Natalie Babbit", 144);
pirate.readItAll(); // 你读了 384 页!
giver.readItAll(); // 你读了 179 页!
tuck.readItAll(); // 你读了 144 页!
此代码给了我们三个相似的书 - 它们都具有同样的属性与方法,但也有些不一样。棒!
接着,想一想事实的世界,猫和狗是不同的对象类型,因此你可能会为他们创建不同的对象类型,如果你要变成一个
Cat
和一个 Dog
。猫会 meow()
,狗会 bark()
。但它们也有共同 - 猫和狗都会 eat()
,都有 age
,以及 birth
,以及 death
。它们都是哺乳动物,也就是说它们有很多共同点,即使它们又些不同点。既然如此,我们要运用 对象继承 的概念。一个对象类型可以从父类对象类型继承属性与方法,但还可以又自己独有的特性。所有的
Cat
和 Dog
都可以从 Mammal
(哺乳动物) 继承,因此它们就不需要从头创造 eat()
的功能。这在 Javascript 中要怎样做出?让我们回到书的例子,加入
Book
是 "父类" 对象类型,而我们要创建两个从它继承的对象类型 - Paperback
(平装书)和 EBook
(电子书)。一本平装书就像一本书,但有一个关键的不同点,至少对我们的程序来说:它有封面图像。因此,我们的构造函数必须接受四个参数,来接受那些多余的信息:
var PaperBack = function(title, author, numPages, cover) {
// ...
}
那么,我们不想要重新做我们在
Book
构造函数中已做过的来记住前面三个参数的代码 - 我们要利用与之前同样的代码。因此我们其实可以从 PaperBack
的构造函数调用 Book
的构造函数,并运用同样的参数:var PaperBack = function(title, author, numPages, cover) {
Book.call(this, title, author, numPages);
// ...
};
我们还需要在对象中存储
cover
属性,因此我们还需要一行代码:var PaperBack = function(title, author, numPages, cover) {
Book.call(this, title, author, numPages);
this.cover = cover;
};
现在,我们有了
PaperBack
的构造函数,帮助它与 Book
分享属性,但我们也要 PaperBack
继承它的方法。我们要这样做,即告诉程序 PaperBack
原型应该依据 Book
原型:PaperBack.prototype = Object.create(Book.prototype);
我们有可能还要添加平装书独有的行为,例如能够被烧。为了完成这个功能,我们可以在原型上定义函数,在上面这行代码的后面:
PaperBack.prototype.burn = function() {
println("天啊, 你烧了所有 "+ this.numPages + " 页");
this.numPages = 0;
};
现在我们可以创建一个新的平装书,读完,烧掉!
var calvin = new PaperBack("The Essential Calvin & Hobbes", "Bill Watterson", 256, "http://ecx.images-amazon.com/images/I/61M41hxr0zL.jpg");
calvin.readItAll(); // 你读了 256 页!
calvin.burn(); // 天啊, 你烧了 所有 256 页!
(其实,我们其实不一定会把它烧掉,因为那是一本很棒的书,但也许我们被困在寒冷的沙漠中而在将死的时候渴望温暖就会这样做)。
现在你可以看到怎样在你的 Javascript 中用面向对象设计的概念来为你的程序创建更复杂的数据而更好地构造你程序世界的模型。