雖然使用Object構造函數或對象字面量可以方便地創建對象,但這些方式有明顯不足:創建具有同樣接口的多個對象需要重復編寫很多代碼
工廠模式可以用不同的參數多次調用函數,每次都會返回一個新對象,這種模式雖然可以解決創建多個類似對象的問題,但沒有解決對象表示問題,即新創建的對象是什么類型
構造函數和工廠函數的區別:
- 沒有顯式的創建對象
- 屬性和方法字節賦值給了this
- 沒有return
使用new操作符調用構造函數會執行如下操作:
- 在內存中創建一個新對象
- 這個新對象內部的[[Prototype]]特性被賦值為構造函數的prototype屬性
- 構造函數內部的this被賦值為這個新對象,即this指向新對象
- 執行構造函數內部的代碼,給新對象添加屬性
- 如果構造函數返回非空對象,則返回該對象,否則,返回剛創建的新對象
constructor本來是用于標識對象類型的,不過,一般認為instanceof操作符是確定對象類型更可靠的方式
相比于工廠模式,定義自定義構造函數可以確保實例被標識為特定類型。
構造函數不一定要寫成函數聲明的形式,賦值給變量的函數表達式也可以表示構造函數,在實例化時,如果不想傳參數,那么構造函數后面的括號可加可不加。只要有new操作符,就可以調用相應的構造函數
構造函數與普通函數唯一的區別就是調用方式不同,除此之外,構造函數也是函數,并沒有把某個函數定義為構造函數的特殊語法。任何函數只要使用new操作符調用就是構造函數,而不是用new操作符調用的函數就是普通函數。
構造函數的主要問題在于,其定義的方法會在每個實例上都創建一遍,因為都是做一樣的事,所以沒必要定義兩個不同的Function實例,況且,this對象可以把函數與對象的綁定推遲到運行時
每個函數都會創建一個prototype屬性,這個屬性是一個對象,包含應該由特定引用類型的實例共享的屬性和方法。實例上,這個對象就是通過調用構造函數創建的對象的原型,使用原型對象的好處是,在它上面定義的屬性和方法可以被對象實例共享。原來在構造函數中直接賦給對象實例的值,可以直接賦值給它們的原型
無論何時,只要創建一個函數,就會按照特定的規則為這個函數創建一個prototype屬性指向原型對象,默認情況下,所有原型對象自動獲得一個名為constructor的屬性,指回與之關聯的構造函數。
在自定義構造函數時,原型對象默認只會獲得constructor屬性,其他的所有方法都繼承自Object,每次調用構造函數創建一個新實例,這個實例的內部[[Prototype]]指針就會被賦值為構造函數的原型對象。腳本中沒有訪問這個[[Prototype]]的標準方式,一些瀏覽器會在每個對象上暴露__proto__屬性,通過這個屬性可以訪問對象的原型。
實例與構造函數原型之間有直接的聯系,但實例與構造函數之間沒有
雖然不是所有實現都對外暴露了[[Prototype]],但可以使用isPrototypeOf()方法確定兩個對象之間的關系,isPrototypeOf()會在傳入參數的[[Prototype]]指向調用它的對象時返回true
Object有一個方法叫Object.getPrototype(),返回參數的內部特性[[Prototype]]的值
Object.setPrototypeOf()可以向實例的私有特性[[Prototype]]寫入一個新值,這樣就可以重寫一個對象的原型繼承關系,但是會影響代碼性能
Object.create()可以創建一個新對象,同時為其指定原型
原型層級
在通過對象訪問屬性時,會按照這個屬性的名稱開始搜索,搜索開始于對象實例本身,如果在這個實例上發現了給定的名稱,則返回該名稱對應的值,如果沒有找到這個屬性,則搜索會沿著指針進入原型對象,然后在原型對象上找到屬性后,再返回對應的值,這就是原型用于在多個對象實例間共享屬性和方法的原理
雖然可以通過實例讀取原型對象上的值,但不能通過實例重寫這些值,如果在實例上添加了一個原型對象中同名的屬性,那就會在實例上創建這個屬性,這個屬性會遮住原型對象上的屬性。
使用delete操作符可以刪除實例上的屬性
hasOwnProperty()方法用于確定某個屬性是在實例上還是在原型對象上,這個方法是繼承自Object的,會在屬性存在于調用它的對象實例上時返回true
原型和in操作符
有兩種方式使用in操作符:單獨使用和在for-in循環中使用
在單獨使用時,in操作符會在可以通過對象訪問指定屬性時返回true,無論該屬性在實例上還是原型上。
如果要確定某個屬性是否在原型上,可以同時使用hasOwnProperty()和in操作符
在for-in循環中使用in操作符時,可以通過對象訪問且可以被枚舉的屬性都會返回,包括實例和原型屬性
要獲得對象上所有可枚舉的實例屬性,可以使用Object.keys()方法,這個方法接收一個對象作為參數,返回包含該對象所有可枚舉屬性名稱的字符串數組
如果想列出所有實例屬性,無論是否可以枚舉,都可以使用Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()方法針對符號
屬性枚舉順序
for-in循環和Object.keys()的枚舉順序是不確定的
Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和Object.assign()的枚舉順序是確定的,先以升序枚舉數值鍵,然后以插入順序枚舉字符串和符號鍵,在對象字面量中定義的鍵以它們逗號分隔的順序插入
Object.values()和Object.entries()將對象內容轉換為序列化的,可迭代的格式,它們接收一個對象,返回它們內容的數組。Object.values()返回對象值的數組,Object.entries()返回鍵值對的數組,非字符串屬性會被轉換為字符串輸出,這兩個方法執行對象的淺復制,符號屬性會被忽略
原型上搜索值是動態的,所以即使實例在修改原型之前已經存在,任何時候對原型對象的修改也會在實例上反映出來
實例的[[Prototype]]指針是在調用構造函數時自動賦值的,這個指針即使把原型修改為不同的對象也不會變,重寫整個原型會切斷最初原型與構造函數的聯系,但實例引用的仍然是最初的原型
重寫構造函數上的原型之后再創建的實例才會引用新的原型
通過原生對象的原型可以取得所有默認方法的引用,也可以給原生類型的實例定義新的方法。可以像修改自定義對象原型一樣修改原生對象原型,因此隨時可以添加方法
原型模式弱化了向構造函數傳遞初始化參數的能力,會導致所有實例默認都取得相同的屬性值,原型的最主要問題是它的共享特性