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