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