[TOC]
# 原型與繼承
## 原型與原型鏈

- `prototype`: 每個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法,如果使用這個函數生成了實例,那么稱這個對象為所有實例的原型。
- `__proto__`: 每個對象都擁有`__proto__`屬性,該屬性用于實現原型鏈,當訪問一個對象的屬性時,如果該對象內部不存在這個屬性,就通過原型鏈找直到找到或者到終點 null。
- `constructor`:每個原型都有一個 constructor 屬性指向關聯的構造函數
## Object() 與 Function()
所有的對象都是由 Object() 構造函數構造的,所有的函數聲明 / 函數表達式都是 Function() 構造函數的實例,而 Object() 構造函數本身又是 Function() 構造函數的實例,其原型關系如下:

需要注意的是 Function() 的`__proto__ `屬性直接指向的是其原型對象。
我們可以用下面的代碼來驗證這張圖:在 node 環境及瀏覽器環境下都是一樣的結果
```js
console.log(Object.__proto__ === Function.prototype) // true
console.log(Function.__proto__ === Function.prototype) // true
console.log(Function.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__ === null) // true
```
## 繼承
首先要理解構造函數 new 時執行了哪些操作
1. 創建一個新對象,并做原型綁定(該對象的 \_\_proto\_\_ 屬性指向構造函數的 prototype 屬性指向的對象)
2. 將 this 綁定到這個新對象上
3. 執行構造函數中的代碼(為這個新對象添加屬性)
4. 返回新對象(一般情況下,構造函數不返回值,但是用戶可以選擇主動返回對象,來覆蓋正常的對象創建步驟)
模擬實現 new
```js
function _new (fn, ...args) {
const obj = {}
obj.__proto__ = fn.prototype
fn.apply(obj, args)
return Object.prototype.toString.call(obj) === '[object Object]' ? obj : {}
}
```
### 借用構造函數
在構造函數中使用 Parent.call(this) 的方法繼承父類屬性。
原理: 將子類的 this 使用父類的構造函數跑一遍
缺點: Parent 原型鏈上的屬性和方法并不會被子類繼承(子類連接不到父類)
``` javaScript
function Parent () {
this.name = 'parent'
}
Parent.prototype.sayHello = function () {
console.log('say Hello')
}
function Child () {
Parent.call(this) //函數名.call 調用這個函數但是更改其 this
this.type = 'child'
}
let p1 = new Parent()
let c1 = new Child()
p1.sayHello() // 'say Hello'
c1.sayHello() // error c1.sayHello is not a function
```
### 原型鏈實現繼承
原理:把子類的 prototype(原型對象)直接設置為父類的實例
缺點:因為子類只進行一次原型更改,所以子類的所有實例保存的是同一個父類的值。 當子類對象上進行值修改時,如果是修改的原始類型的值,那么會在實例上新建這樣一個值; 但如果是引用類型的話,他就會去修改子類上唯一一個父類實例里面的這個引用類型,這會影響所有子類實例(一句話:子類修改原型上的引用類型會影響父類)
``` javaScript
function Parent () {
this.name = 'parent'
this.arr = [1,2,3]
}
function Child () {
this.type = 'child'
}
Child.prototype = new Parent() // 擁有了這個 Parent 實例上的屬性和方法
```
通過下面這個例子來觀察該方法的缺點
```
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
}
Child.prototype = new Parent()
var child1 = new Child()
child1.names.push('yayu')
console.log(child1.names) // ["kevin", "daisy", "yayu"]
var child2 = new Child()
console.log(child2.names) // ["kevin", "daisy", "yayu"]
```
### 組合繼承方式(上面兩種方法的配合)
組合構造函數中使用 call 繼承和原型鏈繼承。
原理: 子類構造函數中使用 Parent.call(this) 的方式可以繼承寫在父類構造函數中 this 上綁定的各屬性和方法; 使用 Child.prototype = new Parent() 的方式可以繼承掛載在父類原型上的各屬性和方法
缺點: 父類構造函數在子類構造函數中執行了一次,在子類綁定原型時又執行了一次
``` js
function Parent () {
this.name = 'parent'
this.arr = [1,2,3]
}
function Child () {
Parent.call(this) // 繼承 Parent 構造函數上的屬性和方法
this.type = 'child'
}
Child.prototype = new Parent(); // 繼承 Parent 的父類原型上的屬性和方法
```
### 組合繼承方式優化
使用 Object.create() 方法創建一個新對象,使用現有的對象(參數)來提供新創建的對象的 \_\_proto\_\_
``` js
function Parent () {
this.name = 'parent'
this.arr = [1,2,3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = Object.create(Parent.prototype) // 提供__proto__
Child.prototype.constructor = Child
```
這種方式也叫寄生組合式繼承,相比于之前的組合繼承方式,其減少了一次父類構造函數的調用,如果不使用 Object.create() 方法,有時候也會這么封裝:
```js
function object (o) {
function F() {}
F.prototype = o
return new F()
}
function prototype (child, parent) {
var prototype = object(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
// 當我們使用的時候:
prototype(Child, Parent)
```
### ES6 實現繼承
ES6 的 Class 相當于構造函數的語法糖,extends 也是語法糖,其本質還是通過原型鏈實現繼承
``` js
// Extends 關鍵字配合 Class 實現繼承
class People { // 定義一個類 People
constructor(name) { // constructor 函數,必須存在,接收實例化參數
this.name = name
}
getName() {
console.log(this.name) // 類的屬性
}
}
class Student extends People { // Student 類繼承 People 類
constructor(name, grade) { // 聲明 constructor 方法
super(name) // 執行父類的構造函數 相當于 People.prototype.constructor.call(this)
this.grade = grade
}
getGrade() { // Student 類的屬性
console.log(this.grade)
}
}
let s = new Student('Tom', 6) // 實例化 Student 類
s.getName() // 調用繼承的屬性,輸出'Tom'
s.getGrade()
```
ES5 的繼承實質是先創造子類的實例對象 this,然后再將父類的方法添加到 this 上面。ES6 的繼承機制是先創造父類的實例對象 this(所以必須先調用 super 方法),然后再將子類的構造函數修改 this
ES5 的寄生組合式繼承:
```js
function Parent (name) {
this.name = name
}
function Child (name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = Object.create(Parent.prototype)
var child1 = new Child('kevin', '18')
console.log(child1)
```
對應的 ES6 的 class:
```js
class Parent {
constructor(name) {
this.name = name
}
}
class Child extends Parent {
constructor(name, age) {
super(name) // 調用父類的 constructor(name)
this.age = age
}
}
let child1 = new Child('kevin', '18')
console.log(child1)
```
對應的原型鏈示意圖為:
<img src="https://box.kancloud.cn/b37f76b440aeb6b0bec9f8a7315fac55_584x497.png" />
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs