問(wèn)答:
1. 函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 (*)
在日常的任務(wù)中,JavaScript主要使用下面兩種方式創(chuàng)建函數(shù):
- 函數(shù)聲明:
function sayName(){
console.log('hunger');
}
sayName();
- 函數(shù)表達(dá)式
var sayName = function(){
console.log('hunger');
};
區(qū)別:
a. 函數(shù)聲明必須以function開(kāi)頭,否則,則為表達(dá)式。
b. 函數(shù)表達(dá)式在語(yǔ)句的結(jié)尾要加上分號(hào),表示語(yǔ)句結(jié)束,而函數(shù)聲明則不需要在結(jié)尾加上分號(hào)。
c. 函數(shù)聲明在JS解析的時(shí)候會(huì)進(jìn)行函數(shù)提升,即在同一個(gè)作用域內(nèi),不管函數(shù)聲明在哪里創(chuàng)建,它會(huì)提升至作用域頂部。

但是函數(shù)表達(dá)式不會(huì)被提升,它的表現(xiàn)和聲明變量提升的規(guī)則相同,也就是說(shuō),函數(shù)名(這里指var后面定義的變量名)會(huì)聲明前置,但是函數(shù)表達(dá)式的賦值沒(méi)有被提前,操作還是在原來(lái)的位置。

2. 什么是變量的聲明前置?什么是函數(shù)的聲明前置 (**)
-
變量的聲明前置:JavaScript引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然后再一行一行地運(yùn)行。這造成的結(jié)果,就是所有的變量的聲明語(yǔ)句,都會(huì)被提升到代碼的頭部,這就叫做變量提升。
如:

上面運(yùn)行的代碼會(huì)報(bào)錯(cuò),是因?yàn)槲覀儧](méi)有聲明變量xxx。
接下來(lái)我們將聲明變量xxx

我們發(fā)現(xiàn),變量是在語(yǔ)句調(diào)用之后定義的,但是結(jié)果并沒(méi)有報(bào)錯(cuò)。這就是變量聲明前置的作用。JS解析器會(huì)把當(dāng)前作用域內(nèi)聲明的所有變量和函數(shù)都放到作用域的開(kāi)始。但是對(duì)于變量來(lái)說(shuō),它只是把變量的聲明提到了作用域的開(kāi)始,而變量的賦值仍然按照順序執(zhí)行。那么沒(méi)有被賦值的變量會(huì)自動(dòng)賦值為undefined,所以此處運(yùn)行的結(jié)果為初始值undefined。它等同于如下代碼:
var xxx;
console.log(xxx);
xxx = 2;
再來(lái)看看如下的列子:

我們先聲明一個(gè)全局變量xxx,然后再函數(shù)內(nèi)部定義一個(gè)局部變量。我們希望是第一次打印出來(lái)的結(jié)果是全局范圍內(nèi)定義的xxx變量,第二次打印出來(lái)的是局部變量xxx的值。但是輸出結(jié)果并沒(méi)有跟我們?cè)O(shè)想的一樣,原因就是定義的局部變量在其作用域內(nèi)聲明提前了。故第一次打印出來(lái)的是沒(méi)有賦值的undefined,第二次打印出12。它等同于如下代碼:
xxx = 2;
(function(){
var xxx;
console.log(xxx);
xxx = 12;
console.log(xxx);
})();
- 函數(shù)的聲明前置:和變量的聲明會(huì)前置一樣,函數(shù)聲明同樣會(huì)前置,如果我們使用函數(shù)表達(dá)式那么規(guī)則和變量一樣;如果我們使用函數(shù)聲明的方式,那么即使函數(shù)寫在最后也可以在前面語(yǔ)句調(diào)用,前提是函數(shù)聲明部分已經(jīng)被下載到本地。
-
函數(shù)表達(dá)式:
在上面代碼中,變量sayAge被前置了,但是它的賦值并沒(méi)有被提前,這樣就印證了函數(shù)表達(dá)式的提前和變量提前是一回事了。上面的代碼等同于:
var sayAge;
sayAge(10);
sayAge = function(age){
console.log(age);
}
-
函數(shù)聲明:
函數(shù)聲明并不僅僅是函數(shù)名被提前了,整個(gè)函數(shù)的定義都被提前了。它等同于如下代碼:
function fn(){
console.log('1');
}
fn();
總結(jié):
- 變量聲明會(huì)提前到作用域的頂部,而賦值會(huì)被保留在原地,仍然按照次序執(zhí)行。
- 函數(shù)表達(dá)式是變量前置了,但是賦值沒(méi)有提前。和變量提升一樣。
- 函數(shù)聲明整個(gè)會(huì)被前置到變量聲明的后面,即使函數(shù)寫在最后面也可以在前面語(yǔ)句調(diào)用。
那么,我們?cè)诜治龃a的時(shí)候,可以把變量和函數(shù)聲明放在作用域的頂部,這樣分析出來(lái)的結(jié)果一般不容易出錯(cuò)。
3. arguments 是什么 (*)
arguments是JS里的一個(gè)內(nèi)置對(duì)象,它只在函數(shù)內(nèi)部有效,可以在函數(shù)內(nèi)部通過(guò)使用arguments對(duì)象來(lái)獲取函數(shù)的所有參數(shù)。這個(gè)對(duì)象為傳遞給函數(shù)的每個(gè)參數(shù)建立一個(gè)條目,條目的索引從0開(kāi)始。參數(shù)也可以重新被賦值。

