🗞簡介
下面這張圖大家應該很很熟悉了,各位大佬講原型及原型鏈的時候是大部分都會用到下面這張圖片的
我想以自己的方式來講述一遍,一是幫助我自己更好的復習,二是希望能夠幫助到想要復習或者學習原型的同學
- 在講解之前,我先講點基礎概念
- JS對象中他有一個內置原型
[[prototype]]
,這個原型就是隱式原型__proto__,而構造函數因為其本身就是 一個對象,所以它有一個隱式原型,但是構造函數又是一個函數,所以他有一個特殊的屬性顯式原型prototype
,大家也可以看到constructor
這個屬性,默認情況下,函數對象上都會有一個constructor
屬性,里面存放着是函數的原型對象 - 好,接下來我會分步講解這張圖的內容,同時會輔以圖片來講解
🍀構造函數與實例化的關系
- 我們先看這個地方
function Foo()
他是一個構造函數 - 那么它的顯式原型一定是指向
Foo
的原型對象上 - 同時我們看
f1
這個實例化Foo的對象,因為是對象,所以它只有一個__proto__
隱式原型的 - ❓我們可以看到圖片中,隱式原型是指向了
Foo
的原型對象上的,這是為什么呢? - ✅其實這里我要講下
new
這個操作 到底干了些什么,這應該是一道經典的面試題吧- 1️⃣ 首先會創建一個空對象
{}
- 2️⃣ 將
this
指向該空象,然后將構造函數的顯式原型賦予給這個對象的隱式原型上去__proto__=prototype
- 3️⃣ 開始執行構造函數內的代碼,將構造函數內部屬性和方法賦值給該對象
- 1️⃣ 首先會創建一個空對象
- 💯所以大家看,
f1
的隱式原型是指向了Foo
的顯式原型的吧,是不是很容易理解呢
- 接下來我們接着往下看,Foo的原型對象也就是一個普通的對象,所以他也就一個隱式原型,他的隱式原型就是指向
Object
的原型對象的,小伙伴看到這里會不會很懵? 覺得為啥就是指向Object
的原型對象呢? - 我們重點看向o1這個普通對象,大家可能對
new Object
會有點陌生,因為大家創建對象的時候,往往都是通過創建一個字面量來申明一個對象的const obj={}
,實際上這個操作就是等於new Object()
.所以看到了new Object()
大家有沒有明白什么呢🤪? - 哦 原來
Object
是一個構造函數,那么他的顯式原型就是指向Object.Prototype
了,即原型對象,那么在實例化Object
的時候,就會做我上面講的new操作符操作了,所以那個Foo.prototype
的隱式原型指向那個Object.prototype
是不是理解了呢?😛
- 接下來,我們要把重點放在構造函數
Foo
的隱式原型上,函數的隱式原型是誰給的呢? 上文講到,隱式原型是在實例化的時候,構造函數所賦予的,那么誰會是Foo
的構造函數呢? - 看圖可知,它便是
Function
,實例化了Function
所得到的Foo
,所以Foo
的隱式原型是指向了Function
的顯式原型Function.prototype
的
〽站在高層的兩個構造函數
它便是 Function和Object構造函數,他們兩個有點特殊,所以單獨拿出來講講
- 大家看上面的圖,首先關注
Object
這個構造函數 Object
的顯式原型是指向了Object.prototype
,這一點大家肯定都沒有問題- 我們看,
Object.prototype
的隱式原型是指向了null
,這說明了一個什么問題,隱式原型是在實例化的時候才會被賦予的,但是他是為空,所以我們可以得到一個結論,就是在JS中,頂層原型是Object.prototype
,🤣🤣Object.prototype
是站在最頂峰的辣個男人 - 好,思緒收回來,我們看下
Object
的隱式原型是指向哪的,會發現它的隱式原型是指向函數的顯式原型的,說明Object
這個構造函數是通過Function
這個構造函數所實例化得到的 - 我們接着看下
Function
這個構造函數的顯式原型和隱式原型- 首先 它的顯式原型是
Function.prototype
,這點沒問題 - 然后 它的隱式原型也是
Function.prototype
嗯? 嗯? 好怪,不行,我再看一眼🥴 - 然后你會發覺不對勁,很不對勁❗❗❗ 這不就是說明了
Function
是通過實例化自己得到的嗎? - 有點類似於 是先有雞還是先有蛋這一說法.不知道他們哪個是先出來的,應該是JS內部自己做了特殊處理,這一部分需要小伙伴記好了
- 首先 它的顯式原型是
- 然后我們在看下
Function.prototype
的隱式原型指向了Object.prototype
說明了函數的原型對象是通過實例化Object
所得到的 - 講到這 這副圖的內容我已經講的差不多了,最后我自己畫了一幅圖來幫助大家更好的理解
💨習題練習
我出幾道練習題,大家自己練習一下,看看自己掌握的怎么樣
/** 1.普通對象
* 2.構造函數
* 3.Function
* 4.Object
**/
function Foo(name) {
this.name = name;
}
const obj = {};
const obj2 = {
name: "給他一個新的原型",
}
const foo = new Foo("czx");
console.log(foo.__proto__ === Foo.prototype);
console.log(obj.__proto__ === Object.prototype)
console.log(Foo.prototype === Foo.__proto__);
console.log(Foo.__proto__ === Function.prototype);
console.log(Function.prototype === Function.__proto__);
console.log(Function.prototype.__proto__ === Object.prototype);
console.log(Object.__proto__ === Function.prototype);
console.log(Object.prototype.__proto__);
Foo.prototype = obj2;
console.log(Foo.prototype.__proto__ === Object.prototype)
console.log(foo.__proto__ === Foo.prototype);
答案
大家一定要自己寫完后再來看答案啊,這樣印象才深刻
console.log(foo.__proto__ === Foo.prototype);//true
console.log(obj.__proto__ === Object.prototype)//true
console.log(Foo.prototype === Foo.__proto__);//false
console.log(Foo.__proto__ === Function.prototype);//true
console.log(Function.prototype === Function.__proto__); //true
console.log(Function.prototype.__proto__ === Object.prototype);//true
console.log(Object.__proto__ === Function.prototype);//true;
console.log(Object.prototype.__proto__);//null
Foo.prototype = obj2;
console.log(Foo.prototype.__proto__ === Object.prototype)//true;
console.log(foo.__proto__ === Foo.prototype);//false
💫原型鏈
只要理解了上面我講的原型,原型鏈自然而然就會了
- 大家可以將原型鏈理解為,如果我在這一層沒找到的東西,我可以去對應的上一層找,直到頂層為止,我來給大家出幾道題,就能很快知道了解了
題目一
function Foo(name, age) {
this.name = name;
this.age = age;
}
const bar = {
say: () => {
console.log("執行說話");
},
age2: 32,
}
Foo.prototype = bar;
const foo = new Foo("Spirit", 18)
foo.say();
console.log(foo.age2)
- 大家可以自己想下這段代碼執行出來會是什么結果
- 可以知道,我將Foo的顯式原型進行了替換,那么我foo去調用原型對象上的方法是可以調用到的,而我構造函數Foo上是沒有這兩個屬性的,但因為他的顯式原型對象即bar,它上面是有的
- 所以foo是可以調用到的
題目二
var A = function () { };
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3
}
var c = new A();
console.log(b.n);
console.log(b.m);
console.log(c.n);
console.log(c.m);
😶🌫️ 大家可以想下這道題的輸出結果是什么,這道題也是很考察大家對原型鏈的理解的,接下來我就會開始講解了
- 首先
A
是一個構造函數,它有顯式原型和隱式原型 A
在顯式原型上添加了一個屬性n,其值為1- 通過實例化A得到了b,有隱式原型,這時候就考察大家對
new
操作熟不熟悉,這時候有b.__proto__===A.prototype
- 這時候A的顯式原型被整個替換了,所以之前的實例化的b就取不到這個更換原型之后的值了
- 實例化A得到了c,這時候有
c.__proto__===A.prototype
只不過這個顯式原型不再是之前的了 - 開始輸出
b.n
是輸出1的,因為是加在了最開始A的顯式原型上面,所以是能取到值的b.m
是取不到值的,因為這個m
是后面更換了A的顯式原型所加上的值,此時的b是取不到的c.n
輸出為2,因為更換后的顯式原型上面是有n 是2c.m
輸出為3,同理- 大家聽懂了嗎,這道題主要就是考你
new
操作做了什么,讓你來判斷到底實例化之后是賦予了哪個顯式原型,下面的代碼是帶注解版的
var A = function () { };
A.prototype.n = 1; //顯示原型上加了一個 n屬性 值為1
var b = new A(); //實例化了的b __proto__===A.prototype
A.prototype = {
n: 2,
m: 3
} //A的顯式原型被替換掉了
var c = new A(); //實例化的c 的__proto__===A更換后的顯式原型
console.log(b.n); //1
console.log(b.m); //undefined
console.log(c.n);//2
console.log(c.m);//3
題目三
var F = function () { };
Object.prototype.a = function () {
console.log('a');
};
Function.prototype.b = function () {
console.log('b');
}
var f = new F();
f.a();
f.b();
F.a();
F.b();
🤔這道題是我認為比較難的一道題,大家可以好好想下這道題的輸出到底是什么,接下來我會開始講解這道題
F
是一個構造函數,它擁有顯式原型和隱式原型- 此時
F.prototype.__proto__===Object.prototype
F.__proto__===Function.prototype
- 這個時候,他們是有這種關系的
- 此時
- 在
Object.prototype
上加上了一個函數a
- 在
Function.prototype
加上了一個函數b
- 實例化F得到了
f
,這個時候他有隱式原型f.__proto__===F.prototype
- 接下來我們看輸出
f.a()
首先它的隱式原型是等於構造函數的顯式原型的,而F.prototype.__proto__
是等於Object.prototype
,也就是說f
是可以沿着這條原型鏈一路往上找到,最后是可以找到這個a
函數的,所以可以輸出a
f.b()
我們可以想下,沿着原型鏈我們可以找到這個函數b嘛,並不可以,對不對🤔,所以這里會報錯,說不存在這么一個函數F.a()
我們來看下這個構造函數是有顯式原型和隱式原型的,所以我們看回剛開始的那個解釋,它是能找到Object.prototype
和Function.prototype
的,所以能夠輸出aF.b()
也是同理,所以輸出b
- 這道題大家看懂了嘛,主要是考察了函數和普通對象的區別,也是很好的考察了原型鏈的關系的,下面是帶注解的代碼
var F = function () { }; //構造函數
//他有隱式原型和顯式原型 此時他的隱式原型__proto__===Function.prototype.__proto__===Object.prototype
Object.prototype.a = function () { //Object.prototype是頂層原型
console.log('a');
};
Function.prototype.b = function () {
console.log('b');
}
var f = new F(); //實例化了的f 此時是一個對象 只有隱式原型 __proto__ 所以此時有 __proto__===F.prototype
//😶🌫️ F.prototype是一個對象,是Object所實例化得到的 所以F.prototype.__proto__===Object.prototype
f.a(); //a
f.b(); //報錯 沒有這個函數
F.a(); //a
F.b();//b