兩道前端的面試題,求各位大神解答一下?

第一題:

var a = [];

for (var i = 0; i &< 10; i++) {

a[i] = function () {

return i;

}

}

a[8]();

問:結果是多少?

正確答案是10,我也知道是怎麼來的,可是面試官說,上面的代碼如何能獲取正確結果,請用ES5和ES6分別作答,這個我就不懂了。

第二題:

console.log(text)

function text() {}

var text = 0;

console.log(text);

結果我是知道的,第一個就是function,第二個是0,但是面試官問我為什麼?如果把第二行和第三行互換,結果是怎樣,為什麼?


第一題是標準的變數作用域問題,es5用閉包解決,es6把 var 換成 let

第二題是標準的變數提升問題,寫成變數提升後等價代碼,很容易理解輸出結果

function text() {}

var text; // 因為已有同名函數,此行會被 js 引擎忽略

console.log(text)

text = 0;

console.log(text);


第一道題考你對作用域的理解,結果是10,因為作用域分為全局作用域和函數作用域,這裡的i當你調用這個函數的時候,已經是循環後的i了,也就是10。

es5的方法是用立即執行函數創建獨立作用域,如下

for (var i = 0; i &< 10; i++) {

a[i] = function bar(i){

return function () {

return i;

};

}(i);

}

a[8]();

es6的方法直接var改為let,這樣子for 的 i 變數就有塊作用域,如下

var a = [];

for (leti = 0; i &< 10; i++) {

a[i] = function () {

return i;

}

}

第二道題考你變數名和函數聲明都要提升,為什麼?因為代碼解釋器會預編譯成:

function text() {}

var text;

console.log(text)

text = 0;

console.log(text);

交換第二行和第三行位置,如下:

console.log(text)

var text = 0;

function text() {}

console.log(text);

解釋器同樣會預編譯成:

function text() {}

var text;

console.log(text)

text = 0;

console.log(text);

所以結果還是一樣

....不知道我這樣解釋能看懂么。。

謝謝指正,已經修改。函數優先順序高所以在變數名之前,變數名不會覆蓋函數名。


為什麼面試官們總是糾結在js的設計缺陷上做文章呢?這樣做意義何在?

考察基本功有演算法和數據結構。

考察業務能力,可以讓面試者寫個樹形菜單什麼的。

var根本就是應該禁用的東西好嗎!


var 沒有塊級作用域,所以 i 是聲明在 for 循環之外的

在調用方法時,依次向上查找,找到的最近的一個 i 已經是循環結束的 i 了

ES5 的解決方法就是使用 IIFE 創造一個新的作用域,讓每次循環的 i 都保留在這個作用域中

ES6 的解決方法很簡單, for 中的 var 改成 let 即可,此時 let 的作用域為塊級作用域,定義在循環體內。

第二個問題

變數提升

編譯器會對即將執行的代碼做以下處理

1. 對不管在哪裡定義的變數進行聲明並定義為 undefined

2. 如果是函數聲明,則定義為聲明的函數(以 function 開頭,而函數表達式不會被提升)

3. 按照順序執行代碼


首先上代碼。

// for 循環
var a = [];
for (var i = 0; i &< 10; i++) { a[i] = function() { return i; } } console.log(a[1]()); // 10 console.log(a[2]()); // 10 console.log(a[8]()); // 10 console.log(i); // 10

// 手動循環
var a = [];
var i = 0;
a[i] = function() {
return i;
}
i++; // i = 1;

a[i] = function() { // a[i] 等同於 a[0]
return i; // i 會在函數使用時再查詢 而不是等於 1
}
i++; // i = 2;

a[i] = function() {
return i;
}
i++; // i = 3;

console.log(i); // 3
console.log(a[0]()); // 3
console.log(a[2]()); // 3

結論:

  1. for循環中的 i 是全局變數。
  2. js採取使用時查詢變數的值,(這點很重要)舉個栗子。

var a = [];
var i = 0;
a[i] = function() {
return i;
}
i++; // i = 1;

