JS——函数

作者: 进击的阿群 | 来源:发表于2016-08-18 18:37 被阅读112次

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

  • 函数在JS中有三种方式来定义:
    1. 函数声明(function declaration)
    2. 函数表达式(function expression)
    3. 调用new function返回
  • 区别:函数声明是和变量声明类似,函数声明的解析是在预执行(pre-execution)阶段,也就是浏览器准备执行代码的时候,因此,通过函数声明来定义函数,可以在定义前或后被调用。然而函数表达式不能做到这点,因为函数表达式是将函数赋值给变量,是将函数放在语句中而不是代码主流中,只有当浏览器解析到该语句的时候函数才能被调用,并且不能在其他位置调用,就是其实际作用是给变量赋值而非作为一个函数被调用。
  • 例子:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17</title>
  </head>
  <body>
    <script>
      var test1 = function say() {
        console.log('hello world');
      };              //  函数表达式,有分号,是语句
      test1();        //  hello world
      sayName();      //  大阿群
      say();          //  error
      function sayName() {
        console.log('大阿群');
      }               //  函数声明,无分号
    </script>
  </body>
</html>
函数声明&函数表达式

参考[翻译]函数:声明和表达式


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

  • 变量的声明前置:JS语法中,在指定的作用域内,声明的变量会在提升到代码的顶部,而赋值操作不会跟随提升,如果未声明变量,则无论变量在任何作用域中,都视为该变量为全局变量。
  • 函数的声明前置:JS语法中,如果函数定义是通过函数声明的,那么在指定作用域内,函数会提升到代码顶部,并且放在变量声明的下面。
  • 例子:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17</title>
  </head>
  <body>
    <script>
      console.log(a);    // undefined
      var a = 1;
      demo();
      console.log(a);    // 2
      function demo() {
        a = 2;
      }
      console.log(b);    // error
    </script>
  </body>
</html>
声明前置
可以看出,未定义的b会出现错误,而a变量声明前置,但是执行第一个console.log的时候a未初始化,所以值未undefined,继续往下执行demo()函数,虽然demo()函数声明是在执行之后,但是由于函数声明前置,所以正确执行显示2。

3. arguments 是什么 (*)

  • arguments意为参数。
  • 在JavaScript中,arguments对象是比较特别的一个对象,实际上是当前函数的一个内置属性。arguments非常类似Array,但实际上又不是一个Array实例。可以通过如下代码得以证实(当然,实际上,在函数funcArg中,调用arguments是不必要写成funcArg.arguments,直接写arguments即可)。
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17</title>
  </head>
  <body>
    <script>
      Array.prototype.testArg = "test";
      function funcArg() {
        console.log(funcArg.arguments[0]);
        console.log(funcArg.arguments.testArg);
      }
      console.log(new Array().testArg);
      funcArg(1,2);
    </script>
  </body>
</html>
arguments类似数组,但不是数组实例
  • arguments对象的长度是由实参个数而不是形参个数决定的。形参是函数内部重新开辟内存空间存储的变量,但是其与arguments对象内存空间并不重叠。对于arguments和值都存在的情况下,两者值是同步的,但是针对其中一个无值的情况下,对于此无值的情形值不会得以同步。如下代码可以得以验证。
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17</title>
  </head>
  <body>
    <script>
      function f(a, b, c) {
        console.log(arguments.length); // 2
        console.log(a);                // 1
        a = 100 ;
        console.log(a);                // 100
        console.log(arguments[0]);     // 100
        console.log(c);                // undefined
        c = 200;
        console.log(arguments[2]);     // undefined
      }
      f(1,2);
    </script>
  </body>
</html>
arguments为实参
  • 由JavaScript中函数的声明和调用特性,可以看出JavaScript中函数是不能重载的。
    1. Javascript函数的声明是没有返回值类型这一说法的;
    2. JavaScript中形参的个数严格意义上来讲只是为了方便在函数中的变量操作,实际上实参已经存储在arguments对象中了。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
function f(a) {
return a + 10;
}
function f(a) {
return a - 10;
}
console.log(f(1));
</script>
</body>
</html>

