DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> 深入理解JavaScript系列(17):面向對象編程之概論詳細介紹
深入理解JavaScript系列(17):面向對象編程之概論詳細介紹
編輯:JavaScript基礎知識     

介紹

在本篇文章,我們考慮在ECMAScript中的面向對象編程的各個方面(雖然以前在許多文章中已經討論過這個話題)。我們將更多地從理論方面看這些問題。 特別是,我們會考慮對象的創建算法,對象(包括基本關系 - 繼承)之間的關系是如何,也可以在討論中使用(我希望將消除之前對於JavaScript中OOP的一些概念歧義)。

英文原文:http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/

概論、范式與思想

在進行ECMAScript中的OOP技術分析之前,我們有必要掌握一些OOP基本的特征,並澄清概論中的主要概念。

ECMAScript支持包括結構化、面向對象、函數式、命令式等多種編程方式,某些情況下還支持面向方面編程;但本文是討論面向對象編程,所以來給出ECMAScript中面向對象編程的定義:

ECMAScript是基於原型實現的面向對象編程語言。
基於原型的OOP和基於靜態類的方式直接有很多差異。 讓我們一起來看看他們直接詳細的差異。

基於類特性和基於原型

注意,在前面一句很重要的一點已經指出的那樣-完全基於靜態類。 隨著“靜態”一詞,我們了解靜態對象和靜態類,強類型(雖然不是必需的)。

關於這種情況,很多論壇上的文檔都有強調這是他們反對將在JavaScript裡將“類與原型”進行比較的主要原因,盡管他們在實現上的有所不同(例如基於動態類的Python和Ruby)不是太反對的重點(某些條件寫,盡管思想上有一定不同,但JavaScript沒有變得那麼另類),但他們反對的重點是靜態類和動態原型(statics + classes vs. dynamics + prototypes),確切地說,一個靜態類(例如:C + +,JAVA)和他的屬下及方法定義的機制可以讓我們看到它和基於原型實現的准確區別。

但是,讓我們來一個一個列舉一下。 讓我們考慮總則和這些范式的主要概念。

基於靜態類

在基於類的模型中,有個關於類和實例的概念。 類的實例也常常被命名為對象或范例 。

類與對象

類代表了一個實例(也就是對象)的抽象。在這方面有點像數學,但我們一把稱之為類型(type)或分類(classification)。

例如(這裡和下面的例子都是偽代碼):
復制代碼 代碼如下:
C = Class {a, b, c} // 類C, 包括特性a, b, c

實例的特點是:屬性(對象描述 )和方法(對象活動)。特性本身也可視為對象:即屬性是否可寫的,可配置,可設置的(getter/setter)等。因此,對象存儲了狀態 (即在一個類中描述的所有屬性的具體值),類為他們的實例定義了嚴格不變的結構(屬性)和嚴格不變的行為(方法)。
復制代碼 代碼如下:
C = Class {a, b, c, method1, method2}
 
c1 = {a: 10, b: 20, c: 30} // 類C是實例:對象с1
c2 = {a: 50, b: 60, c: 70} // 類C是實例:對象с2,擁有自己的狀態(也就是屬性值)

層次繼承

為了提高代碼重用,類可以從一個擴展為另一個,在加上額外的信息。 這種機制被稱為(分層)繼承 。
復制代碼 代碼如下:
D = Class extends C = {d, e} // {a, b, c, d, e}
d1 = {a: 10, b: 20, c: 30, d: 40, e: 50}

在類的實例上調用方的時候,通常會現在原生類本書就查找該方法,如果沒找到就到直接父類去查找,如果還沒找到,就到父類的父類去查找(例如嚴格的繼承鏈上),如果查到繼承的頂部還沒查到,那結果就是:該對象沒有類似的行為,也沒辦法獲取結果。
復制代碼 代碼如下:
d1.method1() // D.method1 (no) -> C.method1 (yes)
d1.method5() // D.method5 (no) -> C.method5 (no) -> no result

