天天看點

6.javascript對象:面向對象還是基于對象1.什麼是面向對象2. JavaScript 對象的特征3.javascript的兩類屬性

1.什麼是面向對象

我們先來說說什麼是對象,因為翻譯的原因,中文語境下我們很難了解“對象”的真正含義。事實上

Object

(對象)在英文中,是一切事物的總稱,這和面向對象程式設計的抽象思維有互通之處。

中文的“對象”卻沒有這樣的普适性,我們在學習程式設計的過程中,更多是把它當作一個專業名詞來了解。

但不論如何,我們應該認識到,對象并不是計算機領域憑空造出來的概念,它是順着人類思維模式産生的一

種抽象(于是面向對象程式設計也被認為是:更接近人類思維模式的一種程式設計範式)。

那麼,我們先來看看在人類思維模式下,對象究竟是什麼。

對象這一概念在人類的幼兒期形成,這遠遠早于我們程式設計邏輯中常用的值、過程等概念。在幼年期,我們總是先認識到某一個蘋果能吃(這裡的某一個蘋果就是一個對象),繼而認識到所有的蘋果都可以吃(這裡的所有蘋果,就是一個類),再到後來我們才能意識到三個蘋果和三個梨之間的聯系,進而産生數字“3”(值)的概念。

在《面向對象分析與設計》這本書中,Grady Booch替我們做了總結,他認為,從人類的認知角度來說,對象應該是下列事物之一:

  1. 一個可以觸摸或者可以看見的東西;
  2. 人的智力可以了解的東西;
  3. 可以指導思考或行動(進行想象或施加動作)的東西。

有了對象的自然定義後,我們就可以描述程式設計語言中的對象了。在不同的程式設計語言中,設計者也利用各種不同的語言特性來抽象描述對象,最為成功的流派是使用“類”的方式來描述對象,這誕生了諸如 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對象:面向對象還是基于對象?

繼續閱讀