在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 面向?qū)ο缶幊讨话憷碚?/span>
代碼復用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設計模式之狀態(tài)模式
JavaScript 核心(晉級高手必讀篇)
設計模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設計模式之中介者模式
設計模式之裝飾者模式
設計模式之模板方法
設計模式之外觀模式
強大的原型和原型鏈
設計模式之構造函數(shù)模式
揭秘命名函數(shù)表達式
深入理解J avaScript 系列(結局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設計模式之適配器模式
設計模式之組合模式
設計模式之命令模式
S.O.L.I.D 五大原則之單一職責 SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點
求值策略
閉包(Closures)
對象創(chuàng)建模式(上篇)
This? Yes,this!
設計模式之代理模式
變量對象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設計模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設計模式之迭代器模式
立即調(diào)用的函數(shù)表達式
設計模式之享元模式
設計模式之原型模式
根本沒有“JSON 對象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實現(xiàn)
全面解析 Module 模式
對象創(chuàng)建模式(下篇)
設計模式之職責鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設計模式之橋接模式
設計模式之策略模式
設計模式之觀察者模式
代碼復用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設計模式之工廠模式

面向?qū)ο缶幊讨话憷碚?/h1>

介紹

在本篇文章,我們考慮在 ECMAScript 中的面向?qū)ο缶幊痰母鱾€方面(雖然以前在許多文章中已經(jīng)討論過這個話題)。我們將更多地從理論方面看這些問題。 特別是,我們會考慮對象的創(chuàng)建算法,對象(包括基本關系 - 繼承)之間的關系是如何,也可以在討論中使用(我希望將消除之前對于 JavaScript 中 OOP 的一些概念歧義)。

概論、范式與思想

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

ECMAScript 支持包括結構化、面向?qū)ο?、函?shù)式、命令式等多種編程方式,某些情況下還支持面向方面編程;但本文是討論面向?qū)ο缶幊蹋詠斫o出 ECMAScript 中面向?qū)ο缶幊痰亩x:

ECMAScript 是基于原型實現(xiàn)的面向?qū)ο缶幊陶Z言。

基于原型的 OOP 和基于靜態(tài)類的方式直接有很多差異。 讓我們一起來看看他們直接詳細的差異。

基于類特性和基于原型

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

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

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

基于靜態(tài)類

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

類與對象

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

例如(這里和下面的例子都是偽代碼):

C = Class {a, b, c} // 類C, 包括特性a, b, c

實例的特點是:屬性(對象描述 )和方法(對象活動)。特性本身也可視為對象:即屬性是否可寫的,可配置,可設置的(getter/setter)等。因此,對象存儲了狀態(tài) (即在一個類中描述的所有屬性的具體值),類為他們的實例定義了嚴格不變的結構(屬性)和嚴格不變的行為(方法)。

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,擁有自己的狀態(tài)(也就是屬性值)

層次繼承

為了提高代碼重用,類可以從一個擴展為另一個,在加上額外的信息。 這種機制被稱為(分層)繼承 。

D = Class extends C = {d, e} // {a, b, c, d, e}
d1 = {a: 10, b: 20, c: 30, d: 40, e: 50}

在類的實例上調(diào)用方的時候,通常會現(xiàn)在原生類本書就查找該方法,如果沒找到就到直接父類去查找,如果還沒找到,就到父類的父類去查找(例如嚴格的繼承鏈上),如果查到繼承的頂部還沒查到,那結果就是:該對象沒有類似的行為,也沒辦法獲取結果。

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. 創(chuàng)建一個對象之前,必須聲明類,首先有必要界定其類;
  2. 因此,該對象將由抽象成自身“象形和相似性”(結構和行為)的類里創(chuàng)建;
  3. 方法是通過了嚴格的,直接的,一成不變的繼承鏈來處理;
  4. 子類包含了繼承鏈中所有的屬性(即使其中的某些屬性是子類不需要的);
  5. 創(chuàng)建類實例,類不能(因為靜態(tài)模型)來改變其實例的特征(屬性或方法);
  6. 實例(因為嚴格的靜態(tài)模型)除了有該實例所對應類里聲明的行為和屬性以外,是不能額外的行為或?qū)傩缘摹?/li>

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

