一道超經(jīng)典js面試題Foo.getName()的故事
下面是一道超經(jīng)典的JS面試題。
蘊含了靜態(tài)屬性與實例屬性,變量提升,this指向,new一個函數(shù)的過程
function Foo() { getName = function () { console.log(1); }; return this; }; Foo.getName = function () { console.log(2); }; Foo.prototype.getName = function () { console.log(3); }; var getName = function () { console.log(4); }; function getName() { console.log(5); }; Foo.getName(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName();
輸出一下結果
Foo.getName(); //2 getName(); //4 Foo().getName(); //1 getName(); //1 new Foo.getName(); //2 new Foo().getName(); //3 new new Foo().getName(); //3
一、解析:
1.Foo.getName()
我們先看此題的上半部分做了什么,首先定義了一個叫Foo的函數(shù),之后為Foo創(chuàng)建了一個叫getName的靜態(tài)屬性存儲了一個匿名函數(shù),之后為Foo的原型對象新創(chuàng)建了一個叫getName的匿名函數(shù)。之后又通過函數(shù)變量表達式創(chuàng)建了一個getName的函數(shù),最后再聲明一個叫getName函數(shù)。
第一問的 Foo.getName 自然是訪問Foo函數(shù)上存儲的靜態(tài)屬性,自然是2
二、解析:
2.getName()
為何輸出是4,這里考的是變量提升與函數(shù)聲明提升。我們知道使用var聲明變量會存在變量提升的情況,比如下面的例子中,即使在聲明前使用變量a也不會報錯,舉例:
console.log(a)// undefined var a = 1; console.log(a)// 1
因為聲明提前會讓聲明提升到代碼的最上層,而賦值操作停留在原地,所以上面代碼等同于:
var a console.log(a)// undefined a = 1; console.log(a)// 1
而函數(shù)聲明(注意是函數(shù)聲明,不是函數(shù)表達式或者構造函數(shù)創(chuàng)建函數(shù))也會存在聲明提前的情況,即我們可以在函數(shù)聲明前調用函數(shù):
fn() // 1 function fn() { console.log(1); }; fn() // 1 //因為函數(shù)聲明提前,導致函數(shù)聲明也會被提到代碼頂端,所以等同于 function fn() { console.log(1); }; fn() // 1 fn() // 1
那這樣就存在一個問題了,變量聲明會提升,函數(shù)聲明也會提升,誰提升的更高呢?在你不知道的JavaScript中明確指出,函數(shù)聲明會被優(yōu)先提升,也就是說都是提升,但是函數(shù)比變量提升更高,所以題目中的兩個函數(shù)順序可以改寫成:
function getName() { console.log(5); }; var getName; getName = function () { console.log(4); };
這樣就解釋了為什么是輸出4。
三、解析:
3.Foo().getName()
其實可以看出來,我們在執(zhí)行Foo()函數(shù)的時候getName這個變量提升到外部的全局作用域中了,因為在js中,如果對于一個變量沒用用var 或者 let等聲明的話,他就默認是全局屬性,就是window對象的一個屬性。所以在這里我們的全局的getName又被改了
因為我們Foo()執(zhí)行的時候返回了this而這里的this就是window對象 我們需要知道的是在瀏覽器中所有全局的聲明都是window對象的屬性和方法,所以這里我們調用this.getName()就會返回1了。
四、解析:
4.getName()
這里輸出1已經(jīng)毫無懸念,上一分析中,getName的值在Foo執(zhí)行時被修改了,所以再調用getName一樣等同于window.getName(),同樣是輸出1。
五、解析:
5.new Foo.getName()
首先還是先看運算符優(yōu)先級吧,我自個看完的結果是【new Foo() > Foo() > new Foo】,先運算方式2的Foo.getName() 結果為“2”,再new一個Foo實例對象,因此這里new的過程就相當于單純把Foo.getName執(zhí)行了一遍輸出2。
六、解析:
6.new Foo().getName()
這里考了new基本概念,首先這個調用分為兩步,第一步new Foo()得到一個實例,第二步調用實例的getName方法。
我們知道new一個構造函數(shù)的過程大致為,以構造函數(shù)原型創(chuàng)建一個對象(繼承原型鏈),調用構造函數(shù)并將this指向這個新建的對象,好讓對象繼承構造函數(shù)中的構造器屬性,如果構造函數(shù)沒有手動返回一個對象,則返回這個新建的對象。
所以在執(zhí)行new Foo()時,先以Foo原型創(chuàng)建了一個對象,由于Foo.prototype上事先設置了一個getName方法(輸出3的那個),所以這個對象可通過原型訪問到這個方法,其次由于Foo內(nèi)部也沒提供什么構造器屬性,最終返回了一個this(這個this指向實例),因此這里的this還是等同于我們前面概念提到的以Foo原型創(chuàng)建的對象,可以嘗試輸出這個實例,除了原型上有一個getName方法就沒有其它任何屬性,因此這里輸出3。
七、解析:
7.new new Foo().getName()
相當于new(new Foo().getName())
先執(zhí)行new Foo().getName()由6部知道了輸出3,再創(chuàng)建Foo.prototype.getName()的實例返回。結果為3
總結
到此這篇關于js面試題Foo.getName()的文章就介紹到這了,更多相關js面試題Foo.getName()內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
原生javascript運動函數(shù)的封裝示例【勻速、拋物線、多屬性的運動等】
這篇文章主要介紹了原生javascript運動函數(shù)的封裝,結合實例形式分析了JavaScript封裝勻速、拋物線、多屬性的運動等函數(shù)及相關使用方法,需要的朋友可以參考下2020-02-02JavaScript數(shù)組去重的方法總結【12種方法,號稱史上最全】
這篇文章主要介紹了JavaScript數(shù)組去重的方法,結合實例形式較為詳細的總結分析了12種方法數(shù)組去重的方法,需要的朋友可以參考下2019-02-02改進 JavaScript 和 Rust 的互操作性并深入認識 wasm-bindgen 組件
這篇文章主要介紹了改進 JavaScript 和 Rust 的互操作性并深入認識 wasm-bindgen 組件,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-07-07微信小程序出現(xiàn)wx.navigateTo頁面不跳轉問題的解決方法
這篇文章主要介紹了微信小程序出現(xiàn)wx.navigateTo頁面不跳轉問題的解決方法,簡單分析了微信小程序出現(xiàn)wx.navigateTo頁面不跳轉情況的原因及相應的解決方法,需要的朋友可以參考下2017-12-12JS實現(xiàn)獲取時間已經(jīng)時間與時間戳轉換
這篇文章主要為大家提供了用JavaScript編寫的獲取時間的類,以及時間戳轉時間的三種格式,文中的示例代碼講解詳細,感興趣的可以了解一下2022-03-03