4、JavaScript函数

一、JavaScript函数

1. JavaScript函数基本使用

思考:下面代码有什么问题?

alert('欢迎马先生光临红浪漫');
alert('男宾3位')alert('马先生消费480')alert('欢迎马先生光临红浪漫');
alert('男宾3位')alert('马先生消费480')alert('欢迎马先生光临红浪漫');
alert('男宾3位')alert('马先生消费480')

JS中的函数:把一段需要重复使用的代码,用function语法包起来,方便重复调用,分块和简化代码。复杂一点的,也会加入封装、抽象、分类等思想。

函数:一组可以被重复调用的JavaScript语句

1.1 JavaScript函数声明
  1. 用function关键字进行函数声明

    function 函数的名字(参数) {
         //函数体:要执行的代码
    }
    
    • 函数名字与变量名一样都必须是JavaScript合法的标识符。
    • 在函数名之后是一个由小括号包含的参数列表,参数之间以逗号分隔,参数是可选的。
    • 在小括号之后是一对大括号,大括号内包含的语句就是函数体的主要内容。
  2. 通过函数表达式得到函数

    可以用函数表达式声明 (如果 function 是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。)

    var sum = function() {
    	console.log('谁在让我东张西望');
    }
    sum();
    
  3. 通过new Function()得到函数

    //格式: new Function([参数,]'函数体');
    
    var sum = new Function('a', 'b', 'console.log(a+b)');
    sum(1, 2); //3
    
1.2 JavaScript函数调用
  • 调用方式:名字(); 函数可以多次调用
//函数声明
function fn(){
	console.log(1);
}
//函数的调用
fn();
fn();

思考:调用函数,名字不加()会返回什么?

2. JavaScript函数参数

思考:下面代码的变量(名字,性别,人数,花销)有什么问题吗?

alert('欢迎马先生光临红浪漫');
alert('男宾3位')alert('马先生消费480')
2.1 JavaScript函数参数
  • 形参:形式上的参数——给函数声明一个参数;

  • 实参:实际的参数——在函数调用时给形参赋的值

    function func(形参1,形参2){
        //函数执行代码
    }
     
    func(实参1,实参2);//调用时传参
    
  • 一般情况下,函数的实参和形参的数量应该相同,但是JS并没有这样的要求。可以不相同

  • 如果函数的实参数量少于形参数量,那么多出来的形参会默认会undefined

  • 如果函数实参数量多余形参数量,那么多出来的实参就不能通过形参访问。函数忽略掉多余的实参。

案例1:正常传递对应参数

function f(a,b) {
  	alert('正常传递参数');
}
f(1,2);

案例2:实参少于形参

function f2(a,b,c) {
    console.log(a,b,c);//1 2 undefined
    console.log(typeof c);//undefined
}
f2(1,2);

案例3:实参多余形参

function f3(a,b,c) {
    console.log(a,b,c)1
}
f3(1,2,3,4,5,6,7)

3. 函数的返回值

  • 在函数内部通过return设置函数的返回值

  • 没有return语句的函数,默认返回undefined

  • 在函数体内部,遇到return语句,则终止函数的执行

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>函数的返回值</title>
    </head>
    <body>
        <script>
            //在函数内部通过return设置函数的返回值
            function testFn() {
                // return; //这样写默认返回undefined
                // return 1;
                return 'hello world'; //可以返回任意类型的数据
                console.log(321);
                console.log(321);
                console.log(321);
                console.log(321);
                console.log(321);
                return 123;
            }
    
            var res = testFn();
            console.log(res);
    
            //思考下:这个会返回什么?
            function testFn2() {
                return 1+1, 2*3, 3*3;
            }
    
            console.log(testFn2());
    
            /* 
            总结:
                1. 在函数内部通过return设置函数的返回值
                2. 没有return语句的函数,默认返回undefined
                3. 在函数体内部,遇到return语句,则终止函数的执行
            */
        </script>
    </body>
    </html>
    
    
  • 思考:如果return后返回多个值,结果是什么样的?

案例练习:

  1. 计算2和3的和
  2. 输入两个数,返回两个数字的和
    • sum(1, 8); 返回2个数字的和 9
    • sum(4, 7); //11
  3. 返回n到m之间所有数字的和
    • sum(1, 10); 返回1~10之间所有数字相加的和 55
    • sum(1, 100); //5050
  4. 输入两个数,返回两个数中的最大值
    • max(2, 8); //8
  5. 输入三个数,返回三个数中的最大值
    • max(2, 8, 3); //8
  6. 求一个数组中所有元素的和
    • sum([1,2,3,4,5]); //15

