JavsScript基础语法02-函数

1. 函数

JavaScript 里面,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用。虽然 for 循环语句也能实现一些简单的重复操作,但是比较具有局限性,此时我们就可以使用 JavaScript 中的函数。

函数:就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用

1.1 函数的定义

JavaScript 中,可以使用 function 语句来定义一个函数。这种形式是由关键字 function函数名一组参数 以及置于大括号中需要执行的 一段代码 构成的。使用 function 语句定义函数的基本语法如下:

function 函数名([参数1, 参数2,……]){
    语句
    [return 返回值]
}

参数说明:

  1. 函数名:必选,用于指定函数名。在同一个页面中,函数名必须是唯一的,并且区分大小写。
  2. 参数:可选,用于指定参数列表。当使用多个参数时,参数间使用逗号进行分隔。一个函数最多可以有255个参数。
  3. 语句:必选,是函数体,用于实现函数功能的语句。
  4. 返回值:可选,用于返回函数值。返回值可以是任意的表达式、变量或常量。

例如,定义一个不带参数的函数 hello(),在函数体中输出 Amo 你好~~~ 字符串。示例代码如下:

<script>
    function hello() {
        console.log("Amo 你好~~~");
    }
</script>

例如,定义一个用于计算商品金额的函数 account(),该函数有两个参数,用于指定单价和数量,返回值为计算后的金额。示例代码如下:

<script>
    function account(price, number) { // 定义有两个参数的函数
        let sum = price * number; // 计算金额
        return sum; //返回计算后的金额
    }
</script>

常见错误:在同一页面中定义了两个名称相同的函数。例如,下面的代码中定义了两个同名的函数 hello()

<script>
    function hello() {     //定义函数名称为hello
        document.write("Amo 你好~~~"); //定义函数体
    }

    function hello() {    //定义同名的函数
        alert("Amo 你好~~~");     //定义函数体
    }
</script>

上述代码中,由于两个函数的名称相同,第一个函数被第二个函数所覆盖,所以第一个函数不会执行,因此在同一页面中定义的函数名称必须唯一。

1.2 函数的调用

函数定义后并不会自动执行,要执行一个函数需要在特定的位置调用函数。调用函数的过程就像是启动一个机器一样,机器本身是不会自动工作的,只有按下相应的开关来调用这个机器,它才会执行相应的操作。调用函数需要创建调用语句,调用语句包含函数名称、参数具体值。

1.2.1 函数的简单调用

函数调用的语法如下:

函数名(传递给函数的参数1, 传递给函数的参数2, ……);

例如,定义一个函数 outputImage(),这个函数的功能是在页面中输出一张图片,然后通过调用这个函数实现图片的输出,示例代码如下:

<script>
    function outputImage() {
        document.write("<img src='https://ss3.bdstatic.com/70cFv8Sh_" +
            "Q1YnxGkpoWK1HF6hhy/it/u=1039034310,233114253&fm=26&gp=0.jpg'/>");
    }

    outputImage()
</script>

运行结果如下图所示。
在这里插入图片描述

1.2.2 在事件响应中调用函数

当用户单击某个按钮或某个复选框时都将触发事件,通过编写程序对事件做出反应的行为称为响应事件,在 JavaScript 语言中,将函数与事件相关联就完成了响应事件的过程。比如,按下开关按钮打开电灯就可以看作是一个响应事件的过程,按下开关相当于触发了单击事件,而电灯亮起就相当于执行了相应的函数。

例如,当用户单击某个按钮时执行相应的函数,可以使用如下代码实现该功能。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>函数</title>
</head>
<body>
<input type="button" value="点我试试" onclick="test();">
</body>
</html>
<script>
    function test() {
        alert("Amo 好帅呀~~~");
    }
</script>

在上述代码中可以看出,首先定义一个名为 test() 的函数,函数体比较简单,使用 alert() 语句输出一个字符串,最后在按钮 onclick 事件中调用 test() 函数。当用户单击 点我试试 按钮后将弹出相应对话框。运行结果如下图所示。
在这里插入图片描述

1.2.3 通过链接调用函数

函数除了可以在响应事件中被调用之外,还可以在链接中被调用,在 <a> 标签中的 href 属性中使用 javascript:函数名() 格式来调用函数,当用户单击这个链接时,相关函数将被执行,下面的代码实现了通过链接调用函数。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>函数</title>
</head>
<body>
<a href="javascript:test();">点我弹弹弹~</a>
</body>
</html>


<script>
    function test() {
        alert("Amo 好帅呀~~~");
    }
</script>

