前言 - ES6 之前,JS没有块级作用域,只有全局作用域和函数作用域

用了许久ES6,春招在即,重写下博文。

还是讲讲闭包。我们要知其然,知其所以然。

仿佛大众情人一般,很多前端面试官都会问一问,说来复杂,说来也简单。就是这种既可以复杂又可以简单的东西往往让面试官能收获很多(对被面试者也是如此)。

闭包所谓的专业定义:闭包是指有权访问另一个函数作用域中的变量的函数。

其实这是一句后话,对于入门者来说毫无作用。一个不会喝酒的人,突然喝了一杯高浓度的酒,它只会醉倒,而不会体味到其中酒的美好滋味。只有阅尽千帆,才可能化繁为简。只有不断打磨自己的语言,最后产出的才能是最简练的东西。所谓知识储备。


一. 闭包是什么?它来自哪里,又是什么模样

专业定义其实是真正理解后再来看的。大家都肌肉记忆了,自然觉得简单。

我们只需要明白我们要关注的:作用域。函数。变量。

所以我们直接来看几个闭包的例子吧。

  • 例子A.局部变量

    function out() {
    var a = 3;
    }
    out();
    console.log(a); // error,报错

    a是局部变量。我们从 全局获取 a,并且得到了失败的结果。

  • 例子B.函数的调用 - 这就是闭包!!!

    function out() {
    var a = 3;
    function closure() {
    console.log(a);
    }
    closure();
    }
    out(); // 3

    a还是局部变量,被函数内部的 closure 调用了。out 内部 先声明、后调用 了 closure。

    这一次,我们从 全局(其实依靠了外物out), whatever 获取 a -> 成功了。

    其实闭包从例子B就结束了。访问另一个函数作用域中的变量的函数即是闭包。然而人们总是要探寻其中的原理,所以才有了大量的后文。

  • 例子C.经典例子 -- 常用于给list数组里面的每个item挨个绑定函数

    var oBtn = [];
    for (var i = 0; i < 6; i++) {
    oBtn[i] = function () {
    console.log(i);
    }
    // oBtn[i]();
    }
    oBtn[0](); // 6
    for (k = 0; k < 6; k++) {
    oBtn[k](); // 6个 6
    }
例子C的出现是为什么呢?我们先来看正确解答。

二. 闭包引起的问题的逐句分析

参考至 https://www.cnblogs.com/zhuzhenwei918/p/6131345.html

1.var oBtn = []; // 定义一个数组(在我们平常使用中通常获取的是html节点数组)
2.for (var i = 0; i < 6; i++) // 用 var 声明,所以 i 是全局变量,不在局部作用域中
for (var i = 0; i < 6; i++) {}
console.log(i); // 6 所谓的 跳出三界外,不在五行中!!!全局可以访问到 i.
3.oBtn[i] = function() { console.log(i); } 这里进行了一个 变量赋值 操作。

注意,只是赋值。没有进入 执行环境。所以,这里的 i 其实是还没有被确定的。由于for循环不具有块级作用域,所以这里的函数定义就是全局作用域。

4.var i = 1; // 到了第二次循环,这时候的 var i=1 覆盖了 第一次的 var i=0

此后每次循环不断覆盖。当我们最后在全局真正的 调用的时候:

5.oBtn[0](); // 6

三. 闭包引起问题的解决。(精细的原理还请仔细观看下方)

  • 办法一.现代方法 四两拨千斤。var 改 let。

    这就是现代的 四两拨千金。其中的关键在于 i 不在三界之内、五行之中,而在 全局里。

    var oBtn = [];
    for (let i = 0; i < 6; i++) {
    oBtn[i] = function () {
    console.log(i);
    }
    }
    oBtn[0](); // 0

    let的效果 - 我们只要让 i 拥有自己的作用域即可。ES6中,使用let之后,能够让定义的变量在 {} 之内拥有其块级作用域。

    {
    var a = 10;
    let b = 10;
    }
    console.log(a); // 10;
    console.log(b); // error

    既然知道了现代的解决办法,也让我们回顾一下以往的解决办法

  • 办法二.用匿名函数来形成自己的局部作用域(目的其实还是一样的,将i变为局部的变量)

    var oBtn = [];
    for (var i = 0; i < 6; i++) {
    (function(index) {
    oBtn[index] = function () {
    console.log(index);
    }
    })(i);
    }
    oBtn[0](); // 0
    oBtn[5](); // 5
  • 办法三.通过新建一个变量保存当前状态的 i

    (类似与我们的拍照,随着时间的变迁,我们慢慢长大,但我却可以用相片记录下曾经的那个我)

    var oBtn = [];
    for (var i = 0; i < 6; i++) {
    oBtn[i] = {};
    oBtn[i].index = i;
    oBtn[i].func = function() {
    console.log(this.index);
    }
    }
    oBtn[3].func();

代码变多了是不是?

至此、闭包带来的问题解决了。出问题的原因的关键原因其实还是 作用域的锅。另外,闭包本身不是问题,是特性。然使用不当则出现问题,从而被众人关注。


四. 让我们回到对闭包的剖析上来。

A.为什么全局里获取不到函数的局部变量呢?

JS采用一种垃圾清除的机制,分别用 引用计数 和 标记清除。

  • 1.引用计数的原则是当使用了变量,则给变量记为 1。当大家都不使用了,就像那个被忘掉的人一样,就被清除了。
  • 2.标记清除则是 当变量进入环境则全部变量标记一遍;然后去掉环境中要使用的变量的标记;最后,垃圾收集器完成内存清除工作,销毁那些还带有标记的(即是没被环境中使用的)。

闭包实现了一种特殊的情况。闭包中的变量,这个函数的空间将会一直在内存中保留。

function test() {
var a = 3;
return function() {
return a;
}
}
b = test();

虽然在外部没法输出a,这是因为没法访问,但a还是存在于内存之中。因为内部的函数引用了外部的变量a(引用计数法垃圾清除,为0则删除),所以a还被人惦记着,自然也不会消亡(只要b还在,js还在运行)

局部变量作为函数环境内的变量,当函数运行结束,它就被销毁了,从而在全局中是找不到它的。而闭包通过对其引用,让其不被消亡,从而使其能够在全局中生存。

B.函数是怎么查找变量的呢?为什么 内部函数 找到了 外部函数的 变量?并且将其带回并保留在了 全局 的世界里。

  • 函数中识别变量,是一层层向外找的。
  • 首先在函数内部找,看看是不是内部声明的,然后再到上一层找,没找到,再往上,直到全局作用域。
  • 如果全局页面都没声明,那浏览器就报错了。

    这一层层中的层是什么东西呢,就是函数,因为函数提供最小的作用域.

内部函数发现自身没有找到那个变量。于是往外找,找到了外部函数的变量。将其返回则实现了 内部函数对外部函数的变量的引用,也就是闭包本身的定义。

五. 它能够用来做什么呢?又带了哪些后果呢?

万物都有优点和缺点。因为我们是人,纠结又迷茫,自卑而不敢确信。不唯一的想法带来的后果就是一切皆有可能,一切皆有两面性。

所有的结果其实都是因为人意识的存在。


闭包的优点

  • A. 全局变量可能会造成命名冲突。使用闭包则不用担心这个问题。它是私有化的,加强了封装性。
  • B. 变相地实现了数据的私有。跟C++的私有变量相似。
  • C. 缓存。
  • D. 减少了函数的 参数量(实际现象)。

每个模块相互调用。当程序越来越复杂,全局变量可能带来不可预测的危险。

闭包让局部变量发挥出了全局变量的作用,降低了风险。


闭包的缺点

  • A. 内存消耗。由于闭包携带了包含它函数的作用域,所以比其他的函数占用内存占用的更多。

参考+非常好的一篇文章:http://www.360doc.com/content/15/1008/17/19812575_504201072.shtml


complete.

JavaScript专题(二)闭包的更多相关文章

  1. 让你分分钟学会Javascript中的闭包

    Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...

  2. 《深入理解javascript原型和闭包系列》 知识点整理(转)

    深入理解javascript原型和闭包系列 对原型和闭包等相关知识的讲解,由浅入深,通俗易懂,每个字都值得细细研究. 一.一切都是对象 1. typeof操作符输出6种类型:string boolea ...

  3. 《深入理解javascript原型和闭包系列》 知识点整理

    深入理解javascript原型和闭包系列 对原型和闭包等相关知识的讲解,由浅入深,通俗易懂,每个字都值得细细研究. 一.一切都是对象 1. typeof操作符输出6种类型:string boolea ...

  4. JavaScript高级之闭包的概念及其应用

    主要内容: 什么是闭包 闭包使用的一般模式 闭包都能做些什么 本文是我的JavaScript高级这个系列中的第二篇文章. 在这个系列中,我计划分析说明 一下JavaScript中的一些常用的而又神秘的 ...

  5. 如何才能通俗易懂的解释javascript里面的"闭包"?

    看了知乎上的话题 如何才能通俗易懂的解释javascript里面的‘闭包’?,受到一些启发,因此结合实例将回答中几个精要的答案做一个简单的分析以便加深理解. 1. "闭包就是跨作用域访问变量 ...

  6. Javascript中的闭包(转载)

    前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它.下面是作者从作用域链慢慢讲到 ...

  7. 狗日的Javascript中的闭包

    前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它.下面是作者从作用域链慢慢讲到 ...

  8. JavaScript之详述闭包导致的内存泄露

    一.内存泄露 1. 定义:一块被分配的内存既不能使用,也不能回收.从而影响性能,甚至导致程序崩溃. 2. 起因:JavaScript的垃圾自动回收机制会按一定的策略找出那些不再继续使用的变量,释放其占 ...

  9. 学习javascript数据结构(二)——链表

    前言 人生总是直向前行走,从不留下什么. 原文地址:学习javascript数据结构(二)--链表 博主博客地址:Damonare的个人博客 正文 链表简介 上一篇博客-学习javascript数据结 ...

随机推荐

  1. Java面试题10(如何取到set集合的第一个元素)

    1.如何取到set集合的第一个元素. public static void main(String[] args) { Set set = new HashSet(); set.add("x ...

  2. bzoj 3754: Tree之最小方差树 模拟退火+随机三分

    题目大意: 求最小方差生成树.N<=100,M<=2000,Ci<=100 题解: 首先我们知道这么一个东西: 一些数和另一个数的差的平方之和的最小值在这个数是这些数的平均值时取得 ...

  3. 51nod 1250 排列与交换——dp

    题目:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1250 仔细思考dp. 第一问,考虑已知 i-1 个数有多少种方案. ...

  4. 【转】 Pro Android学习笔记(二二):用户界面和控制(10):自定义Adapter

    目录(?)[-] 设计Adapter的布局 代码部分 Activity的代码 MyAdapter的代码数据源和构造函数 MyAdapter的代码实现自定义的adapter MyAdapter的代码继续 ...

  5. 记一次SQL xml字段关联查询

    需求: 一张表是APP表,结构如下: app_category为该游戏所属的类别ID,xml字段类型 另一张表是类别表,就ID对应名称,这就不上图了. 还有一张表是每个游戏的下载记录,结构如下: Do ...

  6. sort,uniq,cut,wc命令详解

    sortsort 命令对 File 参数指定的文件中的行排序,并将结果写到标准输出.如果 File 参数指定多个文件,那么 sort 命令将这些文件连接起来,并当作一个文件进行排序. sort语法 s ...

  7. 项目一:第四天 1、快递员的条件分页查询-noSession,条件查询 2、快递员删除(逻辑删除) 3、基于Apache POI实现批量导入区域数据 a)Jquery OCUpload上传文件插件使用 b)Apache POI读取excel文件数据

    1. 快递员的条件分页查询-noSession,条件查询 2. 快递员删除(逻辑删除) 3. 基于Apache POI实现批量导入区域数据 a) Jquery OCUpload上传文件插件使用 b) ...

  8. 面试题: Spring 框架 Bean的生命周期

    [Java面试五]Spring总结以及在面试中的一些问题.   1.谈谈你对spring IOC和DI的理解,它们有什么区别? IoC Inverse of Control 反转控制的概念,就是将原本 ...

  9. R语言中的字符处理

    R语言中的字符处理 (2011-07-10 22:29:48) 转载▼ 标签: r语言 字符处理 字符串 连接 分割 分类: R R的字符串处理能力还是很强大的,具体有base包的几个函数和strin ...

  10. 16、GATK使用简介 Part1/2

    转载:http://blog.sina.com.cn/s/blog_6721167201018fyw.html GATK (全称The Genome Analysis Toolkit)是Broad I ...