千锋教育-做有情怀、有良心、有品质的IT职业教育机构

400-811-9990
当前位置:千锋视频教程 > html5视频教程  >  HTML5开发设计之从闭包看Let

HTML5开发设计之从闭包看Let

时间:2018-04-03 14:09:48     来源:千锋视频教程 作者:武汉

 

1、闭包

 

1.1 闭包的定义

 

闭包的定义是这样的:内部函数被保存到了外部,即为闭包

先来看一个简单的例子:

 

//我们声明一个函数test(),这个函数返回了一个function

 function test(){

     var i = 0;

     return function(){

        console.log(i++)

     }

 };

//test()的返回值赋给ab变量,所以其实这时候的a/b=function(){console.log(i++);}

 var a = test();

 var b = test();

 //依次执行a,a,b,控制台会输出什么呢?

 a();a();b();

先思考一下,然后去浏览器验证一下

 

答案是:0,1,0

 

d5c5008aa76a1a2a90902eea9bacdf70

 

这是因为,a/b=test()时,a/b各自保留了testAO,所以各自上面均有一个i=0;

 

1.2 实现共有变量

 

这样其实是实现了一个共有变量,比如我们把上面的代码稍稍调整一下,就实现了一个累加计数器;

 

//累加器

function add(){

    var count = 0;

    function demo(){

        count++;

        console.log(count);

    }

    return demo;

}

var counter = add();

counter();

这也是闭包的第一个功能,实现共有变量;

 

1.3 可以做缓存

 

闭包的第二个功能是可以用作缓存,比如下面这个例子,我们用push把准备用到的东西放进去,当eat调用时使用:

 

//隐式缓存应用

function eater(){

    var food = "";

    var obj = {

        eat: function(){

            console.log("I'm eating " + food);

            food = "";

        },

        push: function(myFood){

            food = myFood;

        }

    }

    return obj;

}

var eater1 = eater();

eater1.push('banana');

eater1.eat();

1.4 可以实现封装,属性私有化

 

这个典型的例子是圣杯继承实现的雅虎的写法

 

雅虎写法

var inherit = (function(){

    var F = function(){};

    return function(Target,Origin){

        F.prototype = Origin.prototype;

        Target.prototype = new F();

        Target.prototype.constructor = Target;

        //超类

        Target.prototype.uber = Origin.prototype;

    }

}())

//理解,return时保留了F变量,闭包私有化变量

1.5 模块化开发,防止污染全局变量

 

利用闭包变量私有化,避免命名空间的问题

var name = "heh";

var init = (function(){

   var name = "zhangsan";

   function callName(){

       console.log(name);

   }

   return function (){

       callName();

   }

}())

init();

1.6 闭包的危害

 

以上四点,其实都是闭包的好处,善加利用是能够帮助到我们的,所以大家不要先入为主觉得闭包是不好的,闭包其实是我们解决很多问题的一种思路,但当然,闭包确实有它危害的方面:

 

闭包会导致原有作用域链不释放,造成内存泄漏,即占用导致剩下内存变少

 

1.如何清除闭包:

 

闭包函数=null

如第一个例子:a = null;

否则闭包会一直占用内存,直到浏览器进程结束。

 

2.闭包会在父函数外部,改变父函数内部变量的值。

 

1.3的例子,我们修改了内部的food值,所以,对于闭包,一定要小心使用。

 

3.还有一个最常见的情况是for循环中的闭包

 

我们写一个ul列表,当点击时输出对应的i

 

 

96707970eb323e1af7d98d4fc2b64899

<body>

    <ul>

        <li>1</li>

        <li>2</li>

        <li>3</li>

        <li>4</li>

    </ul>

    <script>

        var items = document.getElementsByTagName('li');

        var len = items.length;

 

        for (var i = 0; i < len; i++) {

            items[i].onclick = function () {

                console.log(i);

            }

        }

    </script>

</body>

这和我们之前事件委托的例子很像,但是这里我们输出的不是对应的this对象,而是函数所在作用域的i值,可以看到,我们输出的都是4,而我们的i应该是从0123,加到4的时候已经不满足条件了,不会进入循环。

 

这到底是怎么回事呢?

 

这同样形成了一个闭包,内部的函数console.log(i)被保存到了外部的items[i].onclick()之中,所以我们有一个外部的AO,里面保存了一个i,但是这个ifor循环执行完之后的i,当我们执行点击函数时,始终用到的就是这个i,但这明显和我们要的不一样,我们希望每一个执行点击时输出的都是for循环时对应的那个i

 