一般我们可以使用这种方式来阻止 <a> 标签的跳转。示例代码如下:

<!-- 不建议使用-->
<a href="javascript:void(0)"></a>

其它方式,示例代码如下:

<!-- 这种a标签会在页面比较长的时候回到页面顶部-->
<a href="#" class="demo"></a>
<a href="https://www.baidu.com/" onclick="return false">跳转到百度</a>

1.3 函数的参数

我们把定义函数时指定的参数称为形式参数,简称 形参 。而把调用函数时实际传递的值称为实际参数,简称 实参 。如果把函数比喻成一台生产的机器,那么,运输原材料的通道就可以看作形参,而实际运输的原材料就可以看作是实参。

JavaScript 中定义函数参数的格式如下:

function函数名(形参1,形参2,……){
    函数体
}

定义函数时,在函数名后面的圆括号内可以指定一个或多个参数(参数之间用逗号 , 分隔)。指定参数的作用在于,当调用函数时,可以为被调用的函数传递一个或多个值。如果定义的函数有参数,那么调用该函数的语法格式如下:

函数名(实参1,实参2,……)

通常,在定义函数时使用了多少个形参,在函数调用时也会给出多少个实参,这里需要注意的是,实参之间也必须用逗号 , 分隔。例如,定义一个带有三个参数的函数,这三个参数用于指定姓名和年龄以及爱好,然后对它们进行输出,代码如下:

<script>
    function personInfo(name, age, hobby) { //定义含有三个参数的函数
        alert(`我的英文名是${name},今年${age}岁,爱好${hobby}`);//输出字符串和参数的值
    }

    personInfo("Amo", 18, "唱歌跳舞"); //调用函数并传递参数
</script>

运行结果如下图所示。
在这里插入图片描述
定义一个用于输出图书名称和图书作者的函数,在调用函数时将图书名称和图书作者作为参数进行传递。代码如下:

<script>
    function bookInfo(bookName, author) { //定义图书相关的信息函数
        alert(`图书名称: ${bookName}\n图书作者: ${author}`);
    }

    bookInfo("ES6标准入门", "阮一峰");
</script>

运行结果如下图所示。
在这里插入图片描述
JavaScript 中,形参的默认值是 undefined。函数形参和实参数量不匹配时:

参数个数 说明
实参个数等于形参个数 输出正确结果
实参个数多于形参个数 只取到形参的个数
实参个数小于形参个数 多的形参定义为undefined,结果为NaN

1.4 函数的返回值

对于函数调用,一方面可以通过参数向函数传递数据,另一方面也可以从函数获取数据,也就是说函数可以返回值。在 JavaScript 的函数中,可以使用 return 语句为函数返回一个值。语法:

return 表达式;

这条语句的作用是结束函数,并把其后的表达式的值作为函数的返回值。例如,定义一个计算两个数的和的函数,并将计算结果作为函数的返回值,代码如下:

<script>
    function sum(num1, num2) { //定义含有两个参数的函数
        let result = num1 + num2;//获取两个参数的和
        return result;//将变量result的值作为函数的返回值
    }

    let a = 10;
    let b = 20;
    alert(`${a} + ${b} = ${sum(a, b)}`);
</script>

运行结果如下图所示。
在这里插入图片描述
函数返回值可以直接赋给变量或用于表达式中,也就是说函数调用可以出现在表达式中。例如,将上面示例中函数的返回值赋给变量 result ,然后再进行输出,代码如下:

<script>
    function sum(num1, num2) { //定义含有两个参数的函数
        let result = num1 + num2;//获取两个参数的和
        return result;//将变量result的值作为函数的返回值
    }

    let a = 10;
    let b = 20;
    let result = sum(a, b); //将函数的返回值赋给变量result
    alert(`${a} + ${b} = ${result}`);//输出结果
</script>

1.5 函数的嵌套定义

JavaScript 中允许使用嵌套函数,嵌套函数就是在一个函数的函数体中使用了其他的函数。嵌套函数的使用包括函数的嵌套定义和函数的嵌套调用。函数的 嵌套定义 就是在函数内部再定义其他的函数。例如,在一个函数内部嵌套定义另一个函数的代码如下:

<script>
    function outFun() {      //定义外部函数
        function inFun(x, y) {    //定义内部函数
            alert(x + y);   //输出两个参数的和
        }

        inFun(20, 50);      //调用内部函数并传递参数
    }

    outFun(); //调用外部函数
</script>

