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


<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();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容