js函数、函数作用域、函数作用域链

二. Function

1. 复习:

(1). 什么是:程序中专门保存一段可反复使用的代码段的程序结构,再起一个名字

 (2). 为什么:代码重用

 (3). 何时:今后,只要一段代码,可能被多处反复使用时,都用函数。

 (4).如何:

a. 定义函数:
function 函数名(形参列表){
函数体;

return 返回值
} //
强调:函数,不调用不执行!

b. 调用函数:

    var 变量=函数名(实参值列表);

(5). 形参:
  a. 什么是:专门接收函数执行时所必须的数据的变量
 b. 为什么:函数执行时,有的数据可能会变化,或不确定!就需要用一个变量,临时占位!

 c. 何时:今后,只要定义一个函数时,发现有些数据暂时不确定,或将来可能发生变化,都用形参占位!

 d. 凡是定义了形参变量的函数,在调用时都要传入实参值。原则上,实参值的个数和顺序,要和形参列表保持一致!

 (6). 返回值:

  a. 什么是:一个函数的执行结果

  b. 为什么:默认情况下,函数内部的变量和数据只能在函数内使用,出了函数,无法使用

 c. 何时:今后,只要出了函数之后,还想用函数的执行结果时,都必须定义返回值。

 (7). 总结:

  a. 如果函数自身包含不确定的值,就用形参

 b. 如果函数外部需要用函数的执行结果时,就用返回值

  (8). 示例:定义函数对数组求和

 3_fun.html

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Document</title>

</head>

<body>

  <script>

    //想定义一个函数,对任意一个数组中的所有元素值,求和

    function sum(arr){

      //数组求和固定套路

      //1. 下定义一个变量保存临时汇总值

      var result=0;

      //2. 遍历数组中每个元素值

      for(var i=0;i<arr.length;i++){

        //每遍历一个元素值,就将元素值累加到result变量上

        result+=arr[i];

      }

      //3. 遍历结束,返回result变量中的和

      return result;

    }

 

    var arr1=[1,2,3];

    var arr2=[1,2,3,4,5];

    //还想对每个数组中的元素值求平均

    var result1=sum(arr1);

    var result2=sum(arr2);

    console.log(`和:${result1},平均:${result1/arr1.length}`);

    console.log(`和:${result2},平均:${result2/arr2.length}`);

  </script>

</body>

</html>

运行结果:

和:6,平均:2

和:15,平均:3

2. 创建函数三种方式:

(1). 声明方式:

 a.  function函数名(形参列表){
                  
函数体;

        return 返回值;
}

 b. 问题:会被声明提前(hoist),会打乱程序正常的执行顺序

 c. 什么是声明提前:

         1). 在程序开始执行前

         2). js引擎会先扫描所有声明语法:2种:

         i. var 变量名;

    ii. function 函数名( ... ){ ... }

    3). 将所有声明语法提前到当前作用域的顶部集中执行

    4). 赋值留在原地。

这个是在nodejs中提到过的变量提升,会将var声明的变量与function声明的函数提前到函数作用域的最前面

(2). 赋值方式:

  a.  var 函数名=function(形参列表){ 函数体};

  b. 优点:因为不符合声明语法,所以,不会被声明提前。保证了程序顺序执行!

         这种方式变量提升的不是函数而是变量函数名

  c. 今后,几乎所有的函数定义,都首选赋值方式!

  d. 示例:定义函数解决声明提前的问题:

4_hoist.html

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Document</title>

</head>

<body>

  <script>

    // function fun(){ console.log(1) }

    // fun();//2

    // function fun(){ console.log(2) }

    // fun();//2

 

    var fun=function(){ console.log(1) }

    fun();//1

    var fun=function(){ console.log(2) }

    fun();//2

  </script>

</body>

</html>

运行结果:

1

2

 

 

(3). 用new: 几乎不用

a. var 函数名=new Function("形参1", "形参2", "函数体");

b. 揭示: js中,函数也是一个对象。

         1). 函数也可以像数组和普通对象一样赋值给其它变量!

         2). 函数也可以像数组和普通对象一样作为实参值传入到另一个函数中。

函数也是对象,这种方式是创建一个对象的实例的方式,但是基本不使用

c. 示例:演示函数赋值和将函数作为参数传递(回调函数)

5_fun2.html

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Document</title>

</head>

<body>

  <script>

    var a=function(){ console.log("疼!") };

    a();

    var b=a;

    // var b=a(); //只是将a()调用后的执行结果给b,并不是把a函数本身给b

    b();

 

    var arr=[3,23,1,12,123,2];

    arr.sort(function(a,b){return a-b});

    console.log(arr);

  </script>

