zj3 函數與作用域

講解函數聲明、函數表達式、聲明前置、作用域、作用域鏈相關概念

  1. 函數聲明和函數表達式有什么區別
  1. 什么是變量的聲明前置?什么是函數的聲明前置
  2. arguments 是什么
  3. 函數的"重載"怎樣實現
  4. 立即執行函數表達式是什么?有什么作用
  5. 求n!,用遞歸來實現
  6. 以下代碼輸出什么?

1. 函數聲明和函數表達式有什么區別

  • 解析過程的區別
  1. 聲明總是在作用域開始時先行解析;也就是會聲明前置。
    因此聲明不必放到調用的前面,函數表達式聲明必須放到調用的前面
  2. 表達式在遇到時候才運算。
alert(fn()); //輸出Helloworld!   
 
function fn() {
return 'Helloworld!';
}
//函數 fn 是在 alert 后面聲明的。但是,在alert 執行的時候,fn已經有定義了
  • 函數聲明 必須始終帶有一個標識符(Identifier),也就是我們所說的函數名,而函數表達式則可以省略。
function Identifier ( FormalParameterList opt){ FunctionBody }
 //函數聲明
function Identifier opt( FormalParameterList opt){ FunctionBody }  
//具名函數表達式
function ( FormalParameterList opt){ FunctionBody }  
//匿名函數表達式
  • ECMAScript是通過上下文來區分這兩者的:假如 function foo(){} 是一個賦值表達式的一部分,則認為它是一個函數表達式。而如果 function foo(){} 被包含在一個函數體內,或者位于程序(的最上層)中,則將它作為一個函數聲明來解析。
function foo(){}; // 聲明,因為它是程序的一部分
 
var bar = function foo(){}; // 表達式,因為它是賦值表達(AssignmentExpression)的一部分
 
new function bar(){}; // 表達式,因為它是New表達式(NewExpression)的一部分
 
(function(){
    function bar(){}; // 聲明,因為它是函數體(FunctionBody)的一部分
})();
 
(function foo(){}); // 函數表達式:注意它被包含在分組操作符中,而分組操作符只能包含表達式
 
try {
(var x = 5); // 分組操作符只能包含表達式,不能包含語句(這里的var就是語句)
}
catch(err) {
// SyntaxError(因為“var x = 5”是一個語句,而不是表達式——對表達式求值必須返回值,但對語句求值則未必返回值。——譯
}

詳細請參考:
JavaScript中的函數聲明和函數表達式區別淺析

2. 什么是變量的聲明前置?什么是函數的聲明前置

  • 變量的聲明前置
    所有的變量聲明語句,都會被提升到代碼的頭部,然后給他初始值undefined,然后才逐句執行程序,這就叫做“變量提升”,也即“變量的聲明前置”。
var a = 1;
function main() {
    console.log(a);
    var a = 2;
}
main()//輸出undefined

解析如下:

var a = 1;
function main() {
    var a;        //這時的a是undefined
    console.log(a);
    a = 2;
}
  • 函數的聲明前置
console.log(fn());   //雖然函數fn()寫在后面,但是由于函數的聲明前置,所以在調用fn()的時候函數是已經被解析的。
function fn(){
    console.log('hello')
}
//輸出hello

3. arguments 是什么

Arguments是個類似數組但不是數組的對象,說他類似數組是因為其具備數組相同的訪問性質及方式,能夠由arguments[n]來訪問對應的單個參數的值,并擁有數組長度屬性length。還有就是arguments對象存儲的是實際 傳遞給函數的參數,而不局限于函數聲明所定義的參數列表,而且不能顯式創建 arguments 對象。

function howManyArgs() {
  console.log(arguments.length);
}
howManyArgs("string", 45);
howManyArgs();
howManyArgs(12);
//依次顯示2,0,1

4. 函數的"重載"怎樣實現

C++允許在同一范圍中聲明幾個功能類似的同名函數,但是這些同名函數的形式參數(指參數的個數、類型或者順序)必須不同,也就是說用同一個運算符完成不同的運算功能。這就是重載函數。

但是JS是不允許重載的,同名函數會覆蓋。只能通過一些判斷來模擬重載。

function doAdd() {
  if(arguments.length == 1) {
    console.log(arguments[0] + 5);
  } else if(arguments.length == 2) {
   console.log(arguments[0] + arguments[1]);
  }
}
doAdd(10);//輸出 15
doAdd(10,20);//輸出 30

