前端小学问--从Javascript闭包看let

let和闭包

之前一向迷迷糊糊记得,let处理了某个闭包题目,想用时又不敢肯定,本日终究碰到这个题目了,那我们就一起来剖析一下,什么是let,let有什么作用,以及,他是怎样处理闭包的,固然,也趁便好好聊聊闭包。

1、闭包

1.1 闭包的定义

闭包的定义是如许的:内部函数被保留到了外部,即为闭包
先来看一个简朴的例子:

//我们声明一个函数test(),这个函数返回了一个function
 function test(){
     var i = 0;
     return function(){
        console.log(i++)
     }
 }; 
//把test()的返回值赋给a和b变量,所以实在这时刻的a/b=function(){console.log(i++);}
 var a = test();
 var b = test();
 //顺次实行a,a,b,控制台会输出什么呢?
 a();a();b();

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

答案是:0,1,0

《前端小学问--从Javascript闭包看let》

这是由于,a/b=test()时,a/b各自保留了test的AO,所以各自上面均有一个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;

《前端小学问--从Javascript闭包看let》

<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应该是从0到1、2、3,加到4的时刻已不满足前提了,不会进入轮回。

这究竟是怎么回事呢?

这一样构成了一个闭包,内部的函数console.log(i)被保留到了外部的items[i].onclick()当中,所以我们有一个外部的AO,内里保留了一个i,然则这个i是for轮回实行完以后的i,当我们实行点击函数时,一直用到的就是这个i,但这显著和我们要的不一样,我们愿望每个实行点击时输出的都是for轮回时对应的谁人i;

这时刻的闭包,是存在肯定题目的,应用马上实行函数可以处理这个题目。

2、马上实行函数

马上实行函数,望文生义,马上会实行的函数(Immediately-Invoked Function Expression),即当js读取到该函数,会马上实行。
我们1.4、1.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 作用

经由过程定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个“私有”的定名空间,该定名空间的变量和要领,不会损坏污染全局的定名空间。此时假如想接见外部对象,将外部对象以参数情势传进去即可。

3、let

照样上面谁人题目,我们看看下面的代码

        // let
        for (let i = 0; i < len; i++) {
            items[i].onclick = function () {
                console.log(i);
            }
        }

和我们第一个版本一样,只是把var声明的i换成了let的声明体式格局,然则效果已没有任何题目了。
为何仅仅改动了这一点,就处理了我们之前的题目呢?
实在这个题目的实质缘由,照样var带来的作用域的题目,接下来,我们来看一看,let,究竟是什么?

3.1 什么是let

let语句,声明一个块局限变量。

let是ES6中新增关键字。它的作用相似于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);//报错

《前端小学问--从Javascript闭包看let》

相反,关于var,最小级别是函数作用域的。
所以在for轮回中,var声明的i是地点函数的作用域,而let则是for轮回及轮回体内的作用域,所以内里的语句可以接见到对应的i。
2.运用 let 声明的变量,在声明前没法运用,不然将会致使毛病。(不存在变量提升了)

console.log(index);
let index;

《前端小学问--从Javascript闭包看let》

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

let index;
console.log(index);

《前端小学问--从Javascript闭包看let》

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变量声明来处理,其次,详细情详细剖析。

引荐浏览

http://web.jobbole.com/82520/
https://www.sogou.com/link?ur…
http://hao.jser.com/archive/5…
https://msdn.microsoft.com/li…
http://www.jb51.net/article/2…
https://segmentfault.com/a/11…

    原文作者:JiaXinYi
    原文地址: https://segmentfault.com/a/1190000013827309
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