arguments對(duì)象就像數(shù)組(array),但是它卻不是真正的數(shù)組,除了length,它沒(méi)有數(shù)組所特有的屬性和方法。
當(dāng)一個(gè)函數(shù)的參數(shù)數(shù)量比它顯示聲明參數(shù)數(shù)量更多時(shí)候,我們就可以使用arguments對(duì)象獲取到該函數(shù)的所有傳入?yún)?shù)。
如:

4. 函數(shù)的重載怎樣實(shí)現(xiàn) (**)
重載是很多面向?qū)ο笳Z(yǔ)言實(shí)現(xiàn)多態(tài)的手段之一,在靜態(tài)語(yǔ)言中確定一個(gè)函數(shù)的手段是靠方法簽名——函數(shù)名+參數(shù)列表,也就是說(shuō)相同名字的函數(shù)參數(shù)個(gè)數(shù)不同或者順序不同都被認(rèn)為是不同的函數(shù),稱為函數(shù)重載。
在JavaScript中沒(méi)有函數(shù)重載的概念,函數(shù)通過(guò)名字確定唯一性,參數(shù)不同也被認(rèn)為是相同的函數(shù),后面的覆蓋前面的。
在JavaScript中可以通過(guò)arguments來(lái)實(shí)現(xiàn)函數(shù)的重載,如:

5. 立即執(zhí)行函數(shù)表達(dá)式是什么?有什么作用 (***)
立即執(zhí)行函數(shù)表達(dá)式(Immediately-Invoked Function Expression),是將函數(shù)定義放在一個(gè)圓括號(hào)里,讓JavaScript引擎將其理解為一個(gè)表達(dá)式,再在函數(shù)的定義后面加一個(gè)(),以達(dá)到定義函數(shù)后立即調(diào)用該函數(shù)的效果。有下面兩種寫法:
<pre>
(function(){ /code/ }());
(function(){ /code/ })();</pre>
如:

