js闭包其实不难,你需要的只是了解何时使用它

  • 究竟什么是闭包?
  • 闭包在什么场景下使用?
  • 写前端程序需要用到闭包吗?我用jQuery也能写的好好滴呀?

对于初学者来说,常常会觉得闭包是个很难理解的概念,我认为之所以觉得难以理解,是因为没有了解到闭包的用途以及它通常的使用场景,实际开发中,闭包的运用非常广泛。
如果知道了使用闭包可以解决哪些问题,使用闭包会带来哪些好处,完全掌握并熟练使用闭包就不再是一个难题了。还是那句话:学以致用,如果不知道如何使用,就是没有学会。do it, make your hand dirty!

“闭包”的概念 ——《百度百科》

闭包是指可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。在PHP、Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python、Go、Lua、objective c、swift 以及Java(Java8及以上)等语言中都能找到对闭包不同程度的支持。--百度百科

从“闭包”的概念上可以知道,闭包其实是一个通用的概念,在很多领域中(比如“离散数学”,“计算机”等领域都有使用)。

“闭包”的概念 ——《javascript权威指南 第6版》

Like most modern programming languages, JavaScript uses lexical scoping. This means that functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked. In order to implement lexical scoping, the internal state of a JavaScript function object must include not only the code of the function but also a reference to the current scope chain.This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature. ——引用自“javascript权威指南 第6版”

在javascript语言中,闭包就是函数和该函数作用域的组合。从这个概念上来讲,在js中,所有函数都是闭包(函数都是对象并且函数都有和他们相关联的作用域链scope chain)。

既然所有函数都是闭包,还有必要专门提这个概念吗?

大多数函数被调用时(invoked),使用的作用域和他们被定义时(defined)使用的作用域是同一个作用域,这种情况下,闭包神马的,无关紧要。但是,当他们被invoked的时候,使用的作用域不同于他们定义时使用的作用域的时候,闭包就会变的非常有趣,并且开始有了很多的使用场景,这就是你之所以要掌握闭包的原因。

理解“闭包” step 1:掌握嵌套函数的词法作用域规则(lexical scoping rules)

var scope = "global scope"; 
function checkScope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f();
}
checkScope();   //=> "local scope"

分析一下上面的代码,该代码定义了一个全局变量 scope,以及一个函数checkScope,在函数checkScope中,定一个一个局部变量,同样命名为scope,以及一个函数f(嵌套函数)。

  • 在js中,函数可以用来创建函数作用域;
  • 函数就像一层半透明玻璃,在函数内部可以看到函数外部的变量,但是在函数外部,看不到函数内部的变量。
  • 变量的搜索是从内向外而不是从外向内搜索的。

代码执行过程分析:


函数作用域.png

checkScope被invoke时,return f(),运行内部嵌套函数f,f沿着作用域链从内向外寻找变量scope,找到“local scope”,停止寻找,因此,函数返回 “local scope”;

接下来,代码稍作修改:

var scope = "global scope"; 
function checkScope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f;
}
checkScope()();   //=> "这次返回什么?"

代码执行过程分析:
checkScope被invoke时,将内部嵌套的函数f返回,因此checkScope()()这句执行时,其实运行的是f(),f函数返回scope变量,在这种情况下,f会从哪个作用域里去寻找变量scope呢?

remember 词法作用域的基础规则:函数被执行时(executed)使用的作用域链(scope chain)是被定义时的scope chain,而不是执行时的scope chain

嵌套函数f(), 被定义时,所在的作用域链中,变量scope是被绑定的值是“local scope”,而不是"global scope",因此,以上代码的结果是啥?没错,是"local scope"!

这就是闭包的神奇特性:闭包可以捕获到局部变量和参数的外部函数绑定,即便外部函数的调用已经结束。

只要记住一点:词法作用域的规则,即函数被执行时(executed)使用的作用域链(scope chain)是被定义时的scope chain,而不是执行时的scope chain,就可以很容易的理解闭包的行为了。

理解“闭包” step 2:掌握闭包的使用场景

在js版本的设计模式中,很多模式的实现都需要借助于闭包,因此,掌握闭包的使用场景,可以结合设计模式一起理解学习。这里引用了《JavaScript设计模式与开发实践》中的很多例子,书很好,推荐大家阅读学习。

闭包经典使用场景一:通过循环给页面上多个dom节点绑定事件

