04 - 函数(方法)

4.01 - 概述

函数是一段可以反复调用的代码块,其存在的意义是为了做重复的事情,同时也可以接收不同的参数,与一个返回值

  • 函数的申明方式

    1. 命名函数
    // function 标识符(argmengs){  }
    function helloFn(args){
      alert(\"传入的是:\" + args );
    }
    helloFn(\"amo\"); // \"传入的是:amo\"
    1. 函数表达式
    // 1. 赋值的方式
    var fn = function(){
      alert(\"赋值\");
    };
    // 2. 函数自执行
    // 前面的表达式必须 ; 结束
    // + - ! ~
    !function(){ alert(\"!function\"); }();
    // ()
    (function(){ alert( \"(function)()\" ); })();
    (function(){ alert( \"(function())\" ); }());
    1. 箭头函数(ES6)
    var fn = (a)=>{alert(a)};
    fn(1);// 1

  • 重复申明函数

    • 函数重复申明时,之后的覆盖之前的
    function a(){
      alert(\"第一个\");
    }
    function a(){
      alert(\"第二个\");
    }
    a(); // \"第二个\"
    • 与变量名重复时
    // 1. 没有赋值的情况下 函数的优先级高于变量
    function a(){
      alert(\"第一个\");
    }
    var a;
    console.log( a ); // 函数块
    
    // 2. 赋值后则为变量值
    a = 10;
    console.log( a ); // 10
  • 函数名提升

    • 函数申明的方式定义的函数,和变量一样,会被提升到最顶部,所以调用函数可以写在函数定义之前
    fn(); // \"hello function\"
    function fn(){
      console.log(\"hello function\");
    }
  • 函数相关

    • 函数是一等公民

    语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。

    由于函数与其他数据类型地位平等,所以在 语言中又称函数为第一等公民。

    • 函数的申明规范

    不能在非函数的块中申明函数,比如 if for try 等函数块中,虽然不会报错能够运行

4.02 - 函数执行,返回值,递归

1. 函数的执行方式

函数定义之后如果不进行调用,函数体内定义的语句并不会执行,所以函数定义了之后需要进行调用才有存在的价值

  • 括号调用:函数名,或者函数表达式直接在最后添加一对小括号,就可以直接调用执行函数
// 函数名加括号执行
fn(); // \"我被调用啦!\"
function fn(){
  alert(\"我被调用啦!\");
}

// 函数表达式自执行
(function(){
  alert(\"我也执行啦!!!\");// \"我也执行啦!!!\"
})();
  • 事件调用:将函数与事件进行绑定,当对应事件触发的时候,会执行与之绑定的函数
// 点击文档的时候触发函数
document.  = function(){
  alert(\"不要点我!!\");
}
  • 方法调用:call() apply() bind()

2. 函数返回值

每个函数执行过后必定有一个返回值,默认情况下返回 undefined 我们可以通过 return 自己定义函数的返回值,返回值可以是任意的数据类型,包括函数,并且许多时候我们定义的函数的目的就是通过 return 得到一个对应的返回值

  • 默认返回 undefined
function fn(){
  alert(\"我执行完后返回 undefined~\");
}
var fnReturnValue = fn(); // \"我执行完后返回 undefined~\"
console.log( fnReturnValue ); // undefined
  • 自定义返回值
function fn(){
  alert(\"我执行完后返回 hello~\");
  return \"hello~\";
}
console.log( fn() ); // 弹出:\"我执行完后返回 hello~\"  打印:\"hello~\"
  • return 关键字相关

    • return 语句会终止函数,之后的语句都不会执行
    • 可以返回任意数据类型
    • return 之后可以是一个表达式,并且表达式会进行运算,最终的返回值为运算结果
    function fn(){
      return 10<2? \"hello~\" : \"world~\";
      alert(\"hello world~\"); // 不会执行
    }
    console.log( fn() ); // \"world~\"

3. 递归

简单来说,函数在执行的过程中调用自己,就是递归。当函数的逻辑遵循一定规律重复的时候我们就可以通过递归来实现,比如递归求阶乘,斐波那契数列等等

使用的递归的时候从递归的特性来说,必须存在两个条件:

  1. 函数执行逻辑存在相同的规律
  2. 突破点,及终止递归的条件,否则递归将无限调用
  • 递归求阶乘
/* 求 1~n 的阶乘
	1. 规律:n * (n - 1)
	2. 突破点:n < 1
*/
var n = 10;
function factorial(){
  var _n = n--;
  if(_n<1){return 1;}
  return _n * factorial();
}
console.log( factorial() ); // 3628800

4.03 - 作用域

不同函数块中的定义的变量或者方法,实际上是不一定能相互访问的,作用域(scope)就是指变量或者方法可以访问的范围,遵循一定的规则,通常我们可以把作用域分为两种:

  1. 全局作用域:无论在哪里都可以访问,其一般直接申明在 标签下
  2. 局部作用域:只在局部生效,定义在函数体中
  • 域与域之间的访问规则

    • 全局变量或者方法,可以在任意位置访问或修改
    < >
      var a = \"全局变量\";
      function fn(){alert(\"全局函数\");}
      
      /* 全局访问 */
      console.log( a ); //  \"全局变量\"
      fn(); // alert(\"全局函数\")
      
      function childFn(){
         /* 局部访问 */ 
        console.log( a );
        fn();
      }
      childFn();// 打印:\"全局变量\"  弹出:alert(\"全局函数\")
    
    </ >
    • 局部变量或者方法,只能在局部或者其子作用域访问或修改
    < >
      
      function childFn(){
        var a = \"局部变量\";
        function fn(){alert(\"局部函数\");}
         /* 局部访问 */ 
        console.log( a );
        fn();
      }
      childFn();// 打印:\"全局变量\"  弹出:alert(\"全局函数\")
      
      /* 全局访问 */
      console.log( a ); //  error
      fn(); // error
    
    </ >
    • 同级作用域下的变量或者方法不能相互访问或修改
    < >
      
      function fn_1(){
        var a_1 = \"a_1变量\";
        function fn_1Fn(){alert(\"a_1函数\");}
      }
      
      function fn_2(){
        var a_2 = \"a_2变量\";
        function fn(){alert(\"a_2函数\");}
        // 无法访问 fn_1 的成员
        console.log(a_1); // error
        fn_1Fn(); // error 
      }
      fn_2();
    
    </ >
    • 总结:儿子能用爸爸的,爸爸不能用儿子的,兄弟明算账
  • 就近原则

    • 当父域与子域之间有同名的变量或者方法的时候并不会冲突,但是当进行成员访问的时候遵循就近原则
    • 同时子级的变量的改变不会影响父级,父级的变量改变也不会影响子级
< >
  var a = \"全局变量\";
  function fn(){alert(\"全局函数\");}
  
  function fnChild(){
    var a = \"局部变量\";
    function fn(){alert(\"局部函数\");}
    
    // 优先访问就近的属性和方法
    console.log(a); // \"局部变量\"
    fn(); // alert(\"局部函数\")
  }
  fnChild();

</ >

4.04 - 参数列表

函数调用时都可以为其传递一个或多个参数,根据参数的不同,同一个函数运行后的结果就有可能不同,实际上参数列表的设置类似于定义一个局部变量保存外部的值再进行使用

  • 参数列表设置

    • 形式参数:写在方法定义的小括号中,可以是任意合法标识符,其作用类似于局部变量
    • 实际参数:通常写在方法调用时的小括号中,与形参的顺序一一对应,实际参数可以是任意的数据类型或者变量,或者是一个表达式,当为一个表达式的时候会把运算结果作为参数传递
    var a = 2;
    var b = fn( 1 , a , a + 1 ); // 实际参数(实参) 1 2 3
    console.log( b );// 6
    
    function fn(a,b,c){ // 形式参数(形参)
      return a + b + c;
    }
    • 参数列表相关

      • 方法调用时没有实参传递,则默认值为 undefined
      • 实质上形参在函数内等同于申明了一个对应的局部变量
      function fn(a){
        console.log(a);
      }
      fn(10);
      
      // 基本等同于
      function fn_(){
        var a = 10;
        console.log(a);
      }
      fn();
    • 传参方式求阶乘

    function factorial(n){
      if(n<1){return 1;}
      return n * factorial(n-1);
    }
    console.log( factorial(10) ); // 3628800

  • arguments 参数集:在函数体内 arguments 代表所有实参的集合,哪怕没有形参与之对应,我们可以通过下标获取到对应序列的参数

    function a(a){
      var args = arguments;
      alert(a); // 1
      for(var i=0;i<args.length;i++){
         alert(args[i]); // 1 2 3
      }
    }
    a( 1 , 2 , 3 );

4.05 - 闭包

简单来说闭包是,一个定义在函数内部的函数,访问了外部函数的变量,但是并没有在所定义的函数体内直接执行。

闭包最大的特点,就是它可以访问并存储外部函数的变量。

闭包存在的意义在于,让局部变量被存储或者通过返回值等方式和外界产生联系

  • 实例1
function fn(n){
  function fn_(){
    console.log(n ++);
  }
  return fn_;
}
var f = fn(0);
f(); // 0
f(); // 1
f(); // 2
f(); // 3
  • 实例2
function fn(n){
  function fn_(){
    console.log(n ++);
  }
  document.  = fn_; // 点击一次document n ++ 一次
}
fn(0);
收藏 打印