一、概要

红宝书(P178)对于闭包的定义:闭包就是有权访问另外一个函数作用域中变量的函数

MDN,对于闭包的定义:闭包就是指能够访问自由变量的函数

那么什么是自由变量?自由变量就是在函数中使用,但既不是函数参数arguments,也不是函数的局部变量的变量,就是说另外一个函数作用域中的变量。

文章首发地址:https://www.mwcxs.top/page/573.html

闭包组成?闭包 = 函数 + 函数能够访问的变量

二、分析

举个栗子:

var a = 1;

function foo() {
console.log(a);
} foo();

foo函数可以访问到变量a,但是a并不是foo函数的局部变量,也不是foo函数的参数,所以a就是自由变量,那么函数foo+foo函数可以访问自由变量a不就是构成了一个闭包嘛。

我们再来看一个栗子:

function getOuter(){
var date = '1127';
function getDate(str){
console.log(str + date); //访问外部的date
}
return getDate('今天是:'); //"今天是:1127"
}
getOuter();

其中date不是函数getDate的参数,也不是局部变量,所以date是自由变量。

总结起来就是两点:

1、是一个函数(比如:内部函数从父函数中返回)

2、能够访问上级函数作用域中的变量(哪怕上级函数的上下文已经销毁)

然后我们再来看一个栗子(来自《JavaScript权威指南》)来分析:

var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
} var foo = checkscope(); // foo指向函数f
foo();

这时候需要我们来分析一下这段代码中执行上下文栈执行上下文的变化情况。

简要的分析一下执行过程:

1、进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈;

2、全局执行上下文初始化;

3、执行checkscope函数,创建sheckscope函数执行上下文,checkscope执行上下文被压入执行上下文栈;

4、checkscope执行上下文初始化,创建变量对象,作用域链,this等;

5、checkscope函数执行完毕,checkscope执行上下文从执行上下文栈中弹出;

6、执行f函数,创建f函数执行上下文,f执行上下文压入执行上下文栈;

7、f执行上下文初始化,创建变量对象,作用域链,this等;

8、f函数执行完毕,f函数上下文从执行上下文栈中弹出

那么问题来了,函数f执行的时候,checkscope函数上下文已经被销毁了,那函数f是如何取到scope变量的?

答:函数f执行上下文维护了一盒作用域链,作用域链会指向checkscope作用域,作用域链是一个数组,结构如下:

fContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

所以指向关系:当前作用域-->checkscope作用域-->全局作用域,即使checkscopeContext被销毁了,但是js依然会让checkscopeContext.AO(活动对象)继续存在内存中,f函数依然可以通过f函数的作用域链找到它,这就是闭包的关键

注:AO 表示活动对象,储存了函数的参数、函数内声明的变量等

三、概念

上面分析介绍的是闭包的实践角度,其实闭包有很多种介绍,说法不一。

汤姆大叔在关于闭包对的文章的定义。ECMAScript中,闭包指的是:

1、从理论角度:所有的函数,因为它们都是创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就是相当于再访问自由变量,这个时候使用最外层的作用域。

2、从实践角度:以下函数才算闭包:

(1)即使创建它的上下文已经摧毁了,它依然存在(比如,内部函数中返回)

(2)在代码中引用了自由变量

四、面试必刷的题

var data = [];

for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
} data[0]();
data[1]();
data[2]();

如果知道是闭包,答案很明显,都是3。

循环结束后,全局执行上下文的VO是

globalContext = {
VO: {
data: [...],
i: 3
}
}

执行data[0]函数的时候,data[0]函数的作用域链为:

data[0]Context = {
Scope: [AO, globalContext.VO]
}

由于其自身没有i变量,就会向上查找,所有从全局上下文查找到i为3,data[1] 和 data[2] 是一样的。

注:

1、for 循环不会创建一个执行上下文,所有不会有 AO, i 的值是在全局对象的 AO 中,代码初始的时候为:

globalContext = {
VO: {
data: [...],
i: 0
}
}

2、data[0] 是一个函数名,data[0]() 表示执行这个函数,当执行函数的时候,循环已经走完了;

3、函数能够读取到的值跟函数定义的位置有关,跟执行的位置无关。

解决办法

改成闭包,方法就是data[i]返回一个函数,并且访问变量i

var data = [];

for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
} data[0](); // 0
data[1](); // 1
data[2](); // 2

循环结束后的全局执行上下文没有变化。

执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

data[0]Context = {
Scope: [AO, 匿名函数Context.AO, globalContext.VO]
}

匿名函数执行上下文的AO为:

匿名函数Context = {
AO: {
arguments: {
0: 0,
length: 1
},
i: 0
}
}

data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是0

五、思考题

把for循环中的var i = 0,改成let i = 0。结果是什么,为什么?

var data = [];

for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
} data[0]();
data[1]();
data[2]();

或者这样:

var data = [];