a[i] = function() { // a[i] 等同於 a[0]
return i; // i 會在函數使用時再查詢 而不是等於 1
}

再上代碼。

console.log(text); // [Function: text] (node 環境)
function text() {}
var text = 0;
console.log(text); // 0

console.log(text); // [Function: text] (node 環境)
var text = 0;
function text() {}
console.log(text); // 0

結論:

  1. 變數提升。
  2. 函數聲明(shua)提(liu)升(mang)——函數是JavaScript的一等公liu民mang,編譯器會忽略同名變數聲明(同名變數在前面函數聲明直接覆蓋,在後面編譯器忽略。)

變數:這就是你說的忽略,確定不是shuaLiuMang?。


第二題,看其他回答吧。


var b=[];

for(var j=0;j&<10;j++){

/* 正常情況下應該是這樣的 方式1*/

// b[j]=j;

/* 正常情況下應該是這樣的 方式2 */

// b.push(j);

/* 面試過程基本上就變成了這樣 方式3 */

b[j]=(function(j){

var ret=j;

return function(){

return ret;

}

})(j);

}

// console.log(b)

// console.log(j)

// console.log(b[6]());

function exam(age){

this.age=age;

return this.age;

}

// 正常情況下使用

var exam_1=new exam(1);

//面試過程中基本就變成了這樣了

var exam_2=new exam((new exam(2)));

var exam_3=new exam(new exam((new exam(3))));

console.log(exam_1.age)

console.log(exam_2)

console.log(exam_3)

針對題主第一題:考察的是作用域

針對題主第二題:考察的是函數聲明和函數表達式的區別和作用場景。

我也有過幾次面試經歷,覺得比較觸動的一件事情就是,先找些相關面試題過濾一下自己。例如:

前端基礎面試題(JS部分)


第一題 閉包解決

var a = [];

for (var i = 0; i &< 10; i++) {

(function(arg){

a[i] = function () {

return arg;

}

})(i)

}

a[8]();

第二題 hosting 變數提升 就醬!(評論里有人指出是hoisting 畢竟我英語不好 原諒我!)


關於第一題,很多人都回答的很好,我就不多說了。

第二題,很多人回答的也對,但是總感覺講的不夠明白。我建議題主認真看看 方應杭-我用了兩個月的時間才理解 let。我這裡也簡單按自己的理解說下這篇文章的結論:

1) 變數是使用 var/let/const/function(不含函數表達式) 定義的

2) 變數有 創建、初始化、賦值的過程(const 沒有賦值過程),其中賦值可以多次,創建和初始化只有一次。其中初始化有差異,見下面代碼描述:

var a = 5;
// 可分解為下面的步驟:
// 1. 創建 a
// 2. 初始化 a 為 undefined
// 3. 給 a 賦值為 5

let b = 6;
// 可分解為:
// 1. 創建 b
// 2. 初始化 b 為 6

const c = 7;
// 可分解為:
// 1. 創建 c
// 2. 初始化 c 為 7

function foo () {}
// 可分解為:
// 1. 創建 foo
// 2. 初始化 foo 為 undefined
// 3. 給 foo 賦值「函數」

3) 變數都會「提升」,只是程度不一樣:

var 會提升創建和初始化;

