JavaScript 中的繼承

2018-05-15 17:26 更新
預(yù)備知識: 基本的計(jì)算機(jī)素養(yǎng),對 HTML 和 CSS 有基本的理解,熟悉 JavaScript 基礎(chǔ)(參見 First stepsBuilding blocks)以及面向?qū)ο蟮腏avaScript (OOJS) 基礎(chǔ)(參見 Introduction to objects)。
目標(biāo): 理解在 JavaScript 中如何實(shí)現(xiàn)繼承。

原型式的繼承

到目前為止,我們已經(jīng)看到了一些行為中的繼承 - 我們已經(jīng)看到了原型鏈?zhǔn)侨绾喂ぷ鞯模约俺蓡T如何沿著鏈繼承。 但大多數(shù)情況下,這涉及內(nèi)置的瀏覽器功能。 我們?nèi)绾卧趶牧硪粋€(gè)對象繼承的JavaScript中創(chuàng)建一個(gè)對象?

如前所述,有些人認(rèn)為JavaScript不是一個(gè)真正的面向?qū)ο蟮恼Z言。 在"經(jīng)典OO"語言中,您傾向于定義某種類的對象,然后可以簡單地定義哪些類繼承自其他類(參見 .htm"class ="external"> C ++繼承一些簡單的例子)。 JavaScript使用不同的系統(tǒng) - "繼承"對象沒有功能復(fù)制到它們,而是它們繼承的功能通過原型鏈(通常稱為原型繼承)鏈接。

讓我們通過一個(gè)具體的例子探討如何做到這一點(diǎn)。

開始

首先,將自己的本地副本復(fù)制到我們的 class ="external"> oojs-class-inheritance-start.html 文件(請參閱 class-inheritance-start.html"class ="external"> running live )。 在這里,你會發(fā)現(xiàn)相同的 Person()構(gòu)造函數(shù)示例,我們一直使用模塊,只有一點(diǎn)區(qū)別 - 我們只定義了構(gòu)造函數(shù)中的屬性:

function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

方法是在構(gòu)造函數(shù)原型上定義的所有,例如:

Person.prototype.greeting = function() {
  alert('Hi! I\'m ' + this.name.first + '.');
};

假設(shè)我們想創(chuàng)建一個(gè) Teacher 類,就像我們在初始面向?qū)ο蠖x中描述的那樣,它繼承了來自 Person 的所有成員,但還包括:

  1. A new property, subject — this will contain the subject the teacher teaches.
  2. An updated greeting() method, which sounds a bit more formal than the standard greeting() method — more suitable for a teacher addressing some students at school.

定義?Teacher() 構(gòu)造函數(shù)

我們需要做的第一件事是創(chuàng)建一個(gè) Teacher()構(gòu)造函數(shù) - 在現(xiàn)有代碼下面添加以下代碼:

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

這看起來類似于Person構(gòu)造函數(shù)在很多方面,但有一些奇怪的地方,我們以前沒有見過 - Web / JavaScript / Reference / Global_Objects / Function / call"> call() 函數(shù)。 這個(gè)函數(shù)基本上允許你調(diào)用定義在其他地方,但在當(dāng)前上下文中的函數(shù)。 第一個(gè)參數(shù)指定要在運(yùn)行函數(shù)時(shí)使用的 this 的值,其他參數(shù)指定函數(shù)在運(yùn)行時(shí)應(yīng)該傳遞給它的參數(shù)。

注意:在這種情況下,我們在創(chuàng)建新對象實(shí)例時(shí)指定繼承的屬性,但請注意,您需要在構(gòu)造函數(shù)中將其指定為參數(shù),即使實(shí)例不需要它們 指定為參數(shù)(例如,您可能已經(jīng)有一個(gè)屬性在創(chuàng)建對象時(shí)設(shè)置為隨機(jī)值)。

因此,在這種情況下,我們有效地運(yùn)行 Teacher()構(gòu)造函數(shù)中的 Person()構(gòu)造函數(shù)(見上文),導(dǎo)致相同的屬性定義在 > Teacher(),但是使用傳遞給 Teacher()而不是 Person()的參數(shù)的值(我們使用 / code>作為 this 傳遞給 call()的值,這意味著 this 將是 Teacher >函數(shù))。

構(gòu)造函數(shù)中的最后一行簡單地定義了教師將要擁有的新的主體屬性,而普通人沒有。

注意,我們可以這樣做:

function Teacher(first, last, age, gender, interests, subject) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
  this.subject = subject;
}

