函数&声明前置&作用域链

1. 函数声明和函数表达式有什么区别

JavaScript函数是指一个特定代码块,可能包含多条语句,可以通过名字来供其它语句调用以执行函数包含的代码语句。
ECMAScript规定了三种声明函数方式:

  • 构造函数(不推荐):
    首先函数也是对象的一种,我们可以通过其构造函数,使用new来创建一个函数对象。
    var sayHello = new Function("console.log('hello world');");

  • 函数声明:
    使用function关键字可以声明一个函数。

  //函数声明
  function sayHello(){
    console.log('hello');
  }
  //函数调用
  sayHello();
  • 函数表达式:
var sayHello = function(){
    console.log('hello');
  }
//函数调用
sayHello();

区别:

函数声明:function functionName(){} ; //函数声明会提前,声明不必放到调用的前面。
函数表达式:var fn = function(){} ; //函数表达式可以省略函数名,声明必须放到调用的前面。

2. 什么是变量的声明前置?什么是函数的声明前置?

  • 变量声明前置
    JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部。
    例如:
console.log(a);
var a = 3;
console.log(a); 

经过变量的声明前置,可以理解为:

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

注意:
var重复声明一个已经存在的变量,原变量值不变。
不写var会声明一个全局的变量,在编程中应该要避免的,即使真的需要全局变量,也应该在最外层作用域使用var声明。

  • 函数声明前置
    JavaScript引擎将函数名视同变量名,所以采用function声明函数时,整个函数会像var声明变量一样,被提升到代码头部。所以,形如下面的代码是不会报错的。
sayHello();
function sayHello(){
  console.log('hello');
}

声明前置后,即:

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

3. arguments 是什么?

arguments是一个类数组对象。代表传给一个function的参数列表。
可以在函数内部通过使用 arguments 对象来获取函数的所有参数。这个对象为传递给函数的每个参数建立一个条目,条目的索引号从0开始:arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。

function printPersonInfo(name, age, sex){
    //console.log(name);
    //console.log(age);
    //console.log(sex);
    console.log(arguments);
}

4. 函数的"重载"怎样实现的?

  • 重载是很多面向对象语言实现多态的手段之一,在静态语言中确定一个函数的手段是靠方法签名——函数名+参数列表,也就是说相同名字的函数参数个数不同或者顺序不同都被认为是不同的函数,称为函数重载。
  • 在JavaScript中没有函数重载的概念,函数通过名字确定唯一性,参数不同也被认为是相同的函数,后面的覆盖前面的,但可以在函数体针对不同的参数调用执行相应的逻辑。
function userInfo(name,age,sex){
      if(name){
            console.log(name);
      }
      if(age){
            console.log(age);
      }
      if(sex){
            console.log(sex);
      }
}
userInfo("andy",22);    //参数依次传递值,缺少的参数返回的是undefined
userInfo("andrea",20,"female");

5. 立即执行函数表达式是什么?有什么作用?

立即执行函数表达式(Immediately-Invoked Function Expression),简称IIFE。表示定义函数之后,立即调用该函数。

(function(){
  var a  = 1;
})()
console.log(a);    //undefined

其他写法:

    (function fn1() {});
  • 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。
  • 当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明。
  • 作用:隔离作用域。

6. 求n!,用递归来实现

function factor(n){
     if(n === 1 || n === 0){
          return 1;
     }
     if(n > 1){
          return n*factor(n-1);
     }else{
          console.log('Input Error');
     }  
}
factor(5);   //120

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('谷哥', 20, '女');    
getInfo('小谷', 30);
getInfo('男');

答案如下:

getInfo('谷哥', 20, '女');
       name : 谷哥
       age : 20
       sex : 女
       [name:谷哥, age:20,sex:女]
       name valley
       name valley

getInfo('小谷', 30);
       name:小谷
       age:30
       sex:undefined
       [name:小谷,age:30,sex:undefined]
       name valley
       name valley

getInfo('男');
       name:男
       age:undefined
       sex:undefined
       [name:男,age:undefined,sex:undefined]
       name valley
       name valley

8. 写一个函数,返回参数的平方和

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);

变量声明前置后,即:

var a
console.log(a) ; //undefined
a = 1
console.log(b) ; // b is not defined 没有声明变量b

10. 如下代码的输出是什么?为什么?

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

函数声明前置后,而函数表达式则不会前置,所以结果即:

sayAge(10);  // sayAge is not a function(报错)。 (因为函数表达式不会被前置)
function sayName(name){
    console.log('hello ', name);
}
var sayAge = function(age){
    console.log(age);
};
sayName('world');  // hello world

11. 如下代码的输出是什么?写出作用域链查找过程伪代码

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

声明提前,如下:

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

作用域链代码,如下:

1. 
   globalContext = {
         AO {
               x : 10
               foo : function
               bar : function
         },
        Scope: null
   }
   foo.[[scope]] = globalContext.AO
   bar.[[scope]] = globalContext.AO
  全局变量 x = 10
2. 调用函数bar() 
   barContext = {
        AO:{
              x : 30
              foo : function
        },
        Scope:bar.[[scope]] = globalContext.AO
    }
    函数内局部变量 x = 30

3. 调用函数foo() 
   fooContext = {
        AO:{ };
   },
   Scope:foo.[[scope]] = globalContext.AO
   //因为作用域链的关系, 全局变量 x = 10 , 所以 console.log(x) 的结果也是 10 。

12. 如下代码的输出是什么?写出作用域链查找过程伪代码

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

声明提前,如下:

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

作用域链代码,如下:

1.
   globalContext = {
        AO:{
              x : 10;
              bar : function
        },
        Scope: null
   }
   bar.[[scope]] = globalContext.AO
2. 调用函数bar()
   barContext = {
        AO:{
              x : 30;
             foo: function
        },
       Scope:bar.[[scope]] = globalContext.AO
   }
   foo.[[scope]] = barContext.AO
3. 调用函数foo()
   fooContext = {
        AO:{},
   }
   foo.[[scope]] = barContext.AO
   //调用foo时会在barContext.AO中找到x = 30。

13. 如下代码的输出是什么?写出作用域链查找过程伪代码

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

声明提前,如下:

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

作用域链代码,如下:

1.
    globalContext = {
        AO:{
            x:10
            bar:function
        },
        Scope:null
    }
    bar.[[scope]] = globalContext.AO
2.调用bar()
    barContext = {
        AO:{
            x:30
            function
        },
        Scope:bar.[[scope]] = globalContext.AO
    }
    function.[[scope]] = barContext.AO
3.调用立即执行函数
    functionContext = {
        AO:{},
        Scope:function.[[scope]] = barContext.AO
    }
调用bar函数时,由于它的活动对象中 x=30,所以当自执行函数执行时,会先在它自己的AO中查找,找不到再向bar()函数的AO中查找,所以结果为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);

作用域链代码,如下:

1.
    globalContext = {
        AO:{
            a:1
            fn:function
            fn3:function
        },
        Scope:null
    }
    fn.[[scope]] = globalContext.AO
    fn3.[[scope]] = globalContext.AO

2.调用fn()
    fnContext = {
        AO:{
            a:undefined
            fn2:function
        },
        Scope:fn.[[scope]]  = globalContext.AO
    }
    fn2.[[scope]] = fnContext.AO

3.
    fn3Context = {
        AO:{
            a:200
        },
        Scope:fn3Context.[[scope]] = globalContext.AO
    }
    fn2ConText = {
        AO:{
            a:20
        },
        Scope:fn2ConText.[[scope]] = fnContext.AO
    }
    开始执行
    console.log(a);    //undefined 打印
    var a = 5;            //fnContext中a变成5
    console.log(a);    //5
    a++ ;                  //fnContext.AO中a变为6

    调用fn3()
    fn3()中
    console.log(a);    //globalContext.AO中的a值为1,打印
    a = 200;              //globalContext.AO中的a变为200

    调用fn2()
    console.log(a);   //fnContext.AO中的a值为6,打印
    a = 20;                //fnContext.AO中的a变为20

    继续执行fn()
    console.log(a);    //fnContext.AO中的a值为20,打印

    fn()结束
    console.log(a);     //globalContext.AO中a值为200,打印

输出的结果: undefined  5  1  6  20  200

你可能感兴趣的:(函数&声明前置&作用域链)