Skip to content

一、函数基本使用

1. 什么是函数

  • 函数就是语句的封装,可以让这些代码方便地被复用。
  • 函数具有"一次定义,多次调用"的优点。
  • 使用函数,可以简化代码,让代码更具有可读性。

2. 函数的定义和调用

  • 和变量类似,函数必须先定义然后才能使用

  • 使用function关键字定义函数,function是"功能"的意思。

    js
      function fun() {
        // 函数体语句
      }
    • function:表示定义函数。
    • fun:函数名,函数名必须符合JS标识符命名规则。
    • ():圆括号中是形参列表,即使没有形参,也必须书写圆括号。
    • {}:大括号中就是函数体语句。
    js
      var fun = function () {
        // 函数体语句
      }
    • function ():匿名函数
  • 函数的调用

    • 执行函数体中的所有语句,就称为"调用函数"
    • 调用函数非常简单,只需在函数名字后书写圆括号对即可
      js
          fun()  // 调用函数,圆括号中是实参列表,如果没有实参,也要书写圆括号。
      fun.png
  • 函数声明的提升

    • 和变量声明提升类似,函数声明也可以被提升

      js
         fun();
         function fun() {
            alert('函数被执行');
         }
      • function fun()在预解析阶段会被提升。
    • 函数优先提升

      fun-1

3. 函数的参数和返回值

  • 参数是函数内的一些待定值,在调用函数时,必须传入这些参数的具体值
  • 函数的参数可多可少,函数可以没有参数,也可以有多个参数,多个参数之间需要用逗号隔开。
  • 函数的参数
    js
      function add(a, b) { // 圆括号中定义"形式参数"
        var sum = a + b;
        console.log('两个数字的和是' + sum); 
      }
      add(3, 5); // 调用函数传入"实际参数"
    • 形参和实参个数不同的情况 fun-2
    • arguments
      • 函数内arguments表示它接收到的实参列表,它是一个类数组对象
      • 类数组对象:所有属性均为从0开始的自然数序列,并且有length属性,和数组类似可以用方括号书写下标访问对象的某个属性值, 但是不能调用数组的方法。
  • 函数的返回值
    • 函数体内可以使用return关键字表示"函数的返回值"
      js
          function sum(a, b) {
            return a + b; // 函数的返回值
          }
          var result = sum(3, 5); // 函数的返回值可以被变量接受
    • 调用一个有返回值的函数,可以被当做一个普通值,从而可以出现在任何可以书写值的地方。
      js
          function sum(a, b) {
            return a + b;
          }
          var result = sum(3, 4) * sum(2, 6);
      js
          function sum(a, b) {
            return a + b;
          }
          var result = sum(3, sum(4, 5));
    • 遇见return即退出函数
      • 调用函数时,一旦遇见return语句则会立即退出函数,将执行权交给调用者。

二、函数算法题

1. 寻找喇叭花数

  • 喇叭花树是这样的三位数:其每一位数字的阶乘之和恰好等于它本身。即abc = a! + b! + c!,其中abc表示一个三位数。试寻找所有喇叭花树。

  • 思路:将计算某个数字的阶乘封装成函数,这样可以让问题简化。

    js
          // 计算一个数字的阶乘
          function factorial(n) {
              // 累乘器
              var result = 1;
              for (var i = 1; i <= n; i++) {
                  result *= i;
              }
              return result;
          }
          // 穷举法,从100到999寻找喇叭花数
          for (var i = 100; i <= 999; i++) {
              // 把数字i变为字符串
              var i_str = i.toString();
              // abc分别表示百位、十位、个位
              var a = Number(i_str[0]);
              var b = Number(i_str[1]);
              var c = Number(i_str[2]);
              // 根据喇叭花数的条件,来判断
              if(factorial(a) + factorial(b) + factorial(c) == i){
                  console.log(i);
              }
          }
  • sort内置排序函数

    • 数组排序可以使用sort()方法,这个方法的参数又是一个函数。
      js
          var arr = [33, 22, 55, 11];
          arr.sort(function (a, b) {
          
          });
    • 这个函数中的a、b分别表述数组中靠前和靠后的项,如果需要它们交换位置,则返回任意正数;否则就返回负数。
      js
         var arr = [33, 22, 55, 11];
         arr.sort(function (a, b) {
            if (a > b) {
              return 1;
            } else {
              return -1;
            }
         }) 
         // 从小到大
         arr.sort(function (a, b) {
           return a - b; 
         })
         // 从大到小
         arr.sort(function (a, b) {
           return b - a;
         })

三、递归

1. 什么是递归

  • 函数的内部语句可以调用这个函数自身,从而发起对函数的一次迭代。在新的迭代中,又会执行调用函数自身的语句,从而又产生一 次迭代。当函数执行到某一次时,不再进行新的迭代,函数被一层一层返回,函数被递归。
  • 递归是一种较为高级的变成技巧,它把一个大型复杂的问题层层转化为一个原问题相似较小的问题来解决。
  • 递归的要素
    • 边界条件:确定递归到何时终止,也称为递归的出口。
    • 递归模式:大问题是如何分解为小问题的,也称为递归体。

