預(yù)備知識: | 基本的計算機素養(yǎng),對 HTML 和 CSS 有基本的理解,熟悉 JavaScript 基礎(chǔ)(參見 First steps 和 Building blocks)以及面向?qū)ο蟮腏avaScript (OOJS) 基礎(chǔ)(參見 Introduction to objects)。 |
---|---|
目標(biāo): | 理解在 JavaScript 中如何實現(xiàn)繼承。 |
到目前為止,我們已經(jīng)看到了一些行為中的繼承 - 我們已經(jīng)看到了原型鏈?zhǔn)侨绾喂ぷ鞯?,以及成員如何沿著鏈繼承。 但大多數(shù)情況下,這涉及內(nèi)置的瀏覽器功能。 我們?nèi)绾卧趶牧硪粋€對象繼承的JavaScript中創(chuàng)建一個對象?
如前所述,有些人認(rèn)為JavaScript不是一個真正的面向?qū)ο蟮恼Z言。 在"經(jīng)典OO"語言中,您傾向于定義某種類的對象,然后可以簡單地定義哪些類繼承自其他類(參見 .htm"class ="external"> C ++繼承一些簡單的例子)。 JavaScript使用不同的系統(tǒng) - "繼承"對象沒有功能復(fù)制到它們,而是它們繼承的功能通過原型鏈(通常稱為原型繼承)鏈接。
讓我們通過一個具體的例子探討如何做到這一點。
首先,將自己的本地副本復(fù)制到我們的 class ="external"> oojs-class-inheritance-start.html 文件(請參閱 class-inheritance-start.html"class ="external"> running live )。 在這里,你會發(fā)現(xiàn)相同的 Person()
構(gòu)造函數(shù)示例,我們一直使用模塊,只有一點區(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)建一個 Teacher
類,就像我們在初始面向?qū)ο蠖x中描述的那樣,它繼承了來自 Person
的所有成員,但還包括:
subject
— this will contain the subject the teacher teaches.greeting()
method, which sounds a bit more formal than the standard greeting()
method — more suitable for a teacher addressing some students at school.我們需要做的第一件事是創(chuàng)建一個 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ù)。 這個函數(shù)基本上允許你調(diào)用定義在其他地方,但在當(dāng)前上下文中的函數(shù)。 第一個參數(shù)指定要在運行函數(shù)時使用的 this
的值,其他參數(shù)指定函數(shù)在運行時應(yīng)該傳遞給它的參數(shù)。
注意:在這種情況下,我們在創(chuàng)建新對象實例時指定繼承的屬性,但請注意,您需要在構(gòu)造函數(shù)中將其指定為參數(shù),即使實例不需要它們 指定為參數(shù)(例如,您可能已經(jīng)有一個屬性在創(chuàng)建對象時設(shè)置為隨機值)。
因此,在這種情況下,我們有效地運行 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()
繼承它們,因此它違背了我們要做的事情。 它還需要更多的代碼行。
一切都很好,到目前為止,但我們有一個問題。 我們定義了一個新的構(gòu)造函數(shù),并且默認(rèn)情況下有一個空的原型
屬性。 我們需要獲取 Teacher()
以繼承在 Person()
的原型上定義的方法。 那么我們該怎么做呢?
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
.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
Teacher.prototype.constructor = Teacher;
Teacher.prototype.constructor
should return Teacher()
, as desired.要完成我們的代碼,我們需要在 Teacher()
構(gòu)造函數(shù)中定義一個新的 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的底部(或您選擇的類似選項)放置以下代碼,從 Teacher()
創(chuàng)建一個對象實例:
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)該工作很好; 前面三個從通用 Person()
構(gòu)造函數(shù)(類)繼承的訪問成員,而最后兩個訪問成員只在更專業(yè)的 Teacher()
構(gòu)造函數(shù)(類)。
注意:如果您無法使用此功能,請將您的代碼與我們的 advanced / oojs-class-inheritance-finished.html"class ="external">完成版本(請參閱 advanced / oojs-class-inheritance-finished.html"class ="external"> running live )。
我們在這里介紹的技術(shù)不是在JavaScript中創(chuàng)建繼承類的唯一方法,但它是可行的,它給你一個好主意如何在JavaScript中實現(xiàn)繼承。
您可能還想查看一些新的 腳本語言,基于JavaScript是基于Ecma國際負(fù)責(zé)標(biāo)準(zhǔn)化ECMAScript。"> ECMAScript 功能,允許我們在JavaScript中更干凈地執(zhí)行繼承(參見 org / zh-CN / docs / Web / JavaScript / Reference / Classes">類)。 我們沒有覆蓋這里,因為他們還沒有支持非常廣泛的瀏覽器。 我們在這組文章中討論的所有其他代碼結(jié)構(gòu)支持早在IE9或更早版本,并且有辦法實現(xiàn)早期的支持。
一個常見的方法是使用JavaScript庫 - 大多數(shù)受歡迎的選項都有一套易于使用的功能,可以更容易和快速地繼承。 CoffeeScript 例如提供 class
, extends
等。
在我們的 OOP理論部分中,我們還包括一個 Student
類作為概念,繼承所有 Person
的特征,并且還具有與 Teacher
不同的 greeting()
方法 >的問候。 看看學(xué)生的問候語在該部分,并嘗試實現(xiàn)自己的 Student()
構(gòu)造函數(shù),它繼承了 Person()
的所有功能,并實現(xiàn) 不同的 greeting()
函數(shù)。
注意:如果您無法使用此功能,請查看我們的 advanced / oojs-class-inheritance-student.html"class ="external">完成版本(請參閱 advanced / oojs-class-inheritance-student.html"class ="external"> running live )。
總而言之,基本上有三種類型的屬性/方法需要擔(dān)心:
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()
).Object.keys()
.myConstructor.prototype.x()
.如果你不確定是哪個,不要擔(dān)心它只是 - 你還在學(xué)習(xí),熟悉將伴隨著實踐。
特別是在這最后一篇文章之后,你可能會想"這是復(fù)雜的"。 嗯,你是對的,原型和繼承代表JavaScript的一些最復(fù)雜的方面,但很多JavaScript的力量和靈活性來自它的對象結(jié)構(gòu)和繼承,這是值得了解它是如何工作的。
在某種程度上,您始終使用繼承 - 每當(dāng)使用WebAPI的各種功能,或者在您對字符串,數(shù)組等調(diào)用的內(nèi)置瀏覽器對象上定義的方法/屬性時,您將隱式使用繼承。
在使用繼承在你自己的代碼,你可能不會經(jīng)常使用它,特別是開始,在小項目中 - 浪費時間使用對象和繼承只是為了它,當(dāng)你 不需要它們。 但是隨著你的代碼庫越來越大,你更有可能找到它的需要。 如果您發(fā)現(xiàn)自己正在開始創(chuàng)建一些具有類似功能的對象,那么創(chuàng)建一個通用對象類型以包含所有共享功能并在更專門的對象類型中繼承這些功能可能是方便和有用的。
注意:由于JavaScript的工作方式,原型鏈等,對象之間的功能共享通常稱為委托 - 專門的對象將該功能委托給 通用對象類型。 這可能比調(diào)用繼承更準(zhǔn)確,因為"繼承"功能不會復(fù)制到正在執(zhí)行"繼承"的對象。 相反,它仍然保留在通用對象中。
當(dāng)使用繼承時,建議不要有太多的繼承,并仔細(xì)跟蹤您定義方法和屬性的位置。 有可能開始編寫代碼來臨時修改內(nèi)置瀏覽器對象的原型,但是你不應(yīng)該這樣做,除非你有一個很好的理由。 太多的繼承會導(dǎo)致無盡的混亂,當(dāng)你嘗試調(diào)試這樣的代碼,無盡的痛苦。
最終,對象只是另一種形式的代碼重用,如函數(shù)或循環(huán),具有自己的特定角色和優(yōu)點。 如果你發(fā)現(xiàn)自己創(chuàng)建了一堆相關(guān)的變量和函數(shù),并希望將它們?nèi)恳黄鸶櫜⒄R打包,一個對象是個好主意。 當(dāng)您想將數(shù)據(jù)集合從一個地方傳遞到另一個地方時,對象也非常有用。 這兩個事情都可以在不使用構(gòu)造函數(shù)或繼承的情況下實現(xiàn)。 如果你只需要一個對象的單個實例,那么你可能最好使用一個對象字面量,你肯定不需要繼承。
本文涵蓋了我們認(rèn)為您現(xiàn)在應(yīng)該知道的核心OOJS理論和語法的其余部分。 此時,您應(yīng)該了解JavaScript對象和OOP基礎(chǔ),原型和原型繼承,如何創(chuàng)建類(構(gòu)造函數(shù))和對象實例,向類添加功能,以及創(chuàng)建從其他類繼承的子類。
在下一篇文章中,我們將了解如何使用JavaScript對象表示法(JSON),這是一種使用JavaScript對象編寫的常見數(shù)據(jù)交換格式。
更多建議: