HOME/Articles/

JS函数与作用域

Article Outline

JS函数与作用域

<!--more-->

#作用域 在JS中(非ES6),只有函数作用域,没有块作用域。 例如,for循环,while等{}内部的变量其实是和外部处于同一个作用域的:

  for (var i =1; i < 5; i ++) {
      var a = 3;
  }
  console.log(a); //3  此时没有输出undefined,说明a和for循环内部的a是同一个作用域。

所以只有函数作用域

    function fn () {
        var a = 1;
        if (a > 2) {
            var b = 3;
        }
        console.log(b);
    }
    fn();
    console.log(a);

上述代码的运行结果等价于:

    function fn () {
        var a; // 进行变量的提升
        var b; // 定义b是在if的{}内进行的,但是没有块作用域,实质上作用域还是fn内部,a和b是同一个作用域
        a = 1;
        if (a > 2) { //a没有满足条件,所以不会执行给b赋值的语句,所以n仅仅声明了但是没有赋值,console.log(b)的结果是undefined
            b = 3;
        }
        console.log(b); //undefined
    }
    fn();
    console.log(a); // a is not defined  报错,因为在当前的作用域即全局作用域中,a并没有定义,a只在fn的作用域里定义了。

var

  • var如果重复声明一个已经存在的变量时,原来的变量的值是不会变的。
    var a =1;
    var a;
    var a;
    var a;
    console.log(a); // 1      重复声明不会改变。
  • 不加var的作用
  • 不写var会声明一个全局变量*,所以不建议不写var,即使需要全局变量,也要在全局作用域中使用var声明变量。
       function fn () {
           a = 1; //没有用var声明,a其实是一个全局变量,在外部作用域中也能访问
       }
       fn();
       console.log(a);//1 说明全局作用域中也能访问a

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

有三种声明函数的方式:

  • 构造函数:var doSomething = new Function("console.log('hello,deejay')"); 不推荐使用
  • 函数声明:
    function doSomething () {    // 函数声明
          console.log('hello,deejay');
    }
    doSomething();    // 调用也可以放到声明的前面
  • 函数表达式:
    var doSomething = function () {  //函数表达式
      console.log("hello,deejay");  
    }
    doSomething(); // 表达式调用只能写在赋值声明后面

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

var和function的声明前置:在一个作用域下,var声明的变量和function声明的函数会前置

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

    sayHello();

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

上述代码在解析时其实为

    var a;
    function sayHello() {console.log('hello,deejay');} //解析时,var声明的变量和function声明的函数 会前置


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

另外,如果有变量名和函数声明的函数名相同的情况,后面的值会覆盖前面的值,产生报错。 对于函数表达式定义的函数,前置的方式跟var一个变量没什么区别。

    console.log(sayHello);//undefined  

    var sayHello = function () {
        console.log('hello,deejay');
    }

    sayHello(); // 特别要注意,对于函数表达式定义的函数,只能先定义,然后再调用,不然会报错。

上述代码等价于

    var sayHello;

    console.log(sayHello);//undefined

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

    sayHello(); // 特别要注意,对于函数表达式定义的函数,只能先定义,然后再调用,不然会报错。
  • 函数内部的声明前置 在一个作用域内,var定义的变量和function声明的函数会前置,那么在函数内部的作用域中,前置规则也是一样的。

          function doSomething () {
          console.log(a); // undefined
          var a = 3;
          console.log(a); //3
    
          //上面代码其实等价于下面代码:
    //        var a;
    //        console.log(a); // undefined
    //        a = 3;
    //        console.log(a); //3
      }
      doSomething(); //调用函数,进入函数作用域
  • 变量和函数命名冲突时 当命名发生冲突时,先进行前置,再进行覆盖

      var fn = 3;
      function fn () {}
      console.log(fn); //3

    上述代码等价于

      var fn;
      function fn() {} //此时fn为函数
    //    console.log(typeof fn); //function
      fn = 3;
    //    console.log(typeof fn); //number
      console.log(fn); //3

    同理:

      function fn() {}
      var fn = 3;
      console.log(fn); //3

    等价于

      function fn() {}  // fn为一个全局函数
      var fn;   // 前面fn函数已经存在,此时兵没有给fn赋值,所以fn仍然是一个函数
    //   console.log(typeof fn);  //function
      fn = 3;  // 此时给fn赋值了之后,fn变为数值
    //    console.log(typeof fn); //number
      console.log(fn); // 3
  • 函数名和参数名重名时,即如下情况:

      function fn (fn) {
          console.log(fn);
    
          var fn = 3;
          console.log(fn);
      }
      fn(5); //5 3

    此时运行的过程等价于:

          function fn (fn) {
          var fn = 5;//这条语句是JS自动隐藏添加的,当传入参数时,给fn赋值
    
          var fn;  // 函数内部作用域变量提升
          console.log(fn);  // 此时输出的为传入的已经赋值的参数fn,而不是undefined
    
          fn = 3;
          console.log(fn); // 输出的是当前作用域内的局部变量fn
      }
    

## 3.arguments 是什么

在函数内部,可以使用arguments对象获取到该函数的所有传入参数,是一个类数组对象。
var getInfo = function () {
    console.log(arguments[0]);
    console.log(arguments[1]);
    console.log(arguments[2]);

}
getInfo('deejay',21,'male');

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

