详解JavaScript中的闭包是如何产生的

目录
  • 闭包的产生
  • 多个内部函数共享一个闭包对象
  • 结尾

这次从内存管理的角度来看看,闭包是怎么产生的。

我们知道,在调用函数时,其实会产生临时的 调用栈。这些调用栈保存的是 执行上下本,并实际保存在 栈内存 中。

每执行一个函数,函数内的局部临时变量会临时保存起来。如果此时函数又调用了另一个函数,另一个函数下的局部变量也要保存下来,就这样,我们产生了栈。

当一个函数执行完后,它对应的局部临时变量就会被销毁。

局部变量保存下来,是为了保护上下文现场。

举例说明一下:

function a() {
  const a_num = 99;
  const a_obj = { val: "a" };
  b();
}

function b() {
  const b_str = "text";
  c();
}

function c() {
  const c_bool = true;
  // debugger
}

a();

这里我们嵌套调用了 a、b、c 函数,会产生如下的调用栈。

基本类型的临时变量,会直接保存到栈内存中,对于引用类型,则是在堆内存中生成,然后将地址拿到,保存到栈内存中。

引用类型为什么不直接放到栈内存中?因为栈内存不是很大,很容易就栈溢出,而引用类型通常很大。

闭包的产生

函数调用完成后,它内部声明的临时变量会被销毁。理论上应该如此,但如果使用了闭包,可以会让临时变量一直保留不被销毁。

例子:

function createCounter() {
  let count = 0;
  let otherVal = "other val";
  return function counter() {
    // debugger;
    console.log(count++);
  };
}

const counter = createCounter();

console.log(counter());

执行过程为:

  • 执行函数 createCounter 时,会创建一个空的上下文对象。
  • 遇到内部函数 counter,会 预扫描内部函数 counter 使用了 createCounter 下的哪些便利,最终扫描出 count 变量。于是在堆内存创建一个闭包 Closure (createCounter) 对象,将 count 加进去。otherVal 不会加到闭包对象上,因为它没有被使用。
  • 这个内部函数最后被返回,被引用,闭包就一直不会销毁。

使用 DevTool 可以观察到这个闭包对象:

所以,如果一个闭包返回的函数执行完后不用了,要设置为 null。否则它关联的闭包对象会一直在那里占用内存

多个内部函数共享一个闭包对象

另外,如果有多个内部函数,这些函数会共用同一个闭包对象。即使其中的一个内部函数不会返回,它也会给闭包对象加东西。

下面我们加了一个 printOtherVal 的内部函数,它并不返回,但还是会导致返回 counter 函数对应的闭包对象带上了它不需要的 otherVal 变量。

这是 JS 引擎处理闭包策略问题,理论不应该有这样奇怪的效果。

结尾

调用函数时,会产生调用栈,将当前函数上下文入栈,会保存基本类型变量。引用变量会在堆内存中创建,然后在栈内存中引用过来。

因为 JavaScript 中函数是第一公民,所以会有闭包的概念。当发现内部函数,会创建一个闭包对象,将其中使用到的外部函数变量保存到该闭包对象下。之后内部函数被调用时,就会从闭包里提取变量,如果找不到则从全局上下文提取。