與在繼承裡方法不復制到一個子類相比,屬性總是被復雜到子類裡的。 我們可以看到子類D繼承自父類C類:屬性a,b,c是復制過去了,D的結構是{a, b, c, d, e} } 。然而,方法{method1, method2}是沒有復制過去,而是繼承過去的。 因此,也就是說如果一個很深層次的類有一些對象根本不需要的屬性的話,那子類也有擁有這些屬性。

基於類的關鍵概念

因此,我們有如下關鍵概念:

1.創建一個對象之前,必須聲明類,首先有必要界定其類
2.因此,該對象將由抽象成自身“象形和相似性”(結構和行為)的類裡創建
3.方法是通過了嚴格的,直接的,一成不變的繼承鏈來處理
4.子類包含了繼承鏈中所有的屬性(即使其中的某些屬性是子類不需要的);
5.創建類實例,類不能(因為靜態模型)來改變其實例的特征(屬性或方法);
6.實例(因為嚴格的靜態模型)除了有該實例所對應類裡聲明的行為和屬性以外,是不能額外的行為或屬性的。

讓我們看看在JavaScript裡如何替代OOP模型,也就是我們所建議的基於原型的OOP。

基於原型
這裡的基本概念是動態可變對象。轉換(完整轉換,不僅包括值,還包括特性)和動態語言有直接關系。下面這樣的對象可以獨立存儲他們所有的特性(屬性,方法)而不需要的類。
復制代碼 代碼如下:
object = {a: 10, b: 20, c: 30, method: fn};
object.a; // 10
object.c; // 30
object.method();

此外,由於動態的,他們可以很容易地改變(添加,刪除,修改)自己的特性:
復制代碼 代碼如下:
object.method5 = function () {...}; // 添加新方法
object.d = 40; // 添加新屬性 "d"
delete object.c; // 刪除屬性 "с"
object.a = 100; // 修改屬性 "а"
 
// 結果是: object: {a: 100, b: 20, d: 40, method: fn, method5: fn};

也就是說,在賦值的時候,如果某些特性不存在,則創建它並且將賦值與它進行初始化,如果它存在,就只是更新。

在這種情況下,代碼重用不是通過擴展類來實現的,(請注意,我們沒有說類沒辦法改變,因為這裡根本沒有類的概念),而是通過原型來實現的。

原型是一個對象,它是用來作為其他對象的原始copy,或者如果一些對象沒有自己的必要特性,原型可以作為這些對象的一個委托而當成輔助對象。

基於委托

任何對象都可以被用來作為另一個對象的原型對象,因為對象可以很容易地在運行時改變它的原型動態。

注意,目前我們正在考慮的是概論而不是具體實現,當我們在ECMAScript中討論具體實現時,我們將看到他們自身的一些特點。

例(偽代碼):
復制代碼 代碼如下:
x = {a: 10, b: 20};
y = {a: 40, c: 50};
y.[[Prototype]] = x; // x是y的原型
 
y.a; // 40, 自身特性
y.c; // 50, 也是自身特性
y.b; // 20 – 從原型中獲取: y.b (no) -> y.[[Prototype]].b (yes): 20
 
delete y.a; // 刪除自身的"а"
y.a; // 10 – 從原型中獲取
 
z = {a: 100, e: 50}
y.[[Prototype]] = z; // 將y的原型修改為z
y.a; // 100 – 從原型z中獲取
y.e // 50, 也是從從原型z中獲取
 
z.q = 200 // 添加新屬性到原型上
y.q // 修改也適用於y

這個例子展示了原型作為輔助對象屬性的重要功能和機制,就像是要自己的屬性一下,和自身屬性相比,這些屬性是委托屬性。這個機制被稱為委托,並且基於它的原型模型是一個委托的原型(或基於委托的原型 ) 。引用的機制在這裡稱為發送信息到對象上,如果這個對象得不到響應就會委托給原型來查找(要求它嘗試響應消息)。