但是這只是重新定義屬性,而不是從 Person()繼承它們,因此它違背了我們要做的事情。 它還需要更多的代碼行。

設(shè)置 Teacher() 的 prototype 和 constructor引用

一切都很好,到目前為止,但我們有一個(gè)問題。 我們定義了一個(gè)新的構(gòu)造函數(shù),并且默認(rèn)情況下有一個(gè)空的原型屬性。 我們需要獲取 Teacher()以繼承在 Person()的原型上定義的方法。 那么我們該怎么做呢?

  1. Add the following line below your previous addition:
    Teacher.prototype = Object.create(Person.prototype);
    Here our friend create() comes to the rescue again — in this case we are using it to create a new prototype property value (which is itself an object that contains properties and methods) with a prototype equal to Person.prototype, and set that to be the value of Teacher.prototype. This means that Teacher.prototype will now inherit all the methods available on Person.prototype.
  2. We need to do one more thing before we move on — the Teacher() prototype's constructor property is currently set as Person(), because of the way we inherited from it (this Stack Overflow post has more information on why) — try saving your code, loading the page in a browser, and entering this into the JavaScript console to verify:
    Teacher.prototype.constructor
  3. This can become a problem, so we need to set this right — you can do so by going back to your source code and adding the following line at the bottom:
    Teacher.prototype.constructor = Teacher;
  4. Now if you save and refresh, entering Teacher.prototype.constructor should return Teacher(), as desired.

向 Teacher() 增加新的函數(shù) greeting()

要完成我們的代碼,我們需要在 Teacher()構(gòu)造函數(shù)中定義一個(gè)新的 greeting()函數(shù)。

最簡單的方法是在 Teacher()的原型上定義它 - 在代碼的底部添加以下內(nèi)容:

Teacher.prototype.greeting = function() {
  var prefix;

  if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
    prefix = 'Mr.';
  } else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
    prefix = 'Mrs.';
  } else {
    prefix = 'Mx.';
  }

  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};

這會警告教師的問候語,其也使用適當(dāng)?shù)拿Q前綴作為他們的性別,使用條件語句。

范例嘗試

現(xiàn)在您已輸入所有代碼,請嘗試通過在JavaScript的底部(或您選擇的類似選項(xiàng))放置以下代碼,從 Teacher()創(chuàng)建一個(gè)對象實(shí)例:

var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');

現(xiàn)在保存并刷新,并嘗試訪問新的 teacher1 對象的屬性和方法,例如:

teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();

這些都應(yīng)該工作很好; 前面三個(gè)從通用 Person()構(gòu)造函數(shù)(類)繼承的訪問成員,而最后兩個(gè)訪問成員只在更專業(yè)的 Teacher() 構(gòu)造函數(shù)(類)。

我們在這里介紹的技術(shù)不是在JavaScript中創(chuàng)建繼承類的唯一方法,但它是可行的,它給你一個(gè)好主意如何在JavaScript中實(shí)現(xiàn)繼承。

您可能還想查看一些新的 腳本語言,基于JavaScript是基于Ecma國際負(fù)責(zé)標(biāo)準(zhǔn)化ECMAScript。"> ECMAScript 功能,允許我們在JavaScript中更干凈地執(zhí)行繼承(參見 org / zh-CN / docs / Web / JavaScript / Reference / Classes">類)。 我們沒有覆蓋這里,因?yàn)樗麄冞€沒有支持非常廣泛的瀏覽器。 我們在這組文章中討論的所有其他代碼結(jié)構(gòu)支持早在IE9或更早版本,并且有辦法實(shí)現(xiàn)早期的支持。

一個(gè)常見的方法是使用JavaScript庫 - 大多數(shù)受歡迎的選項(xiàng)都有一套易于使用的功能,可以更容易和快速地繼承。 CoffeeScript 例如提供 class , extends 等。

更多練習(xí)

在我們的 OOP理論部分中,我們還包括一個(gè) Student 類作為概念,繼承所有 Person 的特征,并且還具有與 Teacher 不同的 greeting()方法 >的問候。 看看學(xué)生的問候語在該部分,并嘗試實(shí)現(xiàn)自己的 Student()構(gòu)造函數(shù),它繼承了 Person()的所有功能,并實(shí)現(xiàn) 不同的 greeting()函數(shù)。

對象成員總結(jié)