var _loop = function _loop(i) {
data[i] = function () {
console.log(i);
};
}; for (var i = 0; i < 3; i++) {
_loop(i);
} data[0]();
data[1]();
data[2]();

六、参考

https://github.com/mqyqingfeng/Blog/issues/9

javascript深入理解-从作用域链理解闭包的更多相关文章

  1. 【进阶2-2期】JavaScript深入之从作用域链理解闭包(转)

    这是我在公众号(高级前端进阶)看到的文章,现在做笔记   https://github.com/yygmind/blog/issues/18 红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一 ...

  2. javaScript执行环境、作用域链与闭包

    一.执行环境 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为:每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象 ...

  3. javascript 执行环境,作用域链和闭包

    首先看下这条语句: (function($) {…})(jQuery): 1.原理: function(arg){…}这就定义了一个匿名函数,参数为arg 而调用函数时,是在函数后面写上括号和实参的, ...

  4. 个人理解的javascript作用域链与闭包

    闭包引入的前提个人理解是为从外部读取局部变量,正常情况下,这是办不到的.简单的闭包举例如下: function f1(){ n=100; function f2(){ alert(n); } retu ...

  5. 理解js中的作用域,作用域链以及闭包

    作用域变量作用域的类型:全局变量和局部变量全局作用域对于最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的 <script> var outerVar = " ...

  6. 1--面试总结-js深入理解,对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This

    参考一手资料:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/中文翻译版本:https://zhuanlan.zhihu.com/p ...

  7. 深入javascript作用域链到闭包

    我之前用过闭包,用过this,虽然很多时候知道是这么一回事,但是确实理解上还不够深入.再一次看javascript高级程序设计这本书时,发现一起很多疑难问题竟然都懂了,所以总结一下一些理解,难免有错, ...

  8. 《浏览器工作原理与实践》<10>作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?

    在上一篇文章中我们讲到了什么是作用域,以及 ES6 是如何通过变量环境和词法环境来同时支持变量提升和块级作用域,在最后我们也提到了如何通过词法环境和变量环境来查找变量,这其中就涉及到作用域链的概念. ...

  9. 在chrome开发者工具中观察函数调用栈、作用域链与闭包

    在chrome开发者工具中观察函数调用栈.作用域链与闭包 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量 ...

随机推荐

  1. 八爪鱼在哪里设置xpath

    分享:35个做好的爬虫规则+160篇图文教程汇总 一般在八爪鱼中,获取网页上某个元素的XPATH有以下几种方式:一.在内置浏览器中点选的操作,八爪鱼自动识别XPATH.但是有时候,自动识别的可能不准确 ...

  2. 对cordova插件配置文件plugin.xml的理解

    1.配置文件表头包括了插件id,是用于唯一标识插件的.同时插件配置了一个插件名称. 2.这个文件从工作机制,也就是js代码一直到native的java插件代码工作分成两个流程.第一个流程是从代码到插件 ...

  3. 异步任务spring @Async注解源码解析

    1.引子 开启异步任务使用方法: 1).方法上加@Async注解 2).启动类或者配置类上@EnableAsync 2.源码解析 虽然spring5已经出来了,但是我们还是使用的spring4,本文就 ...

  4. python 面向对象终极进阶之开发流程

    好了,你现在会了面向对象的各种语法了,  但是你会发现很多同学都是学会了面向对象的语法,却依然写不出面向对象的程序,原因是什么呢?原因就是因为你还没掌握一门面向对象设计利器, 此刻有经验的人可能会想到 ...

  5. Python_替换当前目录下文件类型

    ''' 将当前目录的所有扩展名为html的文件重命名为扩展名为htm的文件 方法一 ''' import os file_list=os.listdir('.') for filename in fi ...

  6. 一种轻量级的微信小程序日志监控的方法

    今天一个活动要写个H5,明天一个功能要用小程序,天天都在写bug.用户反馈小程序用起来有问题还特么还不知道到底出了啥bug,反馈多了,老板要扣工资了!看来挖了太多坑不填也不行,程序异常还是要主动追踪, ...

  7. Maven项目打包为jar的几种方式

    这里收集整理下以往打包MAVEN项目为JAR包的各种方式 直接打包,不打包依赖包 直接打包,不打包依赖包,仅打包出项目中的代码到JAR包中.在POM中添加如下plugin即可,随后执行maven in ...

  8. SHELL脚本--expr命令全解

    bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html expr命令可以实现数值运算.数值或字符串比较.字符串匹配.字 ...

  9. SSM-SpringMVC-31:SpringMVC中利用hibernate-validator做后台校验

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 校验有三种:前台页面校验(例如js和h5),后台校验,数据库校验 但是一般能不用数据库校验就不用数据库校验,因 ...

  10. SSM-Spring-22:Spring+Mybatis+JavaWeb的整合

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 众所周知,框架Spring来整合别的框架,但是Mybatis出现的晚,Spring就没有给他提供支持,那怎么办 ...