Javascript 数组、函数

197 阅读16分钟

数组

1、为什么学习数组?

  • 之前学习的数据类型,只能存储一个值(比如:Number/String)。如果我们想存储班级中 所有学生的成绩,此时该如何存储?
  • 概念: 所谓数组(Array),就是将多个元素(通常是同一类型)按一定顺序排列放到一个集合中, 那么这个集合我们就称之为数组。

2、数组的定义

  • 数组是一组有序的数据集合。数组内部可以存放多个数据,不限制数据类型,并且数组的 长度可以动态的调整。
  • 创建数组最简单的方式就是数组字面量方式。
  • 数组的字面量:[]
  • 一般将数组字面量赋值给一个变量,方便后期对数组进行操作。
  • 如果存放多个数据,每个数据之间用逗号分隔,最后一个后面不需要加逗号。

3、获取数组元素

  • 数组可以通过一个 index(索引值、下标)去获取对应的某一项的数据,进行下一步操作。
  • index: 从0开始,按照整数排序往后顺序排序,例如0,1,2,3......
  • 可以通过 index 获取某一项值之后,使用或者更改数组项的值。
  • 调用数据:利用数组变量名后面直接加 [index] 方式。
    // 创建一个空数组
    var arr = [];
    // 创建包含多个数据的数组,数据类型是不限制
    var arr2 = [1,true,false,null,undefined,"haha",[7,8],9,10];
    console.log(arr2);
    
    // 获取 arr2 中下标为 0 的项
    console.log(arr2[0]);
    console.log(arr2[1]);
    console.log(arr2[2]);
    console.log(arr2[6]);
    console.log(arr2[8]);

注意:

  • 如果索引值超过了数组最大项,相当于这一项没有赋值,内部存储的就是 undefined。
  • 更改数据:arr[index] 调用这一项数据,后面等号赋值更改数据。
    //length长度为9,第4个值就是下标为5
    var arr2 = [1,true,false,null,undefined,"haha",[7,8],9,10];
    // 更改数组中某一项的值
    arr2[4] = 5;
    console.log(arr2);
    
    所以输出结果为1,true,false,null,5,"haha",[7,8],9,10

4、数组的长度

  • 数组有一个 length 的属性,记录的是数组的数据的总长度。
  • 使用方法:变量名.length console.log(arr.length);
  • 数组的长度与数组最后一项的下标存在关系,最后一项的下标等于数组的 length-1。
  • 获取最后一项的数据时,可以这样书写: console.log(arr[arr.length-1]);
  • 数组的长度不是固定不变的,可以发生更改。
    var arr2 = [1,true,false,null,5,"haha",[7,8],9,10];
    // 获取数组的长度为 9
    console.log(arr2.length);
    //可以通过长度-1,获得最后一个数字的下标 8
    console.log(arr2[arr2.length - 1]);
  • 增加数组长度:直接给数组 length 属性赋一个大于原来长度的值。赋值方式使用等号赋值。
    // 更改数组的长度为 12,现在有11个数
    arr2.length = 12;
    console.log(arr2.length);
    console.log(arr2[11]);
    
    // 12值无,undefined
    console.log(arr2[12]);

  • 或者,可以给一个大于最大下标的项直接赋值,也可以强制拉长数组。
    // 给下标为13的赋值 13,所以length为 14
    arr2[13] = 13;
    //输出数组arr的值
    console.log(arr2);   
    结果为: (14) [1, true, false, null, 5, 'haha', Array(2), 9, 10, empty × 4, 13]
    
    //同时计算arr2 的长度
    console.log(arr2.length);   14
    console.log(arr2[12]);      9-12的值都是undefined
  • 缩短数组长度:强制给 length 属性赋值,后面数据被会直接删除,删除是不可逆的。
    var arr2 = [1,true,false,null,5,"haha",[7,8],9,10];
    arr2.length = 5;
    console.log(arr2);
    结果为:(5) [1, true, false, null, 5]
    arr2.length = 9;
    console.log(arr2);
    结果为:(9) [1, true, false, null, undefined, empty × 4]