作用:
- 封裝大量的工作而不會(huì)在背后遺留任何全局變量。
- 定義的所有變量都會(huì)成為立即執(zhí)行函數(shù)的局部變量,所以不用擔(dān)心這些臨時(shí)變量會(huì)污染全局空間。
- 可以將獨(dú)立的功能封裝在自包含模塊中。
注意:立即執(zhí)行函數(shù)通常作為一個(gè)單獨(dú)模塊使用。一般沒(méi)有問(wèn)題,但是,建議在自己寫的立即執(zhí)行函數(shù)前加分號(hào),這樣可以有效地與前面代碼進(jìn)行隔離。否則,可能出現(xiàn)意想不到的錯(cuò)誤。
6. 什么是函數(shù)的作用域鏈 (****)
- 作用域:作用域就是變量與函數(shù)的可訪問(wèn)范圍,即作用域控制著變量與函數(shù)的可見(jiàn)性和生命周期。在JavaScript中,變量的作用域有全局作用域和局部作用域兩種。
- 全局作用域:在代碼中任何地方都能訪問(wèn)到的對(duì)象擁有全局作用域,一般來(lái)說(shuō)以下幾種情形擁有全局作用域:
a. 最外層函數(shù)和在最外面定義的變量擁有全局作用域
var a = 2;
function fn(){
var b;
var c;
function fn2(){
console.log(a);
console.log(b);
}
b = 3;
fn2();
c = 4;
}
fn();
//在這段代碼中,變量a 和函數(shù)fn時(shí)擁有全局作用域,而在函數(shù)里面的變量和函數(shù)并不擁有全局作用域
b. 所有未定義直接賦值的變量自動(dòng)聲明為擁有全局作用域
function fn(){
var a = 2;
b = 3;
console.log(a);
}//變量b用于全局作用域,而變量a則不是。
c. 所有window對(duì)象的屬性擁有全局作用域
- 局部作用域:和全局作用域相反,局部作用域一般只在固定的代碼片段內(nèi)可訪問(wèn)到,最常見(jiàn)的如函數(shù)內(nèi)部,所以在一些地方也會(huì)看到有人把這種作用域稱為函數(shù)作用域。
function fn(){
var a = 2;
function fn2(){
console.log(a);
}
fn2();
} //在此代碼中變量a和函數(shù)fn2用于局部作用域
- 作用域鏈:當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈(scope chain)。作用域鏈的用途,是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)。作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對(duì)象。如果這個(gè)環(huán)境是函數(shù),則將其活動(dòng)對(duì)象(activation object)作為變量對(duì)象。活動(dòng)對(duì)象在最開(kāi)始時(shí)只包含一個(gè)變量,即arguments對(duì)象(這個(gè)對(duì)象在全局環(huán)境中是不存在的)。作用域鏈中的下一個(gè)變量對(duì)象來(lái)自包含(外部)環(huán)境,而再下一個(gè)變量對(duì)象則來(lái)自下一個(gè)包含環(huán)境。這樣,一直延續(xù)到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對(duì)象始終都是作用域鏈中的最后一個(gè)對(duì)象。
參考:
JavaScript 開(kāi)發(fā)進(jìn)階:理解 JavaScript 作用域和作用域鏈
代碼:
1. 以下代碼輸出什么? (難度**)
function getInfo(name, age, sex){
console.log('name:',name);
console.log('age:', age);
console.log('sex:', sex);
console.log(arguments);
arguments[0] = 'valley';
console.log('name', name);
}
getInfo('hunger', 28, '男');
getInfo('hunger', 28);
getInfo('男');
輸出結(jié)果:
//getInfo('hunger', 28, '男');
name:hunger
age:28
sex:男
["hunger", 28, 男]
name valley
//getInfo('hunger', 28);
name:hunger
age:28
sex:undefined
["hunger", 28]
name valley
//getInfo('男');
name:男
age:undefined
sex:undefined
["男"]
name valley
2. 寫一個(gè)函數(shù),返回參數(shù)的平方和?如 (難度**)
function sumOfSquares(){
var s = 0;
for(var i = 0;i < arguments.length;i++){
s += arguments[i] * arguments[i];
}
console.log(s);
}
sumOfSquares(2,3,4);//29
sumOfSquares(1,3);//10`
3. 如下代碼的輸出?為什么 (難度*)
console.log(a); //輸出undefined,因?yàn)樽兞刻嵘耍菦](méi)有賦值
var a = 1;
console.log(b);//輸出b is not defined,因?yàn)樽兞縝沒(méi)有被申明`
4. 如下代碼的輸出?為什么 (難度*)
sayName('world'); //輸出結(jié)果為hello world
sayAge(10); //輸出結(jié)果為Uncaught TypeError: sayAge is not a function
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
第一行代碼由于函數(shù)聲明提升,整個(gè)sayName函數(shù)提到代碼頭部,則在執(zhí)行sayName(“world”)時(shí)輸出正常結(jié)果;第二航代碼由于sayAge是函數(shù)表達(dá)式,則JS引擎在解析代碼時(shí),只是把var sayAge提到代碼的頭部,并未把整個(gè)函數(shù)部分提到代碼頭部,所以在執(zhí)行時(shí)會(huì)報(bào)錯(cuò)。
5. 如下代碼的輸出?為什么 (難度**)
function fn(){}
var fn = 3;
console.log(fn);//輸出3,在同一個(gè)作用域內(nèi)定義了名字相同的變量和方法的話,無(wú)論其順序如何,變量的賦值會(huì)覆蓋方法的賦值。
6. 如下代碼的輸出?為什么 (難度***)
function fn(fn2){
console.log(fn2);
var fn2 = 3;
console.log(fn2);
console.log(fn);
function fn2(){
console.log('fnnn2');
}
}
fn(10);
上面代碼相當(dāng)于:
function fn(fn2){
var fn2;//變量聲明提升
function fn2(){
console.log('fnnn2');
}//函數(shù)聲明提升
console.log(fn2);//當(dāng)函數(shù)執(zhí)行命令有沖突時(shí),函數(shù)載入的順序是變量>函數(shù)>參數(shù),此時(shí)肯定是輸出函數(shù)。
fn2=3;
console.log(fn2);//此時(shí)fn2被賦值3,因?yàn)樵谕蛔饔糜騼?nèi),定義了同一個(gè)名字的變量和方法時(shí),無(wú)論順序如何,變量的賦值會(huì)覆蓋方法的賦值。
console.log(fn);//當(dāng)函數(shù)執(zhí)行命令有沖突時(shí),函數(shù)載入的順序是變量>函數(shù)>參數(shù),此時(shí)肯定是輸出函數(shù)。
}
fn(10);
輸出結(jié)果為:
function fn2(){
console.log('fnnn2');
}
3
function fn(fn2){
console.log(fn2);
var fn2 = 3;
console.log(fn2);
console.log(fn);
function fn2(){
console.log('fnnn2');
}
}
7. 如下代碼的輸出?為什么 (難度***)
var fn = 1;
function fn(fn){
console.log(fn);
}
console.log(fn(fn));
上述代碼相當(dāng)于:
var fn;
function fn(fn){
console.log(fn);
}
fn = 1;
console.log(fn(fn));//輸出結(jié)果:Uncaught TypeError: fn is not a function(…)。在執(zhí)行時(shí),正確代碼應(yīng)該是函數(shù)(參數(shù)),在這個(gè)習(xí)題中,由于變量命名提升,代碼形式為變量(變量),那么交給瀏覽器去執(zhí)行時(shí)會(huì)輸出:fn is not a function(…)。
8. 如下代碼的輸出?為什么 (難度**)
//作用域
console.log(j);
console.log(i);
for(var i=0; i<10; i++){
var j = 100;
}
console.log(i);
console.log(j);
上述代碼相當(dāng)于:
var i;
var j;
//變量提升,將其 提到代碼頭部
console.log(i);//undefined,此時(shí)變量i還未被賦值
console.log(j);//undefined,此時(shí)變量j還未被賦值
for(var i = 0; i<10; i++){
var j = 100;
console.log(i);//10 在for循環(huán)執(zhí)行后,i為10,for循環(huán)語(yǔ)句不會(huì)前置,其定義的變量自然就是全局變量,所以能夠被解析,正常順序執(zhí)行并顯示。
console.log(j);`//100 在for循環(huán)執(zhí)行后,j為100。
9. 如下代碼的輸出?為什么 (難度****)
fn();
var i = 10;
var fn = 20;
console.log(i);
function fn(){
console.log(i);
var i = 99;
fn2();
console.log(i);
function fn2(){
i = 100;
}
}
上述代碼相當(dāng)于:
var i;
var fn;
//變量提升,將var i和var fn提升到代碼頭部
function fn(){
var i;
function fn2(){
i = 100;
}
console.log(i);//輸出undefined,因?yàn)樽兞縤聲明了但是沒(méi)有賦值
i = 99;
fn2();//執(zhí)行后i為100,覆蓋了i = 99
console.log(i);//輸出100
}
fn();
i = 10;
fn = 20;
console.log(i);//輸出為10.
10. 如下代碼的輸出?為什么 (難度*****)
var say = 0;
(function say(n){
console.log(n);
if(n<3) return;
say(n-1);
}( 10 )); //輸出10,9,8,7,6,5,4,3,2
/*function前后加了圓括號(hào),表示該函數(shù)為立即執(zhí)行函數(shù),因此會(huì)馬上執(zhí)行,尾部賦值n=10,得到n-1=9,然后n=9,繼續(xù)循環(huán),直到n=2時(shí),滿足if條件,return終止,不執(zhí)行n-1;所以最終結(jié)果為2*/
console.log(say);`//輸出0
/*因?yàn)樵谠撟饔糜蛑?,變量say已經(jīng)被賦值了0,在同一個(gè)作用域中,變量和方法同名時(shí),無(wú)論順序如何,變量的賦值會(huì)覆蓋方法的賦值*/
本文版權(quán)歸本人及饑人谷所有,轉(zhuǎn)載請(qǐng)注明出處

