一、概要

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

关键点:

1、闭包是一个函数

2、能够访问另外一个函数作用域中的变量

文章首发地址于sau交流学习社区https://www.mwcxs.top/page/571.html

二、闭包特性

对于闭包有下面三个特性:

1、闭包可以访问当前函数以外的变量

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

 

2、即使外部函数已经返回,闭包仍能访问外部函数定义的变量

function getOuter(){
var date = '815';
function getDate(str){
console.log(str + date); //访问外部的date
}
return getDate; //外部函数返回
}
var today = getOuter();
today('今天是:'); //"今天是:815"
today('明天不是:'); //"明天不是:815"

 

3、闭包可以更新外部变量的值

function updateCount(){
var count = 0;
function getCount(val){
count = val;
console.log(count);
}
return getCount; //外部函数返回
}
var count = updateCount();
count(815); //815
count(816); //816

三、作用域链

javascript中有一个执行上下文(execution context)的概念,它定义了变量或函数有权访问的其他数据,决定它们各自的行为。每一个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。你可以把它当做Javascript的一个普通对象,但是你只能修改它的属性,却不能引用它。

变量对象也是有父作用域的。

作用域链定义:当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不再存在父作用域了,这就是作用域链。

作用域链和原型继承有点类似,但又有点小区别:如果去查找一个普通对象的属性时,在当前对象和其原型中都找不到时,会返回undefined;但查找的属性在作用域链中不存在的话就会抛出ReferenceError。

作用域链的顶端是全局对象。对于全局环境中的代码,作用域链只包含一个元素:全局对象。所以,在全局环境中定义变量的时候,它们就会被定义到全局对象中。当函数被调用的时候,作用域链就会包含多个作用域对象。

四、全局环境

关于作用域链讲得略多(红皮书上有关于作用域及执行环境的详细解释),看一个简单地例子:

// my_script.js
"use strict";
var foo = 1;
var bar = 2;

在全局环境中,创建了两个简单地变量。如前面所说,此时变量对象是全局对象:

执行上述代码,my_script.js本身会形成一个执行环境,以及它所引用的变量对象。

4.1无嵌套函数

// my_script.js
"use strict"; var foo = 1;
var bar = 2; function myFunc() { var a = 1;
var b = 2;
var foo = 3;
console.log("inside myFunc"); } console.log("outside");
myFunc();

定义时:当myFunc被定义的时候,myFunc的标识符(identifier)就被加到了全局对象中,这个标识符所引用的是一个函数对象(myFunc function object)。

内部属性[[scope]]指向当前的作用域对象,也就是函数的标识符被创建的时候,我们所能够直接访问的那个作用域对象(即全局对象)。

myFunc所引用的函数对象,其本身不仅仅含有函数的代码,并且还含有指向其被创建的时候的作用域对象。

调用时:当myFunc函数被调用的时候,一个新的作用域对象被创建了。新的作用域对象中包含myFunc函数所定义的本地变量,以及其参数(arguments)。这个新的作用域对象的父作用域对象就是在运行myFunc时能直接访问的那个作用域对象(即全局对象)。

4.2嵌套函数

当函数返回没有被引用的时候,就会被垃圾回收器回收。但是对于闭包,即使外部函数返回了,函数对象仍会引用它被创建时的作用域对象。

"use strict";
function createCounter(initial) {
var counter = initial; function increment(value) {
counter += value;
} function get() {
return counter;
} return {
increment: increment,
get: get
};
} var myCounter = createCounter(100);
console.log(myCounter.get()); // 返回 100 myCounter.increment(5);
console.log(myCounter.get()); // 返回 105

当调用 createCounter(100) 时,内嵌函数increment和get都有指向createCounter(100) scope的引用。假设createCounter(100)没有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。

但是createCounter(100)实际上是有返回值的,并且返回值被存储在了myCounter中,所以对象之间的引用关系如下图:

即使createCounter(100)已经返回,但是其作用域仍在,并且只能被内联函数访问。可以通过调用myCounter.increment() 或 myCounter.get()来直接访问createCounter(100)的作用域。

当myCounter.increment() 或 myCounter.get()被调用时,新的作用域对象会被创建,并且该作用域对象的父作用域对象会是当前可以直接访问的作用域对象。

调用get()时,当执行到return counter时,在get()所在的作用域并没有找到对应的标示符,就会沿着作用域链往上找,直到找到变量counter,然后返回该变量。