2. 递归常见算法

  • 斐波那契数列
    • 斐波那契数列是这样的数列:1、1、2、3、5、8、13、21。
    • 数列下标为0和1的项的值都是1,从下标为2的项开始,每项等于前面两项的和。
      html
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
      </head>
      
      <body>
          <script>
              // 编写一个函数,这个函数的功能是返回斐波那契数列中下标为n的那项的值
              function fib(n) {
                  // 数列的下标为0的项,和下标为1的项值是1
                  if (n == 0 || n == 1) return 1;
                  // 斐波那契数列的本质特征就是每一项,都等于前面两项的和
                  return fib(n - 1) + fib(n - 2);
              }
      
              // 书写一个循环语句,计算斐波那契数列的前15项
              for (var i = 0; i < 15; i++) {
                  console.log(fib(i));
              }
          </script>
      </body>
      
      </html>

3. 实现深克隆

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 原数组
        var arr1 = [33, 44, 11, 22, [77, 88, [33, 44]]];

        // 函数,这个函数会被递归
        function deepClone(arr) {
            // 结果数组,“每一层”都有一个结果数组
            var result = [];
            // 遍历数组的每一项
            for (var i = 0; i < arr.length; i++) {
                // 类型判断,如果遍历到的项是数组
                if (Array.isArray(arr[i])) {
                    // 递归
                    result.push(deepClone(arr[i]));
                } else {
                    // 如果遍历到的项不是数组,是基本类型值,就直接推入到结果数组中,
                    // 相当于是递归的出口
                    result.push(arr[i]);
                }
            }
            // 返回结果数组
            return result;
        }

        // 测试一下
        var arr2 = deepClone(arr1);
        console.log(arr2);

        // 是否藕断丝连
        console.log(arr1[4][2] == arr2[4][2]);
        arr1[4][2].push(99);
        console.log(arr1);
        console.log(arr2);

    </script>
</body>

</html>

四、全局变量和局部变量

  • 变量作用域:JavaScript是函数作用域编程语言:变量只在其定义时所在的function内部有意义。

    js
      function fun() {
        var a = 10;
      }
      fun();
      console.log(a); // 报错
    • 变量a是在fun函数中被定义的,所以变量a只在fun函数内部有定义,fun函数就是a的"作用域"变量a被称为局部变量
  • 全局变量

    • 如果不将变量定义在任何函数的内部,此时这个变量就是全局变量,它在任何函数内都可以被访问和更改。
      js
        var a = 10;
        function fun() {
          a++;
          console.log(a); // 输出11
        }
        fun();
        console.log(a); // 输出11
      • 变量a没有定义在任何函数内部,它是"全局变量"。
  • 遮蔽效应

    • 如果函数中也定义了和全局同名的变量,则函数内的变量会将全局的变量"遮蔽"
      js
         var a = 10;
         function fun() {
            var a = 5;
            a++;
            console.log(a); // 输出6
         }
         fun();
         console.log(a); // 输出 10
      • 局部变量a将全局变量a"遮蔽"了
    • 注意考虑变量声明提升的情况
      js
         var a = 10;
         function fun() {
            a++;        // 局部变量a被自增1,a此时是undefined,自增1结果是NaN
            var a = 5;  // 局部变量a会被提升到a++之前, 重新将a赋值为5
            console.log(a); // 输出5
         }
         fun();
         console.log(a); // 输出10
  • 形参也是局部变量

    js
      var a = 10;
      function fun(a) {
        a++;
        console.log(a); // 输出8
      }
      fun(7);
      console.log(a);  // 输出10
    • 形参a也是函数内部的局部变量

五、作用域链

  • 函数的嵌套:一个函数内部也可以定义一个函数。和局部变量类似,定义在一个函数内部的函数是局部函数。

    js
        function fun() {
          function inner() {
              console.log('你好');
           }
           inner(); // 调用内部函数
        }
        fun();  // 调用外部函数
  • 作用域链

    • 在函数嵌套中,变量会从内到外逐层寻找它的定义。
      js
        var a = 10;
        var b = 20;
        function fun() {
          var c = 30;
          function inner() {
            var a = 40;
            var d = 50;
            console.log(a, b, c, d); // 使用变量时,JS会从当前层开始,逐层向上寻找定义。
          }
          inner();
        }
        fun();
    • 不加var将定义全局变量
      • 在初次给变量赋值时,如果没有加var,则将定义全局变量。
        js
          function fun() {
            a = 3;
          }
          fun();
          console.log(a); // 3
        js
          var a = 1;
          var b = 2;
          function fun() {
            c = 3;
            var b = 4;
            b++;
            console.log(b); // 5
            c++;
          }
          fun();
          console.log(b); // 2
          console.log(c); // 4

