资讯专栏INFORMATION COLUMN

图解作用域及闭包

shiyang6017 / 2044人阅读

摘要:那其实闭包的原因就是外层函数的作用域对象无法释放其实就是一个函数调用会生成的临时作用域图中可看出其实就是在中的匿名函数,所以他的就指向留下的作用域。

引言

网络上关于作用域及闭包的文章很多,自己对于纯理论知识并不能很快的理解,但自己对于图画有很强的记忆和理解能力,因此决定将此知识点以图画的知识表现出来,加深自身理解的同时如果能帮到正在学习的童鞋就再好不过了

下面我以函数的整个生命周期来诉说此部分知识

函数生命周期

先写一下示例代码

var a = 10;
function func(a) {
  var a = 20;
  a++;
  console.log(a);
}
func();
console.log(a);
开始执行程序前

先创建 ECS,ECS 其实就是专门保存正在调用的函数的执行环境的数组,也可以说对象,其实关联数组也就相当于对象。

然后在 ECS 中添加浏览器主程序的执行环境 main

创建全局作用域对象 window

main 执行环境引用 window

定义函数时

原始类型的全局变量会直接存入 window 环境当中,因为函数是引用类型,所以首先用函数名声明全局变量

然后创建函数对象,封装函数定义

函数对象的 scope 属性,指回函数创建时的作用域,意思是,函数执行时如果函数本身提供的变量不能让函数执行完全,那它便会去回它创建时的那个作用域去寻找变量。

函数名后面存入指向函数对象的地址

引用类型在其中只能存储地址,这个在此笔记谈谈值传递中有详细说明

函数调用时

向 ECS 中压入本次函数调用的执行环境元素

创建本次函数调用时使用的函数作用域对象(AO),也就是临时作用域

在 AO 中创建储存所有的局部变量,包括形参变量和函数内用 var 声明的变量

设置 AO 的 parent 属性和引用函数的 scope 属性指向父级作用域对象

函数的执行环境引用 AO

顺着那个箭头,先在 AO 中找变量,也就是局部变量,如果 AO 中没有,再顺着箭头去父级作用域中找

函数调用后

函数的执行环境出栈,AO 释放,AO 中的局部变量一同被释放掉。

我们得知整个结果之后,自然而然那两个 console 的结果也显然意见。

闭包

前面我们提到过,全局变量是可重用但是污染全局,局部变量不会污染全局但是不可重用。

我自己认为闭包就是重用变量又保护变量不被污染的机制,就是为了解决这一情况而生的。

特点

包裹受保护的变量和操作变量的内层函数的外层函数

外层函数要返回内层函数的对象

return function(){..}

直接给全局变量赋值一个内部 function

将内部函数保存在一个对象的属性或数组元素中 return [function function function]return {fun:function(){...}}

调用外层函数,用外部变量接住返回的内层函数对象,形成闭包。

原理

先贴出示例代码

function outer() {
  var num = 1;
  return function() {
    console.log(num++);
  };
}

var getNum = outer();
getNum();
getNum();
num = 1;
getNum();

下面我把闭包形成的原理用画图工具画出来

window 中存入 outer 名并指向 outer 函数对象,getNum 因为声明提前也先将变量名存在 window 中。

getNum = outer() 其实包含 outer 的创建和 getNum 的赋值。

上面的图画的是 outer 函数进行到 var num = 1; ,前面都有说过,不过多重复。

创建了匿名函数,getNum 指向了匿名函数对象,匿名对象的 scope 指向它的父级作用域,也就是 outer 的作用域,那这样就形成了图中的三角关系,此时 outer 执行完毕,离开 ECS 执行环境,outer 的 AO 本也应该随着离开,但是因为这强大的三角关系,强行拉住不让其释放,也就形成了所谓的闭包。

那其实闭包的原因就是:外层函数的作用域对象无法释放

getNum=outer()getNum 其实就是一个函数

