首页 > 其他分享 >在考虑闭包的情况下JS变量存储在栈与堆的区分

在考虑闭包的情况下JS变量存储在栈与堆的区分

时间:2022-10-13 17:03:40浏览次数:79  
标签:闭包 存储 bar 变量 fx JS let const foo


变量存储在闭包中的问题

按照常理来说栈中数据在函数执行结束后就会被销毁,那么 ​​JavaScript​​ 中函数闭包该如何实现,先简单来个闭包:

function count () {
let num = -1;
return function () {
num++;
return num;
}
}

let numCount = count();
numCount();
// 0
numCount();
// 1
复制代码

按照结论,​​num​​​ 变量在调用 ​​count​​​ 函数时创建,在 ​​return​​​ 时从栈中弹出。
既然是这样的逻辑,那么调用 ​​​numCount​​​ 函数如何得出 ​​0​​​ 呢?​​num​​​ 在函数 ​​return​​​ 时已经在内存中被销毁了啊!
因此,在本例中 ​​​JavaScript​​​ 的基础类型并不保存在栈中,而应该保存在堆中,供 ​​numCount​​ 函数使用。

抛开栈,只在堆中存储数据

function test () {
let num = 1;
let string = 'string';
let bool = true;
let obj = {
attr1: 1,
attr2: 'string',
attr3: true,
attr4: 'other'
}
return function log() {
console.log(num, string, bool, obj);
}
}

伴随着 ​​test​​​ 的调用,为了保证变量不被销毁,在堆中先生成一个对象就叫 ​​Scope​​​ 吧,把变量作为 ​​Scope​​ 的属性给存起来。堆中的数据结构大致如下所示:

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript

由于 ​​Scope​​​ 对象是存储在堆中,因此返回的 ​​log​​​ 函数完全可以拥有 ​​Scope​​​ 对象 的访问。下图是该段代码在 ​​Chrome​​ 中的执行效果:

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript_02

例子中 ​​JavaScript​​​ 的变量并没有存在栈中,而是在堆里,用一个特殊的对象(​​Scopes​​)保存。

变量到底是如何在 ​​JavaScript​​ 中存储的

在 ​​JavaScript​​ 中,变量分为三种类型:

  1. 局部变量
  2. 被捕获变量
  3. 全局变量

局部变量

在函数中声明,且在函数返回后不会被其他作用域所使用的对象。下面代码中的fx​​*​​ 都是局部变量。

function fxFn () {
let fx1 = 1
var fx2 = 'str'
const fx3 = true
let fx4 = {name: 'fx'}
return
}

被捕获变量

被捕获变量就是局部变量的反面:在函数中声明,但在函数返回后仍有未执行作用域(函数或是类)使用到该变量,那么该变量就是被捕获变量。下面代码中的 ​​fx*​​ 都是被捕获变量。

function fxFn () {
let fx1 = 1
var fx2 = 'str'
const fx3 = true
let fx4 = {name: 'fx'}
return function () {
console.log(fx1, fx2, fx3, fx4)
}
}
let fx = fxFn()
console.dir(fx)

在考虑闭包的情况下JS变量存储在栈与堆的区分_全局变量_03

function fxFn () {
let fx1 = 1
var fx2 = 'str'
const fx3 = true
let fx4 = {name: 'fx'}
return class {
constructor() {
console.log(fx1, fx2, fx3, fx4)
}
}
}
let fx = fxFn()
console.dir(fx)

复制代码到 ​​Chrome​​​ 即可查看输出对象下的 ​​[[Scopes]]​​​ 下有对应的 ​​Scope​​。

全局变量

全局变量就是 ​​global​​​,在 浏览器上为 ​​window​​​ 在 ​​node​​​ 里为 ​​global​​​。全局变量会被默认添加到函数作用域链的最低端,也就是上述函数中 ​​[[Scopes]]​​ 中的最后一个。

全局变量需要特别注意一点:​​var​​​ 和 ​​let/const​​ 的区别。

var:全局的 var 变量其实仅仅是为 ​global​ 对象添加了一条属性。

var name = 'fx';

// 与下述代码一致
windows.name = 'fx';

let / const:全局的 let/const 变量不会修改 ​windows​ 对象,而是将变量的声明放在了一个特殊的对象下(与 ​Scope​ 类似)。

var pwd = 123

在考虑闭包的情况下JS变量存储在栈与堆的区分_全局变量_04

变量赋值

其实不论变量是存在栈内,还是存在堆里(反正都是在内存里),其结构和存值方式是差不多的,都有如下的结构:

在考虑闭包的情况下JS变量存储在栈与堆的区分_全局变量_05

赋值为常量

何为常量?常量就是一声明就可以确定的值,比如 ​​1​​​、​​"string"​​​、​​true​​​、​​{a: 1}​​,都是常量

假设现在有如下代码:

let foo = 1

​JavaScript​​​ 声明了一个变量 ​​foo​​​,且让它的值为 ​​1​​,内存中就会发生如下变化

在考虑闭包的情况下JS变量存储在栈与堆的区分_全局变量_06

如果现在又声明了一个 ​​bar​​ 变量:

let bar = 2

那么内存中就会变成这样:

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript_07

对于对象类型

let obj = {
foo: 1,
bar: 2
}

内存模型如下:

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript_08