5. 立即執行函數表達式是什么?有什么作用

立即執行函數就是
聲明一個匿名函數,并馬上調用這個匿名函數

表達式

[function fn(){var a=3}]()
,function fn(){var a=3}()
!function fn(){var a=3}()

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

(function fn(){var a=3})
var a=5;

詳細請看:
什么是立即執行函數?有什么作用?

6. 求n!,用遞歸來實現

function fn(n){
    if(n===1){
          return 1
}
    else{
          return n*fn(n-1)
}
}

fn(4)//輸出24

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, '男');
>//輸出結果
 name: 饑人谷
 age: 2
 sex: 男
 ["饑人谷", 2, "男", callee: function, Symbol(Symbol.iterator): function]
name valley
getInfo('小谷', 3);
//
name: 小谷
 age: 3
 sex: undefined
 ["小谷", 3, callee: function, Symbol(Symbol.iterator): function]
 name valley
getInfo('男');
name: 男
age: undefined
sex: undefined
 ["男", callee: function, Symbol(Symbol.iterator): function]

8. 寫一個函數,返回參數的平方和?


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

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

    console.log(a);
    var a = 1;
    console.log(b);  //輸出undefined,同時報錯:b is not defined。因為變量聲明會前置,并賦值undefined
                    即var a=undefined
                        console.log(a);
                        console.log(b);
                        a=1

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

    sayName('world');
    sayAge(10);
    function sayName(name){
        console.log('hello ', name);
    }
    var sayAge = function(age){
        console.log(age);
    };//輸出hello  world,同時報錯sayAge is not a function。因為這是一個函數表達式,聲明必須放到調用的前面,而這里聲明放在了后面。

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

var x = 10
bar() 
function foo() {
  console.log(x)  //輸出10
}
function bar(){ 
  var x = 30
  foo()           //此時barContext: Ao中沒有foo,轉去bar.scope即globalContext中發現有foo,因此還是輸出10
}//輸出10
globalContext:{
  Ao{ x:10;
      foo:function;
      bar:function;
      }
}
  foo[[scope]]=globalContext.Ao
  bar[[scope]]=globalContext.Ao

fooContext:{
  Ao:{}
scope:globalContext.Ao
}

barContext:{
  Ao:{x:30}
scope:globalContext.Ao
}


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

var x = 10;
bar()             //依舊為30
function bar(){
  var x = 30;
  function foo(){
    console.log(x) //輸出30,fooContext.Ao為空,轉去scope即barContext.Ao,有x:30,yinci shuchu 30
  }
  foo();  //調用foo,因此還是30
}//輸出30
globalContext:{
  Ao{ x:10;
      bar:function;
      }
}
  bar[[scope]]=globalContext.Ao


barContext:{
   Ao:{x:30
      foo:function
}
foo[[scope]]=barContext.Ao 
}

fooContext:{
  Ao:{}
scope:barlContext.Ao
}

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

var x = 10;
bar() 
function bar(){
  var x = 30;
  (function (){
    console.log(x)
  })()//這是立即執行函數表達式,外部的var x = 10,并不會影響里面的執行。輸出30
}//輸出30
globalContext:{
  Ao{ x:10;
      bar:function;
      }
}
  bar[[scope]]=globalContext.Ao


barContext:{
   Ao:{x:30}
foo[[scope]]=barContext.Ao 
}

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

var a = 1;
function fn(){
  console.log(a)//輸出undefined
  var a = 5
  console.log(a)//輸出5
  a++
  var a
  fn3()//調用fn3函數,最后輸出1
  fn2()//調用fn2函數,最后輸出20
  console.log(a)//輸出20

  function fn2(){
    console.log(a)
    a = 20
  }//因上方調用調用fn2函數,輸出20
}

function fn3(){
  console.log(a)
  a = 200
}//因上方調用調用fn3函數,輸出1

fn()
console.log(a)//輸出200


依次輸出undefined,5,1,6,2,200
globalContext:{
  Ao:{a:1-->200
    fn:function
    fn3:function
}
}
fn[[scope]]=globalContext.Ao
fn3[[scope]]=globalContext.Ao

fnContext:{
    Ao:{
    a:undefined-->5-->6-->20
    fn2:function
}
scope:globalContext.Ao
}

fn3Context{
Ao:{}
scope:globalContext.Ao
}

fn2Context{
Ao:{}
scope:fnContext.Ao
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容