调用getNum(),会生成 getNum 的临时作用域,图中可看出,getNum 其实就是在 outer 中的匿名函数,所以他的 parent 就指向 outer 留下的作用域。当他执行 console.log(num++) 的时候,在他的作用域中没有 num 变量他就会顺着作用域链去寻找,最终在 outer 中的作用域中找到 num 并对其进行自加操作。所以当下次调用 getNum 的时候 num 会从 2 开始,不会是一开始的 1

num 不是全局变量,还实现了 num 变量的重复调用。就达到了闭包的目的。

设置 num = 1 只是在 window 对象上添加存储 num 的值,当下次调用 getNum 的时候 js 引擎还会从 getNum 作用域开始顺着作用域链寻找 num,在 outerAO 就会寻找到 num,所以根本不会影响到 window 中的 num,也不会受其影响。因此此段代码输出的结果为 1 2 3

缺点

当然闭包也有其缺点

比普通函数占用更多内存,因为外层函数的作用域对象(AO)始终存在

容易造成内存泄漏

解决办法

将引用内存函数对象的外部变量重置为 null

getNum = null;

getNum 指向 outer 函数对象的那根线就会断掉,三角关系破裂,那函数对象和 outerAO 也会相继被销毁。

觉得文章不错的话还请各位大佬给个 star 鼓励一下 gayhub

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94802.html

相关文章

  • javascript系列--javascript深入浅出图解作用域链和闭包

    摘要:变量对象也是有父作用域的。作用域链的顶端是全局对象。当函数被调用的时候,作用域链就会包含多个作用域对象。当函数要访问时,没有找到,于是沿着作用域链向上查找,在的作用域找到了对应的标示符,就会修改的值。 一、概要 对于闭包的定义(红宝书P178):闭包就是指有权访问另外一个函数的作用域中的变量的函数。 关键点: 1、闭包是一个函数 2、能够访问另外一个函数作用域中的变量 二、闭包特性 对...

    Jensen 评论0 收藏0
  • 结合作用域,执行上下文图解闭包

    摘要:作用域链所谓作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证当前执行环境对符合访问权限的变量和函数的有序访问。当我们在执行函数的时候,需要的变量,在自己的作用域内找不到,便会顺着作用域链往上找,直到找到全局作用域。 一 作用域相关      作用域是一套规则,用来管理引擎如何查找变量。在es5之前,js只有全局作用域及函数作用域。es6引入了块级作用域。但是这个块级别作用域...

    msup 评论0 收藏0
  • 浅谈Javascript闭包作用域及内存泄漏问题

    摘要:将作用域赋值给变量这里的作用域是,而不是将作用域赋值给一个变量闭包返回浏览器中内存泄漏问题大家都知道,闭包会使变量驻留在内存中,这也就导致了内存泄漏。 上一章我们讲了匿名函数和闭包,这次我们来谈谈闭包中作用域this的问题。 大家都知道,this对象是在运行时基于函数的执行环境绑定的,如果this在全局就是[object window],如果在对象内部就是指向这个对象,而闭包却是在运行...

    source 评论0 收藏0
  • 《你不知道的JS》读书笔记---作用域及闭包

    摘要:注此读书笔记只记录本人原先不太理解的内容经过阅读你不知道的后的理解。作用域及闭包基础,代码运行的幕后工作者引擎及编译器。 注:此读书笔记只记录本人原先不太理解的内容经过阅读《你不知道的JS》后的理解。 作用域及闭包基础,JS代码运行的幕后工作者:引擎及编译器。引擎负责JS程序的编译及执行,编译器负责词法分析和代码生成。那么作用域就像一个容器,引擎及编译器都从这里提取东西。 ...

    denson 评论0 收藏0
  • 详解js变量、作用域及内存

    摘要:不是引用类型,无法输出简而言之,堆内存存放引用值,栈内存存放固定类型值。变量的查询在变量的查询中,访问局部变量要比全局变量来得快,因此不需要向上搜索作用域链。 赞助我以写出更好的文章,give me a cup of coffee? 2017最新最全前端面试题 基本类型值有:undefined,NUll,Boolean,Number和String,这些类型分别在内存中占有固定的大小空...

    waltr 评论0 收藏0

发表评论

0条评论

shiyang6017

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<