1.什麼是面向對象
我們先來說說什麼是對象,因為翻譯的原因,中文語境下我們很難了解“對象”的真正含義。事實上
Object
(對象)在英文中,是一切事物的總稱,這和面向對象程式設計的抽象思維有互通之處。
中文的“對象”卻沒有這樣的普适性,我們在學習程式設計的過程中,更多是把它當作一個專業名詞來了解。
但不論如何,我們應該認識到,對象并不是計算機領域憑空造出來的概念,它是順着人類思維模式産生的一
種抽象(于是面向對象程式設計也被認為是:更接近人類思維模式的一種程式設計範式)。
那麼,我們先來看看在人類思維模式下,對象究竟是什麼。
對象這一概念在人類的幼兒期形成,這遠遠早于我們程式設計邏輯中常用的值、過程等概念。在幼年期,我們總是先認識到某一個蘋果能吃(這裡的某一個蘋果就是一個對象),繼而認識到所有的蘋果都可以吃(這裡的所有蘋果,就是一個類),再到後來我們才能意識到三個蘋果和三個梨之間的聯系,進而産生數字“3”(值)的概念。
在《面向對象分析與設計》這本書中,Grady Booch替我們做了總結,他認為,從人類的認知角度來說,對象應該是下列事物之一:
- 一個可以觸摸或者可以看見的東西;
- 人的智力可以了解的東西;
- 可以指導思考或行動(進行想象或施加動作)的東西。
有了對象的自然定義後,我們就可以描述程式設計語言中的對象了。在不同的程式設計語言中,設計者也利用各種不同的語言特性來抽象描述對象,最為成功的流派是使用“類”的方式來描述對象,這誕生了諸如 C++、Java等流行的程式設計語言。
而
JavaScript
早年卻選擇了一個更為冷門的方式:原型。
然而很不幸,因為一些公司政治原因,
JavaScript
推出之時受管理層之命被要求模仿
Java
,是以,
JavaScript
創始人Brendan Eich在“原型運作時”的基礎上引入了
new
、
this
等語言特性,使之“看起來更像
Java
”。
在
ES6
出現之前,大量的
JavaScript
程式員試圖在原型體系的基礎上,把
JavaScript
變得更像是基于類的程式設計,進而産生了很多所謂的“架構”,比如
PrototypeJS
、
Dojo
。
事實上,它們成為了某種
JavaScript
的古怪方言,甚至産生了一系列互不相容的社群,顯然這樣做的收益是遠遠小于損失的。
如果我們從運作時角度來談論對象,就是在讨論
JavaScript
實際運作中的模型,這是由于任何代碼執行都必定繞不開運作時的對象模型。
不過,幸運的是,從運作時的角度看,可以不必受到這些“基于類的設施”的困擾,這是因為任何語言運作時類的概念都是被弱化的。
首先我們來了解一下JavaScript是如何設計對象模型的
2. JavaScript 對象的特征
對象有如下幾個特點。
- 對象具有唯一辨別性:即使完全相同的兩個對象,也并非同一個對象。
- 對象有狀态:對象具有狀态,同一對象可能處于不同狀态之下。
- 對象具有行為:即對象的狀态,可能因為它的行為産生變遷
我們先來看第一個特征,對象具有唯一辨別性。一般而言,各種語言的對象唯一辨別性都是用記憶體位址來展現的, 對象具有唯一辨別的記憶體位址,是以具有唯一的辨別。
是以,
JavaScript
程式員都知道,任何不同的
JavaScript
對象其實是互不相等的,我們可以看下面的代碼,
o1
和
o2
初看是兩個一模一樣的對象,但是列印出來的結果卻是
false
。
var o1 = { a: 1 };
var o2 = { a: 1 };
console.log(o1 == o2); // false
關于對象的第二個和第三個特征“狀态和行為”,不同語言會使用不同的術語來抽象描述它們,比如
C++
中稱它們為“成員變量”和“成員函數”,
Java
中則稱它們為“屬性”和“方法”。
在
JavaScript
中,将狀态和行為統一抽象為“屬性”,考慮到
JavaScript
中将函數設計成一種特殊對象,是以
JavaScript
中的行為和狀态都能用屬性來抽象。
下面這段代碼其實就展示了普通屬性和函數作為屬性的一個例子,其中
o
是對象,
d
是一個屬性,而函數
f
也是一個屬性,盡管寫法不太相同,但是對
JavaScript
來說,
d
和
f
就是兩個普通屬性。
var o = {
d: 1,
f() {
console.log(this.d);
}
}
是以,總結一句話來看,在
JavaScript
中,對象的狀态和行為其實都被抽象為了屬性。如果你用過
Java
,一定不要覺得奇怪,盡管設計思路有一定差别,但是二者都很好地表現了對象的基本特征:辨別性、狀态和行為。
在實作了對象基本特征的基礎上, 我認為,JavaScript中對象獨有的特色是:對象具有高度的動态性,這是因為JavaScript賦予了使用者在運作時為對象添改狀态和行為的能力。
我來舉個例子,比如,
JavaScript
允許運作時向對象添加屬性,這就跟絕大多數基于類的、靜态的對象設計完全不同。如果你用過
Java
或者其它别的語言,肯定會産生跟我一樣的感受。
下面這段代碼就展示了運作時如何向一個對象添加屬性,一開始我定義了一個對象
o
,定義完成之後,再添加它的屬性
b
,這樣操作是完全沒問題的。
var o = {a: 1};
o.b = 2;
console.log(o.a, o.b); //1 2
為了提高抽象能力,
JavaScript
的屬性被設計成比别的語言更加複雜的形式,它提供了資料屬性和通路器屬性(
getter/setter
)兩類。
3.javascript的兩類屬性
對
JavaScript
來說,屬性并非隻是簡單的名稱和值,
JavaScript
用一組特征(
attribute
)來描述屬性(
property
)。
3.1 資料屬性
它比較接近于其它語言的屬性概念。資料屬性具有四個特征
- value:就是屬性的值。
- writable:決定屬性能否被指派。
- enumerable:決定for in能否枚舉該屬性。
- configurable:決定該屬性能否被删除或者改變特征值。
我們通常用于定義屬性的代碼會産生資料屬性,其中的
writable
、
enumerable
、
configurable
都預設為
true
。我們可以使用内置函數
Object.getOwnPropertyDescripter
來檢視,如以下代碼所示:
var o = { a: 1 };
o.b = 2;
//a和b皆為資料屬性
Object.getOwnPropertyDescriptor(o,"a") // {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o,"b") // {value: 2, writable: true, enumerable: true, configurable: true}
我們在這裡使用了兩種文法來定義屬性,定義完屬性後,我們用
JavaScript
的API來檢視這個屬性,我們可以發現,這樣定義出來的屬性都是資料屬性,
writeable
、
enumerable
、
configurable
都是預設值為
true
。
如果我們要想改變屬性的特征,或者定義通路器屬性,我們可以使用
Object.defineProperty
,示例如下:
var o = { a: 1 };
Object.defineProperty(o, "b", {value: 2, writable: false, enumerable: false, configurable: true});
//a和b都是資料屬性, 但特征值變化了
Object.getOwnPropertyDescriptor(o,"a"); // {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o,"b"); // {value: 2, writable: false, enumerable: false, configurable: true}
o.b = 3;
console.log(o.b); // 2
這裡我們使用了
Object.defineProperty
來定義屬性,這樣定義屬性可以改變屬性的
writable
和
enumerable
。
我們同樣用
Object.getOwnPropertyDescriptor
來檢視,發現确實改變了
writable
和
enumerable
特征。因為
writable
特征為
false
,是以我們重新對
b
指派,
b
的值不會發生變化。
3.2 通路器屬性
四個特征:
- getter:函數或undefined,在取屬性值時被調用。
- setter:函數或undefined,在設定屬性值時被調用。
- enumerable:決定for in能否枚舉該屬性。
- configurable:決定該屬性能否被删除或者改變特征值。
通路器屬性使得屬性在讀和寫時執行代碼,它允許使用者在寫和讀屬性時,得到完全不同的值,它可以視為一種函數的文法糖。
在建立對象時,也可以使用
get
和
set
關鍵字來建立通路器屬性,代碼如下所示:
var o = { get a() { return 1 } };
console.log(o.a); // 1
通路器屬性跟資料屬性不同,每次通路屬性都會執行
getter
或者
setter
函數。這裡我們的
getter
函數傳回了
1
,是以
o.a
每次都得到
1
。
這樣,我們就了解了,實際上JavaScript 對象的運作時是一個“屬性的集合”,屬性以字元串或者
Symbol
為
key
,以資料屬性特征值或者通路器屬性特征值為
value
。
對象是一個屬性的索引結構(索引結構是一類常見的資料結構,我們可以把它了解為一個能夠以比較快的速度用
key
來查找
value
的字典)。我們以上面的對象
o
為例,你可以想象一下
“a
”是
key
。
{writable:true,value:1,configurable:true,enumerable:true}
是
value
。我們在前面的類型課程中,已經介紹了
Symbol
類型,能夠以
Symbol
為屬性名,這是
JavaScript
對象的一個特色。
講到了這裡,如果你了解了對象的特征,也就不難了解我開篇提出來的問題。
你甚至可以了解為什麼會有“JavaScript不是面向對象”這樣的說法了。這是由于JavaScript的對象設計跟目前主流基于類的面向對象差異非常大。
可事實上,這樣的對象系統設計雖然特别,但是JavaScript提供了完全運作時的對象系統,這使得它可以模仿多數面向對象程式設計範式(下一節課我們會給你介紹JavaScript中兩種面向對象程式設計的範式:基于類和基于原型),是以它也是正統的面向對象語言。
JavaScript語言标準也已經明确說明,JavaScript是一門面向對象的語言,我想标準中能這樣說,正是因為JavaScript的高度動态性的對象系統。
是以,我們應該在了解其設計思想的基礎上充分挖掘它的能力,而不是機械地模仿其它語言。
參考:winter老師 -JavaScript對象:面向對象還是基于對象?