let/const 會提升創建; (可以解釋暫時死區問題:變數未初始化無法使用

function 會提升創建、初始化、賦值。

根據這些可以完全解釋第二個問題的現象。


建議看一下 《你不知道的Javascript》


第一個,es5解決就是閉包,es6解決用let

第二個,你需要了解變數提升原理,權威指南和高級程序設計上都沒有,涉及到ec ao vo的執行創建過程,自己找文章看一下吧


第一題:

// es5 使用閉包
var a = [];
for (var i = 0; i &< 10; i++) { (function(i){ a[i]= function (){ return i; } })(i) } a[8](); // --- 8 // es6 for每循環一次都會形成一個單獨的作用域, let a = []; for (let i = 0; i &< 10; i++) { a[i]= function (){ return i; } } a[8](); // --- 8

第二題:

1. js引擎去解析一代碼的時候, 會先去掃描一下當前作用域的所有的var,並執行var( var a; var b; var c;等);但是不會執行賦值操作,這時這些a、b、c的值都是undefined,相當於在執行代碼前,會把當前作用域裡面所有用var聲明的變數提前先給你聲明好,但是賦值操作還是老老實實在原地待著,蝦兵蟹將沒有插隊優先權。

2. 不僅僅是var, 它還會去掃描一下所有的函數聲明(並非函數表達式),也把他們提前聲明好。如果有同名的function,那就聲明的結果是最後一個function。如果剛好有同名字的var聲明的變數,那就幹掉它,function才是老大,var小弟一邊去,不過var a = 0中var被幹掉了,賦值操作(a = 0)還是在的,function老大挺忙的,看不上 =。

console.log(text) // text(){}
function text() {}
var text = 0;
console.log(text) // 0

// 調換位置還是一個樣, var總會被幹掉,function聲明會被提前
console.log(text) // text(){}
var text = 0;
function text() {}
console.log(text) // 0

所以就是: var聲明和function聲明有插隊優先權,function是老大,可以幹掉var,賦值操作職位太低,沒人關注,就老老實實的原地排隊。


第二題涉及到JS的執行上下文,變數對象了。簡單的說:JS執行首先進入變數對象,此時函數和變數都申明了(函數是正常的,但是變數test此時是undefined,還未賦值,這就是變數的申明提升),接著代碼進入活動對象,此時就開始賦值(test賦值了)。

模擬偽代碼如下:

function test(){};

var test =undefined ;

console.log(text);

test = 0;

console.log(text);

注意:函數名優先順序高於變數聲明(不會被覆蓋)

好多人把提升(hoisting)的英文寫錯了,看看我寫錯了沒?

參考 前端基礎進階(三):變數對象詳解


第一題:

for循環中的i其實存在於全局變數(Global)中,for循環執行完畢後,也就是i=10,該值才被傳入函數中,因此,10次結果均為10。以下提供兩個解決辦法:let/閉包

建議通讀一下該文章:JavaScript核心

第二題:

屬於現學現賣:

建議閱讀一下該文章:javascript - js 中變數優先順序的問題? - SegmentFault

over.


額額額,第一個es6的話,var換成let可破,es5高票回答寫的很清楚了,第二個的話,聲明時定義的變數不存在變數提升,因此不管怎麼寫第二三行,都只有函數聲明提升了,因此第一個一直是函數,第二個一直是零


第二題的方式我很討厭,我覺得這就是一種失敗的設計,現在不說不用反而要搞懂這種失敗設計的怪異思維。感覺很low啊


第一題 考察閉包 與 let

第二題 考察變數提升

啊 我也想去面試 還招人嗎 實習生


第一,變數作用域沒有被鎖住,拿到的都是最後一次循環的引用,每個函數作用域外部變數i ,第二function hosting 和變數聲明hosting。搜索相關文章即可,看到直接答案對你幫助不大。


第一個問題作用域問題,es5中用閉包解決,es6中把var 變成 let,其他答主講的很清楚了不再贅述。第二個問題屬於變數與函數聲明的提升問題。需要注意的是,函數變數名的提升是優先於普通變數的,所以你那段代碼等價於

function text() {}

var text;

console.log(text);

text = 0;

console.log(text);

所以第一次列印是function,第二次列印是0。


第一個的ES6方案是for的var換let,ES5方案你不知道的話暫時還不適合去面試(非初級職位)。

第二個就是變數申明提升和函數申明提升的區別,不知道可以去看JavaScript Garden


推薦閱讀:

前端界有哪些越早知道越好的小技巧、小知識?
在飢人谷學習前端是一種怎樣的體驗?
如何練習前端技術?
在上海培訓前端,達內,火星,傳智,千鋒綜合考慮哪家好一點?有知道的大神嗎?謝謝
如何快速學習並掌握一項職業技能?

TAG:前端開發 | JavaScript | 前端入門 | 精通web前端 |