闭包对每个前端来说都是一个绕不开的话题。学习之初也因为搞清闭包的概念耗费了不少精力,今天写一篇博客来记录本人对闭包的理解,笔者水平有限,若有疏漏及错误,愿不吝赐教。
你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数,因为ES6有了块级作用域的概念)。
可以先点击上面链接查看MDN对闭包的讲述。
闭包在JavaScript高级程序设计(第3版)中是这样描述:闭包是指有权访问另一个函数作用域中的变量的函数。
这样闭包的作用就很明显了
我们来写一个简单的例子:
console.log('closure');
function add() {
var count = 0;
return function() {
count += 1;
return count;
};
}
var a = add();
function fun() {
alert(a());
}
fun();
fun();
我们来解释一下这段代码:
定义了add() 函数,函数内定义了count,返回值也是一个函数。子匿名函数引用外部变量count,因此形成了闭包。
当下面我们定义并赋值a = add()时,add()函数执行且仅执行一次。执行后a被赋值(匿名函数)了
上面代码中function() { count += 1; return count; };部分就形成了一个闭包。引用了函数外部变量count。因此count在函数执行结束也不会销毁,一直存在于内存中。然后每次调用a()函数时,返回的都是内层匿名函数(闭包)的执行结果,因为内层匿名函数在外层函数的内部,所以即使外层函数执行完了,第二个函数依然能访问count(JS设计的作用域链,当前作用域能访问上级的作用域)。
该案例是牛客网题库的一道题:
<ul>
<li>i</li>
<li>i</li>
<li>i</li>
<li>i</li>
</ul>
js实现:要求点击每个li标签,弹出对应的索引。(0,1,2,3)
实现1:
var elements = document.getElementsByTagName('li');
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
console.log(i);
alert(i);
};
}
获取dom元素,进行遍历不就可以了吗?但是当我们自信的点击标签时候发现弹出都是4……。整个人都觉得不好了!
ps:有人疑惑不应该都是3吗?为什么是4? 这是因为for循环的执行顺序都是以i++结束的,最后一次不进入循环。但是var i 是在全局中的,因此i会被赋值为4.具体for循环知识可以自行搜索这里不做赘述。
下面是这个题目的解答:
每个li标签的onclick事件执行时,本身onclick绑定的function的作用域中没有变量i,i为undefined,则解析引擎会寻找父级作用域,发现父级作用域中有i,且for循环绑定事件结束后,i已经赋值为4,所以每个li标签的onclick事件执行时,alert的都是父作用域中的i,也就是4。这是作用域的问题。这里onclick事件绑定的函数形成闭包,但引用的变量i是全局的。
闭包真正的含义是,如果一个函数访问了此函数的父级及父级以上的作用域变量,就可以称这个函数是一个闭包。
这里其实是一个作用域问题,闭包中引用的变量不会消失,所以我们把每次的i都保存到变量中,就可以实现想要的结果。
for (let i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
return (function (n) {
console.log(n);
alert(n);
})(i);
};
}

这样我们就使用闭包将i作为参数传递到匿名函数(闭包)中,每个点击事件都有了一个局部的num变量,保存的即相应的i值。
拓展:
我们可以直接将for循环的var改为let就可以实现想要的效果。let的到来,让在循环体内创建闭包具有了更好的可读性。可搜索学习let和const的相关知识。或亲自动手,将let通过babel编译查看相关结果进行研读。
有关let的知识点会在以后的博文中进行整理。
闭包怎么引起内存泄漏?谁会存在内存泄漏?
我们借助垃圾回收机制的标记清除法来看一下:
示例:
function fn () {
let count = 1
function fun () {
count ++
console.log(`函数内部调用${count}次`)
}
return fun
}
const result = fn();
result() //2
result() //3
我们来分析一下:
result是一个全局变量,代码执行完毕不会立即销毁result可以找到fn()函数,fn()函数return fun(),因此 fun()也不会销毁。fun()引用到了父级作用域的count,因此,在引用的count便不会被垃圾回收机制回收。result没有销毁,所以它引用到的count也会留存在内存当中。闭包的实际应用主要是函数工厂,以及模拟私有方法。
举例:
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
这是MDN官网的一个makeAdder(x)函数,接受一个参数x,并返回新的函数,返回的函数接受一个参数y,并返回x+y的值。
从本质上讲,makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。
add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。
应用案例:假如我们想在页面添加几个按钮用来调整页面字号。我可以给body元素设置font-size,然后通过em单位设置页面中其他元素的字号:
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
然后可以对body的font-size进行调整:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
这样,size12,size14,size16就可以分别将body字体调整为12,14,16px,再将它们添加到点击事件上。
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
这样就实现了想要的效果。这就是一个简单的闭包实现函数工厂的案例。
var Counter = (function () {
var privteCounter = 0;
function changeBy (val) {
privteCounter += val;
}
return {
increment: function () {
changeBy(1)
},
decrement: function () {
changeBy(-1)
},
value: function () {
return privteCounter
}
}
})()
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
在之前的示例中,每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。
备注: 你应该注意到我们定义了一个匿名函数,用于创建一个计数器。我们立即执行了这个匿名函数,并将他的值赋给了变量Counter。我们可以把这个函数储存在另外一个变量makeCounter中,并用他来创建多个计数器。
var makeCounter = function () {
var privteCounter = 0;
function changeBy(val) {
privteCounter += val;
}
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return privteCounter;
},
};
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
请注意两个计数器 Counter1 和 Counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。
每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。
备注: 以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。
闭包应用部分是引用MDN的案例,大家可以移步阅读(一定要读)。
今天的总结分享就到这里,继续努力搬砖!只作个人总结所用,有不足与疏漏请评论区补充。
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or