场景描述:假如页面上有5个button,要给button绑定onclick事件,点击的时候,弹出对应button的索引编号。

循环给多个button绑定事件.png
<!DOCTYPE html>
<html>
<head>
     <meta charset="UTF-8">
</head>
<body>
    <button>Button0</button>
    <button>Button1</button>
    <button>Button2</button>
    <button>Button3</button>
    <button>Button4</button>
</body>
</html>

先随手来一段for循环:

var btns = document.getElementsByTagName('button');
for(var i = 0, len = btns.length; i < len; i++) {
    btns[i].onclick = function() {
        alert(i);
    }
}

通过执行该段代码,发现不论点击哪个button ,均alert 5;

why?

因为,onclick事件是被异步触发的,当事件被触发时,for循环早已结束,此时变量 i 的值已经是 5 。所以,当onlick事件函数顺着作用域链从内向外查找变量 i 时,找到的值总是 5 。

那怎么能循环给button添加事件,并且还能alert出来不同的值呢?答案当然是:“闭包”!在闭包的作用下,定义事件函数的时候,每次循环的i值都被封闭起来,这样在函数执行时,会查找定义时的作用域链,这个作用域链里的i值是在每次循环中都被保留的,因此点击不同的button会alert出来不同的i。

上代码:

Tip: 在js中,没有块级作用域 ,只有函数作用域。可以采用“立即执行函数Immediately-Invoked Function Expression (IIFE)”的方式创建作用域。

for(var i = 0, len = btns.length; i < len; i++) {
    (function(i) {
        btns[i].onclick = function() {
            alert(i);
        }
    }(i))
}

运行以上代码,是符合我们需求的。

闭包使用场景二:封装变量

闭包可以将一些不希望暴露在全局的变量封装成“私有变量”。

假如有一个计算乘积的函数,mult函数接收一些number类型的参数,并返回乘积结果。为了提高函数性能,我们增加缓存机制,将之前计算过的结果缓存起来,下次遇到同样的参数,就可以直接返回结果,而不需要参与运算。这里,存放缓存结果的变量不需要暴露给外界,并且需要在函数运行结束后,仍然保存,所以可以采用闭包。

上代码:

var mult = (function(){
    var cache = {};
    var calculate = function() {
        var a = 1;
        for(var i = 0, len = arguments.length; i < len; i++) {
            a = a * arguments[i];
        }
        return a;
    }
    
    return function() {
        var args = Array.prototype.join.call(arguments, ',');
        if(args in cache) {
            return cache[args];
        }
        
        return cache[args] = calculate.apply(null, arguments);
    }
}())

闭包使用场景三:延续局部变量的寿命

img对象经常用于数据上报,如下:

var report = function(src) {
    var img = new Image();
    img.src = src;
}
report('http://xxx.com/getUserInfo');

这段代码在运行时,发现在一些低版本浏览器上存在bug,会丢失部分数据上报,原因是img是report函数中的局部变量,当report函数调用结束后,img对象随即被销毁,而此时可能还没来得及发出http请求,所以此次请求就会丢失。

因此,我们使用闭包把img对象封闭起来,就可以解决数据丢失的问题:

var report = (function() {
    var imgs = [];
    return function(src) {
        var img = new Image();
        imgs.push(img);
        img.src = src;
    }
}())

闭包+设计模式

在诸多设计模式中,闭包都有广泛的应用。对象==数据+方法。而闭包是在过程中以环境的形式包含了数据。因此,通常面向对象能实现的功能,使用闭包也可以实现。

涉及到设计模式,闭包就是一种理所当然的存在,必须熟练使用,才可以理解每种设计模式的意图。这里先不引入设计模式了,内容太多,有空再总结吧 :)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量...
    zock阅读 1,074评论 2 6
  • 作用域和闭包是 JavaScript 最重要的概念之一,想要进一步学习 JavaScript,就必须理解 Java...
    劼哥stone阅读 1,168评论 1 13
  • 闭包: 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该...
    小裁缝sun阅读 610评论 0 5
  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量...
    zouCode阅读 1,269评论 0 13
  • 文:杨长懿 今天我们先讲故事: 庄子有个朋友叫惠子,一天庄子去他们家玩儿,惠子便向庄子说:我们家路边有一颗大樗(c...
    杨长懿阅读 304评论 0 0