JavaScript 物件導向介紹
JavaScript 的核心是支持物件導向的,同時它也提供了強大靈活的 OOP 語言能力。本文從對物件導向編程的介紹開始,帶您探索 JavaScript 的物件模型,最後描述 JavaScript 當中物件導向編程的一些概念。
物件導向編程
物件導向編程是用抽象方式新增基於現實世界模型的一種編程模式。它使用先前建立的範例,包括模組化,多態和封裝幾種技術。今天,許多流行的編程語言(如Java,JavaScript,C#,C+ +,Python,PHP,Ruby和Objective-C)都支持物件導向編程(OOP)。
物件導向編程可以看作是使用一系列物件相互協作的軟件設計,相對於傳統觀念,一個程式只是一些函式的集合,或簡單的小算盤指令列表。 在OOP中,每個物件能夠接收消息,處理資料和發送消息給其他物件。每個物件都可以被看作是一個擁有清晰角色或責任的獨立小機器。
物件導向程式設計的目的是在編程中促進更好的靈活性和可維護性,在大型軟件工程中廣為流行。憑藉其對模組化的重視,物件導向的程式碼開發更簡單,更容易理解,相比非模組化編程方法 1, 它能更直接地分析, 編碼和理解複雜的情況和過程。
術語
Namespace 命名空間
允許開發人員在一個獨特, 套用相關的名字的名稱下捆綁所有功能的容器。
Class 類
定義物件的特徵。它是物件的屬性和方法的模板定義.
Object 物件
類的一個範例。
Property 屬性
物件的特徵,比如顏色。
Method 方法
物件的能力,比如行走。
Constructor 建構式
物件初始化的瞬間, 被呼叫的方法. 通常它的名字與包含它的類一致.
Inheritance 繼承
一個類可以繼承另一個類的特徵。
Encapsulation 封裝
一種把資料和相關的方法綁定在一起使用的方法.
Abstraction 抽象
結合複雜的繼承,方法,屬性的物件能夠模擬現實的模型。
Polymorphism 多態
多意為『許多』,態意為『形態』。不同類可以定義相同的方法或屬性。
更多關於物件導向編程的描述,請參照維基百科的 物件導向編程 。
原型編程
基於原型的編程不是物件導向編程中體現的風格,且行為重用(在基於類的語言中也稱為繼承)是通過裝飾它作為原型的現有物件的過程實現的。這種模式也被稱為弱類化,原型化,或基於範例的編程。
原始的(也是最典型的)基於原型語言的例子是由大衛·安格爾和蘭德爾·史密斯開發的。然而,弱類化的編程風格近來變得越來越流行,並已被諸如JavaScript,Cecil,NewtonScript,IO,MOO,REBOL,Kevo,Squeak(使用框架操縱Morphic組件),和其他幾種編程語言採用。1
JavaScript物件導向編程
命名空間
命名空間是一個容器,它允許開發人員在一個獨特的,特定於套用程式的名稱下捆綁所有的功能。 在JavaScript中,命名空間只是另一個包含方法,屬性,物件的物件。
注意:需要認識到重要的一點是:與其他物件導向編程語言不同的是,Javascript中的普通物件和命名空間在語言層面上沒有區別。這點可能會讓JavaScript初學者感到迷惑。
創造的JavaScript命名空間背後的想法很簡單:一個全域物件被新增,所有的變數,方法和功能成為該物件的屬性。使用命名空間也最大程度地減少套用程式的名稱衝突的可能性。
我們來新增一個全域變數叫做 MYAPP
// 全域命名空間
var MYAPP = MYAPP || {};
在上面的程式碼示例中,我們首先檢查MYAPP是否已經被定義(是否在同一文件中或在另一文件)。如果是的話,那麼使用現有的MYAPP全域物件,否則,新增一個名為MYAPP的空物件用來封裝方法,函式,變數和物件。
我們也可以新增子命名空間:
// 子命名空間
MYAPP.event = {};
下面是用於新增命名空間和增加變數,函式和方法的程式碼寫法:
// 給普通方法和屬性新增一個叫做MYAPP.commonMethod的容器
MYAPP.commonMethod = {
regExForName: "", // 定義名字的正則驗證
regExForPhone: "", // 定義電話的正則驗證
validateName: function(name){
// 對名字name做些操作,你可以通過使用「this.regExForname」
// 訪問regExForName變數
},
validatePhoneNo: function(phoneNo){
// 對電話號碼做操作
}
}
// 物件和方法一起申明
MYAPP.event = {
addListener: function(el, type, fn) {
// 程式碼
},
removeListener: function(el, type, fn) {
// 程式碼
},
getEvent: function(e) {
// 程式碼
}
// 還可以增加其他的屬性和方法
}
//使用addListner方法的寫法:
MYAPP.event.addListener("yourel", "type", callback);
標準內置物件
JavaScript有包括在其核心的幾個物件,例如,Math,Object,Array和String物件。下面的例子示範了如何使用Math物件的random()方法來獲得一個隨機數。
console.log(Math.random());
注意:T這裡和接下來的例子都假設名為 console.log 的方法全域有定義。console.log 實際上不是 JavaScript 自帶的。
查看 JavaScript 參考:全域物件 瞭解 JavaScript 內置物件的列表。
JavaScript 中的每個物件都是 Object 物件的範例且繼承它所有的屬性和方法。
自定義物件
類
JavaScript是一種基於原型的語言,它沒類的聲明語句,比如C+ +或Java中用的。這有時會對習慣使用有類申明語句語言的程式員產生困擾。相反,JavaScript可用方法作類。定義一個類跟定義一個函式一樣簡單。在下面的例子中,我們定義了一個新類Person。
function Person() { }
// 或
var Person = function(){ }
物件(類的範例)
我們使用 new obj 新增物件 obj 的新範例, 將結果(obj 類型)賦值給一個變數方便稍後呼叫。
在下面的示例中,我們定義了一個名為Person的類,然後我們新增了兩個Person的範例(person1 and person2).
function Person() { }
var person1 = new Person();
var person2 = new Person();
注意:有一種新增的新增未初始化範例的範例化方法,請參考 Object.create 。
構造器
在範例化時構造器被呼叫 (也就是物件範例被新增時)。構造器是物件中的一個方法。 在JavaScript,中函式就可以作為構造器使用,因此不需要特別地定義一個構造器方法. 每個聲明的函式都可以在範例化後被呼叫執行
構造器常用於給物件的屬性賦值或者為呼叫函式做準備。 在本文的後面描述了類中方法既可以在定義時增加,也可以在使用前增加。
在下面的示例中, Person類範例化時構造器呼叫一個 alert函式。
function Person() {
alert('Person instantiated');
}
var person1 = new Person();
var person2 = new Person();
屬性 (物件屬性)
屬性就是 類中包含的變數;每一個物件範例有若干個屬性. 為了正確的繼承,屬性應該被定義在類的原型屬性 (函式)中。
可以使用 關鍵字 this呼叫類中的屬性, this是對當前物件的引用。 從外部存取(讀/寫)其屬性的語法是: InstanceName.Property; 這與C++,Java或者許多其他語言中的語法是一樣的 (在類中語法 this.Property 常用於set和get屬性值)
在下面的示例中,我們為定義Person類定義了一個屬性 firstName 並在範例化時賦初值。
function Person(firstName) {
this.firstName = firstName;
alert('Person instantiated');
}
var person1 = new Person('Alice');
var person2 = new Person('Bob');
// Show the firstName properties of the objects
alert('person1 is ' + person1.firstName); // alerts "person1 is Alice"
alert('person2 is ' + person2.firstName); // alerts "person2 is Bob"
方法(物件屬性)
方法與屬性很相似, 不同的是:一個是函式,另一個可以被定義為函式。 呼叫方法很像存取一個屬性, 不同的是add () 在方法名後面很可能帶著參數. 為定義一個方法, 需要將一個函式賦值給類的 prototype 屬性; 這個賦值給函式的名稱就是用來給物件在外部呼叫它使用的。
在下面的示例中,我們給Person類定義了方法 sayHello(),並呼叫了它.
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.sayHello = function() {
alert("Hello, I'm " + this.firstName);
};
var person1 = new Person("Alice");
var person2 = new Person("Bob");
// call the Person sayHello method.
person1.sayHello(); // alerts "Hello, I'm Alice"
person2.sayHello(); // alerts "Hello, I'm Bob"
在JavaScript中方法通常是一個綁定到物件中的普通函式, 這意味著方法可以在其所在context之外被呼叫。 思考下面示例中的程式碼:
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.sayHello = function() {
alert("Hello, I'm " + this.firstName);
};
var person1 = new Person("Alice");
var person2 = new Person("Bob");
var helloFunction = person1.sayHello;
person1.sayHello(); // alerts "Hello, I'm Alice"
person2.sayHello(); // alerts "Hello, I'm Bob"
helloFunction(); // alerts "Hello, I'm undefined" (or fails
// with a TypeError in strict mode)
alert(helloFunction === person1.sayHello); // alerts true
alert(helloFunction === Person.prototype.sayHello); // alerts true
helloFunction.call(person1); // alerts "Hello, I'm Alice"
如上例所示, 所有指向sayHello函式的引用 ,包括 person1, Person.prototype, 和 helloFunction 等, 均引用了相同的函式.
在呼叫函式的過程中,this的值取決於我們怎麼樣呼叫函式. 在通常情況下,我們通過一個表達式person1.sayHello()來呼叫函式:即從一個物件的屬性中得到所呼叫的函式。此時this被設置為我們取得函式的物件(即person1)。這就是為什麼person1.sayHello() 使用了姓名「Alice」而person2.sayHello()使用了姓名「bob」的原因。
然而我們使用不同的呼叫方法時, this的值也就不同了。當從變數 helloFunction()中呼叫的時候, this就被設置成了全域物件 (在瀏覽器中即window)。由於該物件 (非常可能地) 沒有firstName 屬性, 我們得到的結果便是"Hello, I'm undefined". (這是鬆散模式下的結果, 在 嚴格模式中,結果將不同(此時會產生一個error)。 但是為了避免混淆,我們在這裡不涉及細節) 。另外,我們可以像上例末尾那樣,使用Function#call (或者Function#apply)顯式的設置this的值。
更多有關訊息請參考 Function#call and Function#apply
繼承
新增一個或多個類的專門版本類方式稱為繼承(Javascript只支持單繼承)。 新增的專門版本的類通常叫做子類,另外的類通常叫做父類。 在Javascript中,繼承通過賦予子類一個父類的範例並專門化子類來實現。在現代瀏覽器中你可以使用 Object.create 實現繼承.
JavaScript 並不檢測子類的 prototype.constructor (見 Object.prototype), 所以我們必須手動申明它.
在下面的例子中, 我們定義了 Student類作為 Person類的子類. 之後我們重定義了sayHello() 方法並增加了 sayGoodBye() 方法.
// 定義Person構造器
function Person(firstName) {
this.firstName = firstName;
}
// 在Person.prototype中加入方法
Person.prototype.walk = function(){
alert("I am walking!");
};
Person.prototype.sayHello = function(){
alert("Hello, I'm " + this.firstName);
};
// 定義Student構造器
function Student(firstName, subject) {
// 呼叫父類構造器, 確保(使用Function#call)"this" 在呼叫過程中設置正確
Person.call(this, firstName);
// 初始化Student類特有屬性
this.subject = subject;
};
// 建立一個由Person.prototype繼承而來的Student.prototype物件.
// 注意: 常見的錯誤是使用 "new Person()"來建立Student.prototype.
// 這樣做的錯誤之處有很多, 最重要的一點是我們在範例化時
// 不能賦予Person類任何的FirstName參數
// 呼叫Person的正確位置如下,我們從Student中來呼叫它
Student.prototype = Object.create(Person.prototype); // See note below
// 設置"constructor" 屬性指向Student
Student.prototype.constructor = Student;
// 更換"sayHello" 方法
Student.prototype.sayHello = function(){
alert("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + ".");
};
// 加入"sayGoodBye" 方法
Student.prototype.sayGoodBye = function(){
alert("Goodbye!");
};
// 測試範例:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk(); // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"
// Check that instanceof works correctly
alert(student1 instanceof Person); // true
alert(student1 instanceof Student); // true
對於「Student.prototype = Object.create(Person.prototype);」這一行,在不支持 Object.create方法的老JavaScript引擎中,可以使用一個"polyfill"(又名"shim",查看文章鏈接),或者使用一個function來獲得相同的返回值,就像下面:
function createObject(proto) {
function ctor() { }
ctor.prototype = proto;
return new ctor();
}
// Usage:
Student.prototype = createObject(Person.prototype);
更多相關訊息請參考 Object.create,連接中還有一個老JavaScript引擎的兼容方案(shim)。
封裝
在上一個例子中,Student類雖然不需要知道Person類的walk()方法是如何實現的,但是仍然可以使用這個方法;Student類不需要明確地定義這個方法,除非我們想改變它。 這就叫做封裝,對於所有繼承自父類的方法,只需要在子類中定義那些你想改變的即可。
抽象
抽象是允許模擬工作問題中通用部分的一種機制。這可以通過繼承(具體化)或組合來實現。
JavaScript通過繼承實現具體化,通過讓類的範例是其他物件的屬性值來實現組合。
JavaScript Function 類繼承自Object類(這是典型的具體化) 。Function.prototype的屬性是一個Object範例(這是典型的組合)。
var foo = function(){};
alert( 'foo is a Function: ' + (foo instanceof Function) ); // alerts "foo is a Function: true"
alert( 'foo.prototype is an Object: ' + (foo.prototype instanceof Object) ); // alerts "foo.prototype is an Object: true"
多型
就像所有定義在原型屬性內部的方法和屬性一樣,不同的類可以定義具有相同名稱的方法;方法是作用於所在的類中。並且這僅在兩個類不是父子關係時成立(繼承鏈中,一個類不是繼承自其他類)。
注意
本文中所展示的物件導向編程技術不是唯一的實現方式,在JavaScript中物件導向的實現是非常靈活的。
同樣的,文中展示的技術沒有使用任何語言hacks,它們也沒有模仿其他語言的物件理論實現。
JavaScript中還有其他一些更加先進的物件導向技術,但這些都超出了本文的介紹範圍。
物件導向編程
物件導向編程是用抽象方式新增基於現實世界模型的一種編程模式。它使用先前建立的範例,包括模組化,多態和封裝幾種技術。今天,許多流行的編程語言(如Java,JavaScript,C#,C+ +,Python,PHP,Ruby和Objective-C)都支持物件導向編程(OOP)。
物件導向編程可以看作是使用一系列物件相互協作的軟件設計,相對於傳統觀念,一個程式只是一些函式的集合,或簡單的小算盤指令列表。 在OOP中,每個物件能夠接收消息,處理資料和發送消息給其他物件。每個物件都可以被看作是一個擁有清晰角色或責任的獨立小機器。
物件導向程式設計的目的是在編程中促進更好的靈活性和可維護性,在大型軟件工程中廣為流行。憑藉其對模組化的重視,物件導向的程式碼開發更簡單,更容易理解,相比非模組化編程方法 1, 它能更直接地分析, 編碼和理解複雜的情況和過程。
術語
Namespace 命名空間
允許開發人員在一個獨特, 套用相關的名字的名稱下捆綁所有功能的容器。
Class 類
定義物件的特徵。它是物件的屬性和方法的模板定義.
Object 物件
類的一個範例。
Property 屬性
物件的特徵,比如顏色。
Method 方法
物件的能力,比如行走。
Constructor 建構式
物件初始化的瞬間, 被呼叫的方法. 通常它的名字與包含它的類一致.
Inheritance 繼承
一個類可以繼承另一個類的特徵。
Encapsulation 封裝
一種把資料和相關的方法綁定在一起使用的方法.
Abstraction 抽象
結合複雜的繼承,方法,屬性的物件能夠模擬現實的模型。
Polymorphism 多態
多意為『許多』,態意為『形態』。不同類可以定義相同的方法或屬性。
更多關於物件導向編程的描述,請參照維基百科的 物件導向編程 。
原型編程
基於原型的編程不是物件導向編程中體現的風格,且行為重用(在基於類的語言中也稱為繼承)是通過裝飾它作為原型的現有物件的過程實現的。這種模式也被稱為弱類化,原型化,或基於範例的編程。
原始的(也是最典型的)基於原型語言的例子是由大衛·安格爾和蘭德爾·史密斯開發的。然而,弱類化的編程風格近來變得越來越流行,並已被諸如JavaScript,Cecil,NewtonScript,IO,MOO,REBOL,Kevo,Squeak(使用框架操縱Morphic組件),和其他幾種編程語言採用。1
JavaScript物件導向編程
命名空間
命名空間是一個容器,它允許開發人員在一個獨特的,特定於套用程式的名稱下捆綁所有的功能。 在JavaScript中,命名空間只是另一個包含方法,屬性,物件的物件。
注意:需要認識到重要的一點是:與其他物件導向編程語言不同的是,Javascript中的普通物件和命名空間在語言層面上沒有區別。這點可能會讓JavaScript初學者感到迷惑。
創造的JavaScript命名空間背後的想法很簡單:一個全域物件被新增,所有的變數,方法和功能成為該物件的屬性。使用命名空間也最大程度地減少套用程式的名稱衝突的可能性。
我們來新增一個全域變數叫做 MYAPP
// 全域命名空間
var MYAPP = MYAPP || {};
在上面的程式碼示例中,我們首先檢查MYAPP是否已經被定義(是否在同一文件中或在另一文件)。如果是的話,那麼使用現有的MYAPP全域物件,否則,新增一個名為MYAPP的空物件用來封裝方法,函式,變數和物件。
我們也可以新增子命名空間:
// 子命名空間
MYAPP.event = {};
下面是用於新增命名空間和增加變數,函式和方法的程式碼寫法:
// 給普通方法和屬性新增一個叫做MYAPP.commonMethod的容器
MYAPP.commonMethod = {
regExForName: "", // 定義名字的正則驗證
regExForPhone: "", // 定義電話的正則驗證
validateName: function(name){
// 對名字name做些操作,你可以通過使用「this.regExForname」
// 訪問regExForName變數
},
validatePhoneNo: function(phoneNo){
// 對電話號碼做操作
}
}
// 物件和方法一起申明
MYAPP.event = {
addListener: function(el, type, fn) {
// 程式碼
},
removeListener: function(el, type, fn) {
// 程式碼
},
getEvent: function(e) {
// 程式碼
}
// 還可以增加其他的屬性和方法
}
//使用addListner方法的寫法:
MYAPP.event.addListener("yourel", "type", callback);
標準內置物件
JavaScript有包括在其核心的幾個物件,例如,Math,Object,Array和String物件。下面的例子示範了如何使用Math物件的random()方法來獲得一個隨機數。
console.log(Math.random());
注意:T這裡和接下來的例子都假設名為 console.log 的方法全域有定義。console.log 實際上不是 JavaScript 自帶的。
查看 JavaScript 參考:全域物件 瞭解 JavaScript 內置物件的列表。
JavaScript 中的每個物件都是 Object 物件的範例且繼承它所有的屬性和方法。
自定義物件
類
JavaScript是一種基於原型的語言,它沒類的聲明語句,比如C+ +或Java中用的。這有時會對習慣使用有類申明語句語言的程式員產生困擾。相反,JavaScript可用方法作類。定義一個類跟定義一個函式一樣簡單。在下面的例子中,我們定義了一個新類Person。
function Person() { }
// 或
var Person = function(){ }
物件(類的範例)
我們使用 new obj 新增物件 obj 的新範例, 將結果(obj 類型)賦值給一個變數方便稍後呼叫。
在下面的示例中,我們定義了一個名為Person的類,然後我們新增了兩個Person的範例(person1 and person2).
function Person() { }
var person1 = new Person();
var person2 = new Person();
注意:有一種新增的新增未初始化範例的範例化方法,請參考 Object.create 。
構造器
在範例化時構造器被呼叫 (也就是物件範例被新增時)。構造器是物件中的一個方法。 在JavaScript,中函式就可以作為構造器使用,因此不需要特別地定義一個構造器方法. 每個聲明的函式都可以在範例化後被呼叫執行
構造器常用於給物件的屬性賦值或者為呼叫函式做準備。 在本文的後面描述了類中方法既可以在定義時增加,也可以在使用前增加。
在下面的示例中, Person類範例化時構造器呼叫一個 alert函式。
function Person() {
alert('Person instantiated');
}
var person1 = new Person();
var person2 = new Person();
屬性 (物件屬性)
屬性就是 類中包含的變數;每一個物件範例有若干個屬性. 為了正確的繼承,屬性應該被定義在類的原型屬性 (函式)中。
可以使用 關鍵字 this呼叫類中的屬性, this是對當前物件的引用。 從外部存取(讀/寫)其屬性的語法是: InstanceName.Property; 這與C++,Java或者許多其他語言中的語法是一樣的 (在類中語法 this.Property 常用於set和get屬性值)
在下面的示例中,我們為定義Person類定義了一個屬性 firstName 並在範例化時賦初值。
function Person(firstName) {
this.firstName = firstName;
alert('Person instantiated');
}
var person1 = new Person('Alice');
var person2 = new Person('Bob');
// Show the firstName properties of the objects
alert('person1 is ' + person1.firstName); // alerts "person1 is Alice"
alert('person2 is ' + person2.firstName); // alerts "person2 is Bob"
方法(物件屬性)
方法與屬性很相似, 不同的是:一個是函式,另一個可以被定義為函式。 呼叫方法很像存取一個屬性, 不同的是add () 在方法名後面很可能帶著參數. 為定義一個方法, 需要將一個函式賦值給類的 prototype 屬性; 這個賦值給函式的名稱就是用來給物件在外部呼叫它使用的。
在下面的示例中,我們給Person類定義了方法 sayHello(),並呼叫了它.
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.sayHello = function() {
alert("Hello, I'm " + this.firstName);
};
var person1 = new Person("Alice");
var person2 = new Person("Bob");
// call the Person sayHello method.
person1.sayHello(); // alerts "Hello, I'm Alice"
person2.sayHello(); // alerts "Hello, I'm Bob"
在JavaScript中方法通常是一個綁定到物件中的普通函式, 這意味著方法可以在其所在context之外被呼叫。 思考下面示例中的程式碼:
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.sayHello = function() {
alert("Hello, I'm " + this.firstName);
};
var person1 = new Person("Alice");
var person2 = new Person("Bob");
var helloFunction = person1.sayHello;
person1.sayHello(); // alerts "Hello, I'm Alice"
person2.sayHello(); // alerts "Hello, I'm Bob"
helloFunction(); // alerts "Hello, I'm undefined" (or fails
// with a TypeError in strict mode)
alert(helloFunction === person1.sayHello); // alerts true
alert(helloFunction === Person.prototype.sayHello); // alerts true
helloFunction.call(person1); // alerts "Hello, I'm Alice"
如上例所示, 所有指向sayHello函式的引用 ,包括 person1, Person.prototype, 和 helloFunction 等, 均引用了相同的函式.
在呼叫函式的過程中,this的值取決於我們怎麼樣呼叫函式. 在通常情況下,我們通過一個表達式person1.sayHello()來呼叫函式:即從一個物件的屬性中得到所呼叫的函式。此時this被設置為我們取得函式的物件(即person1)。這就是為什麼person1.sayHello() 使用了姓名「Alice」而person2.sayHello()使用了姓名「bob」的原因。
然而我們使用不同的呼叫方法時, this的值也就不同了。當從變數 helloFunction()中呼叫的時候, this就被設置成了全域物件 (在瀏覽器中即window)。由於該物件 (非常可能地) 沒有firstName 屬性, 我們得到的結果便是"Hello, I'm undefined". (這是鬆散模式下的結果, 在 嚴格模式中,結果將不同(此時會產生一個error)。 但是為了避免混淆,我們在這裡不涉及細節) 。另外,我們可以像上例末尾那樣,使用Function#call (或者Function#apply)顯式的設置this的值。
更多有關訊息請參考 Function#call and Function#apply
繼承
新增一個或多個類的專門版本類方式稱為繼承(Javascript只支持單繼承)。 新增的專門版本的類通常叫做子類,另外的類通常叫做父類。 在Javascript中,繼承通過賦予子類一個父類的範例並專門化子類來實現。在現代瀏覽器中你可以使用 Object.create 實現繼承.
JavaScript 並不檢測子類的 prototype.constructor (見 Object.prototype), 所以我們必須手動申明它.
在下面的例子中, 我們定義了 Student類作為 Person類的子類. 之後我們重定義了sayHello() 方法並增加了 sayGoodBye() 方法.
// 定義Person構造器
function Person(firstName) {
this.firstName = firstName;
}
// 在Person.prototype中加入方法
Person.prototype.walk = function(){
alert("I am walking!");
};
Person.prototype.sayHello = function(){
alert("Hello, I'm " + this.firstName);
};
// 定義Student構造器
function Student(firstName, subject) {
// 呼叫父類構造器, 確保(使用Function#call)"this" 在呼叫過程中設置正確
Person.call(this, firstName);
// 初始化Student類特有屬性
this.subject = subject;
};
// 建立一個由Person.prototype繼承而來的Student.prototype物件.
// 注意: 常見的錯誤是使用 "new Person()"來建立Student.prototype.
// 這樣做的錯誤之處有很多, 最重要的一點是我們在範例化時
// 不能賦予Person類任何的FirstName參數
// 呼叫Person的正確位置如下,我們從Student中來呼叫它
Student.prototype = Object.create(Person.prototype); // See note below
// 設置"constructor" 屬性指向Student
Student.prototype.constructor = Student;
// 更換"sayHello" 方法
Student.prototype.sayHello = function(){
alert("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + ".");
};
// 加入"sayGoodBye" 方法
Student.prototype.sayGoodBye = function(){
alert("Goodbye!");
};
// 測試範例:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk(); // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"
// Check that instanceof works correctly
alert(student1 instanceof Person); // true
alert(student1 instanceof Student); // true
對於「Student.prototype = Object.create(Person.prototype);」這一行,在不支持 Object.create方法的老JavaScript引擎中,可以使用一個"polyfill"(又名"shim",查看文章鏈接),或者使用一個function來獲得相同的返回值,就像下面:
function createObject(proto) {
function ctor() { }
ctor.prototype = proto;
return new ctor();
}
// Usage:
Student.prototype = createObject(Person.prototype);
更多相關訊息請參考 Object.create,連接中還有一個老JavaScript引擎的兼容方案(shim)。
封裝
在上一個例子中,Student類雖然不需要知道Person類的walk()方法是如何實現的,但是仍然可以使用這個方法;Student類不需要明確地定義這個方法,除非我們想改變它。 這就叫做封裝,對於所有繼承自父類的方法,只需要在子類中定義那些你想改變的即可。
抽象
抽象是允許模擬工作問題中通用部分的一種機制。這可以通過繼承(具體化)或組合來實現。
JavaScript通過繼承實現具體化,通過讓類的範例是其他物件的屬性值來實現組合。
JavaScript Function 類繼承自Object類(這是典型的具體化) 。Function.prototype的屬性是一個Object範例(這是典型的組合)。
var foo = function(){};
alert( 'foo is a Function: ' + (foo instanceof Function) ); // alerts "foo is a Function: true"
alert( 'foo.prototype is an Object: ' + (foo.prototype instanceof Object) ); // alerts "foo.prototype is an Object: true"
多型
就像所有定義在原型屬性內部的方法和屬性一樣,不同的類可以定義具有相同名稱的方法;方法是作用於所在的類中。並且這僅在兩個類不是父子關係時成立(繼承鏈中,一個類不是繼承自其他類)。
注意
本文中所展示的物件導向編程技術不是唯一的實現方式,在JavaScript中物件導向的實現是非常靈活的。
同樣的,文中展示的技術沒有使用任何語言hacks,它們也沒有模仿其他語言的物件理論實現。
JavaScript中還有其他一些更加先進的物件導向技術,但這些都超出了本文的介紹範圍。
留言
張貼留言