5、数组遍历

  • 遍历:遍及所有,对数组的每一个元素都访问一次就叫遍历。利用 for 循环,将数组中的每 一项单独拿出来,进行一些操作。
  • 根据下标在 0 到 arr.length-1 之间,进行 for 循环遍历。
    // 定义一个数组
    var arr = [45,56,76,88,89,90,100,34,56];
    // 数组遍历
    for (var i = 0 ; i <= arr.length - 1 ;i++) {
      console.log(arr[i]);
    }
    
    // 给数组中每一项数据加 5
    for (var i = 0; i < arr.length ; i++) {
      // 获取每一项数组的数据,等号赋新值
      arr[i] += 5;
    }
    console.log(arr);
    结果为:(9) [50, 61, 81, 93, 94, 95, 105, 39, 61]

应用案例 求一组数中的所有数的和以及平均值。

    // 定义一个数组
    var arr = [45,56,76,88,89,90,100,34,56];
    // 累加器。累积数组每一项的和,初始值是 0
    var sum = 0;
    // 数组遍历,将每一项累加到 sum 里
    for (var i = 0 ; i <= arr.length - 1 ; i++) {
      sum += arr[i];
    }
    // 输出 sum
    console.log("这组成绩的总和是" + sum);
    // 求取平均值 = 总和 / 班级人数
    var avg = sum / arr.length ;
    console.log("这个班的平均成绩是" + avg);

函数

1、为什么要用函数?函数的概念

  • 如果要在多个地方求某个数的约数个数,应该怎么做?
  • 函数(function),也叫作功能、方法,函数可以将一段代码一起封装起来,被封装起来的 函数具备某一项特殊的功能,内部封装的一段代码作为一个完整的结构体,要执行就都执 行,要不执行就都不执行。
  • 函数的作用就是封装一段代码,将来可以重复使用。
   // 定义函数
    function fun() {
      console.log(1);
      console.log(2);
      console.log(3);
      console.log(4);
    }
    // 调用函数
    fun();
    fun();
    fun();
    fun();

2、函数声明

  • 函数声明又叫函数定义,函数必须先定义然后才能使用,函数声明的时候,函数体并不会执行,只有当函数被调用的时候才会执行。
  • 如果没有定义函数直接使用,会出现一个引用错误。
    //函数声明语法:
    function 函数名(参数){ 封装的结构体;
    }

3、函数调用

  • 调用方法:函数名();
  • 函数调用也叫作函数执行,调用时会将函数内部封装的所有的结构体的代码立即执行。
  • 函数内部语句执行的位置,与函数定义的位置无关,与函数调用位置有关
  • 函数可以一次定义,多次执行。
   // 函数必须先定义才能使用
   // 函数名命名规则:可以使用字母、数字、下划线、$,数字不能作为开头,区分大小写,不能使用关键字和保留字
   // 函数声明
   function fun() {
      console.log(1);
      console.log(2);
      console.log(3);
      console.log(4);
   }
   console.log(5);
   // 函数调用
   fun();
   fun();

   所以输出结果: 5 1 2 3 4 1 2 3 4
   
   // 函数声明
   function fun() {
      console.log(1);
      console.log(2);
      console.log(3);
      console.log(4);
   }
   // 函数调用
   fun();
   fun();
   console.log(5); 

   所以输出结果:  1 2 3 4 1 2 3 4 5

4、函数的参数 1

  • 我们希望函数执行结果不是一成不变的,可以根据自定义的内容发生一些变化。
  • 函数预留了一个接口,专门用于让用户自定义内容,使函数发生一些执行效果变化。
  • 接口:就是函数的参数,函数参数的本质就是变量,可以接收任意类型的数据,导致函数执行结果根据参数不同,结果也不同。
  • 一个函数可以设置 0 个或者多个参数,参数之间用逗号分隔。

函数的参数 2

  • 函数的参数根据书写位置不同,名称也不同:
  • 形式参数:定义的 () 内部的参数,叫做形式参数,本质是变量,可以接收实际参数传递过来的数据。简称形参。
  • 实际参数:调用的 () 内部的参数,叫做实际参数,本质就是传递的各种类型的数据,传递给每个形参,简称实参。
  • 函数执行过程,伴随着传参的过程:

image.png

函数的参数优点

  • 不论使用自己封装的函数,还是其他人封装的函数,只需要知道传递什么参数,执行什么 功能,没必要知道内部的结构什么。
  • 一般自己封装的函数或者其他人封装的函数需要有一个 API 接口说明,告诉用户参数需要 传递什么类型的数据,实现什么功能。
   // 定义一个求和函数,传入两个数据
    // 参数:传两个参数,数据类型为数字
    // 功能:得到两个数字之和
    function sum(a,b) {
      console.log(a + b);
    }
    // 调用函数中,给小括号内部添加数据
    sum(3,4);
    sum("3",4);

5、函数的返回值

  • 函数能够通过参数接收数据,也能够将函数执行结果返回一个值。
  • 利用函数内部的一个 return 的关键字设置函数的返回值。
  • 作用 1: 函数内部如果结构体执行到一个 return 的关键字,会立即停止后面代码的执行
    // return 可以终止函数的执行
    function fun() {
      console.log(1);
      console.log(2);
      console.log(3);
      return;
      console.log(4);
      console.log(5);
      console.log(6);
      
      输出结果:没有return的时候是1 2 3 4 5 6return的时候是1 2 3
    }
  • 作用 2: 可以在 return 关键字后面添加空格,空格后面任意定义一个数据字面量或者表达式,函数在执行完自身功能之后,整体会被 return 矮化成一个表达式,表达式必须求出一个值继续 可以参与程序,表达式的值就return 后面的数据
    // 定义一个求和函数,传入两个数据
    // 参数:传两个参数,数据类型为数字
    // 功能:得到两个数字之和
    // 使用返回值,制作函数运行结束后的结果
    function sum(a,b) {
      return a + b;
    }
    // return返回值就可以参与程序了,调用函数结果
    console.log(sum(1,2));
    //错误写法:sum(1,2);  因为sum没有书写在任何输出语句里,所以不能输出
函数的返回值应用
  • 函数如果有返回值,执行结果可以当成普通数据参与程序。例如:上述代码块
  • 函数如果有返回值,可以作为一个普通数据赋值给一个变量,甚至赋值给其他函数的实际参数。
    // 使用返回值,制作函数运行结束后的结果
    function sum(a,b) {
      return a + b;
    }
    
    // 将返回值赋值给变量
    var num = sum(3,4);
    console.log(num);
    输出结果:7
    
    // 将返回值赋值给函数的实参
    console.log(sum(2,sum(3,4)));
    输出结果:9

注意
如果函数没有设置 return 语句 ,那么函数有默认的返回值 undefined;如果函数使用return 语句,但是 return 后面没有任何值,那么函数的返回值也是 undefined。

   控制台输入alert(2),会出现一个alert警示框2,再在控制台返回一个值undefined.

6、函数表达式

  • 函数表达式是函数定义的另外一种方式。
  • 定义方法:就是将函数的定义、匿名函数赋值给一个变量。
  • 函数定义赋值给一个变量,相当于将函数整体矮化成了一个表达式。
    // 定义一个函数表达式 function...,既然他是一个表达式,那么他最后肯定是会得到一个结果,然后将结果赋值给foo
    var foo = function fun() {
      console.log(1);
    };
  • 匿名函数:函数没有函数名fun。
  • 调用函数表达式,方法是给变量名加()执行,不能使用函数名加()执行。
    // 定义一个函数表达式
    var foo = function fun() {
      console.log(1);
    };
    var foo2 = function () {
      console.log(2);
    };
    // 调用函数式,只能用变量名调用,函数名调用不成功,fun是函数名
    // fun();
    foo();     结果为1
    foo2();    结果为2

