問答:
1. 函數(shù)聲明和函數(shù)表達式有什么區(qū)別 (*)
在日常的任務中,JavaScript主要使用下面兩種方式創(chuàng)建函數(shù):
- 函數(shù)聲明:
function sayName(){
console.log('hunger');
}
sayName();
- 函數(shù)表達式
var sayName = function(){
console.log('hunger');
};
區(qū)別:
a. 函數(shù)聲明必須以function開頭直晨,否則谤草,則為表達式。
b. 函數(shù)表達式在語句的結尾要加上分號抵窒,表示語句結束,而函數(shù)聲明則不需要在結尾加上分號叠骑。
c. 函數(shù)聲明在JS解析的時候會進行函數(shù)提升李皇,即在同一個作用域內,不管函數(shù)聲明在哪里創(chuàng)建宙枷,它會提升至作用域頂部掉房。
但是函數(shù)表達式不會被提升,它的表現(xiàn)和聲明變量提升的規(guī)則相同慰丛,也就是說卓囚,函數(shù)名(這里指var后面定義的變量名)會聲明前置,但是函數(shù)表達式的賦值沒有被提前诅病,操作還是在原來的位置哪亿。
2. 什么是變量的聲明前置粥烁?什么是函數(shù)的聲明前置 (**)
-
變量的聲明前置:JavaScript引擎的工作方式是,先解析代碼蝇棉,獲取所有被聲明的變量讨阻,然后再一行一行地運行。這造成的結果篡殷,就是所有的變量的聲明語句变勇,都會被提升到代碼的頭部,這就叫做變量提升贴唇。
如:
上面運行的代碼會報錯搀绣,是因為我們沒有聲明變量xxx。
接下來我們將聲明變量xxx
我們發(fā)現(xiàn)戳气,變量是在語句調用之后定義的链患,但是結果并沒有報錯。這就是變量聲明前置的作用瓶您。JS解析器會把當前作用域內聲明的所有變量和函數(shù)都放到作用域的開始麻捻。但是對于變量來說,它只是把變量的聲明提到了作用域的開始呀袱,而變量的賦值仍然按照順序執(zhí)行贸毕。那么沒有被賦值的變量會自動賦值為undefined,所以此處運行的結果為初始值undefined夜赵。它等同于如下代碼:
var xxx;
console.log(xxx);
xxx = 2;
再來看看如下的列子:
我們先聲明一個全局變量xxx明棍,然后再函數(shù)內部定義一個局部變量。我們希望是第一次打印出來的結果是全局范圍內定義的xxx變量寇僧,第二次打印出來的是局部變量xxx的值摊腋。但是輸出結果并沒有跟我們設想的一樣,原因就是定義的局部變量在其作用域內聲明提前了嘁傀。故第一次打印出來的是沒有賦值的undefined兴蒸,第二次打印出12。它等同于如下代碼:
xxx = 2;
(function(){
var xxx;
console.log(xxx);
xxx = 12;
console.log(xxx);
})();
- 函數(shù)的聲明前置:和變量的聲明會前置一樣细办,函數(shù)聲明同樣會前置橙凳,如果我們使用函數(shù)表達式那么規(guī)則和變量一樣;如果我們使用函數(shù)聲明的方式笑撞,那么即使函數(shù)寫在最后也可以在前面語句調用岛啸,前提是函數(shù)聲明部分已經被下載到本地。
-
函數(shù)表達式:
在上面代碼中娃殖,變量sayAge被前置了值戳,但是它的賦值并沒有被提前,這樣就印證了函數(shù)表達式的提前和變量提前是一回事了炉爆。上面的代碼等同于:
var sayAge;
sayAge(10);
sayAge = function(age){
console.log(age);
}
-
函數(shù)聲明:
函數(shù)聲明并不僅僅是函數(shù)名被提前了,整個函數(shù)的定義都被提前了。它等同于如下代碼:
function fn(){
console.log('1');
}
fn();
總結:
- 變量聲明會提前到作用域的頂部芬首,而賦值會被保留在原地赴捞,仍然按照次序執(zhí)行。
- 函數(shù)表達式是變量前置了郁稍,但是賦值沒有提前赦政。和變量提升一樣。
- 函數(shù)聲明整個會被前置到變量聲明的后面耀怜,即使函數(shù)寫在最后面也可以在前面語句調用恢着。
那么,我們在分析代碼的時候财破,可以把變量和函數(shù)聲明放在作用域的頂部掰派,這樣分析出來的結果一般不容易出錯。
3. arguments 是什么 (*)
arguments是JS里的一個內置對象,它只在函數(shù)內部有效左痢,可以在函數(shù)內部通過使用arguments對象來獲取函數(shù)的所有參數(shù)靡羡。這個對象為傳遞給函數(shù)的每個參數(shù)建立一個條目,條目的索引從0開始俊性。參數(shù)也可以重新被賦值略步。
arguments對象就像數(shù)組(array),但是它卻不是真正的數(shù)組定页,除了length趟薄,它沒有數(shù)組所特有的屬性和方法。
當一個函數(shù)的參數(shù)數(shù)量比它顯示聲明參數(shù)數(shù)量更多時候典徊,我們就可以使用arguments對象獲取到該函數(shù)的所有傳入參數(shù)竟趾。
如:
4. 函數(shù)的重載怎樣實現(xiàn) (**)
重載是很多面向對象語言實現(xiàn)多態(tài)的手段之一,在靜態(tài)語言中確定一個函數(shù)的手段是靠方法簽名——函數(shù)名+參數(shù)列表宫峦,也就是說相同名字的函數(shù)參數(shù)個數(shù)不同或者順序不同都被認為是不同的函數(shù)岔帽,稱為函數(shù)重載。
在JavaScript中沒有函數(shù)重載的概念导绷,函數(shù)通過名字確定唯一性犀勒,參數(shù)不同也被認為是相同的函數(shù),后面的覆蓋前面的妥曲。
在JavaScript中可以通過arguments來實現(xiàn)函數(shù)的重載贾费,如:
5. 立即執(zhí)行函數(shù)表達式是什么?有什么作用 (***)
立即執(zhí)行函數(shù)表達式(Immediately-Invoked Function Expression)檐盟,是將函數(shù)定義放在一個圓括號里褂萧,讓JavaScript引擎將其理解為一個表達式,再在函數(shù)的定義后面加一個()
葵萎,以達到定義函數(shù)后立即調用該函數(shù)的效果导犹。有下面兩種寫法:
<pre>
(function(){ /code/ }());
(function(){ /code/ })();</pre>
如:
作用:
- 封裝大量的工作而不會在背后遺留任何全局變量唱凯。
- 定義的所有變量都會成為立即執(zhí)行函數(shù)的局部變量,所以不用擔心這些臨時變量會污染全局空間谎痢。
- 可以將獨立的功能封裝在自包含模塊中磕昼。
注意:立即執(zhí)行函數(shù)通常作為一個單獨模塊使用。一般沒有問題节猿,但是票从,建議在自己寫的立即執(zhí)行函數(shù)前加分號,這樣可以有效地與前面代碼進行隔離滨嘱。否則峰鄙,可能出現(xiàn)意想不到的錯誤。
6. 什么是函數(shù)的作用域鏈 (****)
- 作用域:作用域就是變量與函數(shù)的可訪問范圍太雨,即作用域控制著變量與函數(shù)的可見性和生命周期吟榴。在JavaScript中,變量的作用域有全局作用域和局部作用域兩種躺彬。
- 全局作用域:在代碼中任何地方都能訪問到的對象擁有全局作用域煤墙,一般來說以下幾種情形擁有全局作用域:
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ù)并不擁有全局作用域
b. 所有未定義直接賦值的變量自動聲明為擁有全局作用域
function fn(){
var a = 2;
b = 3;
console.log(a);
}//變量b用于全局作用域仿野,而變量a則不是。
c. 所有window對象的屬性擁有全局作用域
- 局部作用域:和全局作用域相反她君,局部作用域一般只在固定的代碼片段內可訪問到脚作,最常見的如函數(shù)內部,所以在一些地方也會看到有人把這種作用域稱為函數(shù)作用域缔刹。
function fn(){
var a = 2;
function fn2(){
console.log(a);
}
fn2();
} //在此代碼中變量a和函數(shù)fn2用于局部作用域
- 作用域鏈:當代碼在一個環(huán)境中執(zhí)行時球涛,會創(chuàng)建變量對象的一個作用域鏈(scope chain)。作用域鏈的用途校镐,是保證對執(zhí)行環(huán)境有權訪問的所有變量和函數(shù)的有序訪問亿扁。作用域鏈的前端,始終都是當前執(zhí)行的代碼所在環(huán)境的變量對象鸟廓。如果這個環(huán)境是函數(shù)从祝,則將其活動對象(activation object)作為變量對象∫眨活動對象在最開始時只包含一個變量牍陌,即arguments對象(這個對象在全局環(huán)境中是不存在的)。作用域鏈中的下一個變量對象來自包含(外部)環(huán)境员咽,而再下一個變量對象則來自下一個包含環(huán)境毒涧。這樣,一直延續(xù)到全局執(zhí)行環(huán)境贝室;全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈中的最后一個對象契讲。
參考:
JavaScript 開發(fā)進階:理解 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('男');
輸出結果:
//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. 寫一個函數(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,因為變量提升了误趴,但是沒有賦值
var a = 1;
console.log(b);//輸出b is not defined霹琼,因為變量b沒有被申明`
4. 如下代碼的輸出?為什么 (難度*)
sayName('world'); //輸出結果為hello world
sayAge(10); //輸出結果為Uncaught TypeError: sayAge is not a function
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
第一行代碼由于函數(shù)聲明提升凉当,整個sayName函數(shù)提到代碼頭部枣申,則在執(zhí)行sayName(“world”)時輸出正常結果;第二航代碼由于sayAge是函數(shù)表達式看杭,則JS引擎在解析代碼時忠藤,只是把var sayAge提到代碼的頭部,并未把整個函數(shù)部分提到代碼頭部楼雹,所以在執(zhí)行時會報錯模孩。
5. 如下代碼的輸出?為什么 (難度**)
function fn(){}
var fn = 3;
console.log(fn);//輸出3贮缅,在同一個作用域內定義了名字相同的變量和方法的話榨咐,無論其順序如何,變量的賦值會覆蓋方法的賦值谴供。
6. 如下代碼的輸出块茁?為什么 (難度***)
function fn(fn2){
console.log(fn2);
var fn2 = 3;
console.log(fn2);
console.log(fn);
function fn2(){
console.log('fnnn2');
}
}
fn(10);
上面代碼相當于:
function fn(fn2){
var fn2;//變量聲明提升
function fn2(){
console.log('fnnn2');
}//函數(shù)聲明提升
console.log(fn2);//當函數(shù)執(zhí)行命令有沖突時,函數(shù)載入的順序是變量>函數(shù)>參數(shù)桂肌,此時肯定是輸出函數(shù)数焊。
fn2=3;
console.log(fn2);//此時fn2被賦值3,因為在同一作用域內崎场,定義了同一個名字的變量和方法時佩耳,無論順序如何,變量的賦值會覆蓋方法的賦值谭跨。
console.log(fn);//當函數(shù)執(zhí)行命令有沖突時干厚,函數(shù)載入的順序是變量>函數(shù)>參數(shù),此時肯定是輸出函數(shù)饺蚊。
}
fn(10);
輸出結果為:
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));
上述代碼相當于:
var fn;
function fn(fn){
console.log(fn);
}
fn = 1;
console.log(fn(fn));//輸出結果:Uncaught TypeError: fn is not a function(…)。在執(zhí)行時污呼,正確代碼應該是函數(shù)(參數(shù))裕坊,在這個習題中,由于變量命名提升燕酷,代碼形式為變量(變量)籍凝,那么交給瀏覽器去執(zhí)行時會輸出: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);
上述代碼相當于:
var i;
var j;
//變量提升饵蒂,將其 提到代碼頭部
console.log(i);//undefined声诸,此時變量i還未被賦值
console.log(j);//undefined,此時變量j還未被賦值
for(var i = 0; i<10; i++){
var j = 100;
console.log(i);//10 在for循環(huán)執(zhí)行后退盯,i為10彼乌,for循環(huán)語句不會前置,其定義的變量自然就是全局變量渊迁,所以能夠被解析慰照,正常順序執(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;
}
}
上述代碼相當于:
var i;
var fn;
//變量提升箱叁,將var i和var fn提升到代碼頭部
function fn(){
var i;
function fn2(){
i = 100;
}
console.log(i);//輸出undefined墅垮,因為變量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前后加了圓括號算色,表示該函數(shù)為立即執(zhí)行函數(shù),因此會馬上執(zhí)行孤个,尾部賦值n=10,得到n-1=9剃允,然后n=9,繼續(xù)循環(huán),直到n=2時齐鲤,滿足if條件斥废,return終止,不執(zhí)行n-1给郊;所以最終結果為2*/
console.log(say);`//輸出0
/*因為在該作用域中牡肉,變量say已經被賦值了0,在同一個作用域中淆九,變量和方法同名時统锤,無論順序如何,變量的賦值會覆蓋方法的賦值*/
本文版權歸本人及饑人谷所有炭庙,轉載請注明出處