任務(wù)17-函數(shù)

問(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ù)聲明示例

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

函數(shù)表達(dá)式示例

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)注明出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容