**JS没有重载!** 同名的函数会覆盖,但是可以在函数体内针对**不同的参数**调用执行相应的逻辑

可以模拟重载,举例说明:
  var getInfo = function (name,age,sex) {
    if (name) {
        console.log(name);
    }
    if (age) {
       console.log(age);
    }
    if (sex) {
        console.log(sex)
    }
}

getInfo('deejay',21); //deejay 21 
getInfo('deejay',21,'male'); //deejay 21 male


## 5.立即执行函数表达式是什么?有什么作用
(function () {
    console.log('hello,deejay');
})();
创建一个匿名函数并且立即调用它,一般用于隔离作用域(因为其内部的函数作用域不受外部作用域的影响)


## 6.求n!,用递归来实现
#####递归
1. 自己调用自己
2. 设定终止条件
3. 优点:算法简单
4. 缺点:效率低

求n!的递归实现:
function fn (n) {
    if (n <= 0){
        console.log('n为正整数');
        return;
    }
    else if (n === 1) {
        return 1;
    }
    else if (n >=1 ){
        return n * fn(n - 1);
    }
}
var result = fn(5);
console.log(result);


## 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('deejay', 21, '男');
    getInfo('dee', 3);
    getInfo('男');

输出结果为:
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('deejay', 21, '男');

// 输出结果为: // name: deejay // age: 2 // sex: 男 // ['deejay',21,'男'] // name valley

getInfo('deejay', 3);

// 输出结果为 // name: deejay // age: 3 // sex: undefined // ['deejay',3] // name valley

getInfo('男');

// 输出结果为 // name: 男 // age: undefined // sex: undefined // ['男'] // name valley

## 8. 写一个函数,返回参数的平方和?
 function sumOfSquares(){

 }
  var result2 = sumOfSquares(1,3)
 console.log(result)  //29
 console.log(result2)  //10
平方和代码如下:
function sumOfSquares(){
    var sum = 0;
    for (var i = 1; i <= arguments.length; i ++) {
        sum += Math.pow(arguments[i-1],2);
    }
    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);

输出解释如下:
console.log(a);
var a = 1;
console.log(b);

// 等价于 var a; console.log(a); //undefined 预解析,声明了a,但是没赋值,为undefined a = 1; console.log(b); //Uncaught ReferenceError: b is not defined 没有声明b,报错



## 10.如下代码的输出?为什么
sayName('world');
sayAge(10);
function sayName(name){
    console.log('hello ', name);
}
var sayAge = function(age){
    console.log(age);
};
解释如下:
sayName('world');
sayAge(10);
function sayName(name){
    console.log('hello ', name);
}
var sayAge = function(age){
    console.log(age);
};

// 表达式定义的函数,在进行前置的时候,跟用var声明的变量规则一样 // 等价于: // function sayName(name){ // console.log('hello ', name); // } // var sayAge; // sayName('world');//hello, world // sayAge(10); //Uncaught TypeError: sayAge is not a function 此时sayAge()只是被声明,并不是一个函数, 报错 // sayAge = function (age) { // console.log(age); // }


## 11. 如下代码输出什么? 写出作用域链查找过程伪代码
var x = 10
bar() 
function foo() {
  console.log(x) //10
}
function bar(){
  var x = 30
  foo()
}

伪代码如下:
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
}
可以看出输出的是GlobalContext.AO中的x的值,为10。
##12.如下代码输出什么? 写出作用域链查找过程伪代码

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

伪代码如下:
GlobalContext = {
    AO: {
        x:10,
        bar: function() {}
    },
}
bar.[[scope]] = GlobalContext.AO

barContext = {
    AO : {
        x: 30,
        foo: function () {}
    },
    scope: GlobalContext.AO
}

fooContext = {
    AO: {},
    scope: barContext.AO
}
很明显输出的是barContext.AO中的x值,为30

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

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


伪代码如下:
GlobalContext = {
    AO: {
        x: 10,
        bar: function() {}
    }
}
bar.[[scope]] = GlobalContext.AO

barContext = {
    AO: {
        x: 30,
        匿名函数: function () {}
    },
    scope: GlobalContext.AO
}

匿名函数Context = {
    AO: {},
    scope: barContext.AO
}
显然输出的是barContext.AO中的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 () {} }, } fnContext = { AO: { a:undefined,//解析时的值是undefined, fn2: function () {} }, scope: GlobalContext.AO // fn的上一级作用域为global } fn3Context = { AO: { 没有任何活动对象,注意:a = 200,没有用var声明,不是当前作用域即fn3的作用域中的活动对象 }, scope: GlobalContext.AO // fn3的上一级为global } fn2Context = { AO: { 没有任何活动对象,注意:a = 20,没有用var声明,不是当前作用域即fn2的作用域中的活动对象 }, scope: fnContext.AO // fn2的上一级作用域为fn }

最终的输出结果为:
var a = 1;
function fn (){
    console.log(a); //undefined
    var a = 5;
    console.log(a); //5
    a++;
    var a;
    fn3();
    fn2();
    console.log(a); //20

    function fn2() {
        console.log(a); //6
        a = 20; //改变了fn中的a(6 ----> 20)
    }
}
function fn3 () {
    console.log(a); //1
    a = 200; //改变了全局中的a (1 ----> 200)
}
fn();
console.log(a);  //200
按照运行顺序依次输出为: undefined 5 1 6 20 200