在這種情況下的代碼重用被稱為基於委托的繼承或基於原型的繼承。由於任何對象可以當成原型,也就是說原型也可以有自己的原型。 這些原型連接在一起形成一個所謂的原型鏈。 鏈也像靜態類中分層次的,但是它可以很容易地重新排列,改變層次和結構。
復制代碼 代碼如下:
x = {a: 10}
 
y = {b: 20}
y.[[Prototype]] = x
 
z = {c: 30}
z.[[Prototype]] = y
 
z.a // 10
 
// z.a 在原型鏈裡查到:
// z.a (no) ->
// z.[[Prototype]].a (no) ->
// z.[[Prototype]].[[Prototype]].a (yes): 10

如果一個對象和它的原型鏈不能響應消息發送,該對象可以激活相應的系統信號,可能是由原型鏈上其它的委托進行處理。

該系統信號,在許多實現裡都是可用的,包括基於括動態類的系統:Smalltalk中的#doesNotUnderstand,Ruby中的​​method_missing;Python中的__getattr__,PHP中的__call;和ECMAScript中的__noSuchMethod__實現,等等。

例(SpiderMonkey的ECMAScript的實現):
復制代碼 代碼如下:
var object = {
 
  // catch住不能響應消息的系統信號
  __noSuchMethod__: function (name, args) {
    alert([name, args]);
    if (name == 'test') {
      return '.test() method is handled';
    }
    return delegate[name].apply(this, args);
  }
 
};
 
var delegate = {
  square: function (a) {
    return a * a;
  }
};
 
alert(object.square(10)); // 100
alert(object.test()); // .test() method is handled

也就是說,基於靜態類的實現,在不能響應消息的情況下,得出的結論是:目前的對象不具有所要求的特性,但是如果嘗試從原型鏈裡獲取,依然可能得到結果,或者該對象經過一系列變化以後擁有該特性。

關於ECMAScript,具體的實現就是:使用基於委托的原型。 然而,正如我們將從規范和實現裡看到的,他們也有自身的特性。

Concatenative模型

老實說,有必要在說句話關於另外一種情況(盡快在ECMASCript沒有用到):當原型從其它對象復雜原來代替原生對象這種情況。這種情況代碼重用是在對象創建階段對一個對象的真正復制(克隆)而不是委托。這種原型被稱為concatenative原型。復制對象所有原型的特性,可以進一步完全改變其屬性和方法,同樣作為原型可以改變自己(在基於委托的模型中,這個改變不會改變現有存在的對象行為,而是改變它的原型特性)。 這種方法的優點是可以減少調度和委托的時間,而缺點是內存使用率搞。

Duck類型

回來動態弱類型變化的對象,與基於靜態類的模型相比,檢驗它是否可以做這些事和對象有什麼類型(類)無關,而是是否能夠相應消息有關(即在檢查以後是否有能力做它是必須的) 。

例如:
復制代碼 代碼如下:
// 在基於靜態來的模型裡
if (object instanceof SomeClass) {
  // 一些行為是運行的
}
 
// 在動態實現裡
// 對象在此時是什麼類型並不重要
// 因為突變、類型、特性可以自由重復的轉變。
// 重要的對象是否可以響應test消息
if (isFunction(object.test)) // ECMAScript
 
if object.respond_to?(:test) // Ruby
 
if hasattr(object, 'test'): // Python

這就是所謂的Dock類型 。 也就是說,物體在check的時候可以通過自己的特性來識別,而不是對象在層次結構中的位置或他們屬於任何具體類型。

基於原型的關鍵概念

讓我們來看一下這種方式的主要特點:

1.基本概念是對象
2.對象是完全動態可變的(理論上完全可以從一個類型轉化到另一個類型)
3.對象沒有描述自己的結構和行為的嚴格類,對象不需要類
4.對象沒有類類但可以可以有原型,他們如果不能響應消息的話可以委托給原型
5.在運行時隨時可以改變對象的原型;
6.在基於委托的模型中,改變原型的特點,將影響到與該原型相關的所有對象;
7.在concatenative原型模型中,原型是從其他對象克隆的原始副本,並進一步成為完全獨立的副本原件,原型特性的變換不會影響從它克隆的對象
8.如果不能響應消息,它的調用者可以采取額外的措施(例如,改變調度)
9.對象的失敗可以不由它們的層次和所屬哪個類來決定,而是由當前特性來決定