7、函数的数据类型

  • 函数是一种单独的数据类型 Function。
    // 定义一个函数
    function fun() {
      console.log(1);
    }
    // 定义一个函数表达式
    var foo = function () {
      console.log(2);
    };
    // 检测函数的数据类型
    console.log(typeof(fun));
    console.log(typeof(foo));
    输出结果都是function
  • 由于函数是一种数据类型,可以参与其他程序。
  • 例如,可以把函数作为另一个函数的参数,在另一个函数中调用。
    // 函数是一种数据类型,可以当成其他函数的参数.
    setInterval是定时器,定时器里面有2个参数,第一个参数是函数,第2个参数是时间1000毫秒
    setInterval(function(){
      console.log(1);
    },1000);
  • 或者,可以把函数可以作为返回值从函数内部返回。
    // 将函数当成另一个函数的返回值
    function fn(b) {
      var a = 10;
      return function () {
        alert(a + b);
      };
    }

8、arguments 对象

  • JavaScript 中,arguments 对象是比较特别的一个对象,实际上是当前函数的一个内置属性 。也就是说所有函数都内置了一个arguments 对象,arguments对象中存储了传递的所有的实参。arguments 是一个伪数组,因此及可以进行遍历。
  • 函数的实参个数和形参个数可以不一致,所有的实参都会存储在函数内部的 arguments 类数组对象中。
    // 定义一个函数
    function sum(a,b) {
      return a + b;
    }
    // 调用函数的时候,实参的个数可以与形参不同
    console.log(sum(1,2));
    console.log(sum(1));
    console.log(sum(1,2,3,4));

image.png

    // 函数内部有一个 arguments 对象,会接收所有的实参
    function fun() {
      console.log(arguments);
      console.log(arguments.length);
    // 使用数组的遍历方法可以获取每一项实参
      for (var i = 0 ; i <= arguments.length - 1 ; i++) {
        console.log(arguments[i]);
      }
    }
    // // 调用函数
    fun(1,2,3,4,5,6,7);

image.png

案例

  • 定义一个求和函数,如果传入 1 个参数,返回它自己,如果传入两个参数,返回他们的和 ,如果传入三个参数,先比较前两个的大小,大的与第三个参数求和返回,如果传入 4 个 及以上,输出错误提示。
    // 案例:定义一个求和函数,如果传入 1 个参数,返回它自己,如果传入两个参数,返回他们的和,
    如果传入三个参数,先比较前两个的大小,大的与第三个参数求和返回,如果传入 4 个及以上,输出错误提示。
    function sum(a,b,c) {
      // 条件分支语句,根据实参个数走不同的分支
      switch (arguments.length) {
        case 1:
          return a;
          break;
        case 2:
          return a + b;
          break;
        case 3:
          return a > b ? a + c : b + c ;
          break;
        default:
          // 提示用户,实参个数传递错误
          // 模拟控制台报错
          throw new Error("参数个数不能超过 3 个");
      }
    }
    
    // 调用函数
    console.log(sum(1));           1
    console.log(sum(1,2));         3
    console.log(sum(1,2,3));       5
    console.log(sum(1,2,3,4,5));   报错参数不能超过3

image.png

9、函数递归

  • 函数内部可以通过函数名调用函数自身的方式,就是函数递归现象。
  • 递归的次数太多容易出现错误:超出计算机的计算最大能力。
    // 函数,如果 传入的参数是1,返回1,如果传入的是1以上的数字,让他返回参数 + 函数调用上一项
    function fun(a) {
      if (a === 1) {
        return 1;
      } else {
        return a + fun(a - 1);
      }
    }
    // 调用函数(传一些值给形参)
    console.log(fun(1));            1
    console.log(fun(2));            3
    console.log(fun(3));            6
    console.log(fun(1000000000));   报错,超出计算机的计算最大能力。     
  • 更多时候,使用递归去解决一些数学中的现象。 • 例如可以输出斐波那契数列的某一项的值。
    // 菲波那切数列
    // 参数:正整数
    // 返回值:对应的整数位置的菲波那切数列的值
    function fibo(a) {
      if (a === 1 || a === 2) {
        return 1;
      } else {
        return fibo(a - 1) + fibo(a - 2);
      }
    }
    // 调用函数
    console.log(fibo(1));
    console.log(fibo(2));
    console.log(fibo(3));
    console.log(fibo(4));
    console.log(fibo(5));
    console.log(fibo(6));

