JavaScript葵花宝典之闭包
闭包,写过JS脚本的人对这个词一定不陌生,都说闭包是JS中最奇幻的一个知识点, 虽然在工作中,项目里经常都会用到~ 但是是不是你已经真正的对它足够的了解~~
又或者是你代码中出现的闭包,并不是你刻意而为之的行为~ 又或者是因为能达到效果,也知道是闭包,但是原理却不知道?。。。。
一千个人就有一千个哈姆雷特~ 每个人也许都有自己对闭包的理解, 我也不例外, 曾经N次百度过闭包,却没有真正的消化过这个知识点, 也曾工作中无数次运用过闭包, 却不知其所以然~~
所以,我想把我理解的闭包,自己总结一下,虽然很多都是自己的理解, 但是总结再更正,才会越来越好~~
首先是闭包的定义:
- 维基百科对闭包的定义:
闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
- 书籍《你不知道的Javascript》对闭包的定义: (个人觉得对闭包阐释最好的一本书)
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数实在当前词法作用域之外执行。
上面的概念基本上已经说明了闭包是什么,这只是一个概括,不知道的人看了之后还是不会知道,所以要知道闭包还是需要庖丁解牛一样的一层一层的去理解~~
给自己几个疑问? 然后对这些疑问一层一层的理解,直到理解为止!
- 闭包产生的环境?
- 闭包的作用?
- 闭包的一些使用场景?
- 闭包的弊端?
闭包的环境:
既然是叫做闭包,里面有个包字,那么肯定就是被包围的意思咯~ 竟然是被包围,肯定是在一个空间中,而JS能代表空间的是什么? 就是作用域,作用域又有全局作用域跟局部作用域。。
所以闭包产生的环境应该就是在作用域当中了, 所以有闭包肯定有局部作用域, 有局部作用域并不一定有闭包~~
闭包的作用:
竟然有闭包的存在肯定有其存在的价值, 那么闭包的作用体现再哪里呢~~
var a = 1;
function fn1() {
console.log(a); // var b = 2;
} console.log(b); //b is not defined
//当全局中有个变量a,我们在函数fn1中可以随时访问/修改这个变量,但是当我的函数fn1中有一个变量2的时候,我想在外面访问到这个b,很明显,失败了, 因为外部作用域是无法访问内部作用域的;
而闭包要做的事情就是让我们可以访问到变量b!
function fn1() {
var b = 2;
return function() {
return b;
}
} var b = fn1()();
console.log(b); //
显然,我们拿到了b的值,这达到了我们想要的效果, 但是,为什么这样可以拿到b的值呢?
- 在返回的函数中,我们是可以拿到b的值得,因为内部函数是可以访问外部作用域中的任何变量;
- 在返回的函数中,我们把这个值也给返回了,所有fn1中就会一直存在一个b=2的值;
- 一般fn1函数调用后里面的b会被销毁,但是因为我们再它的返回函数中调用了这个b,所以这个b会一直存在于内存中,不会被销毁(这也是闭包的弊端之一,后面详解);
- 这样,我们利用闭包的原理,把b的值给悄悄偷出来了~
从上面这个小例子中我们就可以知道,闭包的作用就是可以让外面不能拿到变量值得地方可以顺利的拿到这些值,虽然外部函数不能拿到内部函数的值是JS对其作用域的一种保护,虽然闭包破坏了这种保护,但却实现了一些通常情况下不能实现的功能。
闭包的一些使用场景:
闭包的使用场景真的是无处不在,虽然有可能你本身并没有察觉到,但是并不能否定它的存在~
在说闭包的一些实际应用的地方之前,先看一段JS中的闭包经典小案例~~
for(var i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i); //结果并不是我们想要的1-5, 而是连续出现5次6
}, i*1000);
}
为什么会是这样呢? 因为作用域问题, 之前说过有闭包就有作用域~ 这里只有个全局作用域, 在全局中只有一个i,所以每次改变i都只是对其赋予一个新的值;
在setTImeout方法执行时, 循环已经结束了,所以每次循环都是i++直到循环完成,变成了6;
因为JS中没有块级变量, 为了让每次i的值我们都可以拿到,所以我们要创造一个作用域用来存储变量i的值, IIFE(函数自执行)可以创造一个块级变量:
for(var i=1; i<=5; i++) {
(function(j) { //2,将i的值赋值给J
setTimeout(function timer() {
console.log(j); //3,j输出的就是每次循环中i的值
}, i*1000);
})(i) //1,每次循环拿到i的值
}
//执行结果 1,2,3,4,5
这样就实现的我们想要的结果,前面说,JS没有块级变量,如果有的话,是不是会更简单,ES5没有,但是ES6中有, 那么用块级变量怎么实现:
for(let i=1; i<=5; i++) { //就是let
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
//1,2,3,4,5
在实际的工作用,我们可能经常解除到的JS模块化,模块化的实现就是利用的JS中的闭包~~~
function moduleFn() {
var name = 'just'; var arr = [1,2,3]; function nameFn() {
console.log(name);
} function arrFn() {
console.log(arr.join("!"));
} return { //产生闭包! 在当前函数中返回了一个对象,这个对象中有nameFn,和arrFn, 这两个函数中又有了moduleFn中的变量。
nameX: nameFn,
arrX: arrFn
}
} var foo = moduleFn(); foo.nameX(); //just
foo.arrX(); //1!2!3 //利用闭包,我们就可以获取作用域中的值了!~~
正是因为闭包的这种特性,在很多场景中可以利用闭包大展身手~ 但是,高手使用大招是需要废除内力的~~而闭包的内力~~~
闭包的弊端:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值
能实现功能固然重要,但是保证性能也同样重要,这其中的取与舍,只有在不断的项目实战中去自由拿捏~
如果能对闭包的使用驾轻就熟,那么JS中的葵花宝典你也就修炼的差不多了~~
下面是一个在网上的闭包终极题目~ 如果你都能够理解,那么恭喜你, 练成了葵花宝典了!~
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
答案自宫之后便会知道!~
JavaScript葵花宝典之闭包的更多相关文章
- 深入理解javascript原型和闭包 (转)
该教程绕开了javascript的一些基本的语法知识,直接讲解javascript中最难理解的两个部分,也是和其他主流面向对象语言区别最大的两个部分--原型和闭包,当然,肯定少不了原型链和作用域链.帮 ...
- 深入理解javascript原型和闭包系列
从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评 ...
- 让你分分钟学会Javascript中的闭包
Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...
- 深入理解javascript原型和闭包(1)——一切都是对象
“一切都是对象”这句话的重点在于如何去理解“对象”这个概念. ——当然,也不是所有的都是对象,值类型就不是对象. 首先咱们还是先看看javascript中一个常用的函数——typeof().typeo ...
- 深入理解javascript原型和闭包(2)——函数和对象的关系
上文(理解javascript原型和作用域系列(1)——一切都是对象)已经提到,函数就是对象的一种,因为通过instanceof函数可以判断. var fn = function () { }; co ...
- 深入理解javascript原型和闭包(3)——prototype原型
既typeof之后的另一位老朋友! prototype也是我们的老朋友,即使不了解的人,也应该都听过它的大名.如果它还是您的新朋友,我估计您也是javascript的新朋友. 在咱们的第一节(深入理解 ...
- 深入理解javascript原型和闭包(4)——隐式原型
注意:本文不是javascript基础教程,如果你没有接触过原型的基本知识,应该先去了解一下,推荐看<javascript高级程序设计(第三版)>第6章:面向对象的程序设计. 上节已经提到 ...
- 深入理解javascript原型和闭包(5)——instanceof
又介绍一个老朋友——instanceof. 对于值类型,你可以通过typeof判断,string/number/boolean都很清楚,但是typeof在判断到引用类型的时候,返回值只有object/ ...
- 深入理解javascript原型和闭包(6)——继承
为何用“继承”为标题,而不用“原型链”? 原型链如果解释清楚了很容易理解,不会与常用的java/C#产生混淆.而“继承”确实常用面向对象语言中最基本的概念,但是java中的继承与javascript中 ...
随机推荐
- Linux下MySQL慢查询分析mysqlsla安装使用
说明: 操作系统:CentOS 5.X 64位 MySQL版本:mysql-5.5.35 MySQL配置文件:/etc/my.cnf MySQL 数据库存放目录:/data/mysql 实现目的:开启 ...
- 6-1 bash脚本编程之四 整数测试及特殊变量
1. exit:退出脚本.可以定义,如 #exit 数字(0-255) 2. 文件测试 -e FILE:测试文件是否存在 -f FILE:测试文件是否为普通 -d FILE:测试指定路径是否为目录 - ...
- 如何安装appium-linux
准备工作: JDK/Android sdk (记得把android sdk的环境变量命名成ANDROID_HOME ) 确保ADB命令可用 git python 和 pip 一,安装node.js和n ...
- 【Swift】Alamofile网络请求数据更新TableView的坑
写这篇BLOG前,有些话不得不提一下,就仅当发发恼骚吧... 今天下午为了一个Alamofire取得数据而更新TableView的问题,查了一下午的百度(360也是见鬼的一样),竟然没有一个简单明了的 ...
- JUnit 4 与 TestNG 对比
原文出处: 付学良的网志 原文出处2: http://www.importnew.com/16270.html -------------------------------------------- ...
- GO语言总结(4)——映射(Map)
上一篇博客介绍了Go语言的数组和切片——GO语言总结(3)——数组和切片,本篇博客介绍Go语言的映射(Map) 映射是一种内置的数据结构,用来保存键值对的无序集合. (1)映射的创建 make ( m ...
- lombok在IntelliJ IDEA下的使用
lombok是一款可以精减java代码.提升开发人员生产效率的辅助工具,利用注解在编译期自动生成setter/getter/toString()/constructor之类的代码.代码越少,意味着出b ...
- FineUI(开源版)v6.0中FState服务器端验证的实现原理
前言 1. FineUI(开源版)是完整开源,最早发起于 2008-04,下载全部源代码:http://fineui.codeplex.com/ 2. 你可以通过捐赠作者来支持FineUI(开源版)的 ...
- hibernate通过注解实现实体和表的映射
参考: 表名的映射: //代表此类参与ORM映射,此注解必须要有 @Entity //代表user这个类映射了一个表user50,如果表名和类名一样,此注解可以省略 @Table(name=" ...
- js兼容性
1.getElementByClassName 在使用原生JavaScript时,获取类选择符时,即使用getElementByClassName,它在Firefox和IE下是不能兼容. Firefo ...