一道很有難度的JavaScript題

  上次分享了一道題,你們反響不錯,很開心本身寫的東西有人願意花時間去看,也給了本身莫大的鼓舞,其實作題雖然不比真正的編程,可是也可以讓你發現一些你以前沒有注意到的語言層面的問題。因此,此次再分享一道稍微有難度的JavaScript題目。javascript

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();

   請問上述代碼在瀏覽器環境下,輸出結果是多少?
   揭曉一下最終答案:java

2 4 1 1 2 3 3

  前四道難度不是很大,主要是後三道,基本是全軍覆沒,感嘆實在是太繞了了。後面慢慢分析了一下,逐個講一下吧。
  首先必須注意一個問題git

function Foo() {
    getName = function () { 
        console.log('1');
    };
    return this;
}

  在函數內部聲明的getName變量,前面是不帶有varlet,const的,因此其實根據LHS(這個的介紹能夠去的我博客看一下關於LHS和RHS的總結),聲明的getName是在全局範圍內(也是就window)。
  其次須要明確你是否知道下面代碼在瀏覽器中的執行結果:github

var getName = function () { 
    console.log('4');
};
function getName() { 
    console.log(5);
}
getName();

  上述代碼的執行結果是:4。緣由是這樣的,var聲明的變量和函數聲明function都會被提高,可是函數聲明的提高的級別是比
var要高的,因此上面的代碼的實際執行結果是:編程

function getName() { 
    console.log(5);
}
var getName = function () { 
    console.log('4');
};
getName();

  後一個函數表達式getName覆蓋了前面的函數聲明getName,實際執行的是函數表達式(也就是是爲何JavaScript永遠不會有函數重載這麼一說了),因此輸出的是4
  首先我給下面的代碼添加一下必要的註釋:瀏覽器

//函數聲明
function Foo() {
    //全局變量
    getName = function () { 
        console.log('1');
    };
    return this;
}
//爲函數添加屬性getName,其類型是Function,因此這裏也能夠看出來,Function也是一種Object
Foo.getName = function () {
    console.log('2');
};
//爲Foo的原型添加方法getName
Foo.prototype.getName = function () { 
    console.log('3');
};
var getName = function () { 
    console.log('4');
};
function getName() { 
    console.log(5);
}

  下面執行第一條語句:函數

Foo.getName();

  函數Foo自己並無執行,執行的是函數的屬性getName,固然輸出的是:2.
  接下來執行:this

getName();

  這是在全局範圍內執行了getName(),有兩條對應的getName的聲明,根據前面咱們所提到的提高的級別來看實際執行是函數表達式:spa

var getName = function () { 
    console.log('4');
};

  因此輸出的是4
  接下來執行prototype

Foo().getName();

首先看一下JavaScript的操做符優先級,從高到低排序
此處輸入圖片的描述
  從上面能夠看出來().優先級相同,因此Foo().getName()從左至右執行。首先運行Foo(),全局的getName被覆蓋成輸出console.log('1'),而且返回的this此時表明的是window。隨後至關於執行的window.getName(),那麼輸出的實際就是1(被覆蓋)。
  下面到了

getName();

  這個不用說了,執行的仍是:1(和上面一毛同樣)。
  下面到了三個最難的部分:

new Foo.getName();

對於這條語句的執行,有兩種可能:

(new Foo).getName()

new (Foo.getName)()

  可是咱們根據操做符優先級表能夠得知,其實上.操做符要比new優先級要高,因此實際執行的是第二種,因此是對

Foo.getName = function () {
    console.log('2');
};

函數執行了new操做,固然輸出的是2
下面到了執行

new Foo().getName();

  這個語句的可能性也有兩種:

(new Foo()).getName();

或者

new (Foo().getName)();

  那麼應該是那種的呢?原來我覺得會是第二種的執行方式,後面經過瀏覽器調試發現真實的執行的方式是第一種。我看到題目的做者是這麼解釋的:

首先看運算符優先級括號高於new。實際執行爲(new Foo()).getName()。遂先執行Foo函數。

  我以爲上面的解釋是有問題的,對比上面兩種執行方式,第一種是先執行new,而後執行的是.操做符,而後執行的是()。第二種是先執行了(),再執行的是.,最後執行new操做符。若是真的按照引用所說的用優先級的方式判別,其實偏偏應該執行的是第二種而不是第一種。
  後來總算找到緣由了,原來以前那個出現的比較多的JavaScript優先級的表並不完整,萬能的MDN給出了最權威的JavaScript優先級表運算符優先級
  我列舉出最重要的部分(由高到低):
  優先級表格
  因此帶參數的new操做符是優先級最高的,這下就沒有問題了,執行順序確實應該是第一種。
  那麼按照(new Foo()).getName();來執行,狀況就就很簡單了,(new Foo())返回了新生成的對象,該對象沒有getName()方法,因此在prototype中找到了getName()方法。因此輸出的是3
  勝利就在眼前,咱們看一下最後一問。

new new Foo().getName();

  和上一步同樣的方法,咱們按照優先級表給分析一下這個語句究竟是怎麼執行的。
  首先帶參數的new操做符優先級最高,第一步劃分爲:

new (new Foo().getName)();

  第二步劃分爲:

new ((new Foo()).getName)();

  因此執行(new Foo()).getName這個函數是對應的Foo.prototype.getName,因此執行new (Foo.prototype.getName)()確定輸出的是3
  哈哈哈,這麼可貴題終於解決了,開心~總結一下吧,首先JavaScript知識最好去MDN去查,萬一別的地方寫錯了真的是貽害不淺。其次,若是在寫代碼的時候仍是少利用操做符優先級這種東西,一旦不明確的地方就馬上用(),代碼的可閱讀性真的是很重要!很重要!很重要!畢竟代碼仍是給人看~
  若是有寫的不正確的地方,歡迎你們指出,資歷深淺,請多指教。歡迎你們去圍觀個人博客呀~~http://mrerhu.github.io  

相關文章
相關標籤/搜索