盡管面向對象JavaScript與其他語(yǔ)言相比之下存在差異,并由此引發(fā)了一些爭論,但毋庸置疑,JavaScript具有強大的面向對象編程能力。本文先從介紹面向對象編程開(kāi)始,然后回顧JavaScript對象模型,最后演示JavaScript中的面向對象編程概念。
盡管面向對象JavaScript與其他語(yǔ)言相比之下存在差異,并由此引發(fā)了一些爭論,但毋庸置疑,JavaScript具有強大的面向對象編程能力。
本文先從介紹面向對象編程開(kāi)始,然后回顧JavaScript對象模型,最后演示JavaScript中的面向對象編程概念。
如果你對諸如變量(variables)、類(lèi)型(types)、函數(functions)、以及作用域(scope)等JavaScript概念覺(jué)得心里沒(méi)底,那么你可以閱讀重新介紹JavaScript中的這些主題。你還可以查閱JavaScript 1.5核心指南
面向對象編程是一種編程范式(paradigm),即使用抽象來(lái)創(chuàng )建基于真實(shí)世界的模型。它使用了幾種以前建立的范式技術(shù),包括模塊化(modularity)、多態(tài)(polymorphism)、和封裝(encapsulation)。今天,許多流行的編程語(yǔ)言(比如Java、JavaScript、C#、C++、Python、PHP、Ruby、以及Objective-C)都支持面向對象編程(OOP)。
面向對象編程可視為使用協(xié)作對象集合來(lái)進(jìn)行軟件設計,這與傳統觀(guān)點(diǎn)相反,傳統觀(guān)點(diǎn)把程序視為函數集合,或者簡(jiǎn)化為計算機指令列表。在面向對象編程中,每個(gè)對象都具有以下能力:接收消息、處理數據、以及給其他對象發(fā)送消息。每個(gè)對象都可以視為一個(gè)獨立的具有不同角色或責任的小機器。
面向對象編程旨在為編程提升更大的靈活性和可維護性,并在大規模軟件工程中廣泛流行。由于其非常重視模塊化,因此面向對象代碼旨在讓開(kāi)發(fā)更簡(jiǎn)單、稍后理解起來(lái)更容易,而且相對于較少采用模塊化的編程方法,使得對于復雜情況及步驟的分析、編碼和理解更加直接。[2]
類(lèi)(Class)
~ 定義對象的特征(characteristics)。
對象(Object)
~ 類(lèi)的實(shí)例(Instance)。
屬性(Property)
~ 某一對象特征(characteristic),例如顏色。
方法(Method)
~ 某種對象能力,例如行走。
構造函數(Constructor)
~ 實(shí)例化(instantiation)時(shí)所調用的方法。
繼承(Inheritance)
~ 一個(gè)類(lèi)可以繼承來(lái)自另一個(gè)類(lèi)的特征。
封裝(Encapsulation)
~ 一個(gè)類(lèi)只定義該對象的特征,一個(gè)方法只定義該方法如何執行。
抽象(Abstraction)
~ 將某一對象的復雜繼承、方法、屬性結合在一起,而且必須能夠模擬某一現實(shí)模型。
多態(tài)(Polymorphism)
~ 不同類(lèi)可能會(huì )定義相同的方法或屬性。
對于面向對象編程的進(jìn)一步描述,參閱維基百科的面向對象編程詞條。
基于原型的編程(Prototype-based programming)是一種面向對象編程風(fēng)格,其中類(lèi)(classes)并不存在,并且行為重用(在基于類(lèi)的語(yǔ)言中稱(chēng)為繼承)是通過(guò)粉飾充當原型的現存對象來(lái)完成的。這種模式也稱(chēng)為無(wú)類(lèi)的(class-less)、面向原型的(prototype-oriented)、或基于實(shí)例(instance-based)的編程。
關(guān)于基于原型語(yǔ)言的最初(且非常規范的)示例就是由David Ungar和Randall Smith開(kāi)發(fā)的Self編程語(yǔ)言。然而,這種無(wú)類(lèi)編程風(fēng)格最近越來(lái)越受歡迎,并且已被一些編程語(yǔ)言采用,例如avaScript、Cecil、NewtonScript、Io、MOO、REBOL、Kevo、Squeak(當使用Viewer框架來(lái)操縱Morphic組件時(shí))、及其他幾種語(yǔ)言。[2]
JavaScript有幾個(gè)包含在其核心中的對象;例如,Math、Object、Array、以及String等對象。下面的示例演示了如何使用Math對象的random()方法獲取隨機數。
alert(Math.random());提示:本例和所有其他示例都假設已在全局范圍內定義了函數名
alert(正如包含在web瀏覽器中的alert一樣)。alert函數實(shí)際上不是JavaScript本身的一部分。
JavaScript核心對象列表,參閱JavaScript 1.5核心參考:全局對象(Global Objects)。
JavaScript中的每個(gè)對象都是一個(gè)Object對象的實(shí)例,并因此繼承其所有屬性和方法。
JavaScript是基于原型的語(yǔ)言,其中不包含可在如 C++或Java中找到的類(lèi)聲明(class statement)。有時(shí)這會(huì )讓一些習慣于具有類(lèi)聲明語(yǔ)言(languages with a class statement)的程序員感到困惑。不過(guò),JavaScript用函數(functions)作為類(lèi)。定義一個(gè)類(lèi)簡(jiǎn)單到就是定義一個(gè)函數。在下例中,我們定義了名為Person(人)的新類(lèi)。
function Person() { }要創(chuàng )建obj對象的一個(gè)新實(shí)例,我們使用語(yǔ)句new obj,同時(shí)將結果(其類(lèi)型是obj)賦給某個(gè)變量(variable),以便稍后訪(fǎng)問(wèn)。
在下例中,我們首先定義名為Person的類(lèi),然后創(chuàng )建兩個(gè)實(shí)例(person1和person2)。
function Person() { }var person1 = new Person();var person2 = new Person();還可參閱新的實(shí)例化替代方法Object.create。
當實(shí)例化時(shí)(創(chuàng )建對象實(shí)例的瞬間)是會(huì )調用構造函數。構造函數是類(lèi)的一個(gè)方法。而在JavaScript中,會(huì )函數(function)作為作為該對象的構造函數;因此,也就無(wú)需顯式定義一個(gè)構造函數方法。類(lèi)中聲明的每個(gè)行為在實(shí)例化時(shí)都會(huì )執行。
構造函數用于設置對象屬性或調用方法為使用該對象做準備。本文稍后會(huì )介紹,通過(guò)使用一種不同的語(yǔ)法來(lái)添加類(lèi)方法及其定義 。
在下例中,當實(shí)例化Person時(shí),Person類(lèi)的構造函數會(huì )顯示一個(gè)警告框。
function Person() { alert('Person instantiated');}var person1 = new Person();var person2 = new Person();屬性是包含在類(lèi)中的變量;每個(gè)對象實(shí)例都有這些屬性。屬性應設置在類(lèi)(函數)的原型(prototype)屬性中,以便繼承正常工作。
在類(lèi)中操作屬性是通過(guò)this關(guān)鍵字實(shí)現的,this引用當前對象。在類(lèi)外部訪(fǎng)問(wèn)(讀或寫(xiě))某個(gè)屬性要通過(guò)以下語(yǔ)法:InstanceName.Property;這與C++、Java、以及其他一些語(yǔ)言所用語(yǔ)法相同。(在類(lèi)內部使用this.Property的語(yǔ)法來(lái)獲取或設置屬性值)。
在下例中,我們?yōu)?code>Person類(lèi)定義gender(性別)屬性,然后在初始化時(shí)定義該屬性。
function Person(gender) { this.gender = gender; alert('Person instantiated');}var person1 = new Person('Male'); // Male: 男var person2 = new Person('Female'); // Female: 女//顯示person1的性別alert('person1 is a ' + person1.gender); // person1 is a Male方法遵循與屬性相同的邏輯;區別在于它們是函數而且被定義為函數。調用方法與訪(fǎng)問(wèn)屬性相似,不過(guò)你要在方法名末尾添加(),可能會(huì )有參數(arguments)。定義一個(gè)方法,就是為該類(lèi)prototype屬性上的某個(gè)命名屬性指定一個(gè)函數;函數被分配到的那個(gè)名稱(chēng)就是在對象上調用該方法的名稱(chēng)。
在下例中,我們?yōu)?code>Person類(lèi)定義并使用sayHello()方法。
function Person(gender) { this.gender = gender; alert('Person instantiated');}Person.prototype.sayHello = function(){ alert ('hello');};var person1 = new Person('Male');var person2 = new Person('Female');// 調用Person的sayHello方法。person1.sayHello(); // hello在JavaScript中,方法是作為屬性被綁定到某個(gè)類(lèi)/對象的普通函數對象,這意味著(zhù),可以“脫離上下文(out of the context)”來(lái)調用它們??紤]如下示例代碼:
function Person(gender) { this.gender = gender;}Person.prototype.sayGender = function(){ alert(this.gender);};var person1 = new Person('Male');var genderTeller = person1.sayGender;person1.sayGender(); // alerts 'Male'genderTeller(); // alerts undefinedalert(genderTeller === person1.sayGender); // alerts truealert(genderTeller === Person.prototype.sayGender); // alerts true此示例一次演示了多個(gè)概念。這表明,在JavaScript中沒(méi)有“基于對象的方法(per-object methods)”,因為該方法的所有引用都指向完全相同的函數,即我們起初在原型上定義的那個(gè)函數。當某個(gè)函數被作為方法(或確切地說(shuō)是屬性)調用時(shí),JavaScript會(huì )將當前的“對象上下文(object context)”“綁定”到特定的“this”變量。這與調用該函數對象的“call”方法等效,如下所示:
genderTeller.call(person1); //alerts 'Male'更多相關(guān)信息,請參閱Function.call和Function.apply
繼承是一種方法,用于創(chuàng )建作為一個(gè)或多個(gè)類(lèi)專(zhuān)用版本的類(lèi)。(JavaScript僅支持單類(lèi)繼承)。這個(gè)專(zhuān)用類(lèi)通常被稱(chēng)為子類(lèi)(child),而其他類(lèi)通常被稱(chēng)為父類(lèi)(parent)。在JavaScript中,你要完成繼承,需將父類(lèi)的實(shí)例賦給子類(lèi),然后將子類(lèi)特化(specializing)。
提示:由于JavaScript不檢測的子類(lèi)的
prototype.constructor(原型的構造函數),參閱Core JavaScript 1.5核心參考:Global Objects:Object:prototype屬性,因此我們必須手動(dòng)指定該值。
在下例中,我們定義Student類(lèi)作為Person的子類(lèi)。然后我們重新定義sayHello()方法,并添加sayGoodBye()方法。
// 定義Person類(lèi)function Person() {}Person.prototype.walk = function(){ alert ('I am walking!');};Person.prototype.sayHello = function(){ alert ('hello');};// 定義Student類(lèi)function Student() { // 調用父類(lèi)構造函數 Person.call(this);}// 繼承PersonStudent.prototype = new Person();// 修正構造函數指針,由于它指向PersonStudent.prototype.constructor = Student;// 替換sayHello方法Student.prototype.sayHello = function(){ alert('hi, I am a student');}// 添加sayGoodBye方法Student.prototype.sayGoodBye = function(){ alert('goodBye');}var student1 = new Student();student1.sayHello();student1.walk();student1.sayGoodBye();// 檢驗繼承alert(student1 instanceof Person); // truealert(student1 instanceof Student); // true在上例中,Student無(wú)須知曉Person類(lèi)的walk()方法是如何實(shí)現的,但仍可使用該方法;Student類(lèi)無(wú)須顯式定義該方法,除非我們想改變它。這稱(chēng)為封裝(encapsulation),這樣每個(gè)類(lèi)繼承其父類(lèi)的方法,并且只需定義它所希望改變的東西。
抽象是一種機制(mechanism),允許對處理中的問(wèn)題的當前部分進(jìn)行建模。這可以通過(guò)繼承(特化)或組合(composition)來(lái)實(shí)現。JavaScript通過(guò)繼承實(shí)現特化(specialization),通過(guò)讓類(lèi)實(shí)例成為其他對象的屬性值實(shí)現組合。
JavaScript的Function類(lèi)繼承自Object類(lèi)(這說(shuō)明模型的特化),并且Function.prototype屬性是Object的實(shí)例(這說(shuō)明了組合)。
var foo = function(){};alert( 'foo is a Function: ' + (foo instanceof Function) );alert( 'foo.prototype is an Object: ' + (foo.prototype instanceof Object) );就像所有的方法和屬性被定義在原型屬性?xún)炔恳粯?,不同的?lèi)可以定義具有相同名稱(chēng)的方法;方法的作用域限于定義它們的類(lèi)之內。這僅當兩個(gè)類(lèi)之間沒(méi)有父子關(guān)系(當一個(gè)類(lèi)沒(méi)有從繼承鏈中的其他類(lèi)繼承時(shí))時(shí)才為真。
本文中所提出的面向對象編程實(shí)現技術(shù)不僅適用于JavaScript,因為就如何進(jìn)行面向對象編程而言,這是非常靈活的。
同樣,這里展示的技術(shù)既沒(méi)有使用任何語(yǔ)言技巧(language hacks),也沒(méi)有模仿其他語(yǔ)言的對象理論實(shí)現。
在JavaScript中,還有其他更高級的面向對象編程的技術(shù),但是那些內容已超出了這篇介紹性文章的范圍。
原始文檔信息
- 作者:Fernando Trasvi?a Email: f_trasvina at hotmail dot com
- 版權信息:? 1998-2005由mozilla.org個(gè)人貢獻者所有;所提供內容采用知識共享許可協(xié)議(Creative Commons license)進(jìn)行許可。
簡(jiǎn)體中文版譯文信息
- 譯者(簡(jiǎn)體中文):高翌翔 Email: yixianggao at 126 dot com
- 版權信息:
本作品采用知識共享署名-相同方式共享 2.5 中國大陸許可協(xié)議進(jìn)行許可。
聯(lián)系客服