js----深入理解闭包
闭包算是js里面比较不容易理解的点,尤其是对于没有编程基础的人来说。
其实闭包要注意的就那么几条,如果你都明白了那么征服它并不是什么难事儿。下面就让我们来谈一谈闭包的一些基本原理。
闭包的概念
一个闭包就是一个函数和被创建的函数中的作用域对象的组合。(作用域对象下面会说)
通俗一点的就是 “ 只要一个函数中嵌套了一个或多个函数,那么我们就可以称它们构成了闭包。 ”
类似这样:
function A() {
var i = 5;
return function() {
console.log('i = '+i);
}
} var a = A();
a(); // i = 5
闭包的原理
1、外部函数的局部变量若会被闭包函数调用就不会在外部函数执行完毕之后立即被回收。
我们知道,不管什么语言,操作系统都会存在一个垃圾回收机制,将多余分配的空间回收掉以便减小内存。而一个函数的生命周期的是从调用它开始的,在函数调用完毕的时候函数内部的局部变量等都会被回收机制回收。
我们拿上述例子来说,当我们的外部函数A调用完毕时,A中的局部变量i按理说就会被操作系统回收而不存在,但是当我们用了闭包结果就不是那样了,i并不会被回收。试想,如果i被回收了那么返回的函数里面岂不是就是打印undefined了?
i为什么没有被回收?
在javascript执行一个函数的时候都会创建一个作用域对象,将函数中的局部变量(函数的形参也是局部变量)保存进去,伴随着那些传入函数的变量一起被初始化。
所以当调用A的时候就创建了一个作用域对象,我们姑且称之为Aa,那么这个Aa应该是这样的: Aa = { i: 5 }; 在A函数返回一个函数之后,A执行完毕。Aa对象本应该被回收,但是由于返回的函数使用了Aa的属性i,所以返回的函数保存了一个指向Aa的引用,所以Aa不会被回收。
所以理解作用域对象,就能理解为什么函数的局部变量在遇到闭包的时候不会在函数调用完毕时立即被回收了。
再来个例子:
function A(age) {
var name = 'wind';
var sayHello = function() {
console.log('hello, '+name+', you are '+age+' years old!');
};
return sayHello;
}
var wind = A(20);
wind(); // hello, wind, you are 20 years old!
你能说出的它的作用域对象Ww是什么吗?
Ww = { age: 20, name: 'wind' };
2、每调用一次外部函数就产生一个新的闭包,以前的闭包依旧存在且互不影响。
3、同一个闭包会保留上一次的状态,当它被再次调用时会在上一次的基础上进行。
每调用一次外部函数产生的作用域对象都不一样,你可以这样想,上面的例子,你每次传入的参数age不一样,所以就每次生成的对象不一样。
每调用一次外部函数那么就会生成一个新的作用域对象。
function A() {
var num = 42;
return function() { console.log(num++); }
}
var a = A();
a(); //
a(); // var b = A(); // 重新调用A(),形成新闭包
b(); // 42
这个代码让我们发现了两个事情,一、当我们连续调用两次a();,num会在原基础上自加。说明同一个闭包会保留上一次的状态,当它被再次调用时会在上一次的基础上进行。 二、我们的b();的结果为42,说明它是一个新的闭包,并且不受其他闭包的影响。
我们可以这样想,就好比我们吹肥皂泡一样,我每次吹一下(调用外部函数),就会产生一个新的肥皂泡(闭包),多个肥皂泡可以同时存在且两个肥皂泡之间不会相互影响。
4、在外部函数中存在的多个函数 “ 同生共死 ”
以下三个函数被同时声明并且都可以对作用域对象的属性(局部变量)进行访问与操作。
var fun1, fun2, fun3;
function A() {
var num = 42;
fun1 = function() { console.log(num); }
fun2 = function() { num++; }
fun3 = function() { num--; }
} A();
fun1(); //
fun2();
fun2();
fun1(); //
fun3();
fun1(); // var old = fun1; A();
fun1(); //
old(); // 43 上一个闭包的fun1()
由于函数不能有多个返回值,所以我用了全局变量。我们再次可以看出在我们第二次调用A()时产生了一个新的闭包。
当闭包遇到循环变量
当我们说到闭包就不得不说当闭包遇到循环变量这一种情况,看如下代码:
function buildArr(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
var item = 'item' + i;
result.push( function() {console.log(item + ' ' + arr[i])} );
}
return result;
} var fnlist = buildArr([1,2,3]);
fnlist[0](); // item2 undefined
fnlist[1](); // item2 undefined
fnlist[2](); // item2 undefined
怎么会这样呢?我们预想的三个输出应该是 item0 1, item1 2, item2 3。为什么结果却是返回的result数组里面存储了三个 item2 undefined ?
我们上文中提到过两点,1、闭包在返回的时候对作用域对象有一个引用。2、在外部函数中存在的多个内部函数 “ 同生共死 ”。
我们的for循环为外部函数创建了多个“同生共死”的内部函数,它们都共享一个环境,而当result数组返回的时候,所有的内部函数都引用了同一个作用域对象:
var bArr = {
item: 'item2',
i: 3,
arr: [1,2,3]
}
为什么作用域对象是这样的?拿我们上面的例子来说,当循环全部结束的时候作用域对象中的属性 i 正好是i++之后的3,而arr[3]是没有值的,所以为undefined。
有朋友会疑惑:为什么item的值是item2,难道不应该是item3吗?
注意,在最后一次循环的时候也就是 i = 2的时候,item的值为item2,当 i++,i = 3循环条件不满足循环结束,此时的item的值已经被保存下来了,所以此时的arr[i]为arr[3],而item为item2。这样能理解吗?
如果我们将代码改成这样那就说得通了:
function buildArr(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
result.push( function() {console.log('item' + i + ' ' + arr[i])} );
}
return result;
} var fnlist = buildArr([1,2,3]);
fnlist[1](); // item3 undefined
那么问题来了,如何改正呢?且看代码:
function buildArr(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
result.push( (function(n) {
return function() {
var item = 'item' + n;
console.log(item + ' ' + arr[n]);
}
})(i));
}
return result;
} var fnlist = buildArr([1,2,3]);
fnlist[0](); // item0 1
fnlist[1](); // item1 2
fnlist[2](); // item2 3
我们可以用一个自执行函数将i绑定,这样i的每一个状态都会被存储,答案就和我们预期的一样了。
所以以后在使用闭包的时候遇到循环变量我们要习惯性的想到用自执行函数来绑定它。
=========================3月14日更新======================================================
关于上面的问题还有一个更简单的方法:
function buildArr(arr) {
var result = [];
for (let i = 0; i < arr.length; i++) {
let item = 'item' + i;
result.push( function() {console.log(item + ' ' + arr[i])} );
}
return result;
} var fnlist = buildArr([1,2,3]);
fnlist[0](); // item0 1
这里使用了let代替var,let的好处是可以“模拟创建”块作用域,点到为止,有兴趣的朋友可以自行深入了解let。
以上就是我对闭包的理解,如果有什么意见或建议希望我们能在评论区多多交流。感谢,共勉。
js----深入理解闭包的更多相关文章
- js深入理解"闭包"
一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量 ...
- js中的闭包之我理解
闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...
- 理解闭包的微观世界和JS垃圾回收机制
function a() { ; function b() { alert(++i); } return b; } var c = a(); c(); 一.闭包的微观世界 如果要更加深入的了解闭包以及 ...
- js 理解闭包
学习Javascript闭包(Closure) 引用: 阮一峰 http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures. ...
- js中的闭包理解一
闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...
- 【学习笔记】深入理解js原型和闭包系列学习笔记——精华
深入理解js原型和闭包笔记: 1.“一切皆是对象”,对象是属性的集合. 丨 函数也是对象,但是使用typeof时为什么函数返回function而 丨 不是object呢,js为何要对函数做这样的区分 ...
- 【学习笔记】深入理解js原型和闭包(18)——补充:上下文环境和作用域的关系
本系列用了大量的篇幅讲解了上下文环境和作用域,有些人反映这两个是一回儿事.本文就用一个小例子来说明一下,作用域和上下文环境绝对不是一回事儿. 再说明之前,咱们先用简单的语言来概括一下这两个的区别. 0 ...
- 【学习笔记】深入理解js原型和闭包(17)——补this
本文对<深入理解js原型和闭包(10)——this>一篇进行补充,原文链接:https://www.cnblogs.com/lauzhishuai/p/10078307.html 原文中, ...
- 【学习笔记】深入理解js原型和闭包(16)——完结
之前一共用15篇文章,把javascript的原型和闭包讲解了一下. 首先,javascript本来就“不容易学”.不是说它有多难,而是学习它的人,往往都是在学会了其他语言之后,又学javascrip ...
- 【学习笔记】深入理解js原型和闭包(15)——闭包
前面提到的上下文环境和作用域的知识,除了了解这些知识之外,还是理解闭包的基础. 至于“闭包”这个词的概念的文字描述,确实不好解释,我看过很多遍,但是现在还是记不住. 但是你只需要知道应用的两种情况即可 ...
随机推荐
- C++11实现Qt的信号槽机制
概述 Qt的信号槽机制是Qt的核心机制,按钮点击的响应.线程间通信等都是通过信号槽来实现的,boost里也有信号槽,但和Qt提供的使用接口很不一样,本文主要是用C++11来实现一个简单的信号槽,该信号 ...
- PHP.12-PHP-设计文件上传类
设计文件上传类 [PHP参数详解]{文件上传} ********************** *#构造方法编写 ********************** 此种传参方法规定必须用户必须按响应位置输入 ...
- Triangular Sums
描述 The nth Triangular number, T(n) = 1 + … + n, is the sum of the first n integers. It is the number ...
- jsp包含的讲解
<%@page language="java" import="java.util.*" pageEncoding="UTF-8"%& ...
- SQL SERVER – Count Duplicate Records – Rows
SELECT YourColumn, COUNT(*) TotalCount FROM YourTable GROUP BY YourColumn HAVING COUNT(*) > 1 ORD ...
- 基于 Equinox 的 OSGi Console 的研究和探索
自定制 OSGi Console 进行组建和服务生命周期管理模块化编程的好处已经很好地被理解了约 40 年,但在 OSGi 之前,开发人员不得不自己发明模块化设计和系统.随着软件应用体系结构的不断发展 ...
- oracle日志总结
①. Oracle日志分类: Alert log files--警报日志 , redo log 重做日志(记录数据库的更改,Trace files--跟踪日志(用户和进程) Oracle的重做日志(r ...
- iOS 设计模式之单例
设计模式:单例 一. 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并 ...
- [UML]UML之开篇
前言 大学时,学习软件工程时,学到了UML,由于当时接触项目太少,认识不清,再加上毕业后一直忙于coding,很少有时间去真正的认识和学习UML. 现在感觉有必要去回头看看这些东西啦. 什么是UML ...
- Kinect For Windows V2开发日志八:侦测、追踪人体骨架
简介 Kinect一个很强大的功能就是它可以侦测到人体的骨骼信息并追踪,在Kinect V2的SDK 2.0中,它最多可以同时获取到6个人.每个人25个关节点的信息,并且通过深度摄像头,可以同时获取到 ...