运行结果如下图所示。
在这里插入图片描述
在上述代码中定义了一个外部函数 outFun() ,在该函数的内部又嵌套定义了一个函数 inFun(),它的作用是输出两个参数的和,最后在外部函数中调用了内部函数。虽然在 JavaScript 中允许函数的嵌套定义,但它会使程序的可读性降低,因此,尽量避免使用这种定义嵌套函数的方式。

1.6 函数的嵌套调用

JavaScript 中,允许在一个函数的函数体中对另一个函数进行调用,这就是函数的嵌套调用。例如,在函数 b() 中对函数 a() 进行调用,代码如下:

<script>
    function a() {  //定义函数a()                    
        alert("ES6标准入门"); //输出字符串         
    }

    function b() {  //定义函数b()                            
        a();  //在函数b()中调用函数a()                                  
    }

    b();   //调用函数b()                                    
</script>

运行结果如下图所示。
在这里插入图片描述

1.7 递归函数

所谓 递归函数 就是函数在自身的函数体内调用自身,使用递归函数时一定要当心,处理不当将会使程序进入死循环,递归函数只在特定的情况下使用,比如处理 阶乘/斐波那契数列等问题。语法:

function 函数名(参数1){
    函数名(参数2);
}

例如,使用递归函数取得 10! 的值,其中 10!=10*9! ,而9!=9*8! ,以此类推,最后 1!=1,这样的数学公式在 JavaScript 程序中可以很容易使用函数进行描述,可以使用 f(n) 表示 n! 的值,当 1<n<10 时,f(n)=n*f(n-1) ,当 n<=1 时,f(n)=1。代码如下:

<script>
    function f(num) { //定义递归函数
        if (num <= 1) { //如果参数num的值小于等于1
            return 1; //返回1
        } else {
            return f(num - 1) * num; //调用递归函数
        }
    }

    alert(`5!的结果为: ${f(5)}`); //调用函数输出5的阶乘
</script>

上述代码运行结果如下图所示。
在这里插入图片描述
在定义递归函数时需要两个必要条件:

  1. 包括一个结束递归的条件。
    如上面示例中的 if(num<=1) 语句,如果满足条件则执行 return 1 语句,不再递归。
  2. 包括一个递归调用语句。
    如上面示例中的 return f(num-1)*num 语句,用于实现调用递归函数。

1.8 变量的作用域

变量的作用域是指变量在程序中的有效范围,在该范围内可以使用该变量。变量的作用域取决于该变量是哪一种变量。

JavaScript 中,变量根据作用域可以分为两种:全局变量局部变量。全局变量是定义在所有函数之外的变量,作用范围是该变量定义后的所有代码。局部变量是定义在函数体内的变量,只有在该函数中,且该变量定义后的代码中才可以使用这个变量,函数的参数也是局部性的,只在函数内部起作用。如果把函数比作一台机器,那么,在机器外摆放的原材料就相当于全局变量,这些原材料可以为所有机器使用,而机器内部所使用的原材料就相当于局部变量。

例如,下面的程序代码说明了变量的作用域作用不同的有效范围:

<script>
    let a = "这是全局变量"; // 该变量在函数外声明,作用于整个脚本
    function send() { //定义函数
        let b = "这是局部变量"; //该变量在函数内声明,只作用于该函数体
        document.write(a + "<br>");//输出全局变量的值
        document.write(b);//输出局部变量的值
    }

    send();//调用函数
</script>

运行结果为:
在这里插入图片描述
上述代码中,局部变量 b 只作用于函数体,如果在函数之外输出局部变量 b 的值将会出现错误。错误代码如下:

<script>
    let a = "这是全局变量"; // 该变量在函数外声明,作用于整个脚本
    function send() { //定义函数
        let b = "这是局部变量"; //该变量在函数内声明,只作用于该函数体
        document.write(a + "<br>");//输出全局变量的值
        document.write(b);//输出局部变量的值
    }

    send();//调用函数
    document.write(b);//错误代码,不允许在函数外输出局部变量的值
</script>

运行结果为:
在这里插入图片描述
如果在函数体中定义了一个与全局变量同名的局部变量,那么该全局变量在函数体中将不起作用。例如,下面的程序代码将输出局部变量的值:

<script>
    let a = "这是全局变量"; //声明一个全局变量a
    function send() {    //定义函数
        let a = "这是局部变量"; //声明一个和全局变量同名的局部变量a
        document.write(a); //输出局部变量a的值
    }

    send();   //调用函数                                 
</script>

运行结果为:
在这里插入图片描述
上述代码中,定义了一个和全局变量同名的局部变量 a,此时在函数中输出变量 a 的值为局部变量的值。全局变量和局部变量的区别:

  1. 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存。
  2. 局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化。当代码块运行结束后,就会被销毁,因此更节省内存空间。