4.JavaScript作用域

4.1 变量作用域

变量作用域(scope)是指变量在程序中可以访问的有效范围,也称为变量的可见性。分为全局变量和局部变量

  • 全局变量:变量在整个页面中都是可见的,可以被自由的访问,在函数之外定义的变量是全局变量
  • 局部变量,变量仅能在声明的函数内部可见,函数外是不允许访问的,在函数内部定义的变量是局部变量
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>变量作用域</title>
</head>
<body>
    <script>
        //作用域: 起作用的范围(scope)
        /* 
        作用域分为局部和全局
        变量分为局部变量和全局变量
            局部变量: 在函数内部声明的变量叫局部变量
            全局变量: 在全局范围(函数外部)声明的变量
        */

        var name = '张三';

        //声明一个函数就会创建一个函数(局部)作用域
        function fn1() {
            //函数内部可以访问函数外面的变量
            console.log(name);

            var innerName = '张四风';

            console.log(innerName);
        }

        // console.log(name);
        fn1();

        //在函数外面不能访问函数内部的变量
        console.log(innerName);
    </script>
</body>
</html>

4.2 JavaScript 局部变量

变量在函数内声明,变量的作用域为局部作用域。局部变量的作用域 我们称之为局部作用域。

局部变量:只能在函数内部访问。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>局部变量</title>
</head>
<body>
    <script>
        //形参也是局部变量
        function fn1(age) {
            var innerVar = '我是个局部变量';

            console.log('在内部访问innerVar:'+innerVar);
            console.log('在内部访问age:'+age);
        }

        fn1(18);

        // console.log('在外部访问innerVar:'+innerVar); //报错
        // console.log('在外部访问age:'+age); //报错

        //fn2的形参还叫age也行,不会跟fn1的age有任何关系
        function fn2(age) {
            console.log(innerVar); //报错:无法访问fn1里面的内部变量
        }
        // fn2();
    </script>
</body>
</html>

因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。

局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。

4.3 JavaScript 全局变量

变量在函数外定义,即为全局变量。

全局变量有全局作用域: 网页中所有脚本和函数均可使用。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>全局变量</title>
</head>
<body>
    <script>
        var a = 100;

        function fn1() {
            console.log(a);
            //尽量避免在函数内部修改全局变量的值
            a = 200;
        }

        function fn2() {
            //为什么能访问呢?
            //1. 在自己的作用域中找变量a,找到了则直接使用
            //2. 如果没找到,则往上一级作用域中找,找到就用,没找到就报错
            console.log(a);

            //在函数内部,声明变量时,不加var,默认会弄成全局变量
            // var money = 300;
            money = 300; //应该尽量避免
        }

        fn1();
        fn2();

        console.log(money);
    </script>
</body>
</html>

特别需要注意:如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。但是如果该变量出现在形式参数中,则为局部变量。

以下实例中 name在函数内,但是为全局变量。

function myFunction() {
    name = "lisi";
}
myFunction();
// 此处可以访问 name 变量
console.log(name);

当函数内部写了一个变量不带var 有以下几种情况:

1、如果函数内部出现不带var的变量,那么首先找形参;如果形参有,当做是局部变量对待
2、如果函数内部出现不带var的变量,形参也没有这个变量,那么就把它当做全局变量来对待

4.4 作用域链

作用域链:JS中变量的查找规则。

作用域链和作用域不是一回事:

​ 作用域描述是变量起作用的区域和范围

​ 作用域链描述的程序在找变量的过程;

​ 程序在查找变量的时候,先从自己的作用域去查找,如果找到就直接使用这个变量的值,如果没有找到,会继续往上一级作用域去找,同样也是找到就使用没有找到继续往上找;直到找到全局作用域,如果找到就使用,没找到就报错(引用错误,这个变量没有定义);

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>作用域链</title>
</head>
<body>
    <script>
        /* 
        作用域链:当作用域产生嵌套的时候就会形成作用域链
            默认有个全局作用域,声明一个函数就会形成一个函数作用域
            函数内部再声明函数,就会形成作用域嵌套

            特点:
                1. 在自己的作用域中找变量a,找到了则直接使用
                2. 如果没找到,则往上一级作用域中找,找到就用
                3. 直到找到全局作用域,如果还没找到,则报错
        */
        var n1 = 10;
        function fn1() {
            var n2 = 20;

            // console.log(n1);
            // console.log(n2);

            function fn2() {
                console.log(n1);
                console.log(n2);
            }

            fn2();
        }

        fn1();
    </script>