不過,還有一個模型,我們也應該考慮。

基於動態類

我們認為,在上面例子裡展示的區別“類VS原型 ”在這個基於動態類的模型中不是那麼重要,(尤其是如果原型鏈是不變的,為更准確區分,還是有必要考慮一個靜態類)。 作為例子,它也可以使用Python或Ruby(或其他類似的語言)。 這些語言都使用基於動態類的范式。 然而,在某些方面,我們是可以看到基於原型實現的某些功能。

在下面例子中,我們可以看到僅僅是基於委托的原型,我們可以放大一個類(原型),從而影響到所有與這個類相關的對象,我們也可以在運行時動態地改變這個對象的類(為委托提供一個新對象)等等。

復制代碼 代碼如下:
# Python
 
class A(object):
 
    def __init__(self, a):
        self.a = a
 
    def square(self):
        return self.a * self.a
 
a = A(10) # 創建實例
print(a.a) # 10
 
A.b = 20 # 為類提供一個新屬性
print(a.b) # 20 – 可以在"a"實例裡訪問到
 
a.b = 30 # 創建a自身的屬性
print(a.b) # 30
 
del a.b # 刪除自身的屬性
print(a.b) # 20 - 再次從類裡獲取(原型)
 
# 就像基於原型的模型
# 可以在運行時改變對象的原型
 
class B(object): # 空類B
    pass
 
b = B() # B的實例
 
b.__class__ = A # 動態改變類(原型)
 
b.a = 10 # 創建新屬性
print(b.square()) # 100 - A類的方法這時候可用
 
# 可以顯示刪除類上的引用
del A
del B
 
# 但對象依然有隱式的引用,並且這些方法依然可用
print(b.square()) # 100
 
# 但這時候不能再改變類了
# 這是實現的特性
b.__class__ = dict # error

Ruby中的實現也是類似的:也使用了完全動態的類(順便說一下在當前版本的Python中,與Ruby和ECMAScript的對比,放大類(原型)不行的),我們可以徹底改變對象(或類)的特性(在類上添加方法/屬性,而這些變化會影響已經存在的對象),但是,它不能的動態改變一個對象的類。

但是,這篇文章不是專門針對Python和Ruby的,因此我們不多說了,我們來繼續討論ECMAScript本身。

但在此之前,我們還得再看一下在一些OOP裡有的“語法糖”,因為很多之前關於JavaScript的文章往往會文這些問題。

本節唯一需要注意的錯誤句子是:“JavaScript不是類,它有原型,可以代替類”。 非常有必要知道並非所有基於類的實現都是完全不一樣的,即便我們可能會說“JavaScript是不同的”,但也有必要考慮(除了“類”的概念)還有其他相關的特性呢。

各種OOP實現的其它特性

本節我們簡要介紹一下其它特性和各種OOP實現中關於代碼重用的方式,也包括ECMAScript中的OOP實現。 原因是,之前出現的關於JavaScript中關於OOP的實現是有一些習慣性的思維限制,唯一主要的要求是,應該在技術上和思想上加以證明。不能說沒發現和其它OOP實現裡的語法糖功能,就草率認為JavaScript不是不是純粹的OOP語言,這是不對滴。

多態

在ECMAScript中對象有幾種含義的多態性。

例如,一個函數可以應用於不同的對象,就像原生對象的特性(因為這個值在進入執行上下文時確定的):
復制代碼 代碼如下:
function test() {
  alert([this.a, this.b]);
}
 
test.call({a: 10, b: 20}); // 10, 20
test.call({a: 100, b: 200}); // 100, 200
 
var a = 1;
var b = 2;
 
test(); // 1, 2

