JS的函數(shù)和作用域

1、 函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別

  • ECMAScript規(guī)定了三種聲明函數(shù)方式:構(gòu)造函數(shù)函數(shù)聲明和函數(shù)表達(dá)式。

  • 函數(shù)聲明: 函數(shù)聲明通過關(guān)鍵字function來聲明, 關(guān)鍵詞后面是函數(shù)名, 名稱后面有個小括號, 括號里面放的是函數(shù)的參數(shù), 最后是一對花括號,包括函數(shù)的代碼塊

function sum(num1,num2) {
    return num1+num2;
}

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

var sum = function(num1, num2) {
    return num1+num2;
};

定義變量sum并將一個匿名函數(shù)賦值給變量,這時這個匿名函數(shù)又稱函數(shù)表達(dá)式(Function Expression)。function關(guān)鍵字后面沒有函數(shù)名。

區(qū)別:

  1. 函數(shù)聲明最后一般不寫分號, 而函數(shù)表達(dá)式有分號
  2. 函數(shù)聲明和變量聲明都有聲明提前的特點(diǎn), 函數(shù)聲明是函數(shù)名稱和函數(shù)體均提前聲明了, 可以在聲明之前調(diào)用它;
  3. 函數(shù)表達(dá)式的規(guī)則與變量聲明提前一樣,只是函數(shù)聲明變量提前了, 但是它的賦值仍在原來的位置, 不能在函數(shù)表達(dá)式之前調(diào)用。

2. 什么是變量的聲明前置?什么是函數(shù)的聲明前置

  • 變量的聲明前置:
    變量聲明前置是指把變量的聲明提前到當(dāng)前作用域的最前面, 但變量的賦值仍然按照原來的順序執(zhí)行, 如果變量聲明但未被賦值, 變量自動賦值為undefined。
  • 函數(shù)的聲明前置:
    函數(shù)的聲明前置有兩種情況, 一個是使用函數(shù)聲明, 則整個聲明都前置, 另一個是使用函數(shù)表達(dá)式, 那么規(guī)則和變量的聲明前置一樣。

例如:

console.log(a); //undefined
var a = 3;
console.log(a); //3

sayHello();

function sayHello(){
  console.log('hello');
}

執(zhí)行的情況為:

var a;     // 變量聲明前置                     
function sayHello() {     // 函數(shù)聲明前置
    console.log('hello');
}
console.log(a); 
a = 3;
console.log(a);
sayHello();

3. arguments 是什么

  • arguments是一個類數(shù)組對象, 代表傳給一個function的參數(shù)列表, 只在函數(shù)內(nèi)部起作用; arguments的值與函數(shù)傳入?yún)?shù)有關(guān), 與定義參數(shù)無關(guān)。
  • 可以使用arguments對象在函數(shù)中引用函數(shù)的參數(shù)。此對象包含傳遞給函數(shù)的每個參數(shù)的條目,第一個條目的索引從0開始。例如,如果一個函數(shù)傳遞了三個參數(shù),你可以以如下方式引用他們:
arguments[0]
arguments[1]
arguments[2]

參數(shù)也可以被設(shè)置:
arguments[1] = 'new value';

  • arguments對象不是一個 Array 。它類似于Array,但除了長度之外沒有任何Array屬性
  • 屬性
    1.arguments.callee
    指向當(dāng)前執(zhí)行的函數(shù)。
    2.arguments.caller
    指向調(diào)用當(dāng)前函數(shù)的函數(shù)。
    3.arguments.length
    指向傳遞給當(dāng)前函數(shù)的參數(shù)數(shù)量。
    4.arguments[@@iterator]
    返回一個新的Array迭代器對象,該對象包含參數(shù)中每個索引的值。

4. 函數(shù)的"重載"怎樣實(shí)現(xiàn)

  • 重載是很多面向?qū)ο笳Z言實(shí)現(xiàn)多態(tài)的手段之一, 相同名字的函數(shù)參數(shù)個數(shù)不同, 或者順序不同, 都被認(rèn)為是不同的函數(shù).
  • 如果定義了兩個名字相同的函數(shù),則該名字屬于后定義的函數(shù)。
  • ECMAscript函數(shù)不能像傳統(tǒng)意義上的那樣實(shí)現(xiàn)重載, ECMAscript函數(shù)沒有簽名,其參數(shù)是由包含零個或多個值的數(shù)組來表示的。而沒有函數(shù)簽名,真正的重載是不可能做到的。但可以在函數(shù)體針對不同的參數(shù)調(diào)用執(zhí)行相應(yīng)的邏輯
function printPeopleInfo(name, age, sex){
    if(name){
      console.log(name);
    }

    if(age){
      console.log(age);
    }

    if(sex){
      console.log(sex);
    }
  }

  printPeopleInfo('Byron', 26);
  printPeopleInfo('Byron', 26, 'male');

也可以用arguments.length來實(shí)現(xiàn)重載。