</body>
</html>

案例练习:经典案例。

<script>
    var a = 0;
    function fn1() {
        var a = 1;
        function fn2() {
            a = 20;
            function fn3() {
                var a = 3;
                function fn4() {
                    console.log(a); //???
                }
                fn4();
            }
            fn3();
        }
        fn2();
    }
    fn1();
    console.log(a); //这里会输出什么?
</script>

作用域不是在函数调用的时候确定的,而是在函数定义的时候就确定好了

5. JavaScript预解析

5.1 JavaScript函数提升

var语句和function语句都是声明语句,它们声明的变量和函数都在JavaScript预编译时被解析,也被称为变量提升函数提升

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>预解析之提升</title>
</head>
<body>
    <script>
        /* 
        解析器会先分析我们写的js代码,然后再交给引擎去执行。

            所有函数声明也会提升到当前作用域的最前面
            所有带var的变量声明,都会被提升到当前作用域的最前面
            函数提升优先级更高
        */

        console.log(a);
        var a = 1; //声明变量并赋值为1

        fn1();

        //函数声明也会提升,并且比变量的优先级更高
        function fn1() {
            console.log('我是函数1');
        }
        function fn1() {
            console.log('我是函数2');
        }

        //解析器会拆成:
        // var a;

        // console.log(a);
        // a = 1;
        // fn1();
    </script>
</body>
</html>

5.2 预解析
  • 概念:

    • 预解析分为变量预解析(变量提升)和函数预解析(函数提升)
  • 说明:

    • 程序在开始执行之前会做全局代码预解析
    • 函数在开始执行之前也会做局部代码预解析
  • 特点

    • 程序在代码执行之前会先进行预解析;
  • 预解析会解析带var的变量函数声明定义的函数function,解析函数优先级比解析变量要高;
    • 可以认为解析的时候分为两步,先提升函数,再提升变量
  • 如果函数重名,会覆盖(后面的函数会把前面的覆盖掉)
    • 如果变量和函数重名,会忽略;

练习:

  1.     console.log(a);
        a = 0;
    
    
  2.     console.log(a); 
        var a = 0;
        console.log(a);
    
    
  3.     console.log(a);    
        var a = '我是变量';
        function a(){ console.log('我是函数') }
        console.log(a); 
    
    
  4.     console.log(a); 
        a++;
        console.log(a);    
        var a = '我是变量';
        function a(){ console.log('我是函数') }
        console.log(a);
    
    
  5.     console.log(a);   
        var a = 0;
        console.log(a);   
        function fn(){
            console.log(a);    
            var a = 1;
            console.log(a);    
        }
        fn()
        console.log(a);    
    
    
  6.     console.log(a);   
        var a = 0;
        console.log(a);   
        function fn(){
            console.log(a);    
            a = 1;
            console.log(a);    
        }
        fn()
        console.log(a);  
    
    

6. JavaScript函数类型

6.1 函数声明定义函数
function fn(){
    console.log("i am 普通函数");
}

6.2 函数表达式定义函数
var sum = function(){
    console.log('谁在让我东张西望');
}
sum();

6.3 匿名函数

匿名函数顾名思义指的是没有名字的函数,在实际开发中使用的频率非常高!也是学好JS的重点。

匿名函数:没有实际名字的函数。

6.3.1 为什么要学习IIFE

​ 实际上,IIFE(Immediately Invoked Function Expression)的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域、局部作用域,从ES6开始才有块级作用域。对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function,只有function,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。
在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。

6.3.1 立即执行函数 IIFE

Immediately Invoked Function Expression

立即执行函数,也叫做匿名函数自调用

function (){
    console.log("i am IIFE");
}

发现报错啦!!!

  • 解决方法只需要给匿名函数包裹一个括号即可:
(function (){
    //由于没有执行该匿名函数,所以不会执行匿名函数体内的语句。
    console.log("匿名函数不会报错了");
});

  • 匿名函数后面加上一个括号即可立即执行!
(function (){
    console.log("运行匿名函数");
})();

  • 在函数前添加 !~ + - 一元运算符
!function (){
    alert("heng");
}();

  • 倘若需要传值,直接将参数写到括号内即可:
(function (str){
    console.log("Hello   "+str);
})("World");

  • 另一种写法
(function(){
    console.log(11111);
}());

6.3.2 IIFE特点
1、匿名函数自调用是在定义的同时执行函数
2、匿名函数自调用只能执行一次
		如果需要一个函数可以执行多次,这个函数必须是有名函数
		如果函数没有名字要想执行必须是自调用,但是只能执行一次
3、匿名函数自调用,在函数内部执行代码也要去预解析
6.3.3 IIFE作用
  • 防止外部命名空间污染

    • 隔离作用域,防止污染全局命名空间
    • 因为一个页面中会有很多的重复变量名字 那么这样就会命名冲突 eg:都是name 所以使用匿名函数自调用可以隔离不同业务的同名的变量
  • 隐藏内部代码,暴露接口

    • 很多的js文件 只有引入的路径 引入之后就可以直接使用,代码的细节是看不到 这样保证了文件的安全
  • 对项目的初始化,只执行一次

    • 因为立即执行函数只执行一次 所以对于项目加载文件来说是非常有好的,省略了很多加载的时间
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>IIFE的作用</title>
    </head>
    <body>
        <script>
            //1. 防止命名污染
            // var money = 100;
            // console.log(money);
            // console.log(money);
            // console.log(money);
            // console.log(money);
            // console.log(money);
            // console.log(money);
            // console.log(money);
            // console.log(money);
            // money = 200;
            // console.log(money);
            (function(){
                var money = 100;
                // console.log(money);
            })();
            money = 200;
    
            //2.隐藏细节,暴露接口
            (function( window ) {
                var a;
            })( window );
    
            //3.对项目的初始化,因为IIFE只会执行一次
        </script>
    </body>
    </html>
    
    
6.3.4 全局污染

全局变量污染:大家都在全局中写代码,很容易造成命名冲突,导致代码冲突。ES6中代码冲突会直接报错。所以要养成好的习惯不要在全局去声明变量。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>全局污染</title>
    </head>
    <body>
      <script>
          var age = 12;
          var age = 21;
          function func2(){
              var name = 'zhangsan';
              var name = 'lisi';

              console.log(name);
          } 
          func2();
      </script>
    </body>
</html>

结果:发现最后获取的只有一个元素,所以很容易造成代码冲突

解决策略:使用IIFE

立即执行函数本身就是为了避免在全局写代码,避免冲突的。立即执行函数自执行也叫开启一个新的命名空间。即开启新的作用域,此作用域和其他的不会冲突。

7. JavaScript函数参数的高级

7.1 获取函数参数个数
  • 使用arguments对象的length属性可以获取函数的实参个数。

  • arguments对象只能在函数内可见,因此arguments.length也只能在函数体内使用。

  • 使用函数对象的length属性可以获取函数的形参个数,该属性为只读属性,在函数体内,体外都可以使用

    function add(a,b,c) {
        alert(add.length)//函数形参的个数
        alert(arguments.length)//获取实参的个数
        return a+b+c;
    }
    alert(add.length)//函数形参的个数
    console.log(add(1, 2));
    
    
7.2 使用arguments对象
  • arguments对象表示函数的实参集合,仅能够在函数体内可见,并可以直接访间。
  • 参数对象是一个伪类数组,不能够继承Array的原型方法。可以使用数组下标的形式访问每个实参,如参数[0]表示第一个实参
  • 通过修改length属性值,可以改变函数的实参个数。

案例1:其实函数定义的形参可写可不写(ES5当中),但是虽然可以不写,建议以后写上形参

<script>
    function add() {
        console.log(arguments);
        return arguments[0] + arguments[1];
    }
    console.log(add(10, 20));
</script>

案例2:获取实参的个数

案例3:输入一组数字,求平均值的函数

案例4:直接使用函数对象的length属性,就可以获取到函数的形参个数

案例5:传2个参数返回差值,传3个参数返回和

  • t(2, 8);//6
  • t(2,3,5); //10
function fn1(a,b,c) {
    console.log(arguments.length);//获取实参的个数
}
fn1(1,2,3,4)

