源栈培训:JavaScript-4:闭包和立即执行

更多
2019年04月19日 07点17分 作者:叶飞 修改
唉呀!闭包

先不管大(声)名(名)鼎(狼)鼎(藉)的闭包的定义,我们来看一段代码:

        function luckyStack() {
            var _price = 986;
            return function () {
                return _price;
            }
        }
        var getPrice = luckyStack();
        alert(`"源栈"培训的价格是每周${getPrice()}元!`);
想一想:这种代码结构像什么

闭包就是:一种能够让外部获得函数内部私有变量(包括传入参数)的语法特性。

形成条件:
  1. 变量的作用域局限于函数
  2. 函数里面还能够:
    • 嵌套函数,且内部函数可以使用外部函数的变量
    • 直接或间接的)返回一个函数
  3. 外部函数返回的函数中使用了外部函数的变量
作用原理:
  • 本来,函数一旦执行,给出返回值,相关资源就应该被释放(GC回收)
  • 但是,由于函数返回的是一个在它内部声明的函数
  • 而且该内部函数还调用了外部函数的变量
  • 由于这个链条,导致JavaScript解释器无法(不会)释放外部函数资源

带来的问题:

  • 内存泄漏:内存始终无法释放,堆栈溢出(定时器)
  • 难以理解的结果:
            function closureRiddle() {
                var arr = [];    //代码 ②
                for (var i = 0; i < 5; i++) {
                    arr[i] = function () {      //代码 ④
                        return i;
                    };
                }
                return arr;
            }
            var results = closureRiddle();        //代码入口①
            for (var i = 0; i < results .length; i++) {
                console.log(`第${results[i]()}次显示`);       //代码③
            }

理解上述代码运行结果:

  1. 代码④不会在closureRiddle()中执行,而只能是在③调用后执行
  2. 代码④运行时,因为闭包的原因,i 的值仍然被保留,等于 5

解决办法:

  • var -> let:let定义块级作用域,让每一个匿名function都绑定了循环时的 i 变量值。
    深入理解 let
            function luckyStack() {
                {
                    let _price = 986;
                    //如果改成:var _price = 986;
                    var cost = function () {
                        return _price;
                    }
                }
                _price = 198;
                return cost;
            }
    JavaScript中的for-let:(参考:MDN
  • 函数表达式立即执行:再用一个闭包锁定 i 值
            
           function closureRiddle() {
                var arr = [];    
                for (var i = 0; i < 5; i++) {
                    arr[i] = (function (n) {      
                        return n;
                    })(i);
                }
                return arr;
            }

为什么需要闭包?(参考:Why use "closure"?

  1. 事件绑定函数(回调函数)
    回头看之前用IIFE包裹的closureRiddle没有意义,真实的代码应该是这样的:
        <ul>
            <li><a id="1">1</a> </li>
            <li><a id="2">2</a> </li>
            <li><a id="3">3</a> </li>
            <li><a id="4">4</a> </li>
            <li><a id="5">5</a> </li>
        </ul>
                for (var i = 1; i < 6; i++) {
                    document.getElementById(i.toString()).onclick = (function (n) {
                        return function () { console.log(n) };
                    })(i);
                }
  2. 模拟真正面向对象的封装(属性)
            function LuckyStack() {
                var fee = 986, unit = '周';
                return {
                    cost: function () {
                        console.log(`源栈收费:每${unit}${fee}元`);
                    }
                }
            }
            
            var ls = LuckyStack();
            console.log(ls.fee);    //undefined,ls.fee不会被暴露
            ls.cost();              //ls.cost()像一个public方法一样使用

立即执行函数表达式(Immidiately Invoked Function Expression

语法:(function([parameter,...]){})([parameter,...]);

()就会让前面的函数立即执行

为什么需要把被执行函数包裹起来?
  • JavaScript解释器(ES标准)认为 function 关键字开头的语句是函数声明,后面就必须接函数名字(否则抛出一个语法错误信息);
  • 但只要function关键字前面有其他符号,解释器就会认为这是一个(可以匿名的)函数表达式
  • ()能够“欺骗”解释器,而且更有意义,^_^

主要目的:

  • 形成一个单独的作用域(模拟块级作用域 let )
  • 避免全局变量“污染”


作业

  • 在函数student()中声明了函数域变量name、age和female,使用闭包机制,将其暴露到函数外部
  • 解释以下代码运行结果:( condition ? <statement when true> : <statement when false>)
            function foo(x) {
                var tmp = 3;
                return function (y) {
                    x = x ? x + 1 : 1;
                    console.log(x + y + tmp);
                }
            }
    
            var bar = foo(-1);
            //或者:var bar = foo(1);
            //或者:var bar = foo(0);
    
            bar(10);
  • 改动以下代码,让其输出如图所示,并说明理由。
            function buildList(list) {
                var result = [];
                for (vari = 0; i < list.length; i++) {
                    result.push(function () {
                        console.log('item' + i + ': ' + list[i])
                    });
                }
                return result;
            }
    
            (function() {
                var fnlist = buildList([1, 2, 3]);
                for (var i = 0; i < fnlist.length; i++) {
                    fnlist[i]();
                }
            })();


作业点评

  1. 变量声明在函数开头
  2. 减少变量:toString() 不会改变本身类型
  3. js 分文件,避免干扰

  1. if(condition){return true}else{return false} => return condition
  2. || => | , && => &
  3. !== 而不是 !=
  4. 数组 / 对象
  5. 尽量:不要用大小写来区分变量
源栈 JavaScript 前端 闭包
赞: 5 踩: 0

打赏
已收到打赏的 帮帮币

你的 打赏 非常重要!
为了保证文章的质量,每一篇文章的发布,都已经消耗了作者 1 枚 帮帮币
没有“帮帮币”,作者无法发布新的文章。

全系列阅读
评论 / 0
叶飞的系列文章

源栈培训:ASP.NET全栈开发

飞哥的源栈培训:线上全程直播,免费收看;线下拎包入住,按周计费。本系列收录所有讲义(含视频录播地址)

编程那些事:菜鸟入门

大飞哥倾力之作,面向有意入行IT/开发/编程的初学者,欢迎任何形式的留言建议……

从包工头到程序猿

真实故事,讲述我在家装公司关门之后,如何转行成为一个程序猿的故事。(《折腾》第三卷)

《折腾》(卷一)青涩

时间段:从大学毕业到开始创业。离开青葱校园,涉世之初的那些往事……

《折腾》(卷二)风雨 之(1)工地

我一个完全的门外汉(无论装修还是管理),开始给黎叔装修房子。从踌躅满志,到四处碰壁;从一往直前,到左右为难……

《折腾》(卷二)风雨 之(2)胸怀

作为一个律师,接工程没签合同,被狠狠的坑了一把!年轻人暴烈的想要复仇,黎叔教他一个企业家的胸怀……

《折腾》(卷二)风雨 之(3)渠道

成立了公司,招聘了员工,开始大力的拓展业务,一个接一个的坑,摔倒了又爬起来……

《折腾》(卷二)风雨 之(4)视野

经历残酷现实的磨砺,终于明白:干啥事,都不能闭门造车,人要走出去,开阔视野……

未分类

系统自动生成的未分类系列

一锅大杂烩

从律师到包工头,从码农到写手,读书交友生活创业,各种零零碎碎,乱七八糟……

人人都是程序猿

计算机编程普及课程,视频:https://space.bilibili.com/55410301/#/channel/detail?cid=49491

全部
关键字



帮助

反馈