不過,也有例外:Date.prototype.getTime()方法,根據標准這個值總是應該有一個日期對象,否則就會拋出異常。
復制代碼 代碼如下:
alert(Date.prototype.getTime.call(new Date())); // time
alert(Date.prototype.getTime.call(new String(''))); // TypeError

所謂函數定義時的參數多態性也就等價於所有數據類型,只不過接受多態性參數(例如數組的.sort排序方法和它的參數——多態的排序功能)。順便說一下,上面的例子也可以被視為是一種參數多態性。

原型裡方法可以被定義為空,所有創建的對象應重新定義(實現)該方法(即“一個接口(簽名),多個實現”)。

多態性和我們上面提到的Duck類型是有關的:即對象的類型和在層次結構中的位置不是那麼重要,但如果它有所有必要的特征,它可以很容易地接受(即通用接口很重要,實現則可以多種多樣)。

封裝

關於封裝,往往會有錯誤的看法。本節我們討論一下一些OOP實現裡的語法糖——也就是眾所周知的修飾符:在這種情況下,我們將討論一些OOP實現便捷的“糖” -眾所周知的修飾符:private,protected和public(或者稱為對象的訪問級別或訪問修飾符)。

在這裡我要提醒一下封裝的主要目的:封裝是一個抽象的增加,而不是選拔個直接往你的類裡寫入一些東西的隱藏“惡意黑客”。

這是一個很大的錯誤:為了隱藏使用隱藏。

訪問級別(private,protected和public),為了方便編程在很多面向對象裡都已經實現了(真的是非常方便的語法糖),更抽象地描述和構建系統。

這些可以在一些實現裡看出(如已經提到的Python和Ruby)。一方面(在Python中),這些__private _protected屬性(通過下劃線這個命名規范),從外部不可訪問。 另一方面,Python可以通過特殊的規則從外部訪問(_ClassName__field_name)。

復制代碼 代碼如下:
class A(object):
 
    def __init__(self):
      self.public = 10
      self.__private = 20
 
    def get_private(self):
        return self.__private
 
# outside:
 
a = A() # A的實例
 
print(a.public) # OK, 30
print(a.get_private()) # OK, 20
print(a.__private) # 失敗,因為只能在A裡可用
 
# 但在Python裡,可以通過特殊規則來訪問
 
print(a._A__private) # OK, 20

在Ruby裡:一方面有能力來定義private和protected的特性,另一方面,也有特殊的方法( 例如instance_variable_get,instance_variable_set,send等)獲取封裝的數據。

復制代碼 代碼如下:
class A
 
  def initialize
    @a = 10
  end
 
  def public_method
    private_method(20)
  end
 
private
 
  def private_method(b)
    return @a + b
  end
 
end
 
a = A.new # 新實例
 
a.public_method # OK, 30
 
a.a # 失敗, @a - 是私有的實例變量
 
# "private_method"是私有的,只能在A類裡訪問
 
a.private_method # 錯誤
 
# 但是有特殊的元數據方法名,可以獲取到數據
 
a.send(:private_method, 20) # OK, 30
a.instance_variable_get(:@a) # OK, 10

最主要的原因是,程序員自己想要獲得的封裝(請注意,我特別不使用“隱藏”)的數據。 如果這些數據會以某種方式不正確地更改或有任何錯誤,則全部責任都是程序員,但不是簡單的“拼寫錯誤”或“隨便改變某些字段”。 但如果這種情況很頻繁,那就是很不好的編程習慣和風格 ,因為通常值用公共的API來和對象“交談”。

重復一下,封裝的基本目的是一個從輔助數據的用戶中抽象出來,而不是一個防止黑客隱藏數據。 更嚴重的,封裝不是用private修飾數據而達到軟件安全的目的。

封裝輔助對象(局部),我們用最小的代價、本地化和預測性變化來問為公共接口的行為變化提供可行性,這也正是封裝的目的。

另外setter方法​​的重要目的是抽象復雜的計算。 例如,element.innerHTML這個setter——抽象的語句——“現在這個元素內的HTML是如下內容”,而在 innerHTML屬性的setter函數將難以計算和檢查。 在這種情況下,問題大多涉及到抽象 ,但封裝也會發生。