到此这篇关于详解JavaScript中的闭包是如何产生的的文章就介绍到这了,更多相关JavaScript闭包内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript闭包中难点深入分析

    目录 初识闭包 什么是闭包 如何产生闭包 产生闭包条件 闭包的作用 闭包的生命周期 闭包的应用 闭包的缺点及解决方法 闭包案例 初识闭包 闭包可谓是JS的一大难点也是面试中常见的问题之一,今天开始梳理一下闭包的知识,请诸君品鉴. 什么是闭包 闭包是嵌套的内部函数:内部函数包含被引用变量(函数)的对象.闭包存在于嵌套的内部函数中,例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“.在本质上,闭包是将函数内部和函数外部连接起来.当然如何

  • Javascript闭包的作用与使用方法浅析

    目录 一.变量的作用域 二.如何从外部读取局部变量 js 闭包(closure)是Javascript语言的一个难点,也是它的特色. 闭包的作用:通过一系方法,将函数内部的变量(局部变量)转化为全局变量. 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. 一.变量的作用域 Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. var n =999; function f1(){ alert(n); } f1();

  • 详解JavaScript中的作用域链与闭包

    目录 作用域链 词法作用域 闭包 作用域链 首先来看看这段代码: var a = '喜羊羊'; function A(){ console.log(a); a = '美羊羊'; function B(){ console.log(a); } B(); } A(); 在这里毫无疑问结果肯定是我们想到的先打印喜羊羊,再打印美羊羊.因为作用域链嘛,如果当前层没找到,那么就去当前层的上一级找. 那么再看这道 function bar() { console.log(myName) } function

  • JavaScript闭包原理与使用介绍

    目录 1. 认识闭包 2. 变量的作用域和生命周期 2.1 变量的作用域 2.2 变量的生命周期 3. 闭包的概念及其作用 3.1 闭包的概念 3.2 闭包的应用 3.2.1 保存私有变量 3.2.2 使用闭包实现节流 1. 认识闭包 闭包有一个很经典的场景:使用 for循环给上面5个按钮绑定点击事件. <button type="button" class='button'>按钮</button> <button type="button&qu

  • 一文剖析JavaScript中闭包的难点

    目录 一.作用域基本介绍 1. 全局作用域 2. 函数作用域 3. 块级作用域 二.什么是闭包 1. 闭包的基本概念 2. 闭包产生的原因 3. 闭包的表现形式 三.如何解决循环输出问题 1. 利用 IIFE 2. 使用 ES6 中的 let 3. 定时器传入第三个参数 一.作用域基本介绍 ES6之前只有全局作用域与函数作用域两种,ES6出现之后,新增了块级作用域. 1. 全局作用域 在JavaScript中,全局变量是挂载在window对象下的变量,所以在网页中的任何位置你都可以使用并且访问到

  • 详解python中的闭包

    闭包的概念 我们尝试从概念上去理解一下闭包. 在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部函数引用了外部函数的变量,则可能产生闭包.闭包可以用来在一个函数与一组"私有"变量之间创建关联关系.在给定函数被多次调用过程中,这些私有变量能够保持持久性. 用比较容易懂得人话说,就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包.看下例子: def make_printer(msg): def printer(): print(msg) # 夹带私货(外部变量) r

  • 详解JavaScript中的链式调用

    链模式 链模式是一种链式调用的方式,准确来说不属于通常定义的设计模式范畴,但链式调用是一种非常有用的代码构建技巧. 描述 链式调用在JavaScript语言中很常见,如jQuery.Promise等,都是使用的链式调用,当我们在调用同一对象多次其属性或方法的时候,我们需要多次书写对象进行.或()操作,链式调用是一种简化此过程的一种编码方式,使代码简洁.易读. 链式调用通常有以下几种实现方式,但是本质上相似,都是通过返回对象供之后进行调用. this的作用域链,jQuery的实现方式,通常链式调用

  • 详解JavaScript中的4种类型识别方法

    具体内容如下: 1.typeof [输出]首字母小写的字符串形式 [功能] [a]可以识别标准类型(将Null识别为object) [b]不能识别具体的对象类型(Function除外) [实例] console.log(typeof "jerry");//"string" console.log(typeof 12);//"number" console.log(typeof true);//"boolean" console

  • 详解JavaScript 中getElementsByName在IE中的注意事项

    详解JavaScript 中getElementsByName在IE中的注意事项 前言: 在IE5-9中是没有实现js的 getElementsByClassName()方法,但是实现了getElementsByName()方法,但是需要注意的是这个方法在IE5-9中也返回id属性匹配的指定元素,为了兼容,应该小心谨慎使用,不要将同样的字符串同时用作了名字和ID. 测试程序如下: <div id="log"> <div id="innerLog"&

  • 详解Javascript中prototype属性(推荐)

    在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不是基于'类的',而是通过构造函数(constructor)和原型链(prototype chains)实现的.但是在ES6中提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板.通过class关键字,可以定义类.基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能

  • 详解Javascript 中的 class、构造函数、工厂函数

    到了ES6时代,我们创建对象的手段又增加了,在不同的场景下我们可以选择不同的方法来建立.现在就主要有三种方法来构建对象,class关键字,构造函数,工厂函数.他们都是创建对象的手段,但是却又有不同的地方,平时开发时,也需要针对这不同来选择. 首先我们来看一下,这三种方法是怎样的 // class 关键字,ES6新特性 class ClassCar { drive () { console.log('Vroom!'); } } const car1 = new ClassCar(); consol

  • 详解JavaScript中的Object.is()与"==="运算符总结

    三重相等运算符 === 严格检查2个值是否相同: 1 === 1; // => true 1 === '1'; // => false 1 === true; // => false 但是,ES2015规范引入了 Object.is(),其行为与严格的相等运算符几乎相同: Object.is(1, 1); // => true Object.is(1, '1'); // => false Object.is(1, true); // => false 主要问题是:什么时

  • 详解JavaScript中的this指向问题

    题记 JS中的this指向一直是个让初学者头疼的问题.今天,我们就一起来瞅瞅this倒地是咋回事,详细说说this指向原则,从此不再为了this指向操碎了心. 开篇 首先我们都知道this是Javascript语言的一个关键字. 它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.随着函数使用场合的不同,this的值会发生变化.但是有一个总的原则,那就是this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它所在函

  • 详解JavaScript中new操作符的解析和实现

    前言 new 运算符是我们在用构造函数创建实例的时候使用的,本文来说一下 new 运算符的执行过程和如何自己实现一个类似 new 运算符的函数. new 运算符的运行过程 new 运算符的主要目的就是为我们创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例(比如箭头函数就没有构造函数,所以是不能 new 的).new 操作符的执行大概有以下几个步骤: 创建一个新的空对象 把新对象的 __proto__ 链接到构造函数的 prototype 对象(每一个用户定义函数都有一个 proto

  • 详解JavaScript中的执行上下文及调用堆栈

    一.执行上下文是什么 代码运行是在一定的环境之中运行的,这个运行环境我们就成为执行环境,也就是执行上下文,按照执行环境不同,我们可以分为三类: 全局执行环境:代码首次执行时候的默认环境 函数执行环境:每当执行流程进入到一个函数体内部的时候 Eval执行环境:当eval函数内部的文本执行的时候 二.执行上下文栈是什么 既然是'栈',那就得符合'栈'的特性,即数据结构是先进后出.下面我们看一段代码: function cat(a){ if(a<0){ return false; } console.

随机推荐