JS面試題之各種函數和變量的調用

前言

之前面試前端的時候遇到一道JS題雖然不難,但是很刁鑽,所有也未能全部理解,今天剛好有時間深入學習了一下,順便把在原文的基礎上學習的心得分享一下,大神請Ctrl + F4

題目

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//請寫出以下輸出結果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

答案

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//答案:
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3

解題

此題涉及到的JS知識點有變量定義提升this指針指向運算符優先級原型繼承
全局變量污染對象屬性及原型屬性優先級

首先解釋一下題目

function Foo() {
    getName = function () { alert (1); };
    return this;
}  // 定義一個Foo的具名函數
Foo.getName = function () { alert (2);}; 
// 爲具名函數Foo創建了一個叫getName的靜態屬性(該屬性儲存了一個匿名函數)

Foo.prototype.getName = function () { alert (3);};
// 在具名函數Foo的原型鏈上添加了一個叫getName屬性(該屬性儲存了一個匿名函數)

var getName = function () { alert (4);};
// 通過函數變量表達式創建了一個getName的函數(掛載在window下)

function getName() { alert (5);}
// 聲明一個叫getName函數

相關知識點

知識點一之變量定義提升和函數定義提升

重點:在預處理時,函數提升先於變量提升
同名變量提升:對於同名的變量聲明,Javascript採用的是忽略原則,後聲明的會被忽略。

  console.log(a)  //undefined
  var a = 1
  console.log(a)  //1
  var a = function () {
    console.log('a')
  }
  console.log(a) //f () {console.log('a')}
  
  console.log(b) //undefined
  var b = function () {
    console.log('b')
  }
  console.log(b) //f () {console.log('a'
  var b = 1
  console.log(b) //1

同名函數提升:同名的函數聲明,Javascript採用的是覆蓋原則,先聲明的會被覆蓋,因爲函數在聲明時會指定函數的內容,所以同一作用域下一系列同名函數聲明的最終結果是調用時函數的內容和最後一次函數聲明相同。
a()//3
function a() {
console.log(1)
}
a()//3
function a() {
console.log(2)
}
a()//3
同名變量和函數提升:對於同名的函數聲明和變量聲明,採用的是忽略原則,由於在提升時函數聲明會提升到變量聲明之前,變量聲明一定會被忽略,所以結果是函數聲明有效。

  //情況一
  console.log(a)  // f a() {console.log('a')}
  var a = 1
  console.log(a)  // 1
  function a() {
    console.log('a')
  }
  console.log(a) // 1
  //情況二
  console.log(b) // f b() {console.log('b')}
  var b = 1
  console.log(b)// 1
  function b() {
    console.log('b')
  }
  console.log(b)// 1

知識點二之(). new之間的優先級關係

()>.>new
規則一 括號最大
規則二 當a前有new,後有.時,先.後new
規則三 當a()前有new,後有.時,先new後.
規則四 new帶參數的優先級高於函數調用即new F()而非new (F())

new a.b()  //(new a.b)()
new a().b() //(new a()).b()
new new a().b() //new ((new a()).b)()
new new a().b.c()//new (((new a()).b).c)()

知識點三之構造函數的返回值

在傳統語言中,構造函數不應該有返回值,實際執行的返回值就是此構造函數的實例化對象。

而在js中構造函數可以有返回值也可以沒有。

規則一:沒有返回值則按照其他語言一樣返回實例化對象。
規則二:若有返回值則檢查其返回值是否爲引用類型。如果是非引用類型,如基本類型(string,number,boolean,null,undefined)則與無返回值相同,實際返回其實例化對象。
規則三:若返回值是引用類型,則實際返回值爲這個引用類型。

// 情況一
function F(){}
console.log(new F())// F{}
// 情況二
function F(){return true;}
console.log(new F())// F{}
// 情況三
function F(){return {a:1};}
console.log(new F())// Object{a:1}

第一題

Foo.getName();//2
指的是訪問Foo函數上存儲的靜態屬性,靜態屬性只有直接使用函數名加.靜態屬性名可以調用到

第二題

getName();//4
直接調用名在getName的函數,首先找當前上文作用域內的叫getName的函數,雖然題目有

var getName = function () { alert (4);};
function getName() { alert (5);}

但根據知識點一,定義是函數聲明先有效,但是之後執行了getName = function () { alert (4);};,相當於變量重新賦值,所有是4

第三題

Foo().getName();//1

  • 先執行Foo函數
  1. 由於是把Foo當成一個函數,而非一個對象,所有這時this指的是當前運行的環境,即window對象
  2. 函數中第一句getName = function () { alert (1); };由於是賦值一個匿名函數給getName,但此時的getName在當前作用域下並沒有定義,所以解釋器會逐層往上層作用域找getName變量,在window對象中找到了getName ,值爲 function () { alert (4);};所以把他覆蓋了,變成 function () { alert (1); };(如果在window對象還沒找到的話就會創建一個名爲getName 的變量)
function Foo() {
	getName = function () { alert (1); };
	return this;
}
  • 然後調用Foo函數的返回值對象的getName屬性函數。即window.getName() 即 function () { alert (1); };

第四題

getName();//1
直接調用名在getName的函數,即window.getName,由於window下的geiName已被修改,所以跟第三問一樣,執行function () { alert (1); };

第五題

new Foo.getName();//2
由知識點二可得此題執行順序爲new (Foo.getName)()
Foo.getName爲function () { alert (2);};後將其作爲構造函數執行,所以爲2

第六題

new Foo().getName();//3
由知識點二可得此題執行順序爲(new Foo()).getName()

new Foo()此句爲創建Foo對象,返回的this在構造函數中本來就代表當前實例化對象,由知識點三得,最終Foo函數返回實例化對象。
之後調用實例化對象的getName函數,因爲在Foo構造函數中沒有爲實例化對象添加任何屬性,之後到當前對象的原型對象(prototype)中尋找getName,故執行的爲Foo.prototype.getName = function () { alert (3);};
注意: 構造函數內getName = function () { alert (1); };爲賦值一個匿名函數給getName(看第三題),若想執行 alert (1); 構造函數內應爲實例化對象添加屬性,即this.getName = function () { alert (1); };

第七題

new new Foo().getName();//3
由知識點二可得此題執行順序爲new((new Foo()).getName)()
先初始化Foo的實例化對象,然後將其原型上的getName函數作爲構造函數再次new。即Foo.prototype.getName = function () { alert (3);};作爲構造函數,故爲3

最後

由於是本人第一次寫博客,不喜勿噴,謝謝,
此文爲參考 原文 而寫,爲感謝原文作者的付出,特出附上原文連接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章