美文网首页个人收藏JavaScript
JavaScript - 闭包 - 函数回调 - 即时函数

JavaScript - 闭包 - 函数回调 - 即时函数

作者: 西巴撸 | 来源:发表于2017-03-29 18:47 被阅读310次

    <h4>本小节主要讲解闭包,是特别重要的一块内容.希望大家能够用心学习!</h4>

    作用域链的补充内容 ----> 搜索规则

    如果有忘记的人,翻一下上一篇博客

    注意点:如果在内层作用域中声明了和外层作用域中同名的变量,那么这个变量是不会把外层的同名变量覆盖的

    搜索原则:

    • 在作用域中如果访问(读取|设置)某个变量,先在当前作用域中搜索,如果找到那么就直接使用
    • 如果没有找到,那么就向上一级作用域中继续搜索,找到则使用,没有找到就重复上面的过程
    • 直到0级作用域链

    闭包

    闭 : 关闭,封闭的意思,对外界不开方的

    包 : 包装起来

    闭包技术

    • 作用域规则 : 内层的作用域可以访问外层的的作用域,但反过来不行
    • 有的时候,我们确实需要访问一个封闭空间里的数据源(外层作用域 ----> 内层作用域)
    • 就是提供一种间接访问封闭空间中私有数据的方法

    访问数据

    • 直接返回 return
    • 闭包 对直接返回的数据进行包装(函数)
    • 示例代码 :
    <script>
        function f1(){
            var num = 10;
            return num;
        }
    
        //num
        var a = f1();
        var b = f1();
    
        console.log(a);
        console.log(b);
    
        //一次性获得数据
        //1 每次调用函数获得的数据并不是同一份数据
        //2 只能获取值,但是却不能修改值
    </script>
    
    • 示例代码 : 闭包一般写法
    <script>
        function f1(){
            var num = 10;
            return function(param){
                num = param;    //修改函数内部变量的值
                return num;
            };
        }
    </script>
    
    • 示例代码 : 访问获取数据
    <script>
        function f1(){
            var num = 10;
            var name = "张学友";
    
            return function(){
                return [num,name];
            };
        }
    
        var func = f1();
        console.log(func()[0]);
        console.log(func()[1]);
    </script>
    
    • 示例代码 : 闭包基本写法
    <script>
        function f1(){
            var num = 10;
            var name = "xxx";
            return {
                getNum:function(){
                    return num;
                },
                getName:function(){
                    return name;
                }
            }
        }
    
        var func = f1();
        console.log(func.getName());
        console.log(func.getNum());
    </script>
    

    闭包读取数据和设置数据

    • 示例代码 : 获取数据
    <script>
        function foo(){
            var name = "卡罗";
            var age = 18;
            return {
                getName:function(nameValue){
                    //判断
                    if (nameValue != undefined)
                    {
                        name = nameValue;
                    }
                    return name;
                },
                getAge:function(ageValue){
                    age = ageValue;
                    return age;
                }
            }
        }
    
        var func = foo();
    
        console.log(func.getName());     //
        console.log(func.getName("卡罗尔"));
        console.log(func.getName());
    </script>
    
    • 示例代码 :设置数据
    <script>
        function foo(){
            var name = "卡罗";
            var age = 18;
            return {
                getName:function(){
                    return name;
                },
                getAge:function(){
                    return age;
                },
                setName:function(nameValue){
                    name = nameValue;
                },
                setAge:function(ageValue) {
                    age = ageValue;
                }
            }
        }
    
        var func = foo();
        console.log(func.getName());
        func.setName("卡米尔");
        console.log(func.getName());
    
        func.setAge(20);
        console.log(func.getAge());
    </script>
    

    闭包的作用

    提供了一种间接访问函数封闭空间中数据的方法

    • 访问函数内部的变量只能通过指定的借口
    • 对变量的修改设置操作会更加安全,可以在设置之前对数据进行校验
    • **延长变量的生命周期
      **
    <script>
        function f1(){
            var age = 20;
            var author= "今何在";
    
            return {
                getAge:function(){
                    return age;
                },
                setAge:function(ageValue){
    
                    //校验处理
                    if (ageValue <0)
                    {
                        ageValue = 0;
                    }
    
                    //其他的处理(异常检测)
                    age = ageValue;
                }
            }
        }
    
        var func = f1();
        func.setAge(22);
        console.log(func.getAge());
    
        //设置年龄  -3 (因为人的年龄不能是负值 所以得进行安全校验)
        func.setAge(-3);
        console.log(func.getAge());
    </script>
    

    setTimeout和闭包的执行

    setTimeout(); 只调用一次函数(延迟函数) 一次是隔两秒(至少)

    每隔一定的时候就调用一次

    • **参数设置 : **
      第一个参数:函数回调(时间到了之后执行的代码)
      第二个参数:间隔时间(毫秒) 1秒 = 1000毫秒

    setTimeout的问题

    • 示例代码 :
    <script>
        for (var i = 0; i < 100; i++) {
    
            setTimeout(function () {
                console.log(i);
            },0);
    
            console.log("----");
        }
    </script>
    

    解决setTimeout的问题

    • 示例代码 :
    <script>
        for (var i = 0; i < 10; i++) {
    
            setTimeout((function (j) {
    
                return function () {
                    console.log(j);
                }
            })(i),10);
    
            console.log("+++++++++");
        }
    </script>
    

    div事件和闭包

    • **示例代码 : **
    <div>我是第1个div标签</div>
    <div>我是第2个div标签</div>
    <div>我是第3个div标签</div>
    <div>我是第4个div标签</div>
    <div>我是第5个div标签</div>
    <div>我是第6个div标签</div>
    
    <script>
    var divs  = document.getElementsByTagName("div");
    for (var i = 0; i < divs.length; i++) {
            divs[i].onclick = function(){
                alert("我是第" + i + "标签");
            }
        }
        // 每次点击得到的都是第六个标签
    </script>
    
    • **用闭包解决问题 : **
    <script>
        var divs  = document.getElementsByTagName("div");
        for (var i = 0; i < divs.length; i++) {
           (function(a){
               divs[a].onclick = function(){
                   alert("我是第" + a + "标签");
               }
           })(i)
       }
    </script>
    

    函数的特性

    • 函数本身也是对象(第一型的对象) 凡是可以使用对象的地方都可以使用函数
    • 函数可以创建作用域
    • 函数是可以被调用的

    函数的约定和name值

    • 函数声明

    • 函数表达式

    • new 构造函数

    • 示例代码 :

    <script>
    
        //function funName(参数1,参数2){函数体}   //函数声明
    
        //函数表达式(匿名)
        var add = function(a,b){
            console.log(a + b);
        }
    
        //函数表达式(命名)
        var sum = function name(a,b){
            console.log(a + b);
        }
    
        //sum.length // 形参的个数
        console.log(add.name);   //add 跟浏览器的实现有关系(在火狐中name的值为空)
        console.log(sum.name);   //name
    
        //03构造函数创建对象
        new Function(参数1,参数2,参数3)
    </script>
    

    函数的回调(作为参数传递)

    函数是第一型对象(因此可以使用对象的地方都可以使用函数)

    • 函数可以作为函数的参数
    • 函数可以作为函数的返回值

    回调函数(回调),当我们把某个函数作为参数传递给另一个函数的时候,这个函数就称为回调函数。

    回调函数的基本模式

    • 示例代码 :
    <script>
        function func(callBack) {
    
            //处理其他的操作
            callBack();     //调用回调函数
        }
    
        function demo() {
            console.log("这是一个回调函数");
        }
    
        func(demo); //注意调用函数的时候,参数是回调函数的引用(不要加括号);
    </script>
    

    回调函数(关于this丢失的问题)

    • 示例代码 :
    <script>
        //如果回调函数是某个对象的方法,而该对象方法中使用了this指针
        //那么该方法作为回调函数来使用的时候,需要注意this丢失的问题
    
        //01 提供一个对象,该对象中永远有showName方法
        var obj = {
            name:"xxx名字",
            age:30,
            showName:function () {
                console.log(this.name);
            },
            showAge:function () {
                console.log(this.age);
            }
        }
    
    
        //02 提供一个函数,该函数接受一个参数(函数引用)
        function demo(callBack) {
            callBack();
        }
    
        demo(obj.showName); //注意:不要带上括号 打印结果为空(window.name)
        demo(obj.showAge);  //undefined
    </script>
    

    回调函数(解决this丢失的问题)

    • 示例代码 :
    <script>
        //01 提供一个对象,该对象中永远showName方法
        var obj = {
            name:"默认的名字",
            age:30,
            showName:function () {
                console.log(this.name);
            },
            showAge:function () {
                console.log(this.age);
            }
        };
    
        //02 提供一个函数,该函数接受一个参数(函数引用)
        function demo(callBack,callBack_obj) {
            if (typeof callBack == 'function')
            {
                callBack.call(callBack_obj);
                console.log("++++");
            }
        }
    
        demo(obj.showName,obj); //注意:不要带上括号
        demo(obj.showAge,obj);  //undefined
    
    </script>
    

    回调函数(兼容字符串方式)

    • 示例代码 :
    <script>
        //01 提供一个对象,该对象中永远showName方法
        var obj = {
            name:"默认的名字",
            age:30,
            showName:function () {
                console.log(this.name);
            },
            showAge:function () {
                console.log(this.age);
            }
        };
    
        //02 提供一个函数,该函数接受一个参数(函数引用)
        function demo(callBack,callBack_obj) {
    
            //处理第一个参数传递对象方法字符串的形式
            if(typeof callBack == 'string')
            {
                callBack = callBack_obj[callBack];
            }
    
            if (typeof callBack == 'function')
            {
                callBack.call(callBack_obj);
            }
    
    
        }
    
        //demo(obj.showName,obj);
        //demo(obj.showAge,obj);
    
        //传递字符串和对象来进行调用
        demo("showName",obj);
    </script>
    

    函数作为返回值( 计算器)

    • 示例代码 :
    <script>
    
        //使用闭包实现一个计数器(在该示例中setup函数的返回值为一个函数)
        //通过调用返回值(一个函数),可以操作setup函数中的变量
    
        var setup = function () {
            var count = 0;
            return function () {
                return count ++;
            }
        }
    
        var next = setup();
        console.log(next());    //0
        console.log(next());    //1
        console.log(next());    //2
    </script>
    

    自定义函数( 惰性函数 )

    惰性函数

    • 某个函数直到第一次使用的时候才被正确的定义,并且其具有向后惰性,执行更少的工作。

    应用场景

    • 函数有一些初始化的准备工作要做,且只需要执行一次的情况。

    特点

    • 能够更新自己(函数)的实现。

    缺点:

    • 当重新定义自身的时候,已经添加到原始函数的任何属性都会丢失。
    • 如果函数被赋值给了其他的变量或者是对象方法,那么在使用变量或者是对象方法调用时仍然会执行旧的函数体。
    • 示例代码 :
    <script>
        var demo = function foo() {
            console.log("foo!");
            foo = function () {
                console.log("new foo!");
            }
        }
    
        //函数的调用
        //foo();  //foo!
        //foo();  //new foo!
    
        //总结:该函数的实现特点在于能够更新自身的实现。
        demo(); //foo!
        demo(); //foo!
    </script>
    

    惰性定义函数存在的问题

    • 添加属性
    • 把函数赋值给新的变量
    • 以对象的方法调用函数
    • 示例代码 :
    <script>
        //01 声明函数foo
        function foo() {
            console.log("foo!");
            foo = function () {
                console.log("foo! foo!");
            }
        }
    
        //02 为foo函数对象添加属性
        foo.description = "foo函数的描述信息";
    
        //03 把foo函数赋值给其他的变量
        var func = foo;
    
        //04 把foo函数赋值给对象中的方法
        var obj = {
            showFoo:foo
        }
    
        //05 验证并演示输出
        func(); //foo!
        func(); //foo!
        console.log(func.description);  //foo函数的描述信息
        //总结:01 如果把函数赋值给其他的变量,那么在以其他变量的方式调用时不会更新自身,还是执行旧的函数体
    
        obj.showFoo();  //foo!
        obj.showFoo();  //foo!
        console.log(obj.showFoo.description);   //foo函数的描述信息
        //总结:02 如果把函数赋值给对象的方法,那么在以对象方法形式调用时不会更新自身,还是会执行旧的函数体。
    
        foo();  //已经更新过foo函数 foo! foo!
        foo();  //已经更新过foo函数 foo! foo!
        console.log(foo.description);   //undefined
    </script>
    

    即时函数

    在函数定义之后立即执行该函数。

    即时函数模式的组成:

    • 使用函数表达式来定义函数(匿名函数,注意不能使用函数声明方式)
    • 在函数表达式末尾添加一组(),表示立即执行当前函数。
    • 将整个函数包装在()中,有两种方式

    即时函数的作用

    • 用来将所有的代码包装到当前的作用域中,并且不会将任何的变量泄露到全局作用域中。
    • js中没有代码块作用域,而函数是js中唯一可以创建作用域的。
    • 即时函数就是利用了函数创建作用域这一点,来实现对一些需要封装且不允许外部访问的操作。

    即时函数的优点

    • 封装在一起执行的代码,且不会产生全局变量,在即时函数内部定义的所有变量都仅仅只是该函数的局部变量,不会造成全局变量污染问题。
    • 具有更好的封装性,外部无法访问到该函数内部的数据。
    • 示例代码 : 即时函数两种写法
    <script>
        //第一种写法
        (function () {
           console.log("即时函数的第一种写法");
        }());
    
        //第二种写法
        (function () {
            console.log("即时函数的第二种写法");
        })();
    </script>
    
    • 示例代码 : 即时函数可以接受参数并提供返回值
    <script>
        //01 接受参数
        (function (str) {
            console.log(str);           //hello
        })("hello");
    
        //02 提供返回值并赋值给新的变量
        var foo = (function () {
            return 2 + 1;
        })();
    
        console.log(foo);           //3
    </script>
    

    即时函数和闭包

    • 相同点:他们都是函数的一种特殊形态,并且可以共存。而且闭包配合即时函数“效果更佳”。

    • 不同点:即时函数是定义一个函数,并立即执行。它只能被使用一次,相当于“阅后即焚”。闭包是指一个函数与它捕获的外部变量的合体,按照MDN的说法,闭包就像一个对象---一个具有一个方法(行为)和一个或多个私有字段(状态)的对象。从这个角度看,闭包是符合面向对象的封装思想的。

    即时对象初始化

    结构特征

    • 提供一个对象,在该对象内部提供一个init初始化方法,使用()把对象包装起来(让字面量变成表达式)
    • 然后随即调用init方法,完成初始化操作。

    基本结构

    ({}).init();

    模式优点

    • 在执行一次性的初始化任务时保护全局的命名空间。
    • 示例代码 :
    <script>
        ({
            name:"xxx",
            age:23,
            getDescript:function () {
                console.log("名字:" + this.name + "年龄:" + this.age);
            },
    
            //注意:在对象中访问对象的属性和方法都需要使用this.前缀
            init:function () {
                this.getDescript();
                //其他的初始化处理
            }
        }).init();
    

    相关文章

      网友评论

        本文标题:JavaScript - 闭包 - 函数回调 - 即时函数

        本文链接:https://www.haomeiwen.com/subject/bfvrgttx.html