封裝的概念不僅僅只與OOP相關。 例如,它可以是一個簡單的功能,只封裝了各種計算,使得其抽象(沒有必要讓用戶知道,例如函數Math.round(... ...)是如何實現的,用戶只是簡單地調用它)。 它是一種封裝,注意,我沒有說他是“private, protected和public”。

ECMAScript規范的當前版本,沒有定義private, protected和public修飾符。

然而,在實踐中是有可能看到有些東西被命名為“模仿JS封裝”。 一般該上下文的目的是(作為一個規則,構造函數本身)使用。 不幸的是,經常實施這種“模仿”,程序員可以產生偽絕對非抽象的實體設置“getter / setter方法”(我再說一遍,它是錯誤的):
復制代碼 代碼如下:
function A() {
 
  var _a; // "private" a
 
  this.getA = function _getA() {
    return _a;
  };
 
  this.setA = function _setA(a) {
    _a = a;
  };
 
}
 
var a = new A();
 
a.setA(10);
alert(a._a); // undefined, "private"
alert(a.getA()); // 10

因此,每個人都明白,對於每個創建的對象,對於的getA/setA方法也創建了,這也是導致內存增加的原因(和原型定義相比)。 雖然,理論上第一種情況下可以對對象進行優化。

另外,一些JavaScript的文章經常提到“私有方法”的概念,注意:ECMA-262-3標准裡沒有定義任何關於“私有方法”的概念。

但是,某些情況下它可以在構造函數中創建,因為JS是意識形態的語言——對象是完全可變的並且有獨特的特性(在構造函數裡某些條件下,有些對象可以得到額外的方法,而其他則不行)。

此外,在JavaScript裡,如果還是把封裝曲解成為了不讓惡意黑客在某些自動寫入某些值的一種理解來代替使用setter方法,那所謂的“隱藏(hidden)”和“私有(private)”其實沒有很“隱藏”,,有些實現可以通過調用上下文到eval函數(可以在SpiderMonkey1.7上測試)在相關的作用域鏈(以及相應的所有變量對象)上獲取值)。
復制代碼 代碼如下:
eval('_a = 100', a.getA); // 或者a.setA,因為"_a"兩個方法的[[Scope]]上
a.getA(); // 100

或者,在實現中允許直接進入活動對象(例如Rhino),通過訪問該對象的相應屬性可以改變內部變量的值:
復制代碼 代碼如下:
// Rhino
var foo = (function () {
  var x = 10; // "private"
  return function () {
    print(x);
  };
})();
foo(); // 10
foo.__parent__.x = 20;
foo(); // 20

有時,在JavaScript裡通過在變量前加下劃線來達到“private”和“protected”數據的目的(但與Python相比,這裡只是命名規范):

var _myPrivateData = 'testString';
對於括號括住執行上下文是經常使用,但對於真正的輔助數據,則和對象沒有直接關聯,只是方便從外部的API抽象出來:
復制代碼 代碼如下:
(function () {
 
  // 初始化上下文
 
})();

多重繼承

多繼承是代碼重用改進的一個很方便的語法糖(如果我們一次能繼承一個類,為什麼不能一次繼承10個?)。 然而由於多重繼承有一些不足,才導致在實現中沒有流行起來。

ECMAScript不支持多繼承(即只有一個對象,可以用來作為一個直接原型),雖然其祖先自編程語言有這樣的能力。 但在某些實現中(如SpiderMonkey)使用__noSuchMethod__可以用於管理調度和委托來替代原型鏈。

Mixins

Mixins是代碼重用的一種便捷方式。 Mixins已建議作為多重繼承的替代品。 這些獨立的元素都可以與任何對象進行混合來擴展它們的功能(因此對象也可以混合多個Mixins)。 ECMA-262-3規范沒有定義“Mixins”的概念,但根據Mixins定義以及ECMAScript擁有動態可變對象,所以使用Mixins簡單地擴充特性是沒有障礙的。