總而言之,基本上有三種類型的屬性/方法需要擔(dān)心:

  1. Those defined inside a constructor function that are given to object instances. These are fairly easy to spot — in your own custom code, they are the members defined inside a constructor using the this.x = x type lines; in built in browser code, they are the members only available to object instances (usually created by calling a constructor using the new keyword, e.g. var myInstance = new myConstructor()).
  2. Those defined directly on the constructor themselves, that are available only on the constructor. These are commonly only available on built-in browser objects, and are recognized by being chained directly onto a constructor, not a instance. For example, Object.keys().
  3. Those defined on a constructor's prototype, which are inherited by all instances and inheriting object classes. These include any member defined on a Constructor's prototype property, e.g. myConstructor.prototype.x().

如果你不確定是哪個(gè),不要擔(dān)心它只是 - 你還在學(xué)習(xí),熟悉將伴隨著實(shí)踐。

何時(shí)在 JavaScript 中使用繼承?

特別是在這最后一篇文章之后,你可能會想"這是復(fù)雜的"。 嗯,你是對的,原型和繼承代表JavaScript的一些最復(fù)雜的方面,但很多JavaScript的力量和靈活性來自它的對象結(jié)構(gòu)和繼承,這是值得了解它是如何工作的。

在某種程度上,您始終使用繼承 - 每當(dāng)使用WebAPI的各種功能,或者在您對字符串,數(shù)組等調(diào)用的內(nèi)置瀏覽器對象上定義的方法/屬性時(shí),您將隱式使用繼承。

在使用繼承在你自己的代碼,你可能不會經(jīng)常使用它,特別是開始,在小項(xiàng)目中 - 浪費(fèi)時(shí)間使用對象和繼承只是為了它,當(dāng)你 不需要它們。 但是隨著你的代碼庫越來越大,你更有可能找到它的需要。 如果您發(fā)現(xiàn)自己正在開始創(chuàng)建一些具有類似功能的對象,那么創(chuàng)建一個(gè)通用對象類型以包含所有共享功能并在更專門的對象類型中繼承這些功能可能是方便和有用的。

注意:由于JavaScript的工作方式,原型鏈等,對象之間的功能共享通常稱為委托 - 專門的對象將該功能委托給 通用對象類型。 這可能比調(diào)用繼承更準(zhǔn)確,因?yàn)?繼承"功能不會復(fù)制到正在執(zhí)行"繼承"的對象。 相反,它仍然保留在通用對象中。

當(dāng)使用繼承時(shí),建議不要有太多的繼承,并仔細(xì)跟蹤您定義方法和屬性的位置。 有可能開始編寫代碼來臨時(shí)修改內(nèi)置瀏覽器對象的原型,但是你不應(yīng)該這樣做,除非你有一個(gè)很好的理由。 太多的繼承會導(dǎo)致無盡的混亂,當(dāng)你嘗試調(diào)試這樣的代碼,無盡的痛苦。

最終,對象只是另一種形式的代碼重用,如函數(shù)或循環(huán),具有自己的特定角色和優(yōu)點(diǎn)。 如果你發(fā)現(xiàn)自己創(chuàng)建了一堆相關(guān)的變量和函數(shù),并希望將它們?nèi)恳黄鸶櫜⒄R打包,一個(gè)對象是個(gè)好主意。 當(dāng)您想將數(shù)據(jù)集合從一個(gè)地方傳遞到另一個(gè)地方時(shí),對象也非常有用。 這兩個(gè)事情都可以在不使用構(gòu)造函數(shù)或繼承的情況下實(shí)現(xiàn)。 如果你只需要一個(gè)對象的單個(gè)實(shí)例,那么你可能最好使用一個(gè)對象字面量,你肯定不需要繼承。

總結(jié)

本文涵蓋了我們認(rèn)為您現(xiàn)在應(yīng)該知道的核心OOJS理論和語法的其余部分。 此時(shí),您應(yīng)該了解JavaScript對象和OOP基礎(chǔ),原型和原型繼承,如何創(chuàng)建類(構(gòu)造函數(shù))和對象實(shí)例,向類添加功能,以及創(chuàng)建從其他類繼承的子類。

在下一篇文章中,我們將了解如何使用JavaScript對象表示法(JSON),這是一種使用JavaScript對象編寫的常見數(shù)據(jù)交換格式。

另見

  • ObjectPlayground.com — A really useful interactive learning site for learning about objects.
  • Secrets of the JavaScript Ninja, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.
  • You Don't Know JS: this & Object Prototypes — Part of Kyle Simpson's excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We've presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號