只要是代码都在一个作用域中,写在函数内部的为局部作用域,未写在任何函数内部即在全局作用域中。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域,根据在 内部函数可以访问外部函数变量 的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作 作用域链。示例代码如下:

<script>
    function f1() {
        let num = 123;

        function f2() {
            console.log(num);
        }

        f2();
    }

    f1(); //123
</script>

作用域链:采取 就近原则 的方式来查找变量最终的值。案例如下:

<script>
    let a = 1;

    function fn1() {
        let a = 2;
        let b = '22';
        fn2();

        function fn2() {
            let a = 3;
            fn3();

            function fn3() {
                let a = 4;
                console.log(a); //a的值 4
                console.log(b); //b的值 '22'
            }
        }
    }

    fn1();
</script>

1.9 预解析

JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。解释如下:

  1. 预解析:在当前作用域下,JS 代码执行之前,浏览器会默认把带有 varfunction 声明的变量在内存中进行提前声明或者定义。
  2. 代码执行:从上到下执行 JS 语句。

一句话: 预解析会把变量和函数的声明在代码执行之前执行完成.

1.9.1 变量预解析

预解析也叫做变量、函数提升。变量提升(变量预解析):变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。示例代码如下:
在这里插入图片描述

1.9.2 函数预解析

函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。示例代码如下:
在这里插入图片描述
函数声明代表函数整体,所以函数提升后,函数名代表整个函数,但是函数并没有被调用!函数表达式创建函数,会执行变量提升,此时接收函数的变量名无法正确的调用,示例代码如下:

<script>
    fn();
    var fn = function () {
        document.write("<h4>Amo好帅呀~~~~</h4>");
    }
</script>

上述代码执行结果如下图所示:
在这里插入图片描述
解释:该段代码执行之前,会做变量声明提升,fn 在提升之后的值是undefined,而 fn 调用是在 fn 被赋值为函数体之前,此时 fn 的值是 undefined,所以无法正确调用。如下图所示:
在这里插入图片描述

1.9.3 作用域练习题

第一题,示例代码如下:

<script>
    var x = 5;
    a();

    function a() {
        alert(x)
        var x = 10;
    }
</script>

分析过程如下:
在这里插入图片描述
第二题,示例代码如下:

<script>
    a();

    function a() {
        alert(x)
        var x = 10;
    }

    alert(x)
</script>

分析过程如下:
在这里插入图片描述
第三题,示例代码如下:

<script>
    a();

    function a() {
        alert(a)
    }

    var a;
    alert(a)
</script>

分析过程如下:
在这里插入图片描述
第四题,示例代码如下:

<script>
    alert(a)
    var a = 10;
    alert(a);

    function a() {
        alert(20);
    }

    alert(a);
    var a = 30;
    alert(a);

    function a() {
        alert(40);
    }

    alert(a);
</script>

分析过程如下:
在这里插入图片描述
第五题,示例代码如下:

<script>
    var a = 10;
    alert(a)
    a();

    function a() {
        alert(20)
    }
</script>

分析过程如下:
在这里插入图片描述
第六题,示例代码如下:

<script>
    a();
    var a = function () {
        alert(1)
    }
    a();
    function a() {
        alert(2)
    }
    a()
    var a = function () {
        alert(3)
    }
    a()

</script>

分析过程如下:
在这里插入图片描述
第七题,示例代码如下:

<script>
    var a = 0;

    function fn() {
        alert(a)
        var a = 1;
        alert(a)
    }

    fn();
    alert(a)
</script>

分析过程如下:
在这里插入图片描述

1.10 在表达式中定义函数

JavaScript 中提供了一种定义匿名函数的方法,就是在表达式中直接定义函数,它的语法和 function 语句非常相似。其语法格式如下:

var 变量名 = function(参数1,参数2,……) {
    函数体
};

这种定义函数的方法不需要指定函数名,把定义的函数赋值给一个变量,后面的程序就可以通过这个变量来调用这个函数,这种定义函数的方法有很好的可读性。例如,在表达式中直接定义一个返回两个数字和的匿名函数,示例代码如下:

<script>
    let sum = function (x, y) {  //定义匿名函数
        return x + y;//返回两个参数的和
    };
    alert(`10+20=${sum(10, 20)}`);//调用函数并输出结果
</script>

运行结果如下图所示。
在这里插入图片描述
在以上代码中定义了一个匿名函数,并把对它的引用存储在变量 sum中。该函数有两个参数,分别为 xy。该函数的函数体为 return x+y,即返回参数 x 与参数 y 的和。

