js原型及原型鏈

Pivot發表於2018-06-29

前言

有關於原型及原型鏈方面的知識,總能聽見身邊學習玄學js的同學朋友說起,經過學習訝羽的部落格,今天簡單談一下這方面的理解。

注:

為了思路清晰,這裡從建構函式並建立其物件、prototype、___proto___、constructor、例項與原型、原型的原型、原型鏈幾方面來分析原型整體構思。 首先看下面原型整體思維圖,來逐步進行分析。

js原型及原型鏈

建構函式及建立物件

先用建構函式建立物件:

function Person() {

}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin
複製程式碼

這裡面Person為建構函式,person為例項物件。

prototype

先看個例子

function Person() {}
// 雖然寫在註釋裡,但是你要注意:
// prototype是函式才會有的屬性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin
複製程式碼

每個函式都有一個prototype屬性,那該屬性指向啥呢?當然是一個物件了,這個物件正是呼叫該建構函式而建立的例項的原型(見圖理解),也就是這個例子中person1和person2的原型。(原型:每一個js物件建立的時候都有與之對應的另外一個物件,該物件就是原型,且每個物件都可以從原型“繼承”屬性)。 這裡的繼承:比如給Person的原型賦name屬性,例項person1和person2就可以繼承name屬性

__proto__

1.上面的建構函式Person可通過prototype指向原型,那例項化物件比如person1有沒有辦法指向原型呢?當然有了,看下面例子(建議用控制檯試一下)

function Person() {}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
複製程式碼

這裡可見例項化物件person通過__proto__指向原型,除此之外,__proto__還有啥用呢?

2.當讀取例項屬性時候,如果找不到,就去繼承原型中的屬性,如果還查不到?那咋整?當然是再去找原型的原型嘍,知道找到最頂層為止。看下面的例子:

function Person() {}

Person.prototype.name = 'Kevin';

var person = new Person();

person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // Kevin
複製程式碼

這裡面的首先給例項原型賦屬性值name為“Kevin”,下面給例項化物件也賦屬性name為“Daisy”,第一遍訪問person的name屬性時,值為“Daisy”,delete之後,例項化本身的name已經為空,所以再次訪問的時候,就會往上一層尋找name屬性,即通過prototype賦的name值,所以這時的值是“Kevin“。

那這裡原型的值是通過什麼途徑被例項化物件繼承的呢?答案當然是__proto__屬性嘍(見圖)

那逆向思考一下,原型有沒有途徑指向建構函式和例項化物件呢?看下面

constructor

指向例項的倒是沒有,但是有指向建構函式的,看下面例子:

function Person() {}
console.log(Person === Person.prototype.constructor); // true
複製程式碼

顯而意見,Person的prototype指向原型,原型的constructor指向Person(見圖)

綜上:可見建構函式、例項化物件、原型之間的大致關係,現在說一下原型的原型:

原型的原型

前面說到,如果例項化物件沒有要被訪問的屬性,就從其原型身上找,那要是還找不到呢?前面我們已經講了,原型也是物件啊,既然是物件,就可以用最原始的辦法建立物件嘍,看下面:

var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin
複製程式碼

這裡面可通過最原始的方法建立obj,表示的是不管你是什麼玩意,只要型別是物件,都有一個原型與之對應。思考一下建構函式的原型的原型是怎麼來的呢?當然是原始Object的原型了,再結合之前所講,例項的__proto__指向建構函式的prototype,所以有Person.prototype通過__proto__指向其原型Object.prototype(見上圖)

原型鏈

舉一反三一下,Object.prototype的原型有麼?理論上當然是有的:

console.log(Object.prototype.__proto__ === null) // true
複製程式碼

什麼意思呢?null表示什麼呢?《javascript高階程式設計》的理解是這樣的:

null值表示一個空物件指標

只要意在儲存物件的變數,就應該明確地讓變數儲存null值,這樣做可以提現null作為空指標的慣例

通過這個我理解的是,null是一個物件,但是其值為空值,所以雖然理論上Object.prototype有原型,但也可以很粗暴的理解為Object.prototype”沒有“原型(沒辦法,玄學js就是這麼奇妙)

那什麼是原型鏈呢?就是通過__proto__傳遞屬性的紅色線(見圖理解)

補充

constructor

function Person() {}
var person = new Person();
console.log(person.constructor === Person); // true
複製程式碼

person是沒有constructor屬性的,所以當不能讀取該屬性的時候怎麼辦?去原型找(這就是我們理解原型的目的啊),也就是:

person.constructor === Person.prototype.constructor
複製程式碼

繼承or委託?

這裡補充繼承,引用《你不知道的JavaScript》中的話就是:

繼承意味著複製操作,然而 JavaScript 預設並不會複製物件的屬性,相反,JavaScript 只是在兩個物件之間建立一個關聯,這樣,一個物件就可以通過委託訪問另一個物件的屬性和函式,所以與其叫繼承,委託的說法反而更準確些。

__proto__

這個屬性其實不屬於建構函式的原型,而是來自於Object.prototype

感悟

學習原型及原型鏈什麼用呢?我理解就是要拓寬眼界,在學習React或者vue那些框架的時候,模組化思維很抽象有時讓你很懵逼,為什麼呢?因為各個元件間需要龐大的資料傳輸網,要是不能很好的理解繼承(委託)的思想,要是不明白原型及原型鏈的概念很容易2333,比如剛開始寫React元件開始有

class Add expends Component{
  constructor(props){
    super(props)
    this.state={}
  }
  ......
}
複製程式碼

當時就不知道這裡的constructor什麼意思,對於資料網路也沒有什麼清晰的思路,經過實踐專案的練習已經清晰多了

相關文章