基于原型

這里的基本概念是動態(tài)可變對象。轉(zhuǎn)換(完整轉(zhuǎn)換,不僅包括值,還包括特性)和動態(tài)語言有直接關系。下面這樣的對象可以獨立存儲他們所有的特性(屬性,方法)而不需要的類。

object = {a: 10, b: 20, c: 30, method: fn};
object.a; // 10
object.c; // 30
object.method();

此外,由于動態(tài)的,他們可以很容易地改變(添加,刪除,修改)自己的特性:

object.method5 = function () {...}; // 添加新方法
object.d = 40; // 添加新屬性 "d"
delete object.c; // 刪除屬性 "с"
object.a = 100; // 修改屬性 "а"
// 結果是: object: {a: 100, b: 20, d: 40, method: fn, method5: fn};

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

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

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

基于委托

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

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

例(偽代碼):

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

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

在這種情況下的代碼重用被稱為基于委托的繼承或基于原型的繼承。由于任何對象可以當成原型,也就是說原型也可以有自己的原型。 這些原型連接在一起形成一個所謂的原型鏈。 鏈也像靜態(tài)類中分層次的,但是它可以很容易地重新排列,改變層次和結構。

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

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

該系統(tǒng)信號,在許多實現(xiàn)里都是可用的,包括基于括動態(tài)類的系統(tǒng):Smalltalk中的 #doesNotUnderstand,Ruby 中的?? method_missing;Python 中的__getattr__,PHP 中的__call;和 ECMAScript 中的__noSuchMethod__實現(xiàn),等等。

例(SpiderMonkey 的 ECMAScript 的實現(xiàn)):

var object = {
  // catch住不能響應消息的系統(tǒng)信號
  __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

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

關于 ECMAScript,具體的實現(xiàn)就是:使用基于委托的原型。 然而,正如我們將從規(guī)范和實現(xiàn)里看到的,他們也有自身的特性。

Concatenative模型

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

Duck類型

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

例如:

// 在基于靜態(tài)來的模型里
if (object instanceof SomeClass) {
  // 一些行為是運行的
}
// 在動態(tài)實現(xiàn)里
// 對象在此時是什么類型并不重要
// 因為突變、類型、特性可以自由重復的轉(zhuǎn)變。
// 重要的對象是否可以響應test消息 
if (isFunction(object.test)) // ECMAScript
if object.respond_to?(:test) // Ruby
if hasattr(object, 'test'): // Python

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

基于原型的關鍵概念

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

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

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

基于動態(tài)類

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

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

\# Python
class A(object):
    def __init__(self, a):
        self.a = a
    def square(self):
        return self.a * self.a
a = A(10) # 創(chuàng)建實例
print(a.a) # 10
A.b = 20 # 為類提供一個新屬性
print(a.b) # 20 – 可以在"a"實例里訪問到
a.b = 30 # 創(chuàng)建a自身的屬性
print(a.b) # 30
del a.b # 刪除自身的屬性
print(a.b) # 20 - 再次從類里獲?。ㄔ停?\# 就像基于原型的模型
\# 可以在運行時改變對象的原型
class B(object): # 空類B
    pass
b = B() # B的實例
b.__class__ = A # 動態(tài)改變類(原型)
b.a = 10 # 創(chuàng)建新屬性
print(b.square()) # 100 - A類的方法這時候可用
\# 可以顯示刪除類上的引用
del A
del B
\# 但對象依然有隱式的引用,并且這些方法依然可用
print(b.square()) # 100
\# 但這時候不能再改變類了
\# 這是實現(xiàn)的特性
b.__class__ = dict # error

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

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

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

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

各種 OOP 實現(xiàn)的其它特性

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

多態(tài)

在 ECMAScript 中對象有幾種含義的多態(tài)性。

例如,一個函數(shù)可以應用于不同的對象,就像原生對象的特性(因為這個值在進入執(zhí)行上下文時確定的):

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()方法,根據(jù)標準這個值總是應該有一個日期對象,否則就會拋出異常。

alert(Date.prototype.getTime.call(new Date())); // time
alert(Date.prototype.getTime.call(new String(''))); // TypeError

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

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

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

封裝

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

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

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

訪問級別(private,protected 和 public),為了方便編程在很多面向?qū)ο罄锒家呀?jīng)實現(xiàn)了(真的是非常方便的語法糖),更抽象地描述和構建系統(tǒng)。