单独调用increment(5)时,参数value保存在当前的作用域对象。当函数要访问counter时,没有找到,于是沿着作用域链向上查找,在createCounter(100)的作用域找到了对应的标示符,increment()就会修改counter的值。除此之外,没有其他方式来修改这个变量。闭包的强大也在于此,能够存贮私有数据。

创建两个函数:myCounter1myCounter2

//my_script.js
"use strict";
function createCounter(initial) {
/* ... see the code from previous example ... */
} //-- create counter objects
var myCounter1 = createCounter(100);
var myCounter2 = createCounter(200);

关系图如下

myCounter1.increment和myCounter2.increment的函数对象拥有着一样的代码以及一样的属性值(name,length等等),但是它们的[[scope]]指向的是不一样的作用域对象。

五、参考

https://github.com/dwqs/blog/issues/18

javascript深入浅出图解作用域链和闭包的更多相关文章

  1. JS详细图解作用域链与闭包

    JS详细图解作用域链与闭包 攻克闭包难题 初学JavaScript的时候,我在学习闭包上,走了很多弯路.而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战. 闭包有多重要?如果你 ...

  2. 前端高质量知识(四)-JS详细图解作用域链与闭包

    攻克闭包难题 初学JavaScript的时候,我在学习闭包上,走了很多弯路.而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战. 闭包有多重要?如果你是初入前端的朋友,我没有办法 ...

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

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

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

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

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

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

  6. javascript的关键所在---作用域链

    javascript的关键所在---作用域链 javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript ...

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

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

  8. 理解JavaScript中的作用域链

    理解了作用域链,闭包就不难理解了,所以本文主要谈一谈我对作用域链的理解.   关于JavaScript中变量的作用域,全局变量在程序中始终都有定义.局部变量在声明它的函数体内以及其内部所嵌套的函数内始 ...

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

    在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象,闭包,this等关键信息的变化.因此,断点调试对于快 ...

随机推荐

  1. C语言代码

    //计算1/1+1/ (1+2) +1/ (1+2+3) +…+1/(1+2+…n)的值,要求小数点后保留6位,n从键盘输入 #include<stdio.h> main(){ ; ; i ...

  2. kaggle入门项目:Titanic存亡预测(四)模型拟合

    原kaggle比赛地址:https://www.kaggle.com/c/titanic 原kernel地址:A Data Science Framework: To Achieve 99% Accu ...

  3. flash builder 4.6与myecilpse 10.7集成

    一.在flash builder 4.0以后就没有单独提供插件版的flash builder了,因此必须先安装完整版的flash builder,再进行插件集成. 二.集成过程比较简单但也有几个要注意 ...

  4. 类的父类object的一些属性、方法

    # class Test: # """文档字符串""" # name = 'scolia' # # print(Test.__doc__) ...

  5. python之文件操作(基础)

    文件操作作为python基础中的重点,必须要掌握. 1.默认我们在本地电脑E盘新建wp.txt文件进行测试,文件内容如下设置. 2.进行代码编写: f=open("E://wp.txt&qu ...

  6. UML类图10分钟快速入门 - From 圣杰

    虚线箭头指向依赖: 实线箭头指向关联: 虚线三角指向接口: 实线三角指向父类: 空心菱形能分离而独立存在,是聚合: 实心菱形精密关联不可分,是组合: 原文作者:圣杰 原文地址:http://www.j ...

  7. Python_字符串连接

    #join() 与split()相反,join()方法用来将列表中多个字符串进行连接,并在相邻两个字符串之间插入指定字符 li=['apple','peach','banana','pear'] se ...

  8. nginx常用配置系列-反向代理

    接上篇,反向代理的原理与用途很多地方有讲,用文字说再多可能也表达不清楚,下面贴一张拓扑图,介绍下什么叫反向代理 以上图有两种情景 1. 访问者的客户端是 local ,要访问baidu的服务器,bai ...

  9. POI处理Excel中各种日期格式问题

    前不久写过一篇随笔<EXCEL解析之终极方法WorkbookFactory>,提到使用WorkbookFactory来处理Excel文件数据,最近发现一个问题就是这个办法不能很好的处理各种 ...

  10. 【转】spring cloud eureka 参数配置

    eureka.client.registry-fetch-interval-seconds 表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅 ...