闭包算是javascript中一个比较难理解的概念,想要深入理解闭包的原理,首先需要搞清楚其他几个概念:

一、栈内存和堆内存

学过C/C++的同学可能知道,计算机系统将内存分为栈和堆两部分(大学的基础课,忘掉的赶紧重新捡起来)。

栈内存(连续的存储空间,类似数据结构中的栈):主要用来存放数值、字符、内存地址等小数据

堆内存(散列的存储空间,类似数据结构中的链表):存放可以动态变化的大数据

二、基本类型和引用类型

JavaScript将变量分为两种类型:

基本类型:Number、String、Boolean 、undefined、null(值被保存在栈内存中)

引用类型:Object、Array、function(具体内容被保存在堆内存中,在栈内存中仅保存堆内存的地址)

如上图,当在程序中在执行中有如下情况:

1、声明变量a为基本类型时,直接在栈内存中保存它的值为100;

2、当将a赋值给b时,b在栈内存中新建空间,将a的值复制过来

(注:之后a和b就没有关系了,再改变a或b的值,不影响另外一个,它们是独立的)

3、声明变量p1为引用类型时,将p1的内容保存在堆内存中,并将堆内存的物理地址保存在栈内存中

4、当将p1赋值给p2时,p2在栈内存中新建空间,仅复制堆内存的物理地址

(注:p1和p2中都保存的是指向堆内存的地址,即指的是同一个对象,当修改p1对象的属性后,p2对象的属性同时被修改)

另外,在计算机语言中还有一些很重要的特性:

1、修改基本类型的值,实际上是新建空间存一个新值,然后将变量名指向新的空间(旧值依然存在栈内存中,只是缺少变量名指向它)

2、删除引用类型,其实并不删除堆内存中的内容,仅删除了栈内存中的物理地址(对象的内容依然存在堆内存中,只是缺少了地址的指向)

(注:计算机关于内存的管理,跟我们正常想到的不一样,例如硬盘恢复就是利用这个原理,为删除的内容重新建立一个指向即可访问)

二、变量作用域

javascript中变量又分为全局变量和局部变量

全局变量:在全局环境中声明的变量

局部变量:在函数中声明的变量

当函数在执行时,会创建一个封闭的执行期上下文环境,函数内部声明的变量仅可在函数内部使用,外部无法访问,而全局变量则在任何地方都可以使用

三、预编译

JavaScript的运行为三步:语法分析》预编译》解释执行

1、语法分析:通篇扫描js文件,检查是否有低级语法错误

2、预编译四部曲:(发生在解释执行的前一刻)
  a、创建AO对象(执行期上下文对象,全局为GO)
  b、将形参和变量声明作为AO对象的属性名,值为undefined
  c、将实参值传递给形参,即赋值给AO对象对应属性名
  d、将函数声明为AO对象的方法名,值为函数体

3、解释执行:解释一行,执行一行。

function test(a){
var b=1;
function c(){}
}
test(2);
/* 函数预编译四部曲(函数执行前一刻,不执行不会预编译),全局预编译同理
* 1---testAO{}
* 2---testAO{a:undefined,b:undefined}
* 3---testAO{a:2,b:undefined}
* 4---testAO{a:2,b:1,c:function(){}}
*/

四、作用域链

每个JavaScript函数都是一个对象,对象中有些属性可以访问(比如name),有些属性不可以访问(比如[[scope]]仅供js引擎使用)

[[scope]]用来存储了运行期上下文对象的集合(即作用域链),作用域链中除了自身创建的AO对象外,还包括了所有父级运行期上下文对象(AO)

function a(){
function b(){
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();

当b执行完成后,b的AO要被销毁,即b的[[scope]]第0位将被置空,如果再次执行b,将新建一个新的AO将其地址存到第0位,

当a也执行完成后,a的AO要被销毁,即a的[[scope]]第0位将被置空,同时a的AO中存着b,b也将被一同销毁

在了解如上这些概念后,我们再来看下面这个经典的闭包,你会有一个全新的认识

function a(){
var b=123;
function c(){
console.log(b+=1);
}
return c;
}
var d=a();
d();

当这段代码在执行时的顺序如下:

1、预编译全局,生成执行上下文对象GO{d:undefined,a:function(){}}

2、定义a函数,将a函数的[[scope]]属性设置为{0:GO}

3、预编译a函数,生成a的执行上下文对象aAO{b:undefined,c:function(){}},修改a函数的[[scope]]属性为{0:aAO,1:GO}

4、执行a函数,给aAO的属性赋值{b:123,c:function(){}}

5、定义c函数,将c函数的[[scope]]属性设置为{0:aAO,1:GO},并将c返回给d

6、a函数执行完毕,销毁[[scope]]属性第0位对aAO对象的引用

7、执行d函数(等于执行c函数)之前,先预编译生成c的执行上下文对象cAO{},修改c函数的[[scope]]属性为{0:cAO,1:aAO,2:GO}

8、执行c函数,b变量在cAO中没有,到[[scope]]属性中的下一位aAO中获取

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan

JavaScript闭包的深入理解的更多相关文章

  1. JavaScript闭包的一些理解

    原文:JavaScript闭包的一些理解 简单一点的说:闭包就是能够读取其他函数内部变量的函数.那如何实现读取其它函数内部变量呢,大家都知道在JavaScript中内部函数可以访问其父函数中的变量,那 ...

  2. 对JavaScript闭包和原型理解

    最近在学js脚本的一些东西觉得里面有2个知识点比较难理解所以做了如下总结. 1.闭包 简单的理解:一个函数a ,内部有个函数b,那么这个函数b当被作为a函数的返回值得时候被外部的全局变量引用了,那么这 ...

  3. javascript 闭包最简单理解

    首先说3点与闭包有关系的东西. 一.变量的作用域 变量的作用域不难理解. 1.函数内部可以访问函数外部的变量,而函数外部不能访问函数内部的变量. 2.如果在函数内定义变量的时候,不加var,那么是全局 ...

  4. 我也谈javascript闭包的原理理解

    参考原文:http://www.oschina.net/question/28_41112 前言:还是一篇入门文章.Javascript中有几个非常重要的语言特性——对象.原型继承.闭包.其中闭包 对 ...

  5. JavaScript 闭包(个人理解)

    当function里嵌套function时,内部的function可以访问外部function里的变量.但这不是闭包 function foo(x) { var tmp = 3; function b ...

  6. JavaScript闭包函数的理解

    闭包就是一个函数能够访问其函数外部作用域中的变量,即在外面可以调用函数中的函数的变量,其实他就是将函数内外部连接起来的桥梁 闭包三大特点: 1. 函数嵌套函数 2. 内部函数可以访问外部函数的变量 3 ...

  7. 关于JavaScript闭包的粗浅理解

    在JavaScript中,使用var创建变量,会创建全局变量或局部变量. 只有在非函数内创建的变量,才是全局变量,该变量可以在任何地方被读取. 而在函数内创建变量时,只有在函数内部才可读取.在函数外部 ...

  8. JavaScript 闭包的详细分享(三种创建方式)(附小实例)

    JavaScript闭包的详细理解 一.原理:闭包函数--指有权访问私有函数里面的变量和对象还有方法等:通俗的讲就是突破私有函数的作用域,让函数外面能够使用函数里面的变量及方法. 1.第一种创建方式 ...

  9. JavaScript闭包理解【关键字:普通函数、闭包、解决获取元素标签索引】

    以前总觉得闭包很抽象,很难理解,所以百度一下"闭包"概览,百度的解释是:“闭包是指可以包含自由(未绑定到特定对象)变量的代码块:这些变量不是在这个代码块内或者任何全局上下文中定义的 ...

随机推荐

  1. JAVA提高二十:CopyOnWriteArrayList&CopyOnWriteArraySet&ConcurrentHashMap介绍

    前面我们将java集合类的大部分类都进行了深入分析,但我们会发现一个共性问题就是并发的问题,那么如何解决呢?我们前面基本都是通过Collections的一个工具类来进行的解决,但实际大部分使用中人们普 ...

  2. 【批处理】shift用法举例

    @echo off set sum=0 call :sub sum 1 2 3 4 echo sum=%sum% pause :sub set /a %1=%1+%2 shift /2 if not ...

  3. Scrapy爬虫实例——校花网

    学习爬虫有一段时间了,今天使用Scrapy框架将校花网的图片爬取到本地.Scrapy爬虫框架相对于使用requests库进行网页的爬取,拥有更高的性能. Scrapy官方定义:Scrapy是用于抓取网 ...

  4. iOS Swift--UIImageView UIImage

    1.UIImageView + UIImage    Demo import UIKit class UIImageViewViewController: UIViewController { var ...

  5. git正确的删除远程仓库的文件并用.gitignore忽略提交此文件

    我向远程仓库提交了如下文件src/ pom.xml target/ WebContent/,发现没必要提交target目录. 于是做了如下操作: git rm -r --cached target g ...

  6. 详细的DedeCMS(织梦)目录权限安全设置教程

    一.目录权限根据统计,绝大部分网站的攻击都在根目录开始的,因此,栏目目录不能设置在根目录.DEDECMS部署完成后,重点目录设置如下:1)将install删除.2) data.templets.upl ...

  7. KD树

    k-d树 在计算机科学里,k-d树( k-维树的缩写)是在k维欧几里德空间组织点的数据结构.k-d树可以使用在多种应用场合,如多维键值搜索(例:范围搜寻及最邻近搜索).k-d树是空间二分树(Binar ...

  8. spring boot 遇到 supported setting property http://xml.org/sax/properties/lexical-handler

    解决链接:http://apache-fop.1065347.n5.nabble.com/org-xml-sax-SAXNotSupportedException-thrown-by-FOP-td11 ...

  9. 访问vm中centos的web站点

    vm网络连接设置成NAT 需要把centos设置成静态IP 再不行,记得把centos的防火墙先关闭

  10. Kafka的基本概念与安装指南(单机+集群同步)

    最近在搞spark streaming,很自然的前端对接的就是kafka.不过在kafka的使用中还是遇到一些问题,比如mirrormaker莫名其妙的丢失数据[原因稍后再说],消费数据offset错 ...