10、作用域

  • 作用域:变量可以起作用的范围。
  • 如果变量定义在一个函数内部,只能在函数内部被访问到,在函数外部不能使用这个变量,函数就是变量定义的作用域。
    //全局变量
    var a = 0;
    // 定义函数
    function fun() {
      var a = 1;   //局部变量
      console.log(a);
    }
    // 执行函数(只有调用控制台才会有结果,此时结果为1)
    fun();
    
    执行函数过程中,会在页面里面开辟出一个暂时的内存空间,有a的定义,console语句的执行,fun()在调用函数的过程之中,a是可以被访问到的,一旦函数执行完毕之后,就会瞬间销毁作用域内的变量a,因此在作用域外是调用不到的;
    但是全局变量是始终存在,定义的变量不会很快被销毁,直到浏览器被关掉
    
    在函数外部不能使用这个变量
    // 定义函数
    function fun() {
      var a = 1;
      console.log(a);
    }
    // 函数外部调用 a
    console.log(a);
    输出结果没有,并且会报错
  • 任何一对花括号 {} 中的结构体都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。
  • 在es5之前没有块级作用域的的概念,只有函数作用域,现阶段可以认为 JavaScript 没有块级作用域.
全局变量和局部变量
  • 局部变量:定义在函数内部的变量,只能在函数作用域内部被访问到,在外面没有定义的。
  • 全局变量:从广义上来说,也是一种局部变量,定义在全局的变量,作用域范围是全局,在整个 js 程序任意位置都能够被访问到。
  • 变量退出作用域之后会销毁,全局变量关闭网页或浏览器才会销毁。

参数也是局部变量

  • 函数的参数本质是一个变量,也有自己的作用域,函数的参数也是属于函数自己内部的局 部变量,只能在函数内部被使用,在函数外面没有定义。
    // 函数的参数也是局部变量
    function fun(a) {
      a = 2;
      console.log(a);
    }
    // 调用函数,给函数参数传一个1的值
    fun(1);
    console.log(a); //如果在外部调用a,调用不到会报错
    最后作用域中的局部变量a=2覆盖了a=1,所以fun(1)输出结果为2
函数的作用域
  • 函数也有自己的作用域,定义在哪个作用域内部,只能在这个作用域范围内被访问,出了作用域不能被访问的。
  • 函数定义在另一个函数内部,如果外部函数没有执行时,相当于内部代码没写。
如果函数定义在了全局,那么函数的作用域就是全局,在任何位置都可以调用函数
如果定义在了函数内部,那么作用域范围就会被限制在函数的内部,在函数内部可以正常调用,出了函数就不能被调用了

    // 函数也有自己的作用域
    function outer() {
      var a = 1;
      function inner() {
        console.log(2);
      }
      // 函数内部调用子函数才能成功
      inner();
    }
    // 外部调用函数inner不可行,outer()倒是可以输出结果
    outer();
    inner();

11、作用域链

  • 只有函数可以制造作用域结构, 那么只要是代码,就至少有一个作用域, 即全局作用域。凡 是代码中有函数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个 作用域中就又可以诞生一个作用域。
  • 将这样的所有的作用域列出来,可以有一个结构: 函数内指向函数外的链式结构。就称作作用域链。
    // 全局作用域
    var a = 1;
    // 创建函数
    function outer() {
      var a = 2;
      // 内部函数
      function inner() {
        var a = 3;
        console.log(a);
      }
      //要想让inner函数执行成功,必须执行inner(),要在outer函数内的inner函数执行;
      inner();
    }
    //调用outer:要想让outer函数执行成功,必须在自己所处的作用域执行outer
    outer()

image.png

image.png

    // 全局作用域
    var a = 1;
    // 创建函数
    function outer() {
      var a = 2;
      // 内部函数
      function inner() {
        var a = 3;
        console.log(a);
      }
      inner();
      console.log(a);
    }
    // 调用
    outer();
    
    inner函数内部的console.log(a)的值的时候,首先是在自己的作用域链里面找,会查找到一个var a = 3,就会在本层直接执行; 不会往外找,如果找不到就会一直向上级链开始查找.