這些可以在一些實現(xiàn)里看出(如已經(jīng)提到的Python和Ruby)。一方面(在 Python 中),這些__private _protected 屬性(通過下劃線這個命名規(guī)范),從外部不可訪問。 另一方面,Python 可以通過特殊的規(guī)則從外部訪問(_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里,可以通過特殊規(guī)則來訪問
print(a._A__private) # OK, 20

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

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 # 錯誤
\# 但是有特殊的元數(shù)據(jù)方法名,可以獲取到數(shù)據(jù)
a.send(:private_method, 20) # OK, 30
a.instance_variable_get(:@a) # OK, 10

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

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

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

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

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

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

然而,在實踐中是有可能看到有些東西被命名為“模仿 JS 封裝”。 一般該上下文的目的是(作為一個規(guī)則,構造函數(shù)本身)使用。 不幸的是,經(jīng)常實施這種“模仿”,程序員可以產(chǎn)生偽絕對非抽象的實體設置“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

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

另外,一些 JavaScript 的文章經(jīng)常提到“私有方法”的概念,注意:ECMA-262-3 標準里沒有定義任何關于“私有方法”的概念。

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

此外,在 JavaScript 里,如果還是把封裝曲解成為了不讓惡意黑客在某些自動寫入某些值的一種理解來代替使用 setter 方法,那所謂的“隱藏(hidden)”和“私有(private)”其實沒有很“隱藏”,,有些實現(xiàn)可以通過調(diào)用上下文到 eval 函數(shù)(可以在 SpiderMonkey1.7 上測試)在相關的作用域鏈(以及相應的所有變量對象)上獲取值)。

eval('_a = 100', a.getA); // 或者a.setA,因為"_a"兩個方法的[[Scope]]上
a.getA(); // 100

或者,在實現(xiàn)中允許直接進入活動對象(例如 Rhino),通過訪問該對象的相應屬性可以改變內(nèi)部變量的值:

// Rhino
var foo = (function () {
  var x = 10; // "private"
  return function () {
    print(x);
  };
})();
foo(); // 10
foo.__parent__.x = 20;
foo(); // 20

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

var _myPrivateData = 'testString';

對于括號括住執(zhí)行上下文是經(jīng)常使用,但對于真正的輔助數(shù)據(jù),則和對象沒有直接關聯(lián),只是方便從外部的 API 抽象出來:

(function () {
  // 初始化上下文
})();

多重繼承

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

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

Mixins

Mixins 是代碼重用的一種便捷方式。 Mixins 已建議作為多重繼承的替代品。 這些獨立的元素都可以與任何對象進行混合來擴展它們的功能(因此對象也可以混合多個 Mixins)。 ECMA-262-3 規(guī)范沒有定義“Mixins”的概念,但根據(jù) Mixins 定義以及 ECMAScript 擁有動態(tài)可變對象,所以使用 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”),在規(guī)范里并沒有這樣的概念,而且不是 mix 而是常用的通過新特性去擴展對象。(Ruby 中 mixins 的概念是官方定義的,mixin 創(chuàng)建了一個包含模塊的一個引用來代替簡單復制該模塊的所有屬性到另外一個模塊上——事實上是:為委托創(chuàng)建一個額外的對象(原型))。

Traits

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

接口

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

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

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

對象組合

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

已經(jīng)提到的__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 規(guī)格沒有明確定義的“function decorators”的概念(和 Python 相對,這個詞是在 Python 官方定義了)。 不過,擁有函數(shù)式參數(shù)的函數(shù)在某些方面是可以裝飾和激活的(通過應用所謂的建議):

最簡單的裝飾者例子:

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 的概論(我希望這些資料已經(jīng)對你有用了),下一章節(jié)我們將繼續(xù)面向?qū)ο缶幊讨?ECMAScript 的實現(xiàn) 。