JavaScript学习笔记 05、函数

举报
长路 发表于 2022/11/28 20:46:00 2022/11/28
【摘要】 文章目录前言一、函数相关知识点1.1、两种定义方式及调用顺序1.2、函数优先提升案例(2个)1.3、函数的参数定义(普通传参与arguments参数)定义以及使用查看底层结构1.4、函数的返回值二、全局变量与局部变量2.1、全局与局部示例2.2、遮蔽效益(局部变量能够遮蔽全局变量)2.3、局部作用域进行变量声明(外部无法访问)2.4、作用域链(函数嵌套中,变量会从内到外依次找)2.5、不加var表

@[toc]

前言

本篇博客是关于javascript的函数,若文章中出现相关问题,请指出!

所有博客文件目录索引:博客目录索引(持续更新)

一、函数相关知识点

1.1、两种定义方式及调用顺序

两种函数定义方式

方式一:声明函数定义

//function表示定义函数;后面的fun表示函数名(与变量定义规则相同);后面的括号中可以书写形参列表
function fun(){
    //函数体语句
}

方式二:函数表达式形式

//此时它是一个匿名函数,需要注意function后面不需要跟着函数名称,无效的
var hello = function () {
    console.log("hello,world!")
}

两种方式调用顺序

首先是第一个声明函数定义形式,函数声明会提升:

<script>
    say666();//对于第一种定义方式,函数声明会进行提升,可以调用!

    function say666() {
        console.log(666);
    }

    say666();
</script>

image-20210609164427102

对于第二种,由于是通过定义匿名函数给一个变量的形式,那么仅仅只是变量名的提升,其函数并不会声明,与之前赋值类似

<script>
    say666();//对于第一种定义方式,函数声明会进行提升,可以调用!
    var say666 = function () {
        console.log(666);
    }
    say666();
</script>

image-20210609164631335

总结:声明函数式方式会进行提升函数声明;而对于变量赋值匿名函数则只会提升变量名定义!



1.2、函数优先提升案例(2个)

函数表达式与函数声明同时定义

我们通过一个案例来看,其也是一个面试题:

<script>
    fun1();
    var fun1 = function () {
        console.log("fun1-函数表达式");
    }
    function fun1() {
        console.log("fun1-函数声明式");
    }
    fun1();
</script>

image-20210609165035355

分析:第3行的是函数表达式形式,第6行的是函数声明形式;当chrome引擎进行解析js时,fun1(第3行)会提升变量定义,fun1(第6行)函数声明提升,那么自然第一个fun1()就是打印的"fun1-函数声明式",接着fun1()执行完毕后进行函数定义并赋值给变量fun1,此时就会覆盖原先的函数fun1(),再次调用就会打印"fun1-函数表达式"

总结:对于函数表达式与函数声明同时定义的话,js代码执行前都会进行函数声明提示(针对于函数声明式),函数表达式是在js代码执行后定义的,一定要看清定义的顺序。


两个相同的函数声明式或函数表达式

两个相同的函数声明式

<script>
    fun1();
    //两个函数声明式同时定义
    function fun1() {
        console.log("第一个:函数声明式");
    }
    function fun1() {
        console.log("第二个:函数声明式");
    }
    fun1();
</script>

image-20210609165805677

说明:你就这么想,执行程序前会先从上至下对每一个函数声明式定义的函数进行函数声明提前,第一个提前的就是第一个fun1,第二个就是第二个fun1(),那么自然第二个就会覆盖第一个,函数声明提前好后执行程序肯定就是输出的"第二个:函数声明式"

两个相同的函数表达式

<script>
    //fun1(); //不能在函数表达式前进行调用,因为只有变量定义提升效果
    var fun1 = function () {
        console.log("第一个:函数表达式");
    }
    var fun1 = function () {
        console.log("第二个:函数表达式");
    }
    fun1();
</script>

image-20210609170151464

说明:对于函数表达式是在执行程序后指定位置才会进行函数定义的,顺序也是从上至下的,下面的覆盖了上面的了。

总结:对于两个相同的函数声明式或函数表达式,其定义顺序你都可以看成从上至下,下面的覆盖上的!



1.3、函数的参数定义(普通传参与arguments参数)

定义以及使用

定义形参:不需要加变量类型

在函数的()中定义形参,当我们调用时传入多了或少了会有什么情况出现吗?

<script>
    function fun1(a, b, c) {
        console.log('a:' + a + ',b:' + b + ',c:' + c);
    }

    fun1(1, 2, 3);//传入对应实参
    fun1(1, 2, 3, 4);//传入参数>实际形参
    fun1(1, 2);//传入参数<实际形参
</script>

image-20210609172219368

小总结:若是传入多了参数时,程序不会报错依旧会执行。若是参数传入少了,对应函数中的某个没有传值的参数为undefined。


通过arguments类数组对选哪个来获取参数值

image-20210609173002720

  • 获取函数参数,可使用[]访问下标形式获取。
  • image-20210609173036113

在函数中存在一个arguments类数组,我们不需要显示声明它,可直接在函数中进行调用,其存储了我们调用函数时的所有参数,我们可以测试下:

<script>
    function fun1() {
        console.log(typeof arguments);
        console.log(arguments);
    }

    fun1(1, 2, 3);
</script>

image-20210609172940812

小案例:遍历每次函数调用时的所有参数!

<script>
    function fun1() {
        //遍历每一个传入的参数
        let str = "";
        let nstr = "";
        for (let i = 0; i < arguments.length; i++) {
            nstr = "第" + (i + 1) + "个参数为:" + arguments[i];
            str += i == arguments.length - 1 ? nstr : nstr + ",";
        }
        console.log(str);
    }

    fun1(1, 2, 3);
    fun1(1, 2, 3, 4);
    fun1(1, 2);
</script>

image-20210609173120530



查看底层结构

arguments类数组:右边划横线就能够说明其是一个数组

image-20210619095755344

普通数组的情况:__proto__其指向了Array的原型对象,Array指向Object。

image-20210619095844180



1.4、函数的返回值

语法

function fun(){
	return xxx;//通过return关键字来进行返回
}


二、全局变量与局部变量

2.1、全局与局部示例

全局变量

结论:若是定义了一个全局变量时,局部作用域也能够使用该全局变量!

<script>
    var a = 11;
    function fun() {
        console.log("fun中获取到a的值为:" + a);
        a++;
    }
    //调用函数
    fun();
    console.log(a);
</script>

image-20210609191500115


局部变量:包含其中定义的值与形参。

结论:局部变量中定义的值,外部无法访问!

<script>
    function fun() {
        var a = 11;
    }
    //调用函数
    fun();
    console.log(a);
</script>

image-20210609191627731



2.2、遮蔽效益(局部变量能够遮蔽全局变量)

结论:若是在局部作用与中定义了一个与外部作用域相同的变量名,就会起到遮蔽作用!

<script>
    var a = 12;
    function fun() {
        var a = 11;//定义一个局部变量,该变量名与外部作用域变量名相同
        console.log("fun()中的a:" + a);
    }
    //调用函数
    fun();
    console.log(a);
</script>

image-20210609191835419



2.3、局部作用域进行变量声明(外部无法访问)

首先看下面的例子:

<script>
    var a = 12;
    function fun() { //在函数体中定义的变量也会进行变量声明提升的效果
        a++;//执行这步前相当于定义了一个a。var a; 接着进行a++,a=undefined+1=NaN
        var a = 10;//到这一步时进行重新进行赋值,前面的不管,此时a=10
        console.log("fun中的a:" + a)
    }
    //调用函数
    fun();
    console.log(a);//局部作用域中a++是对于其类型提升的a,并不是全局作用域的a
</script>

image-20210609192155900

  • 其他解析:image-20210609192419275

此时我们将其中的var a=5改为var a;看看最后结果究竟是不是NaN

<script>
    var a = 12;
    function fun() {
        a++;
        var a;
        console.log("fun中的a:" + a)
    }
    //调用函数
    fun();
    console.log(a);
</script>

image-20210609192535994

总结:总而言之就是局部作用域下变量名也会进行变量声明提升!!!(记住这个本质就ok!)



2.4、作用域链(函数嵌套中,变量会从内到外依次找)

函数嵌套

在函数中你能够继续定义函数,并且该内部函数只能在内部使用!

若是在外部调用就会报错:

<script>
    var a = 10;
    var b = 20;
    function fun() {
        function fun1() {
            console.log("123");
        }
        fun1();//允许调用内部函数
    }
    fun();
    fun1();//外部调用函数中的函数就会报错!
</script>

image-20210609194059614


作用域链(需要配合学习内部嵌套函数)

在函数嵌套中,变量会从内到外逐层寻找它的定义值:使用先找到的值

<script>
    var a = 10;//4
    var b = 20;//3
    function fun() {
        var c = 30//2
        var d = 40;
        function fun1() {
            var d = 50;//1
            console.log("a=" + a + ",b=" + b + ",c=" + c + ",d=" + d);
        }
        fun1();//只能够在内部进行调用
    }
    fun();//调用函数
</script>

image-20210609193857820



2.5、不加var表示定义全局变量

我们之前也尝试过在函数中定义了一个var变量,外部是无法进行访问的!

结论:若是我们在函数内部定义了一个没有var定义的变量,那么该变量就是全局变量,外部也能够进行访问!

<script>
    function fun() {
        a = 30;//没有写var表示全局变量
    }
    fun();//必须要调用该函数,否则该变量无法被定义
    console.log(a);//外部也能够进行获取该变量
</script>

image-20210609194647346



三、闭包

3.1、认识闭包

闭包:函数能够"记忆住"其定义时所处的环境,即使函数不在其定义的环境中被调用,也能访问定义时所处环境的变量。(记住你在外部调用一个内部函数时,你就将其函数看做是在原本位置运行即可!)

  • js中,每次创建函数都会创建闭包,但是"闭包"特性往往需要将函数"换一个地方"执行,才能被看出。
  • 闭包实际是针对于函数(外)中的函数(内),每次获取函数(内)都会形成闭包,函数(外)中的局部变量都会被单独存储到对应返回的闭包中!!!

闭包功能:记忆性、模拟私有变量。

image-20210609201241926

  • 一个闭包就是一个函数以及对应所处环境

我们来使用一个例子描述闭包现象:

<script>
    var name = "changlu";
    function fun() {
        var name = "liner";
        //方式一:返回非匿名函数(很奇怪的现象就是下面的getFun2是undefined,而返回匿名函数就是下面打印出来的内容)
        //两个实际效果是一样的
        // function fun2() {
        //     alert(name);
        // }
        // return fun2(); //返回的是内部函数
        //方式二:返回一个匿名函数
        return function () {
            alert(name);
        }
    }
    var getFun2 = fun(); //调用该函数并返回了一个内部函数fun2()
    console.log(getFun2);
    //此时在外部调用该内部函数仍然会访问到原本fun()中的变量,这就是闭包情况
    getFun2();
</script>

image-20210609200615903

结论:在上述闭包情况下,外部调用内部的函数依旧能够访问到原本函数中的值!



3.2、闭包功能(实际案例体现)

①闭包的记忆性

通过一个案例来展示闭包的记忆性:

创建体温检测函数checkTemp(n),可以检查体温n是否正常,函数会返回布尔值。
但是,不同的小区有不同的体温检测标准,比如A小区体温合格线是37.1°℃,而B小区体温合格线是37.3°℃,应该怎么编程呢?
  • 使用Java的思想就是创建一个小区类,其中有一个体温合格属性变量(通过构造器传入或其他方式),不同的小区创建不同的对象,然后调用相同的方法即可!

在js里,可以通过闭包来实现该程序:

<script>
    //传入一个合格体温
    function checkTemp(qualifiedTemperature) {//局部变量为合格体温
        //返回一个匿名函数(使用了闭包特性,临时存储了局部变量)
        return function (n) {
            if (n < qualifiedTemperature) {
                console.log("体温小于" + qualifiedTemperature + ",通过!");
            } else {
                console.log("体温大于" + qualifiedTemperature + ",不通过!");
            }
        }
    }

    //测试A小区的:合格体温为:36.2
    var testA = checkTemp(36.2);
    testA(36.3);
    testA(36.1);

    //测试B小区的:合格体温为:36.7
    var testB = checkTemp(36.7);
    testB(36.3);
    testB(36.1);
</script>

image-20210609203908410

总结:闭包对于局部变量(函数内部定义或者形参)都会能够进行保存的,此时也就起到了记忆性的功能。(实际可以将java的思路放在这里个人感觉也是ok的,每次返回的匿名函数就作为一个对象,初始形参就是对象中的一个值)



②模拟私有变量

引出闭包可以实现对私有变量

在Java、C++中会有私有属性的概念,而在js中只能使用闭包来模拟!

外部无法直接对内部函数中的值进行一些如+、-、*、/等一些操作,而是只能通过调用公开方法的方式进行!其实函数中定义的变量实际上已经实现了属性私有化,外部无法得到!!!

<script>
    function fun() {
        var a = 1;
    }
    fun();
    console.log(a);
</script>

image-20210609205511197

此时我们来思考一下,怎么样才能够对函数内部的属性值进行操作呢?此时我们就可以使用到闭包了。


闭包实现对私有属性的操作

我们初始能够想到的一种方式就是返回一个匿名函数,其中进行一些对变量的操作:

<script>
    function fun() {
        var a = 1;
        return function addA() {
            a++;  //实现+1的功能
        }
    }
    var addA = fun();
    addA();
    console.log(addA);
</script>

这种方式其实还有个问题,既然你对变量进行+1操作,那么我下次想要在外部拿到变量呢,怎么拿呢?返回一个匿名函数不就写死了嘛?

解决方案:返回一个对象,该对象中就包含了多个函数,接着利用闭包的特性,不就完美的解决了嘛!

<script>
    function fun() {
        var a = 0;
        //返回一个对象
        return {
            getA: function () {  //获取属性a
                return a;
            },
            addA: function (x) {  //加
                a += x;
            },
            delA: function (x) { //减
                a -= x;
            },
            divideA: function (x) { //除
                a /= x;
            },
            multA: function (x) {  //乘
                a *= x;
            }
        }
    }
    //测试一:进行加法、乘法
    var test1 = fun(); //获取到对象
    test1.addA(3);
    test1.multA(4);
    console.log(test1.getA());//获取到测试一中的值:3*4=12

    //测试二:进行减法、除法
    var test2 = fun();
    test2.delA(2);
    test2.divideA(2);
    console.log(test2.getA());//获取到测试一中的值:-2/2=-1

</script>

image-20210609210822129

总结:我们可以利用闭包特性来对函数中的变量进行私有化并定制对应的方法!



3.3、闭包使用注意点

不能滥用闭包,否则会造成网页的性能问题,严重时可能导致内存泄漏

  • 内存泄漏:就是指程序中动态分配的内存由于某种原因未释放或无法释放!

其实像如今的一些浏览器也不会出现内存泄漏的问题,如Chrome、firefox等。



3.4、闭包的面试题

返回了两个匿名函数(可以看做是两个不同的对象),其各自自带了一个变量属性:

<script>
    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

</script>


四、IIFE(立即调用函数表达式)

4.1、IIFE介绍以及三种表达式

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

可通过使用()+-的方式将函数转为一个表达式:其是一个引擎当中的特性,一般都使用()方式

<script>
    //方式一(最常使用):(xxx)()
    (function (a) {
        console.log("方式一!!!")
        console.log("方式一函数立即调用了!");
        console.log("参数为:" + a);
    })("changlu");  //可以传参

    //方式二:+()
    +function (a) {
        console.log("方式二!!!")
    }();

    //方式三:-()
    -function (a) {
        console.log("方式三!!!")
    }();

</script>

image-20210609214221991

注意点:在写()前没有.,使用该表达式会立即执行。



4.2、两种实际应用场景

①作用一:为变量赋值

应用场景:当我们对某个变量进行一些较为复杂的计算时,如有一些if语句,通过使用IIEF来显得语法更加紧凑。

<script>
    var age = 22;
    var sex = '男';
    //可直接通过IIFE表达式来立即执行获取变量
    var desription = (function () {
        if (age <= 18) {
            return "未成年!"
        } else {
            return '男' ? "成年,男" : "成年,女";
        }
    })();
    console.log(desription);
</script>


②作用二:将全局变量变为局部变量

引出全局变量的问题

<script>
    var arr = [];
    //定义全局变量(var)
    for (var i = 0; i < 5; i++) {
        arr.push(function getArrValue() {
            return i;
        });
    }

    //此时调用数组中的函数得到的i则会是5,因为i这个变量是全局变量(上面for循环结束后i这个全局变量就是5)
    console.log(arr[0]());
    console.log(arr[1]());
    console.log(arr[2]());
    console.log(arr[3]());
    console.log(arr[4]());

</script>

image-20210609215529739

原因:该函数并没有产生闭包,所以当调用数组中的每个函数执行时都会返回当前内存中的i值。


解决方案:利用闭包存储函数中的值(将全局变量变为局部变量)以及使用let定义变量

方案1:通过使用闭包的形式来进行存储对应的值

<script>
    var arr = [];
    //定义全局变量(var)
    for (var i = 0; i < 5; i++) {
        //使用了IIFE方式,此时你可以看到闭包的形成了!!!每个闭包中都存储中对应的i值
        (function (i) {
            arr.push(function getArrValue() {
                return i;
            });
        })(i);
    }

    console.log(arr[0]());
    console.log(arr[1]());
    console.log(arr[2]());
    console.log(arr[3]());
    console.log(arr[4]());

</script>

image-20210609215851347

总结:通过IIFE形式也能够来产生闭包情况,有时候可以解决一些问题!

方案2:使用let关键字来定义变量(暂时不太懂原理)

<script>
    var arr = [];
    //使用let来定义变量
    for (let i = 0; i < 5; i++) {
        arr.push(function getArrValue() {
            return i;
        });
    }

    console.log(arr[0]());
    console.log(arr[1]());
    console.log(arr[2]());
    console.log(arr[3]());
    console.log(arr[4]());

</script>

image-20210609220039322

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。