这时候的闭包,是存在一定问题的,利用立即执行函数可以解决这个问题。

 

2、立即执行函数

 

立即执行函数,顾名思义,立即会执行的函数(Immediately-Invoked Function Expression),即当js读取到该函数,会立即执行。

我们1.41.5对应的例子中就用到了这个方法。

用法如下:

 

<body>

    <ul>

        <li>1</li>

        <li>2</li>

        <li>3</li>

        <li>4</li>

    </ul>

    <script>

        var items = document.getElementsByTagName('li');

        var len = items.length;

 

        for (var i = 0; i < len; i++) {

            items[i].onclick = (function () {

                var index = i;

                return function () {

                    console.log(index);

                }

            }())

        }

    </script>

</body>

每次到了立即执行函数时,都会把当前的i赋值给index保存起来,并返回带有这个值的函数。

 

2.1 立即执行函数的写法

 

官方的两种写法

(function (){}());//w3c建议第一种

(function (){})();

2.2 立即执行函数用于初始化

 

<!--针对初始化功能的函数-->

var num = (

    function (b) {

        var a = 123;

        console.log(a,b);

        d = a + b;

        return d;

    }(2);

)

<!--返回值赋值给num-->

<!--执行完就被释放-->

2.3 常见写法的注意事项

 

//1.只有表达式才能被执行符号执行,会忽略表达式的名字

//2.我们正常函数执行的写法如下

function test(){

};

test();

// 但是直接在函数声明后接执行符号,是不可以的,会报语法错误

function test(){

}();

 

//3.凡是能变成表达式就能被执行

var test = function () {

}();

// 执行一次后被永久销毁

// 表达式部分 = function(){

// }()

 

//4.最先识别哪个括号

(--这种写法最外面先

function test(){}()

--);

 

(--这种写法最前面先

function test(){}

--)

();

//可以没有test名称

 

//5.当有参数时,不报错,但也不执行

function test(a,b,c,d){

    console.log(a+b+c+d);

}(1,2,3,4);

 

实际分成了两个部分

function test(a,b,c,d){

    console.log(a+b+c+d);}

(1,2,3,4);//4,输出个数

2.4 作用

 

通过定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个私有的命名空间,该命名空间的变量和方法,不会破坏污染全局的命名空间。此时若是想访问外部对象,将外部对象以参数形式传进去即可。

 

3let

 

还是上面那个问题,我们看看下面的代码

 

        // let

        for (let i = 0; i < len; i++) {

            items[i].onclick = function () {

                console.log(i);

            }

        }

和我们第一个版本一样,只是把var声明的i换成了let的声明方式,但是结果已经没有任何问题了。

为什么仅仅改动了这一点,就解决了我们之前的问题呢?

其实这个问题的本质原因,还是var带来的作用域的问题,接下来,我们来看一看,let,到底是什么?

 

3.1 什么是let

 

 let语句,声明一个块范围变量。

letES6中新增关键字。它的作用类似于var,用来声明变量,用法也类似,但是let是存在块级作用域的。

 

let variable1 = value1

3.2 特性

 

1.使用 let 语句声明一个变量,该变量的范围限于声明它的块中。不能在外部访问该变量,可以在声明变量时为变量赋值,也可以稍后在脚本中给变量赋值。

 

var  l = 10;

{//注意这里仅仅是一个块级作用域

    let l = 2;

    console.log(l);// 这里 l = 2.

    let m = 4;

    console.log(m);// 这里 m = 4.

}

console.log(l);// 这里 l = 10.

 

console.log(m);//报错

mmexport1521995707802

 

相反,对于var,最小级别是函数作用域的。

所以在for循环中,var声明的i是所在函数的作用域,而let则是for循环及循环体内的作用域,所以里面的语句可以访问到对应的i

2.使用 let 声明的变量,在声明前无法使用,否则将会导致错误。(不存在变量提升了)

 

console.log(index);

let index;

mmexport1521995713131

 

3.如果未在 let 语句中初始化您的变量,则将自动为其分配 JavaScript undefined

 

let index;

console.log(index);

 

mmexport1521995997689

3.3 解读for循环中的let

 

        for (let i = 0; i < len; i++) {

            items[i].onclick = function () {

                console.log(i);

            }

        }

对于var来说,形成的闭包始终获取到的都是循环完成后被改变的最终的i

而对于let,每一个i都是独立在当前块级作用域的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量。而JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

 

其实,所谓的for循环带来的闭包问题,其实就是变量作用域的问题,解决方式很多种,基本上可以用立即执行函数和let变量声明来解决,其次,具体情具体分析。

千锋视频平台每日分享:HTML5视频教程。 

 

  • 北京天丰利校区(总部):北京市海淀区宝盛北里西区28号天丰利商城4层
    北京沙河校区:北京市昌平区沙阳路18号北京科技职业技术学院广场服务楼2层、南区服务楼2层
    咨询电话:400-186-9990 010-82790226-801
    面授课程:全栈HTML5+培训、UI交互设计培训、PHP培训、JavaEE+云数据培训、大数据开发培训、VR/AR混合
    现实培训、Python培训、Linux云计算培训、软件测试培训、Android培训、iOS培训、好程序员
  • 深圳西部硅谷校区地址:深圳市宝安区宝安大道5010号深圳西部硅谷B座A区605-619
    深圳大学城校区地址:深圳市南山区留仙大道1201号大学城创客小镇16栋2楼、3楼
    咨询电话:0755-33582485-801(硅谷校区)0755-86660670-801(大学城校区)
    面授课程:全栈HTML5+培训、UI交互设计培训、PHP培训、JavaEE+云数据培训、Android培训、iOS培训
  • 上海校区地址:上海市宝山区同济支路199号智慧七立方3号楼2-4层
    咨询电话:400-627-7899 021-56166283/56166279
    面授课程:全栈HTML5+培训、UI交互设计培训、JavaEE+云数据培训、Android课程培训、iOS课程培训、好程序员
  • 郑州校区地址:郑州市金水区纬五路21号河南教育学院综合楼(经纬中学楼)7/8层
    咨询电话:0371-55191750 400-186-9990
    面授课程:全栈HTML5+培训、UI交互设计培训、PHP培训、JavaEE+云数据培训、Android课程培训、iOS课程培训
  • 广州校区地址:广州市天河区元岗路310号智汇park创意园E座5层
    咨询电话:020-22119207 400-186-9990
    面授课程:全栈HTML5+培训、JavaEE+云数据培训、Android课程培训、iOS课程培训
  • 大连校区地址:辽宁省大连市甘井子区软件园路2号东软信息学院B5座一楼
    咨询电话:0411-39026086 400-186-9990
    面授课程:全栈HTML5+培训、JavaEE+云数据培训、UI交互设计培训、Android课程培训、iOS课程培训
  • 武汉校区地址:武汉市光谷大道61号智慧园21号楼2层
    咨询电话:027-65523826
    面授课程:全栈HTML5+培训、JavaEE+云数据培训、Android课程培训、iOS课程培训
  • 成都校区地址:成都市武侯区科华北路62号力宝大厦N(北楼)18楼
    咨询电话:028-83178771
    面授课程:全栈HTML5+培训、UI交互设计培训、PHP培训、JavaEE+云数据培训、Android课程培训、iOS课程培训
  • 西安校区地址:西安市雁塔区高新六路52号立人科技C座西区4楼
    咨询电话:029-85260160
    面授课程:全栈HTML5+培训、JavaEE+云数据培训、Android课程培训
  • 杭州校区地址:浙江省杭州市江干区九堡旺田书画城A座4层
    咨询电话:0571-86893632 010-82790226-801
    面授课程:全栈HTML5+培训、JavaEE+云数据培训、Android课程培训、iOS课程培训
  • 青岛校区地址:青岛市市南区金坛路17号青岛职业技术学院南校区实训楼A4层
    咨询电话:0532-80910752/3 010-82790226-801
    面授课程:全栈HTML5+培训、UI交互设计培训、JavaEE+云数据培训、Android课程培训、iOS课程培训
  • 重庆校区地址:重庆市高新区科园一路2号大西洋国际12-1
    咨询电话:023-68883009
    面授课程:JavaEE+云数据课程培训
  • 长沙校区地址:湖南省长沙市岳麓区麓谷企业广场A2栋三单元306号
    咨询电话:0731-85513010/85513210
    面授课程:JavaEE+云数据课程培训
  • 哈尔滨校区地址:哈尔滨市松北区创新一路699号科技创新城19号楼五楼
    咨询电话:15663846969
    面授课程:全栈HTML5+培训
  • 千锋教育服务号

    了解千锋动态
    关注千锋教育服务号

  • 千锋互联服务号

    扫码匿名提建议
    直达CEO信箱