[JS] 物件導向: 原型Prototype與物件Object
前言
這篇會來整理關於JS原型、類別、物件導向、原型鍊、繼承、建構子等。這幾個關聯又有點難懂的關鍵字,在學JS時時常聽到,一開始只是去使用JS、Vue到現在去了解他背後運作的原理,才恍然大悟原是這樣啊!
Javascript
JavaScript 是一個以原型為基礎 (Prototype-based)、多範型的、動態語言。支援物件導向(Object-oriented programming, OOP)、指令式以及宣告式 (如函數式程式設計)。
物件導向程式設計 OOP
是將 軟體 想像成由一群物件交互合作所組成,而非以往以函數 (Function) 或簡單的指令集交互合作所組成。在物件導向的架構中,每個物件都具有接收訊息,處理資料以及發送訊息給其他物件的能力。每個物件都可視為獨一無二的個體,他們扮演不同的角色並有不同的能力及責任。物件導向程式設計強調模組化,使得程式碼變的較容易開發和理解。
類別 (Class) 和 物件 (Object)
類別 (Class)
類別是用來定義物件的屬性 (properties) 和方法 (methods)的藍圖。
物件 (Object)
物件為一個類別的實體 (Instance),包含屬性 (properties)與方法 (methods)的資料結構。
上述有提到Javascript是以原型為基礎 (Prototype-based)的語言,不用先設計藍圖(類別)就可以建立物件,是無類別的 (Classless)。
那JS沒有類別要如何用原型基礎來實現物件導向的概念呢?JS的物件透過原型(Prototype)相互繼承各自功能,形成原型鍊(Prototype Chain)。建立物件時,會用一個函式function也就是建構式 (Constructor)來定義物件的藍圖,類似類別的概念。
ES6有個class的新語法,只是個語法糖,讓建構式 (Constructor)的寫法更簡潔易懂,更近似於其他物件導向語言C++、JAVA定義類別的方式,但JavaScript仍然是基於原型的語言。
建構式 (Constructor) aka 建構子 建構器
Constructor為用來定義物件的藍圖,可以定義物件的屬性、物件本身的方法。也可以藉由.prototype來定義該Constructor原型的方法。
在Javascript中有以下內建的Constructor
String(),Number(),Boolean(),Array(),Object(),Function(),RegExp(),Date(),Error(),Symbol()
當然也可以自定義Constructor,而要建立新物件時,可以在Constructor前面用 new 這個關鍵字
1 | // 自定義Constructor 名稱第一個字為大寫 |
以ES6 Class 的寫法則為
1 | class CarFactory { |
原型(prototype) 和 原型鏈 (Prototype Chain)
若我們用上述的例子來舉例 Console.log(berlingo)後會發現物件上除了本身的屬性和方法外,會有一個__proto__可以展開,這就是CarFactory這個原型,用.prototype定義的屬性和方法則會在原型上,然後我們會發現CarFactory這個原型上還有個__proto__:Object,這個是物件最上層的原型Object Prototype,所有物件最上層的原型都是Object Prototype,再往上則會是空值Null。
在CarFactory這個原型我們定義了discount()這個方法,這個方法是一個函式,展開後我們會發現__proto__:f(),就是函式的原型,可以看到函式的內建方法bind、call、apply等等,函式原型裡頭又會有個最上層的物件原型Object Prototype。
這種一層一層原型接來接去,就是原型鏈 (Prototype Chain)的概念。
繼承(Inheritance)
原型這樣接來接去,會繼承上面那個原型的屬性和方法。用同樣上述例子,berlingo這個物件本身是沒有discount()這個方法,而是berlingo繼承了CarFactory這個原型上的方法,所以berlingo也可以使用。
1 | console.log(vios instanceof CarFactory); // true CarFactory 是vios的原型 |
Array陣列、Array-like類陣列
DOM方法取得的並不是一般的陣列是類陣列,可以看到他的原型是NodeList,跟Array的原型_proto_: Array 不同。因此Array可以使用的方法,NodeList不一定有,像是.map()等。Arguments也是一種類陣列,可用…展開或array.from轉成陣列。
1 | var divList = document.querySelectorAll('div'); |
1 | var a = {name: 'Zoe'}; // a ---> Object.prototype ---> null |
1 | var a = {name: 'Zoe'}; // a ---> Object.prototype ---> null |
- ES6 Class語法
1 | class CarFactory { |
1 | var a = {name: 'Zoe'}; |
1 | console.log('name' in b); //true |
最頂層 物件原型 Object Prototype的方法
更改屬性值 Object.defineProperty
Object.defineProperty(obj, Property, descriptor)
第一個變數是要定義的物件
第二個變數是要定義物件的屬性
第三個則是屬性描述器valueOf() 取得屬性值
屬性描述器(Property descriptor)
可用 Object.getOwnPropertyDescriptor(obj, property) 取得屬性描述
在
屬性描述可分成有六種數值
- value(選填 預設undefined): 屬性的值
- writable(選填 預設false): 定義屬性是否可以改變,如果是 false 那就是唯讀屬性。
- enumerable(選填 預設false): 定義物件內的屬性是否可以透過 for-in 語法來迭代。
- configurable(選填 預設false): 定義屬性是否可以被刪除、或修改屬性內的 writable、enumerable 及 configurable 設定。
- get(選填 預設undefined): 物件屬性的 getter function。
- set(選填 預設undefined): 物件屬性的 setter function。
1 | berlingo.price = 10000; |
get 和 set 存取器描述器 (Accessor Descriptor)
取值器 getter: 取得指定屬性的值的方法
設值器 setter: 設定指定屬性的值的方法
1 | let a = { |
1 | var berlingo = { |
- ES6 語法
1 | var berlingo = { |
參考資料
[JS] 物件導向: 原型Prototype與物件Object