5. 立即執(zhí)行函數(shù)表達(dá)式是什么?有什么作用

  • 在JavaScript中, 一對圓括號()是一種運(yùn)算符, 跟在函數(shù)明后, 表示調(diào)用該函數(shù); 有時我們希望在定義函數(shù)之后, 立即調(diào)用該函數(shù); 這時, 不能再函數(shù)的定義之后加圓括號, 會產(chǎn)生語法錯誤; 因?yàn)閒unction這個關(guān)鍵字既可以當(dāng)做語句, 也可當(dāng)做表達(dá)式; 為了避免解析上的歧義, JavaScript引擎看到行首是function關(guān)鍵字之后, 認(rèn)為這一段都是函數(shù)的定義 , 不應(yīng)該以圓括號結(jié)尾, 所以報(bào)錯;
  • 解決方法就是不要讓function出現(xiàn)在行首, 讓引擎將其理解外一個表達(dá)式, 最簡單的方式, 是將其放在圓括號內(nèi)
(function(){
  var a  = 1;
})()
console.log(a); //undefined

其他寫法

// 在數(shù)組初始化器內(nèi)只能是表達(dá)式
[function fn2() {}]();
 
// 逗號也只能操作表達(dá)式
1, function fn3() {}();

作用:將全局變量與局部變量分隔開,保證全局變量不受污染。

6. 求n!,用遞歸來實(shí)現(xiàn)

function factorial(num) {
  if (num === 1 || num === 0) {
    return 1;
  }
  else if (num < 0) {
    return console.log("minus has no factorial");
  }
  return num*arguments.callee(num-1);
}
console.log(factorial(7));

7. 以下代碼輸出什么

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('饑人谷', 2, '男');
getInfo('小谷', 3);
getInfo('男');

輸出結(jié)果:

name:饑人谷
age:2
sex:男
["饑人谷", 2, "男"]
name:valley
name:小谷
age:3
sex:undefined
[“小谷”, “3”]
name:valley
name:男
age:undefined
sex:undefined
["男"]
name:valley

8.寫一個函數(shù),返回參數(shù)的平方和

function sumOfSquares(){
   var sum = 0;
    for(var i = 0; i < arguments.length; i++) {
        sum = sum + arguments[i] * arguments[i];
    }
    return sum;
   }
   var result = sumOfSquares(2,3,4)
   var result2 = sumOfSquares(1,3)
   console.log(result)  //29
   console.log(result2)  //10

9.如下代碼的輸出?為什么

console.log(a);
    var a = 1;
    console.log(b);

聲明前置實(shí)際為:

var a;
console.log(a);  //undefined
a = 1;
console.log(b); //Uncaught ReferenceError: b is not defined

console.log(a); 這個時候a還沒有賦值; b變量沒有聲明和定義, 所以報(bào)錯。

10.如下代碼的輸出?為什么

sayName('world');
    sayAge(10);
    function sayName(name){
        console.log('hello ', name);
    }
    var sayAge = function(age){
        console.log(age);
    };

聲明前置實(shí)際為:

function sayName(name){
  console.log('hello', name);
}
var sayAge
sayName('world');   //hello world
sayAge(10);    // Uncaught TypeError: sayAge is not a function
sayAge = function(age){
  console.log(age)
};

函數(shù)聲明在作用域內(nèi)會前置, 函數(shù)表達(dá)式不會前置,所以函數(shù)表達(dá)式需要放在調(diào)用的前面,后一個函數(shù)調(diào)用的時候還未被定義,所以出現(xiàn)報(bào)錯。

11. 如下代碼輸出什么? 寫出作用域鏈查找過程偽代碼

var x = 10
bar() 
function foo() {
  console.log(x)
}
function bar(){
  var x = 30
  foo()
}

作用域鏈查找過程偽代碼:

globalContext= {
  AO: {
    x: 10
    foo: funtion
    bar: funtion
         },
        Scope: null
}
//聲明 foo 時 得到下面
foo.[[scope]] = globalContext.AO
//聲明 bar 時 得到下面
bar.[[scope]] = globalContext.AO
當(dāng)調(diào)用 bar() 時, 進(jìn)入 bar 的執(zhí)行上下文
barContext = {
  AO: {
    x: 30
  },
  Scope: bar.[[scope]] //globalContext.AO
}
當(dāng)調(diào)用 foo() 時,先從 bar 執(zhí)行上下文中的 AO里找,找不到再從 bar 的 [[scope]]里找到后即調(diào)用
當(dāng)調(diào)用 foo() 時,進(jìn)入 foo 的執(zhí)行上下文
fooContext = {
  AO: {},
  Scope: foo.[[scope]] // globalContext.AO
}
所以 console.log(x)是 10

12.如下代碼輸出什么? 寫出作用域鏈查找過程偽代碼

var x = 10;
bar() 
function bar(){
  var x = 30;
  function foo(){
  console.log(x) 
  }
  foo();
}       

作用域鏈查找過程偽代碼:

globalContext = {
  AO: {
    x: 10
    bar: function
  },
  Scope: null
}
//聲明 bar 時 得到下面
bar.[[scope]] = globalContext.AO
當(dāng)調(diào)用 bar() 時, 進(jìn)入 bar 的執(zhí)行上下文
barContext = {
  AO: {
    x: 30,
    foo: function
  },
  Scope: bar.[[scope]] //globalContext.AO
}
//在 bar 的執(zhí)行上下文里聲明 foo 時 得到下面
foo.[[scope]] = barContext.AO
當(dāng)調(diào)用 foo() 時,先從 bar 執(zhí)行上下文中的 AO里找,找到后即調(diào)用
當(dāng)調(diào)用 foo() 時,進(jìn)入 foo 的執(zhí)行上下文
fooContext = {
  AO: {},
  Scope: foo.[[scope]] // barContext.AO
}
所以 console.log(x)是 30

13.以下代碼輸出什么? 寫出作用域鏈的查找過程偽代碼

var x = 10;
bar() 
function bar(){
  var x = 30;
  (function (){
    console.log(x)
  })()
}

作用域鏈查找過程偽代碼:

globalContext = {
  AO: {
    x: 10,
    bar: function
  },
  Scope: null
}
//聲明bar時
bar.[[scope]] = globalContext.AO
//調(diào)用bar時,進(jìn)入bar的執(zhí)行上下文
barContext = {
  AO: {
    x: 30,
    function//立即執(zhí)行函數(shù)表達(dá)式
  },
  Scope: bar.[[scope]] // globalContext.AO
//執(zhí)行立即執(zhí)行函數(shù)
functionContext = {
  AO:{ }
},
Scope: function.[[scope]] //barContext.AO
所以console.log(x)為30。

14.以下代碼輸出什么? 寫出作用域鏈查找過程偽代碼

var a = 1;
function fn(){
  console.log(a)
  var a = 5
  console.log(a)
  a++
  var a
  fn3()
  fn2()
  console.log(a)
  function fn2(){
    console.log(a)
    a = 20
  }
}
function fn3(){
  console.log(a)
  a = 200
}
fn()
console.log(a)

作用域鏈查找過程偽代碼:

globalContext = {
  AO: {
    a: 1,
    fn: function,
    fn3: function
  },
  Scope: null
}
//聲明fn時
fn.[[scope]] = globalContext.AO
//聲明fn3時
fn3.[[scope]] = globalContext.AO
調(diào)用fn,進(jìn)入fn的執(zhí)行上下文
fnContext = {
  AO: {
    a: 5
    fn2: function
  },
  Scope: fn.[[scope]] //globalContext.AO
}
//調(diào)用fn3,此時在fn的執(zhí)行上下文的AO中找不到fn3,于是在fn的scope里找,找到后即調(diào)用
fn3Context = {
  AO: {
    a: 200
  },
  Scope: fn3.[[scope]] //globalContext.AO
}
//調(diào)用fn2,在bar的執(zhí)行上下文AO中可以找到fn2,直接調(diào)用
fn2Context = {
AO: {
    a: 20
  },
  Scope: fn2.[[scope]] //fnContext.AO
}
//輸出結(jié)果如下:
//調(diào)用fn
console.log(a) // a的值在fnContext.AO中,此時a變量聲明了但未定義,所以輸出undefined。
var a = 5 // a被賦值為5
console.log(a) // 輸出5
a++ // fnContext.AO中a的值變?yōu)?
//調(diào)用fn3
fn3() //此時a的值為globalContext.AO里的a,即console.log(a) 輸出1
a = 200 // globalContext.AO里的a值變?yōu)?00
//調(diào)用fn2
fn2() //此時a的值為fnContext.AO里的a,即console.log(a) 輸出6
a = 20 // fnContext.AO里的a變?yōu)?0
console.log(a) //輸出20
//fn調(diào)用結(jié)束,此時 globalContext.AO里的a值為200
console.log(a) // 輸出200
// 輸出順序?yàn)椋簎ndefined, 5, 1, 6, 20, 200
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 2,367評論 0 21
  • 1.函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 函數(shù)就是一段可以反復(fù)調(diào)用的代碼塊。函數(shù)還能接受輸入的參數(shù),不同的參數(shù)會返回不同...
    徐國軍_plus閱讀 531評論 0 0
  • 函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 函數(shù)聲明語法:function functionName(arg0,arg1,ar...
    _Dot912閱讀 661評論 0 3
  • 1.函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 (*) 區(qū)別: 函數(shù)聲明后面的分號可加可不加,不加也不影響接下來語句的執(zhí)行,但...
    Sheldon_Yee閱讀 471評論 0 1
  • 月光的平靜是內(nèi)心的獨(dú)白 于是思念被升華為一道無形的傷口 窗外的人們嘻笑著打破這孤寂 他們?nèi)绾味梦矣性鯓拥墓适?風(fēng)...
    走來走去的孫小皮閱讀 208評論 0 1

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