1.11 使用Function()构造函数

除了在表达式中定义函数之外,还有一种定义匿名函数的方法-----使用 Function() 构造函数来定义函数。这种方式可以动态地创建函数。Function() 构造函数的语法格式如下:

let 变量名 = new Function("参数1","参数2",……"函数体");

使用 Function() 构造函数可以接收一个或多个参数作为函数的参数,也可以一个参数也不使用。Function() 构造函数的最后一个参数为函数体的内容。Function() 构造函数中的所有参数和函数体都必须是 字符串类型,因此一定要用双引号或单引号引起来。

例如:使用 Function() 构造函数定义一个计算两个数字和的函数,代码如下:

<script>
    let sum = new Function("x", "y", "alert(x+y);"); // 使用Function构造函数定义函数
    sum(10, 20); // 调用函数
</script>

运行结果如下图所示。
在这里插入图片描述
上述代码中,sum 并不是一个函数名,而是一个指向函数的变量,因此,使用 Function() 构造函数创建的函数也是匿名函数。在创建的这个构造函数中有两个参数,分别为 xy。该函数的函数体为 alert(x+y),即输出 xy 的和。

1.12 arguments的使用

当不确定有多少个参数传递的时候,可以用 arguments 来获取。JavaScript 中,arguments 实际上它是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有实参。arguments 展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:

  1. 具有 length 属性
  2. 按索引方式储存数据
  3. 不具有数组的push , pop 等方法

注意:在函数内部使用该对象,用此对象获取函数调用时传的实参。例如,求任意个数数字之和,示例代码如下:

<script>
  function getSum() {
      // console.log(arguments.length);
      let sum = 0;
      for (let i = 0; i < arguments.length; i++) {
          sum += arguments[i];
      }
      return sum;
  }

  let [a, b, c, d] = [1, 2, 3, 4];
  let [x, y, z] = [8, 9, 10];
  console.log(getSum(a, b, c, d)); // 10
  console.log(getSum(x, y, z)); // 27
</script>

2. ES6 函数的扩展

2.1 基本用法

ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。示例代码如下:

<script>
    function f(x, y) {
        y = y || "Cool";
        console.log(x, y);
    }

    f("Amo");
    f("Amo", "So Cool~~~");
    f("Amo", '');
</script>

上面代码检查函数 f 的参数 y 有没有赋值,如果没有,则指定默认值为 Cool。这种写法的缺点在于,如果参数 y 赋值了,但是对应的布尔值为 false,则该赋值不起作用。就像上面代码的最后一行,参数 y 等于空字符,结果被改为默认值。ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。示例代码如下:

<script>
    function f(x, y = "World") {
        console.log(x, y);
    }

    f("Amo"); 
    f("Amo", "So Cool~~~");
    f("Amo", '');
</script>

上述代码执行结果如下:
在这里插入图片描述

2.2 rest参数

ES6 引入 rest 参数(形式为 ... 变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。示例代码如下:

<script>
    function add(...values) {
        let sum = 0;

        for (let val of values) {
            sum += val;
        }

        return sum;
    }

    console.log(add(2, 5, 3)); // 10
</script>

上面代码的 add 函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。示例代码如下:
在这里插入图片描述

2.3 箭头函数

ES6 允许使用 箭头=> 定义函数。示例代码如下:

<script>
    // 传统js函数的写法
    let f1 = function (v) {
        return v;
    }
    console.log(f1(3));
    // ES6箭头函数
    let f2 = v => v;
    console.log(f2(4));
</script>

如果箭头函数 不需要参数或需要多个参数,就使用一个圆括号代表参数部分。示例代码如下:

<script>
   let f1 = () => 5; //==> let f1 = function(){return 5};
   let sum = (num1, num2) => num1 + num2;
   //等同于如下代码
   /*let sum = function (num1, num2) {
       return num1 + num2;
   }*/
</script>

如果 箭头函数 的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。示例代码如下:
在这里插入图片描述
由于大括号被解释为代码块,所以如果 箭头函数 直接返回一个对象,必须在对象外面加上括号,否则会报错。如下图所示:
在这里插入图片描述
箭头函数的一个用处是简化回调函数。例子如下图所示:
在这里插入图片描述

2.4 箭头函数的注意点

箭头函数有几个使用注意点。

  1. 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
  2. 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
  3. 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。this 对象的指向是可变的,这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了 this 的指向不同,如下图所示:
在这里插入图片描述
但是在箭头函数中,它是固定的。关于 this 的指向问题,在后续高级语法中在进行详细的讲解。关于其它函数的扩展具体可以参照 ECMAScript6 入门

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章