首先,咱們來看一個咱們再代碼中常用的卻沒有深刻細究的實例app
const obj = { key: "value" } obj.toString() // [object Object]
咱們在自定義對象時,僅僅定義 key
的屬性,並無定義 toString
的方法,可是在代碼中卻能夠調用而且得到結果,那麼如今我就來深刻的解釋一下發生在背後的緣由。函數
JavaScript 中的數據類型分爲兩大類,值類型(也叫基本數據類型) 和 引用數據類型this
值類型:String、Number、Boolean、null、undefined、Symbol(ES6 新增)
引用數據類型:Object、Function、Arrayspa
在這裏咱們僅僅來聊一聊 Object
和 Function
prototype
// 使用對象直接量建立對象 // 優勢: // 一、寫法直觀,簡潔無歧義 // 二、強調對象是一個簡單的可變的散列表,而沒必要必定派生自某個類 // 三、當使用 Object() 建立對象時,解析器須要順着原型鏈開始查找,直到找到 Object 構造函數爲止,而直接量的寫法則不會 const obj = { key: "value" } console.log(obj) /* 控制檯輸出結果以下 {key: "value"} key: "value" __proto__: Object */
在 JavaScript 中,全部的對象都有 __proto__
的屬性,指向該對象的原型對象
,並且 Function 也是對象,是一種特殊的對象code
隨便百度一下,就能發現常見的,對 new 的過程有以下描述:對象
function myNew (Parent, arguments) { // 建立一個新對象賦值給this,並 繼承函數的原型 // 這一步實際上是要拆成兩小步的 // 一、建立新對象,賦值給 this // this = {} // 二、繼承函數的原型 // this.__proto__ = Parent.prototype // 合併寫法 this = Object.create(Parent.prototype) // 將屬性和方法添加到 this 指向的對象上 this.argments = arguments // 返回這個 this 指向的新對象(若是沒有手動指定 return) return this }
Object.create 建立一個新對象,並使用傳入的對象做爲該對象的原型繼承
在 JavaScript 中,每個函數都有一個 prototype
的屬性,指向一個對象,該對象便是構造函數的原型對象ip
每一個原型對象都有一個 constructor
屬性,指向該原型對象所屬的構造函數原型鏈
不管是使用下列哪一種方式建立的函數,都會自帶 prototype
屬性:
一、obj.__proto__
,因爲 __proto__
屬性爲內部屬性,因此通常推薦是使用
二、Parent.prototype
,經過父類構造函數的 prototype 訪問
三、Object.getPrototypeOf(obj)
,經過頂層對象的 getPrototypeOf 方法獲取,推薦使用
經過咱們手動的實現 new
運算符能夠發現,新建立的對象經過 __proto__
屬性,引用了父類構造函數的原型對象 prototype
,由此實現了繼承
注意:
一、在 JavaScript 中,每一個對象都有 __proto__
屬性,指向該對象的父類構造函數的原型對象
二、在 JavaScript 中,每一個函數都有 prototype
屬性,即構造函數的原型對象 (因爲函數也是對象,因此函數也有 __proto__
的屬性)
在 JavaScript 中,實現繼承的方式,就是經過對象的原型串聯起來的,一級一級的向上查找而造成的鏈式結構,稱爲原型鏈
可簡單的理解爲,即子類無需定義便可使用父類已定義的屬性和方法
/* 一、原型鏈繼承 子類對象繼承父類構造函數的實例 優勢:既能繼承父類原型上的屬性和方法,也能繼承父類上的屬性和方法 缺點: 一、不能動態的給父構造函數傳遞參數 二、繼承單一 三、全部的子類,都會共享父類實例的屬性和方法 */ function Parent (name, age) { this.name = name this.age = age } const parent = new Parent("obj1", 21) const obj1 = {} obj1.__proto__ = parent /* 二、借用構造函數繼承 經過 call / apply 在子類構造函數中動態調用父類構造函數 優勢: 一、能夠動態的傳遞參數 二、能夠多繼承(多個 call/apply) 缺點: 一、只能繼承父類的實例屬性和方法,不能繼承父類原型的屬性和方法 二、每一個子類構造函數中,都要手動調用父類構造函數,臃腫 */ function Parent (name, age) { this.name = name this.age = age } function Children (name, age) { Parent.call(this, name, age) // ... } const obj2 = new Children("obj2", 22) /* 三、組合繼承 組合 原型鏈繼承 和 借用構造函數繼承 優勢: 一、既能繼承構造函數屬性,又能繼承原型屬性 二、能夠動態傳參 缺點: 一、不支持多繼承(父類原型上的屬性) 二、子類實例原型上的構造函數指向父類構造函數 */ function Parent (name, age) { this.name = name this.age = age } function Children(name, age){ Parent.call(this, name, age) // ... } Children.prototype = new Parent() const obj3 = new Children("obj3", 23) /* 四、原型式繼承 經過一個函數封裝,建立一個新對象,將傳入的對象做爲新對象的原型對象,返回新對象 優勢: 一、能繼承原型上的屬性 缺點: 一、不能繼承實例上的屬性 二、單一繼承 */ function Parent (name, age) { this.name = name this.age = age } function content(p){ function F(){} f.prototype = p return new F() } const obj4 = content(new Parent("obj4", 24)) /* 五、寄生式繼承 在原型式繼承的基礎上,再套上一層函數,用於處理函數的傳參 優勢: 一、支持動態傳參 缺點: 一、沒用到subject的原型,沒法共享該構造函數原型的屬性 */ function Parent (name, age) { this.name = name this.age = age } function content(p){ function F(){} F.prototype = p return new F() } function subject(name, age,level){ const cont = content(new Parent(name, age)) cont.level = level return cont } const obj5 = subject("obj5", 25, 5) /* 六、寄生式組合繼承(推薦) 經過寄生,完成原型上屬性的繼承 經過組合,完成實例上屬性的繼承 */ function content(obj){ function F(){} F.prototype = obj return new F() } // 寄生 const cont = content(Parent.prototype) // 組合 function Child(name, age){ Parent.call(this, name, age) // ... } cont.constructor = Child // 惟一的不足 Child.prototype = cont const obj6 = new Child("obj6", 26)