典型的例子:
復制代碼 代碼如下:
// helper for augmentation
Object.extend = function (destination, source) {
  for (property in source) if (source.hasOwnProperty(property)) {
    destination[property] = source[property];
  }
  return destination;
};
 
var X = {a: 10, b: 20};
var Y = {c: 30, d: 40};
 
Object.extend(X, Y); // mix Y into X
alert([X.a, X.b, X.c, X.d]); 10, 20, 30, 40

請注意,我采取在ECMA-262-3中被提及過的引號中的這些定義(“mixin”,“mix”),在規范裡並沒有這樣的概念,而且不是mix而是常用的通過新特性去擴展對象。(Ruby中mixins的概念是官方定義的,mixin創建了一個包含模塊的一個引用來代替簡單復制該模塊的所有屬性到另外一個模塊上——事實上是:為委托創建一個額外的對象(原型))。

Traits

Traits和mixins的概念相似,但它有很多功能(根據定義,因為可以應用mixins所以不能包含狀態,因為它有可能導致命名沖突)。 根據ECMAScript說明Traits和mixins遵循同樣的原則,所以該規范沒有定義“Traits”的概念。

接口

在一些OOP中實現的接口和mixins及traits類似。然而,與mixins及traits相比,接口強制實現類必須實現其方法簽名的行為。

接口完全可以被視為抽象類。不過與抽象類相比(抽象類裡的方法可以只實現一部分,另外一部分依然定義為簽名),繼承只能是單繼承基類,但可以繼承多個接口,節約這個原因,可以接口(多個混合)可以看做是多繼承的替代方案。

ECMA-262-3標准既沒有定義“接口”的概念,也沒有定義“抽象類”的概念。 然而,作為模仿,它是可以由“空”的方法(或空方法中拋出異常,告訴開發人員這個方法需要被實現)的對象來實現。

對象組合

對象組合也是一個動態代碼重用技術之一。 對象組合不同於高靈活性的繼承,它實現了一個動態可變的委托。而這,也是基於委托原型的基本。 除了動態可變原型,該對象可以為委托聚合對象(創建一個組合作為結果——聚合 ),並進一步發送消息到對象上,委托到該委托上。這可以兩個以上的委托,因為它的動態特性決定著它可以在運行時改變。

已經提到的__noSuchMethod__例子是這樣,但也讓我們展示了如何明確地使用委托:

例如:

復制代碼 代碼如下:
var _delegate = {
  foo: function () {
    alert('_delegate.foo');
  }
};
 
var agregate = {
 
  delegate: _delegate,
 
  foo: function () {
    return this.delegate.foo.call(this);
  }
 
};
 
agregate.foo(); // delegate.foo
 
agregate.delegate = {
  foo: function () {
    alert('foo from new delegate');
  }
};
 
agregate.foo(); // foo from new delegate

這種對象關系稱為“has-a”,而集成是“is-a“的關系。

由於顯示組合的缺乏(與繼承相比的靈活性),增加中間代碼也是可以的。

AOP特性

作為面向方面的一個功能,可以使用function decorators。ECMA-262-3規格沒有明確定義的“function decorators”的概念(和Python相對,這個詞是在Python官方定義了)。 不過,擁有函數式參數的函數在某些方面是可以裝飾和激活的(通過應用所謂的建議):

最簡單的裝飾者例子:
復制代碼 代碼如下:
function checkDecorator(originalFunction) {
  return function () {
    if (fooBar != 'test') {
      alert('wrong parameter');
      return false;
    }
    return originalFunction();
  };
}
 
function test() {
  alert('test function');
}
 
var testWithCheck = checkDecorator(test);
var fooBar = false;
 
test(); // 'test function'
testWithCheck(); // 'wrong parameter'
 
fooBar = 'test';
test(); // 'test function'
testWithCheck(); // 'test function'

結論

在這篇文章,我們理清了OOP的概論(我希望這些資料已經對你有用了),下一章節我們將繼續面向對象編程之ECMAScript的實現 。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved