资讯专栏INFORMATION COLUMN

前端小知识--从Javascript闭包看let

Kross / 245人阅读

摘要:闭包会在父函数外部,改变父函数内部变量的值。立即执行函数立即执行函数,顾名思义,立即会执行的函数,即当读取到该函数,会立即执行。特性使用语句声明一个变量,该变量的范围限于声明它的块中。使用声明的变量,在声明前无法使用,否则将会导致错误。

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

这是因为,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;


    
  • 1
  • 2
  • 3
  • 4

这和我们之前事件委托的例子很像,但是这里我们输出的不是对应的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对应的例子中就用到了这个方法。
用法如下:


    
  • 1
  • 2
  • 3
  • 4

每次到了立即执行函数时,都会把当前的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)
)

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);//报错

相反,对于var,最小级别是函数作用域的。
所以在for循环中,var声明的i是所在函数的作用域,而let则是for循环及循环体内的作用域,所以里面的语句可以访问到对应的i。
2.使用 let 声明的变量,在声明前无法使用,否则将会导致错误。(不存在变量提升了)

console.log(index);
let index;

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

let index;
console.log(index);

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...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93491.html

相关文章

  • 前端进击的巨人(三):作用域走进闭包

    摘要:进击的巨人第三篇,本篇就作用域作用域链闭包等知识点,一一击破。在此我们遵照的方式,暂且称是闭包。所以,一名合格的前端,除了会用闭包,还要正确的解除闭包引用。 进击的巨人第三篇,本篇就作用域、作用域链、闭包等知识点,一一击破。 showImg(https://segmentfault.com/img/bVburWd?w=1280&h=854); 作用域 作用域:负责收集并维护由所有声明的...

    Vicky 评论0 收藏0
  • javascript知识

    摘要:模块化是随着前端技术的发展,前端代码爆炸式增长后,工程化所采取的必然措施。目前模块化的思想分为和。特别指出,事件不等同于异步,回调也不等同于异步。将会讨论安全的类型检测惰性载入函数冻结对象定时器等话题。 Vue.js 前后端同构方案之准备篇——代码优化 目前 Vue.js 的火爆不亚于当初的 React,本人对写代码有洁癖,代码也是艺术。此篇是准备篇,工欲善其事,必先利其器。我们先在代...

    Karrdy 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0
  • JavaScript:面试频繁出现的几个易错点

    摘要:针对于面向对象编程的。因为面向对象就是针对对象例子中的守候来进行执行某些动作。这就是闭包的用途之一延续变量周期。把变量放在闭包里面和放在全局变量里面,影响是一致的。 1.前言 这段时间,金三银四,很多人面试,很多人分享面试题。在前段时间,我也临时担任面试官,为了大概了解面试者的水平,我也写了一份题目,面试了几个前端开发者。在这段时间里面,我在学,在写设计模式的一些知识,想不到的设计模式...

    VincentFF 评论0 收藏0
  • 理解闭包

    摘要:我的理解就是还处于被引用状态。内存机制的内存空间分为栈堆其中栈存放变量,堆存放复杂对象。对堆内数据进行复制修改时理解闭包有了前面的铺垫,我们再来看看闭包是怎么回事。这种反常的现象我们就叫它,中文名闭包。这就是闭包形成的原因了。 知识小储备 ECMAScript 的数据有两种类型:基本类型值和引用类型值,基本类型指的是简单的数据段,引用类型指的是可能由多个值构成的对象。Undefined...

    fox_soyoung 评论0 收藏0

发表评论

0条评论

Kross

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<