JS——函数

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>
arguments模拟重载
arguments模拟重载

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

  • ()将函数声明变为表达式并立即执行,就是立即执行表达式(IIFE):
    (function() {statement} ) ();
    (function() {statement} ());
    两种写法都可以,推荐使用后者;
  • 作用
    • 模拟块作用域:
      众所周知,JavaScript没有C或Java中的块作用域(block),只有函数作用域,在同时调用多个库的情况下,很容易造成对象或者变量的覆盖,比如:
      liba.js
var num = 1;
// code....

libb.js
var num = 2;
// code....
如果在页面中同时引用liba.js和liba.js两个库,必然导致num变量被覆盖,为了解决这个问题,可以通过IIFE来解决:
liba.js

      (function(){
        var num = 1;
        // code....
      }());

libb.js

      (function(){
        var num = 2;
        // code....
      }());

经过改造之后,两个库的代码就完全独立,并不会互相影响。

  • 解决闭包冲突(待补充)
  • 与自执行函数表达式的区别:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17</title>
  </head>
  <body>
    <script>
      // 这是一个自执行的函数,函数内部执行自身,递归
      function foo() { foo(); }

      // 这是一个自执行的匿名函数,因为没有标示名称
      // 必须使用arguments.callee属性来执行自己
      var foo = function () { arguments.callee(); };

      // 这可能也是一个自执行的匿名函数,仅仅是foo标示名称引用它自身
      // 如果你将foo改变成其它的,你将得到一个used-to-self-execute匿名函数
      var foo = function () { foo(); };

      // 有些人叫这个是自执行的匿名函数(即便它不是),因为它没有调用自身,它只是立即执行而已。
      (function () { /* code */ } ());

      // 为函数表达式添加一个标示名称,可以方便Debug
      // 但一定命名了,这个函数就不再是匿名的了
      (function foo() { /* code */ } ());

      // 立即调用的函数表达式(IIFE)也可以自执行,不过可能不常用罢了
      (function () { arguments.callee(); } ());
      (function foo() { foo(); } ());

      // 另外,下面的代码在黑莓5里执行会出错,因为在一个命名的函数表达式里,他的名称是undefined
      // 呵呵,奇怪
      (function foo() { foo(); } ());      
    </script>
  </body>
</html>

参考
JavaScript中的立即执行函数表达式
深入理解JavaScript系列(4):立即调用的函数表达式


6. 什么是函数的作用域链 (****)

  • javascript作用域
    任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
    • 全局作用域(Global Scope)
      在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:
    • 最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:
      var a = 1;
      function add() {
        var c = 1 + 2;
      }  
  • 所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:


    未声明局部变量
  • 所有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. 写一个函数,返回参数的平方和?如 (难度**)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17-2</title>
  </head>
  <body>
    <script>
      function sumOfSquare(){
        var sum = 0;
        for(var i = 0; i < arguments.length; i++){
          sum = arguments[i] * arguments[i] + sum;
        }
        console.log(sum);
      }
      sumOfSquare(2,3,4);
      sumOfSquare(1,3);
    </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>
代码5
代码5

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

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>task17-6</title>
  </head>
  <body>
    <script>
      function fn(fn2){
        console.log(fn2);
        var fn2 = 3;
        console.log(fn2);
        console.log(fn);
        function fn2(){
          console.log('fnnn2');
        }
      }
      fn(10);
    </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。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 函数声明和函数表达式有什么区别 (*)解析器会率先读取函数声明,并使其在执行任何代码之前可以访问;函数表达式则必须...
    coolheadedY阅读 400评论 0 1
  • 概念 1、函数声明和函数表达式有什么区别? ECMAScript规定了三种声明函数方式: 构造函数首先函数也是对象...
    周花花啊阅读 487评论 1 1
  • 函数声明和函数表达式有什么区别? 函数声明和函数表达式是EMACScript规定的两种不同的声明函数的方法。1.函...
    LeeoZz阅读 354评论 0 1
  • 1.函数声明和函数表达式有什么区别 ? 2.什么是变量的声明前置?什么是函数的声明前置 js引擎的工作方式:先解析...
    饥人谷区子铭阅读 454评论 0 1
  • 一、函数声明和函数表达式有什么区别? 函数声明和函数表达式的区别:函数声明可以提升到其他代码之前(即函数声明前置)...
    __Qiao阅读 271评论 0 0