JavaScript函数,作用域,作用域链

144
作者 饥人谷_妖葫芦
2017.01.05 00:06 字数 2522

函数就是可重复使用的代码块,比如:完成某件事需要5条语句,而且这件事需要重复多次,那么我们就可以将完成这件事需要的这5条语句封装成一个函数,在以后需要的时候直接调用函数就行了,不必每次都要写这5条语句。

  • 函数的三种声明方式

    • 构造函数
      使用new Function()来创建一个函数对象,例子:
      var fn = new Function("console.log('hello')");
    • 函数声明
      通过关键字function来声明一个函数,例子:
      function fn(){
        console.log('hello');
      }
    • 函数表达式
      将一个匿名函数赋给一个变量
      例子:
      var fn = function(){
        console.log('hello');
      }
      需要注意的是:
      1. 声明了函数之后,并没有调用该函数,只有通过其函数名加上一对圆括号(),才算调用。例子:
        //这只是声明了一个叫sayName的函数,并没有调用它。
        function sayName(){
             console.log('Tom');
         }
        //通过函数名加()来调用它
        sayName();
      2. 函数名如果是由多个单词组成,可采用驼峰法,驼峰法就是第一个单词全部小写,之后的每个单词开头第一个字母大写。
      3. 构造函数和函数表达式,声明必须在调用之前,函数声明调用可以在声明之前。例子:

        屏幕快照 2017-01-04 下午2.30.57.png
  • 函数的声明前置

    1. 函数声明会将整个函数前置,而构造函数和函数表达式只会将变量声明前置,这也就是为什么构造函数和函数表达式,声明必须在调用之前,函数声明调用可以在声明之前的原因。例子:
      • 通过构造函数创建的函数
      • fn();
        var fn = new Function("console.log(123)");
        上面代码在执行的时候会变成下面这样:
        var fn;
        fn();//这里调用fn的时候,只是声明了fn,fn并不是函数所以调用fn会报错。
        fn = new Function("console.log(123)");
      • 通过函数表达式声明的函数
      • fn1();
        var fn1 = fnuction(){
            console.log(456);
        };
        上面代码在执行的时候会变成下面这样:
        var fn1;
        fn1();//这里也只是声明了fn1,fn1还不是函数所以调用fn1会报错。
        fn1 = fnuction(){
           console.log(456);
        };
        `
      • 通过“函数声明”声明的函数
      • fn2();
        function fn2(){
            console.log(789);
        }
        上面代码在执行的时候会变成下面这样:
        function fn2(){
            console.log(789);
        }
        fn2();//这里输出789是因为在调用fn3之前,fn3已经声明为函数。
    2. 函数内部的声明前置
      在函数内部的变量和函数也会声明前置,例子:
      function fn(){
          console.log(a);
          var a = 1;
          fn2();
          function fn2(){
               a = 3;
          }
          console.log(a);
      }
      fn();
      上面代码在执行的时候会变成这样:
      function fn(){
         function fn2(){
            a = 3;
         }
         var a;
         console.log(a);//输出undefined,因为声明了a,但还未赋值
         a = 1;
         fn2();//这里执行完fn2后,a变为3。
         console.log(a);//这里输出3
      }
      fn();
    3. 同名函数,同名变量
      • 当一个变量已经赋值,重新声明这个变量,这个变量的值不变,例子:

        屏幕快照 2017-01-04 下午3.29.51.png
      • 同名函数,后面的函数会覆盖前面的函数,例子:

        屏幕快照 2017-01-04 下午3.32.34.png
      • 函数和变量同名
        函数和变量同名分为两种情况:
        1. 变量已经赋值的情况下,无论同名变量在同名函数之前还是之后,变量的值都会覆盖函数,例子:

          屏幕快照 2017-01-04 下午3.35.56.png


          屏幕快照 2017-01-04 下午3.37.08.png
        2. 变量没有赋值的情况下,无论同名变量在同名函数之前还是之后,函数都会覆盖变量,例子:

          屏幕快照 2017-01-04 下午3.40.06.png


          屏幕快照 2017-01-04 下午3.40.33.png
  • 函数的参数

    参数写在函数名后面的()内,可以写多个参数,多个参数用,(逗号)隔开,例子:

    function message(name,age,sex){
      console.log('name'+':'+name);
      console.log('age'+':'+age);
      console.log('sex'+':'+sex);
    }
    上面代码中name,age,sex就是函数的参数,也叫形参。
    message('Sandy',20,'woman');
    在调用时传入的'Sandy',20,'woman'叫做实参。

    在JavaScript的函数中,实参的数量和形参可以不同,函数会按照你传入实参的顺序去执行,例子:

    • 当实参数小于形参数时:

      屏幕快照 2017-01-04 下午7.17.27.png
    • 当实参数大于形参数时

      屏幕快照 2017-01-04 下午8.19.32.png
    • 当传入的实参的顺序和形参的顺序不同时:

      屏幕快照 2017-01-04 下午8.23.20.png
  • arguments对象

    • arguments对象是函数内部的一个对象,它是所有传入实参的集合,也就是通过这个对象可以访问所有被传入的实参,arguments对象不是数组,只是与数组类似,因为它可以通过下标的方式来访问它的每一个元素,也可以用length属性确定传入的实参数,例子:

      屏幕快照 2017-01-04 下午8.42.10.png
    • 重载
      在其他语言中,可以根据参数的数量,类型的不同,给一个函数写两个定义,但JavaScript中不行,同名的函数后面的会覆盖前面的,所以JavaScript没有重载,我们可以模拟重载,例子:

      屏幕快照 2017-01-04 下午9.08.36.png
  • 函数的返回值

    如果我们想在函数执行完成后得到一个结果,在别处使用。可以使用return实现,例子:

    屏幕快照 2017-01-04 下午9.16.53.png

    注意
    1. 函数在执行的时候,执行完return语句后会停止执行并立即退出函数,例子:
      function sayHello(){
      return;
      console.log('Hello');//这个语句永远不会执行
      }
    2. 如果没有写return语句,函数会默认返回undefined,例子:

      屏幕快照 2017-01-04 下午9.23.30.png
    3. console.log不等于函数返回值,例子:

      屏幕快照 2017-01-04 下午9.26.25.png

      上面例子中fn函数执行完返回的是undefined,123是console.log输出的。
  • 作用域

    • JavaScript中没有块级作用域,也就说类似if语句这样的语句中声明的变量是全局变量,JavaScript中只有函数作用域(局部作用域)和全局作用域,函数作用域内的变量,函数全局作用域访问不到,函数作用域可以访问全局作用域的变量和函数。例子:

      屏幕快照 2017-01-05 下午12.50.31.png
    • 如果在函数中不加var关键字声明一个变量,在函数执行完后这个变量会变成全局变量,例子:

      屏幕快照 2017-01-04 下午9.53.46.png
  • 作用域链

    作用域链就是函数在执行过程中先在自己的作用域中搜索需要的标识符,如果找不到,就向它的上一级的作用域中搜索,直到全局作用域,如果在全局作用域搜索不到需要的标识符就会报错。注意:搜索标识符的过程是由内向外搜索,比如函数fn内部还有一个函数fn1,函数fn1可以访问函数fn的作用域,而函数fn不能访问fn1的作用域。可以通过下图来加深理解:


    屏幕快照 2017-01-04 下午10.18.37.png


    上图中:

    • 绿色代表全局作用域,它只能访问绿色中的所有变量和函数,也就是变量a和函数changeColor。
    • 橘色代表函数changeColor的作用域,它可以访问到绿色和橘色中的所有变量和函数,也就是变量a,变量b,函数changeColor,changeColorAgain。
    • 蓝色代表函数changeColorAgain的作用域,它能访问到绿色,橘色和蓝色中的所有变量和函数,也就是变量a,变量b,变量c,函数changeColor,changeColorAgain。
  • 递归函数

    递归函数就是自己调用自己,使用递归函数要设置一个终止点。
    例子:

    当我们有计算类似1+2+3+4+5...+20这种表达式的需求的时候就可以使用递归函数;
    function add(num){
               if(num === 1){   //这里设置终止点,因为当传入的参数是1时,结果就是1。
                   return 1
               }else{  
                 // 这里举例来说明这个表达式,假如传入的参数是2,那么这个表达式就是 num = 2 + add(2-1);  add(2-1)的结果是1,所以最终num等于3,同理传入3或者更大的数值都会这样一步一步的计算。      
                   return num = num + add(num-1); 
               }
    }

    结果:


    屏幕快照 2017-01-04 下午11.02.36.png
  • 立即执行函数

    立即执行函数,可以让你的函数在声明后立即被执行,它主要的作用就是隔离作用域,防止不同js文件中同名的变量出现覆盖情况。它的几种常用写法:
    (function(){
       执行的语句
    }())
    (function(){
       执行的语句
    })()
    var fn = (function () {  
     执行的语句
    })();
    例子:

    屏幕快照 2017-01-05 上午12.15.08.png

    也可以给立即执行函数传递参数,例子:

    屏幕快照 2017-01-05 上午12.05.23.png
日记本