通过该图,我们就可以知道,其实 ​​obj​​​ 指向的内存地址保存的也是一个地址值,那好,如果我们让 ​​obj.foo = 'foo'​​​ 其实修改的是 ​​0x1021​​​ 所在的内存区域,但 ​​obj​​ 指向的内存地址不会发生改变,因此,对象是常量!

赋值为变量

何为变量?在上述过程中的 ​​foo​​​、​​bar​​​、​​obj​​,都是变量,变量代表一种引用关系,其本身的值并不确定。

那么如果我将一个变量的值赋值给另一变量,会发生什么?

let x = foo

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript_09

如上图所示,仅仅是将 ​​x​​​ 引用到与 ​​foo​​ 一样的地址值而已,并不会使用新的内存空间。

​OK​​ 赋值到此为止,接下来是修改。

变量修改

与变量赋值一样,变量的修改也需要根据 ​​=​​ 号右边变量的类型分为两种方式:

修改为常量

foo = 'foo'

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript_10

如上图所示,内存中保存了 ​​'foo'​​​ 并将 ​​foo​​​ 的引用地址修改为 ​​0x0204​​。

修改为变量

foo = bar

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript_11

如上图所示,仅仅是将 ​​foo​​ 引用的地址修改了而已。

const 的工作机制

​const​​​ 为 ​​ES6​​​ 新出的变量声明的一种方式,被 ​​const​​ 修饰的变量不能改变。

其实对应到 ​​JavaScript​​ 的变量储存图中,就是变量所指向的内存地址不能发生变化。也就是那个箭头不能有改变。

比如说以下代码:

const foo = 'foo';
foo = 'bar'; // Error

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript_12

如上图的关系图所示,​​foo​​ 不能引用到别的地址值。那好现在是否能解决你对下面代码的困惑:

const obj = {
foo: 1,
bar: 2
};
obj.foo = 2;

其 ​​obj​​ 所引用的地址并没有发生变化,发生变的部分为另一区域。如下图所示

在考虑闭包的情况下JS变量存储在栈与堆的区分_全局变量_13

对象的修改

​OK​​ 进入一个面试时极度容易问到的问题:

let obj1 = {
foo: 'foo',
bar: 'bar'
}

let obj2 = obj1;
let obj3 = {
foo: 'foo',
bar: 'bar'
}

console.log(obj1 === obj2);
console.log(obj1 === obj3);

obj2.foo = 'foofoo';

console.log(obj1.foo === 'foofoo');

请依次说出 ​​console​​ 的结果。

我们不讨论结果,先看看内存中的结构。所以结果为 true false true

在考虑闭包的情况下JS变量存储在栈与堆的区分_javascript_14

标签:闭包,存储,bar,变量,fx,JS,let,const,foo
From: https://blog.51cto.com/u_13028258/5754017

相关文章

  • js 输入框中过滤表情,颜文字(正则)
    letname=this.name //this.name为输入框中的输入内容 varregStr=/[\uD83C|\uD83D|\uD83E][\uDC00-\uDFFF][\u200D|\uFE0F]|[\uD83C|\uD83D|\uD83E][\uDC00-\uD......
  • JS判断数组中是否包含某个值
    方法一:array.indexOf此方法判断数组中是否存在某个值,如果存在,则返回数组元素的下标,否则返回-1。vararr=[1,2,3,4]varindex=arr.indexOf(3)console.log(index)方法......
  • JS实现继承的方法
    方法一:借助callfunctionParent(sex){this.name='fx'this.sex=sex}Parent.prototype.test=function(){console.log('我是函数')}Parent.prototype.wh......
  • js判断手机系统是android还是ios?
    varu=navigator.userAgent;//识别各种浏览器varisAndroid=u.indexOf('Android')>-1||u.indexOf('Adr')>-1;//android终端varisiOS=!!u.match(/\(i[^;]......
  • SqlServer存储将表格数据生成txt文件
    DECLARE@fileNameVARCHAR(100)='dzw.txt'DECLARE@cmdVARCHAR(100)DECLARE@pathVARCHAR(200)--文件名--E:\SendEmailSys\SSIS\docSET@path=......
  • js倒计时
    倒计时//倒计时输入时间-现在时间varinputTime=prompt('请输入当前时间,格式为YYYY-MM-DD或YYYY/MM/DD');//prompt返回值是字符串要进行数据类型转换......
  • js逆向案例
    js逆向案例目录零、概述一、请求参数|Cookie|Referer校验(⭐)1、案例1_有道翻译2、案例2_百度翻译二、参数响应如何获取AES、DES、RSA(⭐)1、案例3_建筑市场_AES2、案例4_毛毛租......
  • Golang复杂json结构体解析
    1、示例一{"id":"8667597b-bcd9-51de31b655cd","name":"ali-redis-analyse","category":"db","category_display":"数据库","type":"redis","type_display":......
  • Microsoft 365 解决方案:如何更改新建Site Collection到的存储配额
    51CTO博客链接:​​​https://blog.51cto.com/u_13637423​​ 目前微软为了满足大部分客户的需求,默认的情况下,将Office365->SharePointOnline管理中心为每个新建的Site......
  • JS 中的require 和 import 区别
    这两个都是为了JS模块化编程使用.遵循规范require 是AMD规范引入方式import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法调用时间require是运......