構造函數
與C++和Java不同,JavaScript語言的對象體系不是基于“類”的,而是基于構造函數(constructor)和原型鏈(prototype)。
JavaScript語言使用構造函數作為對象的模板。所謂“構造函數”,就是專門用來生成“對象”的函數。它提供模板,描述對象的基本結構。
為了與普通函數區別,構造函數名字的第一個字母通常大寫。
另外,函數體內部使用了this關鍵字,代表了所要生成的對象實例;生成對象的時候,必需用new命令,調用構造函數。new命令的作用,就是執行構造函數,返回一個實例對象。
如果不使用new命令來調用構造函數,那么構造函數就變成了普通函數,并不會生成實例對象,同時,this這時代表全局對象,將造成一些意想不到的結果。
使用new命令時,它后面的函數調用就不是正常的調用,而是依次執行下面的步驟:
- 創建一個空對象,作為將要返回的對象實例
- 將這個空對象的原型,指向構造函數的prototype屬性
- 將這個空對象賦值給函數內部的this關鍵字
- 開始執行構造函數內部的代碼
函數內部可以使用new.target屬性。如果當前函數是new命令調用,new.target指向當前函數,否則為undefined。
this關鍵字
JavaScript支持運行環境動態切換,也就是說,this的指向是動態的。為了把this固定下來,避免出現意想不到的情況。JavaScript提供了call、apply、bind這三個方法,來切換/固定this的指向。
function.prototype.call()
:函數實例的call方法,可以指定函數內部this的指向(即函數執行時所在的作用域),然后在所指定的作用域中,調用該函數。function.prototype.apply()
:apply方法的作用與call方法類似,也是改變this指向,然后再調用該函數。唯一的區別就是,它接收一個數組作為函數執行時的參數.function.prototype.bind()
:bind方法用于將函數體內的this綁定到某個對象,然后返回一個新函數。
prototype 對象
JavaScript通過構造函數來生成實例對象,同一個構造函數所創建的對象實例之間,是無法共享屬性(函數)的。
為了便于公共屬性或函數的共享,每個對象都繼承了“原型”(prototype)對象。原型對象上的所有屬性和方法,都能被派生對象共享。
每一個構造函數都有一個prototype屬性,該屬性指向了實例對象所繼承的原型對象。在通過構造函數創建實例對象時,會自動把該原型對象分配給實例對象。
原型對象的屬性不是實例對象自身的屬性。當實例對象本身沒有某個屬性或方法的時候,它會到構造函數的prototype屬性指向的對象,去尋找該屬性或方法。
JavaScript的所有對象都有構造函數,而所有構造函數都有prototype屬性(其實是所有函數都有prototype屬性),所以所有對象都有自己的原型對象。
由于原型本身也是對象,又有自己的原型,所以形成了一條原型鏈。其作用在于,讀取對象的某個屬性時,JavaScript引擎先尋找對象本身的屬性,如果找不到,就沿著它的原型鏈去找,直到最頂層的Object.prototype。
prototype對象有一個constructor屬性,默認指向prototype對象所在的構造函數。
instanceof運算符返回一個布爾值,表示指定對象是否為某個構造函數的實例(只能用于對象,不適用原始類型的值)。它的運算實質是檢查右邊構建函數的原型對象,是否在左邊對象的原型鏈上。
Object.getPrototypeOf
方法返回一個對象的原型。這是獲取原型對象的標準方法。
Object.setPrototypeOf
方法可以為現有對象設置原型,返回一個新對象。
Object.create
方法用于從原型對象生成新的實例對象,可以替代new命令。
Object.prototype.__proto__
可以改寫某個對象的原型對象。應該盡量少用這個屬性,而是用Object.getPrototypeof()(讀取)和Object.setPrototypeOf()(設置),進行原型對象的讀寫操作。
Object
通過原型鏈,對象的屬性分成兩種:自身的屬性和繼承的屬性。
Object.getOwnPropertyNames
方法返回一個數組,數組成員是對象本身的所有屬性的鍵名,不包含繼承的屬性鍵名。該數組中包含了那些不可枚舉的屬性名,如果希望獲取到可枚舉的屬性名數組則使用Object.keys
方法。
對象實例的hasOwnProperty
方法返回一個布爾值,用于判斷某個屬性定義在對象自身,還是定義在原型鏈上。
in
運算符返回一個布爾值,表示一個對象是否具有某個屬性。它不區分該屬性是對象自身的屬性,還是繼承的屬性。
如果要拷貝一個對象,需要做到下面兩件事情:
- 確保拷貝后的對象,與原對象具有同樣的prototype原型對象。
- 確保拷貝后的對象,與原對象具有同樣的屬性。
面向對象編程的模式
在其他OOP語言中,類作為實例對象的模板可以具有繼承關系。在JavaScript中,一個作為對象的模板的構造函數,也可以繼承另外一個構造函數,可以分為兩步:
- 在子類的構造函數中,調用父類的構造函數。
- 是讓子類的prototype屬性指向父類的原型對象,這樣子類就可以繼承父類原型。