凡是搞前端開發的或者玩 JavaScript 的同窗都知道,原型對象和原型鏈是 JavaScript 中最爲重要的知識點之一,也是前端面試必問的題目,因此,掌握好原型和原型鏈勢在必行。所以,我會用兩篇文章(甚至更多)來分別講解原型對象以及原型鏈。前端
在上一篇文章中,咱們詳細介紹了構造函數的執行過程以及返回值,若是沒有看的同窗,請點擊連接 JS進階(1): 人人都能懂的構造函數 閱讀,由於這是本篇文章的基礎知識。面試
廢話很少說,進入正題。bash
經過上一篇文章的介紹,咱們知道:函數
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
。由於咱們在上一篇文章中就說過,每一次經過構造函數的形式來調用時,都會開闢一塊新的內存空間,因此實例 p1
和 p2
所指向的內存地址是不一樣的。但此時又會有一個尷尬的問題,p1
和 p2
調用的say
方法,功能倒是相同的,若是班裏有 60 個學生,咱們須要調用 60 次相同方法,但卻要開闢 60 塊不一樣的內存空間,這就會形成沒必要要的浪費。此時,原型對象就能夠幫助咱們解決這個問題。spa
當一個函數 (注意:不只僅只有構造函數) 建立好以後,都會有一個 prototype
屬性,這個屬性的值是一個對象,咱們把這個對象,稱爲原型對象。同時,只要在這個原型對象上添加屬性和方法,這些屬性和方法均可以被該函數的實例所訪問。prototype
既然,函數的實例能夠訪問到原型對象上的屬性和方法,那咱們不妨把上面的代碼改造一下。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
和 實例 p2
的 say
指向同一塊內存空間。這是什麼緣由呢?咱們經過控制檯的打印結果來看看。code
經過上面的截圖咱們能夠看到,Person.prototype
與 p1.__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');
複製代碼
prototype
屬性prototype
屬性的值是一個對象,咱們稱之爲原型對象參照上面控制檯的截圖,咱們能夠知道:
(1)原型對象上,有一個
constructor
屬性指向 Person; (2)原型對象上,有一個say
方法,會開闢一塊新的內存空間; (3)原型對象上,有一個__proto__
屬性,這個咱們下篇文章再來解釋。
根據上面咱們的分析,繼續繪製:
當 p1
這個實例建立好以後,又會開闢一塊新的內存空間。此時,依舊參照上面控制檯的截圖,咱們能夠知道:
(1)
p1
實例中有一個name
屬性; (2)p1
實例中有一個__proto__
屬性,指向構造函數Person
的原型對象。
根據上面的分析,咱們繼續繪製:
經過上面的解釋,你們應該能夠理解原型對象是什麼以及爲何要使用原型對象了。最後,咱們來總結一下本文的核心知識點。
一個函數建立好以後,就會有一個 prototype
屬性,這個屬性的值是一個對象,咱們把這個 prototype
屬性所指向的內存空間稱爲這個函數的原型對象。
某個函數的原型對象會有一個 constructor
屬性,這個屬性指向該函數自己。
function Person() {
// ...
}
console.log(Person.prototype.constructor === Person); // true
複製代碼
__proto__
屬性,這個屬性指向該實例的構造函數的原型對象(也能夠稱爲該實例的原型對象)。function Person() {
// ...
}
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); // true
複製代碼
最後,本文描述的僅僅是一個構造函數——原型對象——實例的關係圖,並非完整的原型鏈。你們能夠先理解這一部分,等到講解原型鏈的時候,我會繪製一張完整的原型鏈圖供你們理解。童鞋們能夠先試着理解今天的文章,而且本身繪製一下構造函數——原型對象——實例的關係圖,相信你的收穫將會更大。
最後的最後,我所說的不必定都對,你必定要本身試試!
(本文完)