</body>

</html>

运行结果:

疼!

疼!

 (6) [1, 2, 3, 12, 23, 123]

 

 

3. 重载:

定义:

重载是同名函数不同参数,这种称为重载;重载定义了不同情况下的同一事件的处理方式

(1). 问题:如果一件事,有多种执行情况时,如果为每种情况分别定义一个函数,那么函数的个数会很多!

(2). 解决:今后,只要一件事,可能根据传入的实参值不同,而执行不同的逻辑时,都要用重载!

 (3).什么是:相同函数名,不同形参列表的多个函数,在调用时,可以自动根据传入实参值列表的不同,自动选择对应版本的函数执行!比如: Java中:

问题: js语言不允许多个同名函数同时存在

js语言不允许多个同名函数同时存在。如果同时存在多个同名函数,结果只有最后一个函数可以幸存下来,覆盖之前所有同名的函数。

解决:使用自带参数列表arguments,判断参数列表的长度

变通实现的:

  a. 只定义一个函数,且不要定义任何形参变量

b. 在函数中arguments对象会帮我们自动接住所有传入函数的实参值。

         1). 什么是arguments:

    i. 每个函数中自带的- 不用自己创建,直接就可用!

   ii. 专门接收所有传入函数的实参值的- 内容

   iii. 类数组对象- 存储结构

   2). 什么是类数组对象:

    i. 长得像数组的对象

   ii. 相同点:下标, length, 可以用for遍历

   iii. 不同点:类型不同,arguments无法使用数组类型的任何一个函数。

  c. 根据传入的实参值个数(arguments.length)不同,判断执行不同的逻辑

 d. 通过arguments[下标]方式,获取本次调用时,传入的一个实参值。

(6). 示例:定义一个函数支持三种付款方式:

6_overload.html

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Document</title>

</head>

<body>

  <script>

    //定义函数,实现不同的付款方式

    function pay(              ){

    //            ↓  ↓  ↓  ↓

    // arguments[              ].length

    //    参数们   0  1  2  3 ...

      //如果没有传入实参值,就手机支付

      if(arguments.length==0){

        console.log(`手机支付...`)

      }else if(arguments.length==1){//否则如果只传了一个实参值,就现金支付

        console.log(`现金支付,收您${arguments[0]}...`);

      }else{//否则,就刷卡支付

        if(arguments[1]=="123456"){

          console.log(`刷卡支付,从您卡号${arguments[0]}中扣款成功...`)

        }else{

          console.log(`刷卡支付,密码错误,请重试...`)

        }

      }

    }

 

    pay();

    pay(100);

    pay("6553 1234", "12345");

    pay("6553 1234", "123456");

  </script>

</body>

</html>

运行结果:

手机支付...

现金支付,收您100元...

6刷卡支付,密码错误,请重试...

刷卡支付,从您卡号6553 1234中扣款成功...

 

4. 匿名函数:

(1). 什么是:定义函数时,没有指定函数名的函数

(2). 分2种情况:回调函数,自调用函数

  a. 回调函数:

         1). 什么是:我们定义的函数,不是我们自己调用,而是交给别的函数或程序去调用!

         2). 比如:
    i. arr.sort(function(a,b){return a-b});

   ii. setTimeout(function(){ ... }, 1000);

    iii. xhr.onreadystatechange=function(){ ... }

         3). 为什么回调函数,多数都是匿名函数?节约内存!因为匿名函数没有变量引用,用完立刻释放!

b. 匿名函数自调

         1). 什么是:创建一个匿名函数后,立刻调用这个函数

   2). 问题:大型项目中,不同功能之间的变量,极有可能互相干扰!——全局污染!

    3). 何时:原则上每个功能的代码和所用的变量,都应该包裹在一个匿名函数自调中。

         4). 如何:

(function(){  ...   })();

   5). 示例:定义两个银行的取号程序,避免两个银行的号互相干扰。

7_anonyFun.html

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Document</title>

</head>

<body>

  <script>

    //建设银行

    (function(){

      var i=0;

      function getNum1(){

        i++;

        console.log(`建设银行${i}号`)

      }

      setInterval(function(){

        getNum1();

      },1000);

    })();//立刻调用!

 

    //农行

    (function(){

      var i=0;

      function getNum2(){

        i++;

        console.log(`农行${i}号`)

      }

      setInterval(function(){

        getNum2();

      },2000);

    })();

  </script>

</body>

</html>

运行结果:

建设银行1号

建设银行2号

农行1号

建设银行3号

建设银行4号

农行2号

建设银行5号

 

5. 作用域和作用域链:

(1). 什么是作用域:

  a. 一个变量的可用范围。

 b. 其实,作用域也是一个对象。是专门保存各种变量的特殊对象。

(2). 为什么:为了避免不同范围的变量之间互相干扰。(全局污染)

(3). js中共包括2级作用域:

a. 全局作用域:

   1). 什么是:任何代码都可随意访问到的范围。

   2). 在全局作用域中定义的变量,称为全局变量。

b. 函数作用域:

    1).什么是:仅在一个函数内可用的范围

   2). 函数作用域中保存着函数的所有局部变量,只有2种情况:

         i. 在函数内var出的变量

   ii. 函数的形参变量也是局部变量

(4). 示例:作用域练习:

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Document</title>

</head>

<body>

  <script>

    var a=10;

    function fun1(){

      var a=100;

      a++;

      console.log(a);//101

    }

    fun1();

    console.log(a);//10

 

    var b=10;

    function fun2(){

      b=100;

      b++;

      console.log(b);//101

    }

    fun2();

    console.log(b);//101

    

  </script>

</body>

</html>

运行结果:

101

10

101

101

 


(5). 作用域的原理:

做菜4步:找菜谱,备料,做菜,收拾厨房

  a. 开局:

    1). 创建各种全局变量和全局函数:

所有全局变量和全局函数默认都保存在window对象中的

   window对象就是全局作用域对象

 

注意:window的全局作用域是在前端的浏览器中执行js时的全局作用域,在使用nodejs执行时使用的全局作用域是global

 

   2). 每个函数对象身上,都有一个好友列表。

普通函数的好友列表中有两个格子。

离函数近的格子暂时为空

离函数远的格子保存window对象的地址.

 

  b. 调用函数时:做菜三部曲:备料,做菜,收拾厨房

1). 调用函数前:

   i. 临时创建函数作用域对象,保存函数中的所有局部变量。

  ii.并将函数作用域对象的地址,保存到"好友列表"中离函数近的格子里。

 

   2). 函数调用过程中:

    js引擎按函数中记录的步骤,执行操作。

操作过程中,只要用到任何变量,都会先在离函数近的函数作用域中查找

如果函数作用域对象中没有,才被迫去全局window对象中查找。

 

   3). 函数调用后:

   js引擎自动释放函数作用域对象

函数作用域对象中的所有局部变量一同被释放。

所以,函数作用域中的所有局部变量,在函数执行后,就不存在了。

 

(6). 作用域链:

a. 定义

由多级作用域对象串联形成的结构

b.作用

保存着一个函数可用的所有作用域和变量

控制着变量的使用顺序: 就近原则,先局部后全局

 

6. 闭包:

 (1). 问题:

全局变量和局部变量都有不可兼得的优缺点

 a. 全局变量:

         1). 优点:可反复使用

         2). 缺点:极容易被篡改

 b. 局部变量:

    1). 优点:不会被篡改

    2). 缺点:不可重用!

 (2).解决:

今后,只要希望重用一个变量,又保护变量不被篡改时,都必须用闭包!

 (3).步骤方案: 3步:

  a. 用外层函数包裹内层函数和要保护的变量

  b. 将内层函数返回到外层函数外部

  c. 外层函数外部,必须调用外层函数,才能获得返回的内层函数对象,保存在变量中,反复使用。

 

将目标函数作为函数属性将其对象返回给父函数作为父函数的对象,父函数可以自行定义自己的属性参数;最后将父函数的对象给一参数,称为闭包;闭包函数中目标函数的对象经过两次传递,函数的参数无法从外部函数进行修改,原理是局部变量的使用

关键是目标函数对象的返回

 

(4). 示例:使用闭包定义函数,帮小孩儿保管压岁钱

  9_closure.html

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Document</title>

</head>

<body>

  <script>

    //想定义一个函数,帮小孩儿管理零花钱

    //1. 用外层函数包裹内层函数和要保护的变量

    function mother(){

      var total=1000;//全局变量

      //2. 将内层函数返回到外层函数外部

      return function(money){

        total-=money;

        console.log(`花了${money},还剩${total}`);

      }

    }

    //3. 外层函数外部,必须调用外层函数,才能获得返回的内层函数对象,保存在变量中,反复使用。

    var pay=mother();

    //pay:function(money){

    //      total-=money;

    //      console.log(`花了${money},还剩${total}`)

    //}

    pay(100);

    //别人的程序

    total=0;

    pay(100);

  </script>

</body>

</html>

运行结果:

花了100,还剩900

花了100,还剩800

 

posted @ 2021-04-30 10:13  山茶-峰  阅读(128)  评论(0编辑  收藏  举报