六、闭包

  • 什么是闭包

    js
        // 创建一个函数
        function fun() {
           // 定义局部变量
           var name = '张三';
           function innerFun() {
              alert(name);
           } 
           return innerFun; // 返回了内部函数
        }
        var innerFun = fun(); // 内部函数被移动到了外部执行
        innerFun();
    • JavaScript中函数会产生闭包(closure)。闭包是函数本身该函数声明时所处的环境状态的组合。 closure.png

    • 函数能够"记忆住"其定义时所处的环境,即使函数不在其定义的环境中被调用,也能访问定义时所处环境的变量。

  • 观察闭包现象

    • 在JavaScript中,每次创建函数时都会创建闭包
    • 但是,闭包特性往往需要将函数"换一个地方"执行,才能被观察出来。
  • 闭包的作用

    • 闭包很有用,因为它允许我们将数据与操作该数据的函数关联起来,这与"面向对象编程"有少许相似之处。
    • 闭包的功能:记忆性、模拟私有变量。
  • 闭包的用途[记忆性]

    • 当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后被自动清除。这就是闭包的记忆性。
    • 举例:创建体温检测函数checkTemp(n),可以检查体温n是否正常,函数会返回布尔值。 但是不同的小区有不同的体温检测标准,比如A小区体温合格线是37.1℃,而B小区体温合格线是37.3℃
      js
          function createCheckTemp(standardTemp) {
            function checkTemp(n) {
              if (n <= standardTemp) {
                alert('你的体温正常');
              } else {
                alert('你的体温偏高');
              }
            }
            return checkTemp;
          }
          var checkTemp_A = createCheckTemp(37.1);
          var checkTemp_B = createCheckTemp(37.3);
          checkTemp_A(37.2);
          checkTemp_B(37.0);
          checkTemp_A(37.5);
  • 闭包的用途[模拟私有变量]

    • 请定义一个变量a,要求是能保证这个a只能被进行指定操作(如加1、乘2),而不能进行其他操作。
      js
        // 封装一个函数,这个函数的功能就是私有化变量
        function fun() {
           // 定义一个局部变量a
           var a = 0;
           return {
              getA: function() {
                return a;
              },
              add: function () {
                a++;
              },
              pow: function () {
                a *=2;
              }   
           }  
        }
        var obj = fun();
        // 如果想在fun函数外面使用变量a,唯一的方法就是调用getA()方法。
        console.log(obj.getA());
  • 闭包的注意点

    • 不能滥用闭包,否则会造成网页的性能问题,严重时可能导致内存泄漏。所谓内存泄漏是指程序中已动态分配的内存由于某种原因未释放或 无法释放。
  • 闭包的一道面试题

    js
      function addCount() {
        var count = 0;
        return function () {
          count = count + 1;
          console.log(count);
        };
      }
      var fun1 = addCount();
      var fun2 = addCount();
      fun1(); // 1
      fun2(); // 1
      fun2(); // 2
      fun1(); // 2

七、什么是IIFE

  • IIFE(Immediately Invoked Function Expression,立即调用函数表达式)是一种特殊的JavaScript函数写法, 一旦被定义,就立即被调用

    js
      (function () {
        statements
       })();
    • 包裹function的括号:将函数变成表达式
    • ():运行函数
  • 形成IIFE的方法

    • 函数不能直接加圆括号被调用
      text
         function () {
          alert(1)     
         }()
    • 函数必须转为函数表达式才能被调用。
      js
        (function (){
          alert(1)
        })();
      
        +function () {
          alert(1)
        }(); 
      
        -function () {
          alert(1)
        }();
  • IIFE的作用[为变量赋值]

    • 为变量赋值:当给变量赋值需要一些较为复杂的计算时(如if语句),使用IIFE显的语法更紧凑
      html
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
      </head>
      
      <body>
          <script>
              var age = 42;
              var sex = '女';
              var title = (function () {
                  if (age < 18) {
                      return '小朋友';
                  } else {
                      if (sex == '男') {
                          return '先生';
                      } else {
                          return '女士';
                      }
                  }
              })();
      
              alert(title);
          </script>
      </body>
      
      </html>
  • IIFE的作用[将全局变量变为局部变量]

    • IIFE可以在一些场合(如for循环中)将全局变量为局部变量,语法显得紧凑。
      html
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
      </head>
      
      <body>
          <script>
              var arr = [];
      
              for (var i = 0; i < 5; i++) {
                  (function(i){
                      arr.push(function () {
                          alert(i);
                      });
                  })(i);
              }
              arr[0]();
              arr[1]();
              arr[2]();
              arr[3]();
              arr[4]();
          </script>
      </body>
      
      </html>

八、内容难点

  • 什么是函数?函数为开发带来了哪些便利?
  • 函数的参数和返回值
  • 函数的相关算法题
  • 递归、递归算法题
  • 作用域和闭包
  • IIFE

Released under the MIT License.