遮蔽效应 程序在遇到一个变量时,使用时作用域查找顺序,不同层次的函数内都有可能定义相同名 字的变量,一个变量在使用时,会优先从自己所在层作用域查找变量,如果当前层没有变 量定义会按照顺序从本层往外依次查找,直到找到第一个变量定义。整个过程中会发生内 层变量遮蔽外层变量的效果,叫做“遮蔽效应”。

      // 全局作用域
    var a = 1;
    // 创建函数
    function outer() {
      var a = 2;
      // 内部函数
      function inner() {
        // var a = 3;       把本层的变量等于3注释掉,那么本层作用域就查找不到a,
        console.log(a);     那么就会跳出一级往上一级开始查找,遇到outer函数内 var a= 2,就会立即执行,
                            把上级的a遮蔽掉了。
        
      }                     如果上级作用域都没有a的值,这时候console.log(a)就是结果报错提示a没有被定义
      inner();
    }
    // 调用
    outer();

12、不写 var 关键字的影响

  • 在函数内部想要定义新的变量,如果不加关键字 var,相当于定义的全局变量。如果全局也 有相同的标识符,会被函数内部的变量影响,局部变量污染全局变量。
  • 注意:每次定义变量时都必须写 var 关键字,否则就会定义在全局,可能污染全局。
     不写a,也可以被调用
     a = 1;
     console.log(a);
     
     // 创建函数
     //var a;     补全outer函数a=2
    function outer() {
      a = 2;
      // 内部函数
      function inner() {
        var a = 3;
        console.log(a);
      }
      inner();
      console.log(a);
    }
    // 调用
    outer();
    console.log(a);       输出结果为2,因为a=2 ,没写var就相当于a=2 ,是全局变量,就相当于在全局把定义补全了一样,在全局作用域里var a;
     
     
     
    // 全局作用域
    var a = 1;
    // console.log(a);
    // 创建函数
    function outer() {
      a = 2;
      // 内部函数
      function inner() {
        var a = 3;
        console.log(a);
      }
      inner();
      console.log(a);
    }
    // 调用
    outer();
    console.log(a);    如果内部函数outer 内部变量没写var ,他会自动到外部的全局变量补全,从而覆盖一开始的全局变量var a = 1;
    
![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c00214dcd16c4344b2b7838bee7f4f0e~tplv-k3u1fbpfcp-watermark.image?)
    

13、预解析和声明过程

预解析
  • JavaScript 代码的执行是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器执行 JavaScript 代码的时候,分为两个过程:预解析过程和代码执行过程。
预解析过程:
    1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
    1. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
    1. 先提升 var,再提升 function。 JavaScript的执行过程:在预解析之后,根据新的代码顺序,从上往下按照既定规律执行js 代码。
变量声明提升
  • 在预解析过程中,所有定义的变量,都会将声明的过程提升到所在的作用域最上面,在 将来的代码执行过程中,按照先后顺序会先执行被提升的声明变量过程。
  • 提升过程中,只提升声明过程,不提升变量赋值,相当于变量定义未赋值,变量内存储 undefined 值。
  • 因此,在 js 中会出现一种现象,在前面调用后定义的变量,不会报错,只会使用 undefined 值。
    //预解析,把变量声明部分提升到了作用域最顶部
    var a;  //相当于存了一个undefined的值
    // 调用一个变量
    console.log(a);
    // 后定义变量
    //var a = 1;变成下面的a=1;
    a = 1;
    
    输出结果:undefined (按照之前的先定义变量后声明过程,得到结果为1;
            或者只定义没有赋值的情况也会存一个undefined值)
    undefined的原因是:在JS执行过程会进行一个预解析,预解析把变量声明提升到了作用域最顶部,就是把var a;放在了最顶部,相当于原来的位置就是一个a = 1;
