今天,一個技術群里小朋友提出一個問題: 我默念心中的萬能公式,答案一下就出來了: a; 報錯(f.b is not a function); a; b; 這下子出題人產生了疑惑,你是控制台敲出來的吧! 但其實,原型鏈真的很簡單。話不多說,開始表演! 首先,我們簡單介紹一下,實例化一個對象(new)到 ...
今天,一個技術群里小朋友提出一個問題:
Object.prototype.a = function () { console.log('a') } Function.prototype.b = function () { console.log('b') } function F(){} var f = new F(); f.a(); f.b(); F.a(); F.b();
我默念心中的萬能公式,答案一下就出來了:
a;
報錯(f.b is not a function);
a;
b;
這下子出題人產生了疑惑,你是控制台敲出來的吧!
但其實,原型鏈真的很簡單。話不多說,開始表演!
首先,我們簡單介紹一下,實例化一個對象(new)到底做了什麼?
function Base() { this.a = function () { console.log('我是Base中的a') } } Base.prototype.b = function () { console.log('我是Base prototype上的b') } var obj = new Base(); // 實際上做了以下幾件事 // var obj = {}; // obj.__proto__ = Base.prototype; // Base.call(obj); // 第一行,我們創建了一個空對象obj // 第二行,我們將這個空對象的__proto__成員指向了Base函數對象prototype成員對象 // 第三行,我們將Base函數中this上的成員賦值給obj
這就去控制台檢驗一下:
好,可以開始解題,回顧一下小朋友提出的問題:
Object.prototype.a = function () { console.log('a') } Function.prototype.b = function () { console.log('b') } function F(){} var f = new F(); f.a();f.b();F.a();F.b();
我們展開f的原型鏈:
f.__proto__ === F.prototype; // 因為prototype本質也是對象,繼承自Object,所以F.prototype.__proto__ === Object.prototype f.__proto__.__proto__ === Object.prototype; // 按上一條,會得出Object.prototype.__proto__ = Object.prototype,那樣會永無止境,因此js中把Object.prototype.__proto_直接賦值成null f.__proto__.__proto__.__proto__ === null;
我們會發現,f的原型鏈中,根本沒有Function.prototype什麼事,所以答案出來了,f.a()輸出a,f.b會報錯;
我們再展開F的原型鏈:
F.__proto__ === Function.prototype; // 因為prototype本質也是對象,繼承自Object,所以Function.prototype.__proto__ === Object.prototype F.__proto__.__proto__ === Object.prototype; f.__proto__.__proto__.__proto__ === null;
OK,Function.prototype和Object.prototype都在原型鏈上,都會有輸出,答案揭曉,F.a()輸出a,F.b()輸出b;
前文我提到了萬能公式,吃瓜群眾表示,難道公式就是這個?當然不是,請聽我細細道來;
剛纔的故事還沒結束,我心血來潮回問了小朋友一個問題,就剛纔你的條件Object.a();Object.b();Function.a();Function.b();會輸出什麼?
小朋友答曰:a; b; a; b;
不錯不錯,但他接了一句(他的1和4其實答錯了,後續會告訴原因):
1. Object.a(); 直接prototype取值;
2. Object是Function.prototype的實例,可以繼承b;
3. Function.__proto__ === Function.prototype;
4. Function.b();直接prototype取值;
???Object.a第一步就直接到Object.prototype上找?這可能也是大家弄不清原型鏈的一大問題,話不多說,請攤開Object的原型鏈:
// Object是函數,繼承自Function Object.__proto__ === Function.prototype; Object.__proto__.__proto__ === Object.prototype; Object.__proto__.__proto__.__proto__ === null;
因此,細心的朋友已經發現他的錯誤了,Object.a其實是訪問到Object.__proto__.__proto__時才從Object.prototype上找到相應的a();
同樣展開Function的原型鏈,就不再贅述了:
// Function自身也是個函數,因此繼承自己 Function.__proto__ === Function.prototype; Function.__proto__.__proto__ === Object.prototype; Function.__proto__.__proto__.__proto__ === null;
為了引導出萬能公式,我再次發出了靈魂拷問:
// 在剛纔的前提下 var c = 1; console.a();// a console.b();// console.b is not a function c.a();// a c.b();// c.b is not a function console.log.a();// a console.log.b();// b
這時,學會展開原型鏈的同學已經明白答案了,我就直接說出我的萬能公式:
在js中,萬物皆對象,只要在Object.prototype上寫的方法,萬物都能訪問到;而Function.prototype上的方法,只有能通過()方式調用的函數,才能訪問到;
因此我只需要查看這個對象可不可以通過函數()的形式調用,就能確定他是否能訪問Function.prototype。再次回顧出題人的問題,f僅僅是個對象,f()是會報not is a function錯誤的,而F()是可以調用的函數,一下子得出 a 報錯 a b的答案,學到了是不是感覺自己也棒棒噠~~~
最終Cober老師還是給出了自己的殺手鐧面試題,畢竟原型鏈還有另一層面試方法:
Object.prototype.a = function () { console.log('我是Object中的a') } Object.prototype.b = function(){ console.log('我是Object中的b') } Function.prototype.a = function () { console.log('我是Function中的a') } Function.prototype.b = function () { console.log('我是Function中的b') } function F(){ this.a = function () { console.log('我是F中的a') } } F.prototype.a = function () { console.log('我是F的prototype中的a') } var f = new F(); f.a();f.b();F.a();F.b();Object.a();Object.b();Function.a();Function.b();
(提示:原型鏈調用順序是f.a -> f.__proto__.a -> f.__proto__.__proto__.a,直到訪問到null)