案例3:输入一组数字,求平均值的函数

function f() {
    //先获取到所有的实参 使用arguments
    var sum = 0;
    for (var i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
console.log(f(12, 34, 54, 32, 100, 98, 45, 34, 120));

案例4:直接使用函数对象的length属性,就可以获取到函数的形参个数

function f3(a,b,c) {
    console.log(f3.length);//3
}
f3(1,2,3,4,5,6);

案例5:arguments.length,拿得是实参的个数 可以让我们一个函数具有多种功能

<script>
    function addOrSub(a, b, c) {
        if (arguments.length == 2) {
            return a - b;
        } else if (arguments.length == 3) {
            return a + b + c;
        }
    }
    console.log(addOrSub(10, 20)); //传递两个实参就做减法
    console.log(addOrSub(10, 20, 30)); //传递的是三个实参就做加法
</script>

7.3 形参的默认值
//es6新增的,可以直接给形参赋值
function fn1(n1, n2, n3 = 100) {
    //如果没有给n3传值默认是undefined
    if (n3 === undefined) n3 = 10;

    return n1 + n2 + n3;
}
// console.log(fn1(1,2)); //NaN 因为第3个参数是undefined
// console.log(fn1(1,2));
console.log(fn1(1,2,0));
console.log(fn1(1,2));

案例6:写出执行结果

function foo(x, y) {
  x = x || 0;
  y = y || 0;
  console.log(x + y);
}

foo(2, 3);
foo();

案例7:写出执行结果

function bar(x, y) {
  x = (typeof x !== 'undefined') ?  x : 10;
  y = (typeof y !== 'undefined') ?  y : 20;
  console.log(x + y);
}

bar(2, 3);
bar();

8. 函数的递归

在这里插入图片描述

函数的递归调用,就是函数在内部自己调自己

函数的递归调用是一把双刃剑,如果设计的好,可以帮我们简单的处理事情,如果设计不好就是灾难

函数的递归要想设计好必须有两个条件

  • 必须有一个明显的结束条件,不能一直递归下去
  • 每一次调用都要有一个趋近结束条件的趋势
8.1 设计不好的递归
<script>
    function fn() {
        console.log('大哥大哥你好吗?');
        fn();
    }
    fn();
</script>

8.2 好的递归
//必须要有明显的结束条件,不能一直调用下去
function fn1(n) {
    console.log('来了,老弟~' + n);

    //这个就是条件,通常用一个参数来控制
    if (n > 1) {
        fn1(n - 1);
    }

    console.log('走了,拜拜,老弟~' + n);
}

fn1(3);

8.3 递归实现判断用户输入的数字是否是质数
function isZhishu() {
    var num = Number(prompt('请输入一个数字:'));
    if (isNaN(num)) {
        isZhishu();
        // console.log('请输入一个正确的数字');
    } else {
        var flag = true;
        for (var i = 2; i < num; i++) {
            if (num % i == 0) {
                flag = false;
                break; //终止循环
            }
        }
        if (flag) {
            console.log(num + ':是质数');
        } else {
            console.log(num + ':不是质数');
        }
    }
}

isZhishu();

8.4 匿名函数如何递归?
(function(n){
    //匿名函数如何递归?
    //1. 使用 argumens.callee() 实现 (不推荐,新规范已经废弃了)
    console.log(arguments.callee);
    if (n > 1) {
        arguments.callee(n-1);
    }

    console.log(n);
})(3);


//2. 给匿名函数取个名字呗 (推荐)
(function aa(n){

    if (n > 1) {
        // arguments.callee(n-1);
        //这个 aa 也只是在局部范围有效,不会污染全局变量
        aa(n-1);
    }

    console.log(n);
})(3);

9.回调函数(callback)

回调函数是指将函数作为参数传递给另一个函数,并在该函数内部被调用的函数。

9.1 基本语法
function foo(callback) {
  // 执行一些操作
  callback();
}

foo(function() {
  console.log('回调函数被执行');
});

9.2 使用场景
//遍历数组
var arr = [1,2,3,4];
arr.forEach(function(item){
    console.log(item);
});

//定时器函数
setTimeout(function() {
    console.log('2 秒后执行');
}, 2000);

//点击事件
document.querySelector('button').addEventListener('click', function() {
    console.log('按钮被点击了');
});

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值