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

封面.jpeg

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

在上一篇文章中,咱們詳細介紹了構造函數的執行過程以及返回值,若是沒有看的同窗,請點擊連接 JS進階(1): 人人都能懂的構造函數 閱讀,由於這是本篇文章的基礎知識。面試

廢話很少說,進入正題。bash

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
複製代碼

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

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 的結果。可是,它們的內存地址是同樣的麼?咱們打印看看:this

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

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

2、如何使用原型對象

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

原型對象1.png

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

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 指向同一塊內存空間。這是什麼緣由呢?咱們經過控制檯的打印結果來看看。code

原型對象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 。

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

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

咱們就如下面的代碼爲例:

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

4、總結

經過上面的解釋,你們應該能夠理解原型對象是什麼以及爲何要使用原型對象了。最後,咱們來總結一下本文的核心知識點。

  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
複製代碼

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

最後的最後,我所說的不必定都對,你必定要本身試試!

(本文完)

相關文章
相關標籤/搜索