javascript是物件導向的,js每個內建物件都是從object衍生出來的,這樣就有繼承,多型和重構三個物件導向的特性,即使現在js是基於prototype的偽繼承,但是總體思想是一個物件導向的語言。
本教學操作環境:windows7系統、javascript1.8.5版、Dell G3電腦。
與其它的語言相比,JavaScript中的「物件」總是顯得不那麼合群。有些新人在學習JavaScript物件導向時,往往也會有疑惑:為什麼JavaScript(直到ES6)有物件的概念,但是卻沒有像其他的語言一樣,有類別的概念呢?為什麼在JavaScript物件裡可以自由加入屬性,而其他的語言卻不行?在
甚至在一些爭論中,有人強調,JavaScript並非“面向對象的語言”,而是“基於對象的語言”,這個說法一度流傳甚廣,而事實上,我至今遇到的持有這說法的人中,無一能夠回答「如何定義物件導向和基於物件」這個問題。
實際上,基於物件和物件導向兩個形容詞都出現在了JavaScript標準的各個版本當中。我們可以先看看JavaScript標準對基於物件的定義,這個定義的具體內容是:「語言和宿主的基礎設施由物件來提供,並且ECMAScript程式即是一系列互相通訊的物件集合」。這裡的意思根本不是表達弱化的物件導向的意思,反而是表達對象對於語言的重要性。
那麼,在本篇文章中,我會試著讓你去理解物件導向和JavaScript中的物件導向究竟是什麼。
我們先來說說什麼是對象,因為翻譯的原因,中文語境下我們很難理解「對象」的真正意義。事實上,Object(物件)在英文中,是一切事物的總稱,這和物件導向程式設計的抽象思考有互通之處。中文的「物件」卻沒有這樣的普適性,我們在學習程式設計的過程中,更多是把它當作一個專業名詞來理解。
但不論如何,我們應該認識到,物件並不是電腦領域憑空造出來的概念,它是順著人類思維模式產生的一種抽象(於是物件導向程式設計也被認為是:更接近人類思維模式的一種程式設計範式)。
那麼,我們先來看看在人類思維模式下,物件究竟是什麼。
物件這個概念在人類的幼兒期形成,這遠早於我們程式邏輯中常用的值、過程等概念。在幼年期,我們總是先認識到某一個蘋果能吃(這裡的某一個蘋果就是一個對象),繼而認識到所有的蘋果都可以吃(這裡的所有蘋果,就是一個類),再到後來我們才能意識到三個蘋果和三個梨之間的聯繫,進而產生數字「3」(值)的概念。
在《物件導向分析與設計》這本書中,Grady Booch替我們做了總結,他認為,從人類的認知角度來說,物件應該是下列事物之一:
一個可以觸摸或可以看見的東西;
人的智力可以理解的東西;
可以引導思考或行動(進行想像或施加動作)的東西。
有了物件的自然定義後,我們就可以描述程式語言中的物件了。在不同的程式語言中,設計者也利用各種不同的語言特性來抽象描述對象,最為成功的流派是使用「類別」的方式來描述對象,這誕生了諸如 C 、Java等流行的程式語言。而 JavaScript 早年卻選擇了一個更冷門的方式:原型(關於原型,我在下一篇文章會重點介紹,這裡你留個印象就可以了)。這是我在前面說它不合群的原因之一。
然而很不幸,因為一些公司政治原因,JavaScript推出之時受管理層之命被要求模仿Java,所以,JavaScript創始人Brendan Eich在「原型運行時」的基礎上引入了new、 this等語言特性,使其「看起來更像Java」。
在 ES6 出現之前,大量 JavaScript 程式設計師試圖在原型體系的基礎上,把JavaScript變得更像是基於類別的編程,進而產生了很多所謂的“框架”,例如PrototypeJS、Dojo。事實上,它們成為了某種JavaScript的古怪方言,甚至產生了一系列互不相容的社群,顯然這樣做的收益遠小於損失。
如果我們從運行時角度來談論對象,就是在討論JavaScript實際運行中的模型,這是由於任何代碼執行都必定繞不開運行時的對像模型,不過,幸運的是,從運行時的角度看,可以不必受到這些「基於類別的設施」的困擾,這是因為任何語言運行時類的概念都是被弱化的。
首先我們來了解JavaScript是如何設計物件模型的。
在我看來,無論我們使用什麼樣的程式語言,我們都先應該去理解物件的本質特徵(參考Grandy Booch《物件導向分析與設計》)。總結來看,對像有以下幾個特點。
物件具有唯一識別性:即使完全相同的兩個對象,也並非同一個對象。
物件有狀態:物件具有狀態,同一物件可能處於不同狀態。
物件具有行為:即物件的狀態可能因為它的行為產生變化。
我們先來看第一個特徵,物件具有唯一識別性。一般而言,各種語言的物件唯一識別性都是用記憶體位址來體現的,所以,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)兩個類別。
對JavaScript來說,屬性並非只是簡單的名稱和值,JavaScript用一組特徵(attribute)來描述屬性(property)。
先來說第一類屬性,資料屬性。它比較接近其它語言的屬性概念。資料屬性具有四個特徵。
value:就是屬性的值。
writable:決定屬性能否被賦值。
enumerable:決定for in能否列舉該屬性。
configurable:決定該屬性能否被刪除或改變特徵值。
在大多數情況下,我們只關心資料屬性的值即可。
第二類屬性是存取器(getter/setter)屬性,它也有四個特徵。
getter:函數或undefined,在取屬性值時被呼叫。
setter:函數或undefined,在設定屬性值時被呼叫。
enumerable:決定for in能否列舉該屬性。
configurable:決定該屬性能否被刪除或改變特徵值。
存取器屬性使得屬性在讀取和寫入時執行程式碼,它允許使用者寫入和讀出屬性時得到完全不同的值,它可以視為一種函數的語法糖。
我們通常用來定義屬性的程式碼會產生資料屬性,其中的writable、enumerable、configurable都預設為true。我們可以使用內建函數Object.getOwnPropertyDescripter來查看,如下列程式碼所示:
var o = { a: 1 }; o.b = 2;//a和b皆为数据属性 Object.getOwnPropertyDescriptor(o,\u0026quot;a\u0026quot;) // {value: 1, writable: true, enumerable: true, configurable: true} Object.getOwnPropertyDescriptor(o,\u0026quot;b\u0026quot;) // {value: 2, writable: true, enumerable: true, configurable: true}
我們在這裡使用了兩種語法來定義屬性,定義完屬性後,我們用JavaScript的API來查看這個屬性,我們可以發現,這樣定義出來的屬性都是資料屬性,writeable、enumerable、configurable都是預設值為true。
如果我們想要改變屬性的特徵,或定義存取器屬性,可以使用 Object.defineProperty,範例如下:
var o = { a: 1 }; Object.defineProperty(o, \u0026quot;b\u0026quot;, {value: 2, writable: false, enumerable: false, configurable: true});//a和b都是数据属性,但特征值变化了 Object.getOwnPropertyDescriptor(o,\u0026quot;a\u0026quot;); // {value: 1, writable: true, enumerable: true, configurable: true} Object.getOwnPropertyDescriptor(o,\u0026quot;b\u0026quot;); // {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的值不会发生变化。
在创建对象时,也可以使用 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的高度动态性的对象系统。
所以,我们应该在理解其设计思想的基础上充分挖掘它的能力,而不是机械地模仿其它语言。
要想理解JavaScript对象,必须清空我们脑子里“基于类的面向对象”相关的知识,回到人类对对象的朴素认知和面向对象的语言无关基础理论,我们就能够理解JavaScript面向对象设计的思路。
在这篇文章中,我从对象的基本理论出发,和你理清了关于对象的一些基本概念,分析了JavaScript对象的设计思路。接下来又从运行时的角度,介绍了JavaScript对象的具体设计:具有高度动态性的属性集合。
很多人在思考JavaScript对象时,会带着已有的“对象”观来看问题,最后的结果当然就是“剪不断理还乱”了。
【推荐学习:javascript高级教程】
以上是javascript是基於物件嗎的詳細內容。更多資訊請關注PHP中文網其他相關文章!