![JS函数不能重载](http://i2.buimg.com/567571/d9005d24b2e63f63.png)
* arguments对象中有一个非常有用的属性:callee。arguments.callee返回此arguments对象所在的当前函数引用。在使用函数递归调用时推荐使用arguments.callee代替函数名本身。
``` html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17</title>
  </head>
  <body>
    <script>
      function count(a) {
        if(a == 1) {
          return 1;
        }
        return a + arguments.callee(--a);
      }
      var mm = count(10);
      console.log(mm);
    </script>
  </body>
</html>
arguments.callee

不过call属性在严格模式下被禁用了,这点需要注意。

参考arguments对象


4. 函数的重载怎样实现 (**)

在一些编程语言中,函数的返回值类型和参考不同会使得同一个函数有不同的功能一同实现,这是函数重载;而在Javascript中没有重载的概念,所以正常情况下的函数是无法重载的:


JS无重载概念

不过可以用arguments属性来判断实参的个数,模拟重载:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17</title>
  </head>
  <body>
    <script>
      function test() {
        if(arguments.length == 1) {
          console.log(arguments[0] + 3);
        } else if(arguments.length == 2) {
          console.log(arguments[0] * arguments[1]);
        }
      }

      test(1, 2);
    </script>
  </body>
</html>
未声明局部变量
  • 所有window对象的属性拥有全局作用域
  • 局部作用域(Local Scope)
     和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所有在一些地方也会看到有人把这种作用域称为函数作用域:
    局部作用域
  • 作用域链
    作用域链是内部上下文所有变量对象(包括父变量对象)的列表,用来变量查询。在代码执行的过程中,所用到的变量会在当前作用域中进行寻找,如果找不到,就会往沿着作用域链向上一级进行寻找,一直到全局作用域为止,如果找到便会停止(而不理会上一级是否有同名的变量),如果找不到,就会报错。
  •     function add(num1,num2) {
            var sum = num1 + num2;
            return sum;
        }
    

    在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只例举了全部变量中的一部分):



    执行代码:

    var total = add(5, 10);
    

    首先在本身内部作用域中寻找所需对象,当不存在时,像外层找,执行过程中按照从上到下顺序执行,执行一层就会类似于编锁链上的一节,将其保留下来,在其生命周期内反复被查找。

    参考
    JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
    深入理解JavaScript系列(14):作用域链(Scope Chain)


    代码:

    1. 以下代码输出什么? (难度**)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>task17-1</title>
      </head>
      <body>
        <script>
          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('男');
        </script>
      </body>
    </html>
    
    代码2

    3. 如下代码的输出?为什么 (难度*)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>task17-3</title>
      </head>
      <body>
        <script>
          console.log(a);
          var a = 1;
          console.log(b);
        </script>
      </body>
    </html>
    
    代码3

    4. 如下代码的输出?为什么 (难度*)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>task17-4</title>
      </head>
      <body>
        <script>
          sayName('world');
          sayAge(10);
          function sayName(name){
            console.log('hello', name);
          }
          var sayAge = function(age){
            console.log(age);
          };
        </script>
      </body>
    </html>
    
    代码4

    5. 如下代码的输出?为什么 (难度**)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>task17-5</title>
      </head>
      <body>
        <script>
          function fn(){}
          var fn = 3;
          console.log(fn);
        </script>
      </body>
    </html>
    
    代码6

    按照执行顺序重写函数:

        <script>
          function fn(fn2){
            var fn2;
            function fn2(){
              console.log('fnnn2');
            }
            console.log(fn2);
            fn2 = 3;
            console.log(fn2);
            console.log(fn);
          }
          fn(10);
        </script>
    

    7. 如下代码的输出?为什么 (难度***)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>task17-7</title>
      </head>
      <body>
        <script>
          var fn = 1;
          function fn(fn){
            console.log(fn);
          }
          console.log(fn(fn));
        </script>
      </body>
    </html>
    
    代码7

    执行顺序:

        <script>
          var fn;
          function fn(fn){
            console.log(fn);
          }
          fn = 1;
          console.log(fn(fn));
        </script>
    

    fn = 1为最终结果,无法进行()操作,所以显示错误。


    8. 如下代码的输出?为什么 (难度**)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>task17-8</title>
      </head>
      <body>
        <script>
          console.log(j);
          console.log(i);
          for(var i = 0; i < 10; i++){
            var j = 100;
          }
          console.log(i);
          console.log(j);
        </script>
      </body>
    </html>
    
    代码8

    for是循环语句,不是函数,首先并不会前置,其次其定义的自然就是全局变量,所以能够被解析,正常顺序执行并显示。


    9. 如下代码的输出?为什么 (难度****)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>task17-9</title>
      </head>
      <body>
        <script>
          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;
            }
          }
        </script>
      </body>
    </html>
    
    代码9

    执行顺序:

        <script>
          var i, fn;
          function fn(){
            var i;
            function fn2(){
              i = 100;
            }
            console.log(i);
            i = 99;
            fn2();
            console.log(i);
          }
          fn();
          i = 10;
          fn = 20;
          console.log(i);
        </script>
    
    • 首先将变量和函数前置,并且将函数内部嵌套函数作用域内也进行前置操作;
    • 然后按照顺序,首先执行fn(),会遇到第一个console.log(i),由于此时i未赋值,所以为undefined,然后执行fn2()函数,由于fn2()函数中的i未声明,所以定义的是全局变量,所以下一个console.log(i)为100;
    • 继续往下执行,i = 10定义全局变量,所以最后一个console.log(i)为10。

    10. 如下代码的输出?为什么 (难度*****)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>task17-10</title>
      </head>
      <body>
        <script>
          var say = 0;
          (function say(n){
            console.log(n);
            if(n < 3) return;
            say(n - 1);
          }(10));
          console.log(say);
        </script>
      </body>
    </html>
    
    代码10代码10
    • 首先,放在一个()中,并且有分号结尾,说明其实一个语句,即“立即执行的函数表达式”,所以函数不会前置,按照顺序执行,立即执行的表达式中发生迭代,显示10,9,8......3,2,满足if条件,return跳出函数,继续执行下面的console.log(say);语句,由于立即执行的函数表达式中的say生命周期已经结束,所以console.log寻找全局变量say,为0。

    本文版权归本人和饥人谷所有,转载请注明来源,谢谢

    相关文章

    网友评论

      本文标题:JS——函数

      本文链接:https://www.haomeiwen.com/subject/irzmsttx.html