JS進階(2):人人都能懂的原型對象

封面.jpeg

目錄

一、爲什麼要使用原型對象

二、如何使用原型對象

三、繪製 構造函數——原型對象——實例 關係圖

四、總結


凡是搞前端開發的或者玩 JavaScript 的同學都知道,原型對象和原型鏈是 JavaScript 中最爲重要的知識點之一,也是前端面試必問的題目,所以,掌握好原型和原型鏈勢在必行。因此,我會用兩篇文章(甚至更多)來分別講解原型對象以及原型鏈。

在上一篇文章中,我們詳細介紹了構造函數的執行過程以及返回值,如果沒有看的同學,請點擊鏈接 JS進階(1): 人人都能懂的構造函數 閱讀,因爲這是本篇文章的基礎知識。

廢話不多說,進入正題。

一、爲什麼要使用原型對象

通過上一篇文章的介紹,我們知道:

function Person(name, age) {
    this.name = name;
    this.age = age;
}

var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 34);
console.log(p1.name, p1.age);   // 'Tom', 18
console.log(p2.name, p2.age);   // 'Jack', 34
複製代碼

但是,在一個對象中可能不僅僅存在屬性,還存在方法:

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.say = function() {
        console.log('Hello');
    };
}

var p1 = new Person('Tom', 18);
p1.say();  // 'Hello'
var p2 = new Person('Jack', 34);
p2.say();  // 'Hello'
複製代碼

我們發現,實例 p1 和 實例 p2 調用了相同的方法,都打印出 Hello 的結果。但是,它們的內存地址是一樣的麼?我們打印看看:

console.log(p1.say == p2.say); // false
複製代碼

結果當然爲 false 。因爲我們在上一篇文章中就說過,每一次通過構造函數的形式來調用時,都會開闢一塊新的內存空間,所以實例 p1p2 所指向的內存地址是不同的。但此時又會有一個尷尬的問題,p1p2 調用的say 方法,功能卻是相同的,如果班裏有 60 個學生,我們需要調用 60 次相同方法,但卻要開闢 60 塊不同的內存空間,這就會造成不必要的浪費。此時,原型對象就可以幫助我們解決這個問題。

二、如何使用原型對象

當一個函數 (注意:不僅僅只有構造函數) 創建好之後,都會有一個 prototype 屬性,這個屬性的值是一個對象,我們把這個對象,稱爲原型對象。同時,只要在這個原型對象上添加屬性和方法,這些屬性和方法都可以被該函數的實例所訪問。

 

原型對象1.png

 

 

既然,函數的實例可以訪問到原型對象上的屬性和方法,那我們不妨把上面的代碼改造一下。

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.say = function() {
    console.log('Hello');
};

var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 34);

console.log(p1.say === p2.say); // true
複製代碼

此時,我們看到實例 p1 和 實例 p2say 指向同一塊內存空間。這是什麼原因呢?我們通過控制檯的打印結果來看看。

 

原型對象2.png

 

 

通過上面的截圖我們可以看到,Person.prototypep1.__proto__p2.__proto__ 似乎是一樣的。爲了驗證我們的猜想,我們試着在打印:

Person.prototype === p1.__proto__;   // true
Person.prototype === p2.__proto__;   // true
p1.__proto__ === p2.__proto___;      // true
複製代碼

我們發現,所有的結果都爲 true 。 而這正好解釋了爲什麼 p1.say === p2.say 爲 true 。

三、繪製 構造函數——原型對象——實例 關係圖

現在你大概理解了原型對象,也知道了使用原型對象有什麼好處。下面我們通過繪製圖形的方式再來深刻地理解一下上面的過程。

我們就以下面的代碼爲例:

function Person(name) {
    this.name = name;
}

Person.prototype.say = function() {
    console.log('I am saying');
}

var p1 = new Person('Tom');
複製代碼

1. Person 函數創建之後,會產生一塊內存空間,並且有一個 prototype 屬性

 

原型對象3.png

 

 

2. prototype 屬性的值是一個對象,我們稱之爲原型對象

 

原型對象4.png

 

 

3. 原型對象中的屬性和方法

參照上面控制檯的截圖,我們可以知道:

(1)原型對象上,有一個 constructor 屬性指向 Person; (2)原型對象上,有一個 say 方法,會開闢一塊新的內存空間; (3)原型對象上,有一個 __proto__ 屬性,這個我們下篇文章再來解釋。

根據上面我們的分析,繼續繪製:

 

原型對象5.png

 

 

4. 實例中的屬性和方法

p1 這個實例創建好之後,又會開闢一塊新的內存空間。此時,依舊參照上面控制檯的截圖,我們可以知道:

(1)p1 實例中有一個 name 屬性; (2)p1 實例中有一個 __proto__ 屬性,指向構造函數 Person 的原型對象。

根據上面的分析,我們繼續繪製:

 

原型對象6.png

 

 

四、總結

通過上面的解釋,大家應該可以理解原型對象是什麼以及爲什麼要使用原型對象了。最後,我們來總結一下本文的核心知識點。

  1. 一個函數創建好之後,就會有一個 prototype 屬性,這個屬性的值是一個對象,我們把這個 prototype 屬性所指向的內存空間稱爲這個函數的原型對象。

  2. 某個函數的原型對象會有一個 constructor 屬性,這個屬性指向該函數本身。

function Person() {
    // ...
}
console.log(Person.prototype.constructor === Person); // true
複製代碼
  1. 當某個函數當成構造函數來調用時,就會產生一個構造函數的實例。這個實例上會擁有一個 __proto__ 屬性,這個屬性指向該實例的構造函數的原型對象(也可以稱爲該實例的原型對象)。
function Person() {
    // ...
}
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); // true
複製代碼

最後,本文描述的僅僅是一個構造函數——原型對象——實例的關係圖,並不是完整的原型鏈。大家可以先理解這一部分,等到講解原型鏈的時候,我會繪製一張完整的原型鏈圖供大家理解。童鞋們可以先試着理解今天的文章,並且自己繪製一下構造函數——原型對象——實例的關係圖,相信你的收穫將會更大。

最後的最後,我所說的不一定都對,你一定要自己試試!


作者:零和幺
鏈接:https://juejin.im/post/5aadd8da6fb9a028cf324006
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章