函数声明提升
  • 在预解析过程中,所有定义的函数,都会将声明的过程提升到所在的作用域最上面,在 将来的代码执行过程中,按照先后顺序会先执行被提升的函数声明过程。
  • 在预解析之后的代码执行过程中,函数定义过程已经在最开始就会执行,一旦函数定义 成功,后续就可以直接调用函数。
  • 因此,在 js 中会出现一种现象,在前面调用后定义的函数,不会报错,而且能正常执 行函数内部的代码。
    // 预解析,把变量,函数声明部分提升到了作用域最顶部
    // 模拟提升
    var a;  //相当于存了一个undefined的值
    function fun() {
      console.log(a);
    }
    // 先调用函数
      fun();
    // 定义函数
    //function fun() {
       //console.log(2);
      }
    // 调用
     fun();
提升顺序
  • 预解析过程中,先提升 var 变量声明,再提升 function 函数声明。
  • 假设出现变量名和函数名相同,那么后提升的函数名标识符会覆盖先提升的变量名,那么在后续代码中出现调用标识符时,内部是函数的定义过程,而不是 undefined。
  • 如果调用标识符的过程在源代码函数和变量定义后面,相当于函数名覆盖了一次变量名,结果在执行到变量赋值时,又被新值覆盖了函数的值,那么在后面再次调用标识符,用的就是变量存的新值。
  • 建议:不要书写相同的标识符给变量名或函数名,避免出现覆盖。 image.png
    上述代码,最终声明是开始的「变量声明fun」被「定义声明fun」替代,
    调用结果为console.log(a)还是undefined,
    如果没有var fun = "haha";但是函数fun不会报错,结果为2;
    报错原因:变量的赋值又把fun函数覆盖了一次,字符串是不会调用函数执行方法的。
函数表达式的提升
  • 在预解析过程中,函数表达式进行的是变量声明提升,而不是函数声明提升。提升后变 量内部存的是一个 undefined。在前面进行函数方法调用,数据类型会提示错误。
  • 建议:定义函数时,最好使用 function 关键字定义方式,这样函数声明提升可以永远生效。
    //2种调用结果测试函数表达式是哪种声明提升
    // 第一种是变量声明提升,可以输出结果为undefined;
    console.log(foo);
    // 第二种是函数声明提升,输出结果报错“fun is not a function”
    foo();
    // 函数表达式进行的是变量声明提升
    var foo = function () {
      console.log(3);
    };

函数声明提升的应用

  • 函数声明提升可以用于调整代码的顺序,将大段的定义过程放到代码最后,但是不影响 代码执行效果。

13、IIFE 自调用函数

  • IIFE:immediately-invoked function expression,叫做即时调用的函数表达式,也叫做自 调用函数,表示函数在定义时就立即调用。
  • 函数调用方式:函数名或函数表达式的变量名后面加 () 运算符。
  • 函数名定义的形式不能实现立即执行自调用,函数使用函数表达式形式可以实现立即执行,原因是因为函数表达式定义过程中,将一个函数矮化成了一个表达式,后面加()运算符就可以立即执行。
  • 启发:如果想实现 IIFE,可以想办法将函数矮化成表达式。
     // 关键字定义的方式,不能立即执行(错误写法)
    function fun() {
      console.log(1);
    }();

    // 函数表达式方式,可以在定义时被立即执行
    var foo = function fun() {
      console.log(2);
    }();
  • 函数矮化成表达式,就可以实现自调用。
  • 函数矮化成表达式的方法,可以让函数参与一些运算,也就是说给函数前面加一些运算符。
    • 数学运算符:+ - ()
    • 逻辑运算符:!非运算
    // 通过在函数前面添加操作符,可以将函数矮化成表达式
    + function fun() {
      console.log(1);
    }();
    - function fun() {
      console.log(1);
    }();
    (function fun() {
      console.log(1);
    })();
    !function fun() {
      console.log(1);
    }();
    // *function fun() {
    //   console.log(1);
    // }();
    // IIFE 关住了函数的作用域,在外面是调用不了函数的
    // fun();
  • IIFE结构可以关住函数的作用域,可以创建一个自调用的函数,封住内部的函数,在结构外面是不能调用函数的。
  • IIFE最常用的是()运算符,而且函数可以不写函数名,使用匿名函数。
    // 常用的 IIFE 结构
    (function (a) {
      console.log(a);
    })(4);
    
    //再调用a,调用不了作用域被关起来了,报错
    // console.log(a);