前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >js堆栈内存

js堆栈内存

原创
作者头像
ruochen
修改于 2021-11-21 06:54:45
修改于 2021-11-21 06:54:45
1.9K0
举报

1. 基础堆栈内存考核

下面题输出结果是?

代码语言:txt
AI代码解释
复制
let a = {
代码语言:txt
AI代码解释
复制
  n : 1
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
let b = a
代码语言:txt
AI代码解释
复制
a.x = a = {
代码语言:txt
AI代码解释
复制
  n: 2
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
console.log(a.x)  // undefined
代码语言:txt
AI代码解释
复制
console.log(b)  // { n: 1, x: { n: 2 } }

执行过程分析:

  1. a = {n: 1},先在堆内存中创建一个地址(比如:AAAFFF00),内容是 n: 1,然后在全局执行上下文EC(G)中的全局变量环境VO(G),创建全局变量a,指向堆内存地址:AAAFFF00
  2. b = a,同理,在全局变量环境VO(G)创建一个全局变量b,指向堆内存地址:AAAFFF00
  3. a.x = a = { n: 2 },这个的优先级,其实等同于a.x = {n: 1}a = {n: 1}
  4. a.x = {n: 1},在堆内存中再创建一个地址(比如:AAAFFF11),内容是n: 1,在a的内存地址AAFF00中创建一个x变量,指向AAAFFF11,此时,b = a = {n:1, x: {n: 1}}
  5. a = {n: 1},将a的指向由AAAFFF00改为AAAFFF11

所以最后结果为:

b = {n:1, x: {n: 1}}

a = {n: 1}

2. 变态的变量提升

变量提升:当前上下文执行之前,会把var/function声明或者定义提升,带var的只声明,带function的声明+定义

但如果遇到{}块级作用域时,新老版本浏览器表现不一样(兼容ES3、兼容ES6)

  • IE浏览器<=IE10(老版本)undefined不管{},还是一如既往的function声明+定义,而且也不会存在块级作用域
  • 新版本浏览器(这里的{}是除对象中的{})undefined{}中的function,在全局下只声明不再定义;undefined{}中出现function/let/const,会创建一个块级作用域

下面拿一道题来说明:

代码语言:txt
AI代码解释
复制
var a = 0
代码语言:txt
AI代码解释
复制
if(true) {
代码语言:txt
AI代码解释
复制
  a = 1
代码语言:txt
AI代码解释
复制
  function a() {}
代码语言:txt
AI代码解释
复制
  a = 21
代码语言:txt
AI代码解释
复制
  console.log(a)  // 21
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
console.log(a) // 1

如果是在IE10以下的执行过程:

  1. 变量提升function a() {}
  2. 全局变量提升var a
  3. 开始执行,全局 a = 0
  4. 全局a = 1
  5. 全局a = 21
  6. 打印全局a: 21
  7. 打印全局a: 21

如果是在新版本浏览器,也就是向前兼容ES3,向后兼容ES6的执行过程:

  1. 因为变量提升,所以var a,function a都在全局执行上下文EC(G)中的全局变量环境VO(G)中创建一个全局变量a
  2. 代码执行a = 0,全局变量环境VO(G)a = 0
  3. 遇到{}块级作用域,生成一个块级执行上下文EC(block),会生成一个私有变量对象AO(block)
  4. 在这个块级作用域中,因为有function a,所以块级中发生变量提升,声明+定义,在heap堆内存中生成一个函数,在AO(block)创建一个变量a指向函数堆内存, 这之后在这块级中,遇到的a都是私有的

image.png

  1. 块级中执行a = 1AO(block)a = 1

image.png

  1. 遇到function a() {},因为这个函数,为了向前兼容ES3,所以在全局内提升过一回;为了向后兼容ES6,又在块级作用域中提升一回,所以 浏览器为了兼容,真遇到块级中的函数时,它会做一件事:遇到这代码前,会把代码之前所有对a的操作,映射给全局一份,但是后面的则不会再处理了,它会认为这之后的都是自己私有的了 ,即之前a = 1会映射到全局VO(G) 中 a =1

image.png

  1. 之后a = 21,已经是私有的了,所以只影响块级AO(block) 中 a = 21

image.png

  1. 块级中console.log(a) => AO(block) a => 21
  2. 全局中console.log(a) => VO(G) a => 1

扩展练习

为了加深这个变态的规则,我们多做几道题:

变态提升练习1
代码语言:txt
AI代码解释
复制
{
代码语言:txt
AI代码解释
复制
  function foo() {}
代码语言:txt
AI代码解释
复制
  foo = 1
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
console.log(foo);
  • 第1步:{}中的function foo在变量提升,全局中只声明,不定义
代码语言:txt
AI代码解释
复制
EC(G):  
代码语言:txt
AI代码解释
复制
   AO(G) function foo
  • 第2步,在{}块级中有function,所以会产生一个块级作用域EC(Block)foo在这个块级作用域里,变量提升:声明+定义
代码语言:txt
AI代码解释
复制
EC(block):   
代码语言:txt
AI代码解释
复制
   AO(block): funciont foo() {}
  • 第3步,开始执行(执行过程后,AO变量对象 =>VO激活对象),因为foo在全局和私有都声明过,为了兼容ES3ES6,在执行到function foo() {}里,这之前的操作映射到全局,也就是AO(block): funciont foo() {}声明+定义的过程
代码语言:txt
AI代码解释
复制
EC(G):  
代码语言:txt
AI代码解释
复制
   VO(G) function foo() {}
  • 第4步,执行foo = 1,因为在块级作用域中有foo私有变量,所以是在EC(block)中赋值
代码语言:txt
AI代码解释
复制
EC(block):
代码语言:txt
AI代码解释
复制
      VO(G) foo => 1
变态提升练习2

下面我要开始偷懒了,哈哈哈,请结合上下文理解下面注释步骤

代码语言:txt
AI代码解释
复制
// 第1步,EC(G), AO(G): function foo
代码语言:txt
AI代码解释
复制
// 第2步,EC(block), AO(block): foo => function foo() {}
代码语言:txt
AI代码解释
复制
// 第3步,开始执行,EC(G), VO(G): foo => function foo() {}
代码语言:txt
AI代码解释
复制
// 第4步,foo = 1, EC(block), VO(block): foo => 1
代码语言:txt
AI代码解释
复制
// 第5步,EC(G), VO(G): foo => 1
代码语言:txt
AI代码解释
复制
{
代码语言:txt
AI代码解释
复制
  function foo() {}
代码语言:txt
AI代码解释
复制
  foo = 1
代码语言:txt
AI代码解释
复制
  function foo() {} // 1
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
console.log(foo); // 1
变态提升练习2
代码语言:txt
AI代码解释
复制
// 第1步,EC(G), AO(G): function foo
代码语言:txt
AI代码解释
复制
// 第2步,EC(block), AO(block): foo => function foo() {}
代码语言:txt
AI代码解释
复制
// 第3步,开始执行function foo() {},EC(G), VO(G): foo => function foo() {}
代码语言:txt
AI代码解释
复制
// 第4步,foo = 1, EC(block), VO(block): foo => 1
代码语言:txt
AI代码解释
复制
// 第5步,function foo() {} => EC(G), VO(G): foo => 1
代码语言:txt
AI代码解释
复制
// 第6步,foo = 2,EC(block), VO(block): foo => 2
代码语言:txt
AI代码解释
复制
{
代码语言:txt
AI代码解释
复制
  function foo() {}
代码语言:txt
AI代码解释
复制
  foo = 1
代码语言:txt
AI代码解释
复制
  function foo() {}
代码语言:txt
AI代码解释
复制
  foo = 2
代码语言:txt
AI代码解释
复制
  console.log(foo); // 2
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
console.log(foo); // 1

3. 带形参的堆栈内存考察

以下函数输出结果是?

代码语言:txt
AI代码解释
复制
var x = 1
代码语言:txt
AI代码解释
复制
function func(x, y = function func2() { x = 2 }) {
代码语言:txt
AI代码解释
复制
  x = 3;
代码语言:txt
AI代码解释
复制
  y()
代码语言:txt
AI代码解释
复制
  console.log(x)  // 2
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
func(5)
代码语言:txt
AI代码解释
复制
console.log(x)  // 1

下面使用图分析下过程:

  1. 全局执行x = 1,在EC(G)中创建一个VO(G),创建值1,再创建一个对象x,x指向值1

image.png

  1. 发现函数声明,为其创建一个堆内存,定义其函数,在这个内存中,声明其所在的作用域,即全局作用域VC(G),形参x、y,及其函数体字符串,并在全局变量中创建一个func,指向这个堆内存

image.png

  1. 执行func(5),执行一个函数,会为其创建一个私有的执行上下文EC(func),在其中会创建一个私有变量环境AO(func)
  2. EC(func)初始其作用域链:它自身作用域EC(func)和其上级作用域EC(G)(全局作用域)
  3. 形参赋值:传了x = 5y没传参,所以使用其默认值函数func2,碰到函数,为其申请堆内存AAAFFF111,同样,分析其作用域和形参,并将函数体保存到内存中,并将y指向AAAFFF111

image.png

  1. 分析完关系后,开始执行func5函数体
  2. x = 3, 在自己作用域EC(func)中查找x,发现有私有x,所以将x指向3
  3. y(),在自己作用域EC(func)中查找y,发现有y指向func2,执行函数func2,并其又单独创建一个执行上下文EC(func2),为其分析作用域链,自身所在作用域EC(func2)和其上级作用域EC(func),因为其没有形参赋值,所以也没创建相关的私有变量

image.png

  1. 开始执行函数func2的函数体,x = 2,所以在其自身作用域EC(func2)中找不到该变量x,会向其上级作用域EC(func)中查找,发现有x,所以将EC(func)x指向2

image.png

  1. func2执行结束,继续执行EC(func)中的console.log(x),输出其自身的x:2
  2. func执行结束,继续执行EC(G)中的console.log(x),输出全局VO(G)中的x:1

image.png

4. 变态版的带形参函数的堆栈内存

下面题目输出是?

代码语言:txt
AI代码解释
复制
var x = 1
代码语言:txt
AI代码解释
复制
function func(x, y = function func2() { x = 2 }) {
代码语言:txt
AI代码解释
复制
  // 这里,x多一个var声明
代码语言:txt
AI代码解释
复制
  var x = 3;
代码语言:txt
AI代码解释
复制
  y()
代码语言:txt
AI代码解释
复制
  console.log(x)  
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
func(5)
代码语言:txt
AI代码解释
复制
console.log(x)

这里就要讲到浏览器运行es6的机制了:

ES6产生块级作用域的两种情况
第1种,正常的{}产生的块级作用域

这种是我们平常所认识的,ES6中存在块级作用域,即只要{}(除了对象中的{})出现let/const/function

第2种,是浏览器在运行时产生的,即只要符合以下两个条件:
  1. 函数有任意一个形参赋值了默认值
  2. 函数体中有单独声明过某个变量(var/let/const/function都算)undefined那么这个函数就会产生 2个上下文
代码语言:txt
AI代码解释
复制
* 一个是函数本身执行产生的私有上下文,比如上面`func`函数执行时,会生成`EC(FUNC)`
* 一个是函数体大括号包起来的块级上下文`EC(BLOCK)`

下面,我们依然画图来分析:

  1. 第一个过程,和上一题1\2步骤是一致的,依然是生成全局执行上下文EC(G),它在VO(G)声明两个变量:xfuncx指向值1,func指向函数堆内存AAAFFF000

image.png

  1. 第二个过程,执行func(5),这时,因为func中形参y设有默认值,且函数体中声明了变量var x = 3,按照上面的规则,这里会生成两个执行上下文EC(FUNC)EC(BLOCK)
  2. 先来分析EC(FUNC) ,它的作用域是EC(G),作用域链是它自身上下文EC(FUNC) 和上级作用域上下文EC(G),它的形参是x => 5y => 函数func2堆内存AAAFFF111

image.png

4.对EC(BLOCK)的代码块分析,它的作用域是EC(FUNC),作用域链是它自身上下文EC(BLOCK)和上级执行上下文EC(FUNC),在块级中,`var

x = 3它声明了x,所以x是块级作用域中的私有变量,当执行x = 3时,将块级中的x => 3`

image.png

  1. 继续执行y(),发现块级中没有y变量,去它的作用域链上级EC(FUNC)找,找到了y,执行y(),生成func2执行上下文EC(FUNC2),它的作用域是EC(FUNC),所以其作用域链是[自身执行上下文EC(FUNC2),作用域EC(FUNC)],没有形参和变量声明,所以没有自身私有变量;执行里面函数体x = 2,在EC(FUNC2)中找不到x,所以去它作用域上级找EC(FUNC)EC(FUNC)中有x,所以将EC(FUNC)x => 2

image.png

  1. EC(BLOCK)继续执行console.log(x),打印的是 EC(BLOCK)中私有的x,即3
  2. func(5)执行完毕,继续执行到EC(G)console.log(x),打印的是VO(G)中的x,即1

image.png

所以,答案是3和1

下面,为了证明我没有胡说八道='=,我们在浏览器上断点调试下最后一题

  1. 在开始位置打上断点debugger,在浏览器打开,因为window全局变量太多,不好找到全局VO(G)x,所以我在Watch中添加了window.x变量,方便我们观察VO(G)中(也就是浏览器的Global)x的值,可以看到,还没调试之前,全局中的xundefind
代码语言:txt
AI代码解释
复制
// 第4题:变态带形参的堆栈考核
代码语言:txt
AI代码解释
复制
debugger;
代码语言:txt
AI代码解释
复制
var x = 1
代码语言:txt
AI代码解释
复制
function func(x, y = function func2() { x = 2 }) {
代码语言:txt
AI代码解释
复制
  var x = 3;
代码语言:txt
AI代码解释
复制
  y()
代码语言:txt
AI代码解释
复制
  console.log(x)
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
func(5)
代码语言:txt
AI代码解释
复制
console.log(x)

image.png

  1. 当执行完x=1时,func(5)执行前,可以看到全局VO(G)x = 1

image.png

  1. 继续往下执行func(5),可以看到生了Scope中,也就是EC(FUNC)的作用域中,生成两个执行上下文BLOCKLOCAL,对应我们上文说的EC(BLOCK)EC(FUNC),因为私有变量中有x,然后块级中也有声明x,所以会将私有的x映射到块级中的x

image.png

  1. 执行块级中var x = 3,发现块级中私有变量x变为3

image.png

  1. 执行y(),即生成func2的执行上下文EC(FUNC2),因为没有私有变量,所以其Local为空

image.png

  1. 执行完y()x = 2后,看到EC(FUNC)中的x => 2

image.png

  1. 执行EC(BLOCK)console.log(x),输出的是Block中的x

image.png

  1. 执行EC(G)console.log(x),输出的是Global中的x

image.png

由此,验证了上面的分析结论。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
暂无评论
推荐阅读
编辑精选文章
换一批
从 JavaScript 作用域说开去
在电脑程序设计中,作用域(scope,或译作有效范围)是名字(name)与实体(entity)的绑定(binding)保持有效的那部分计算机程序。不同的编程语言可能有不同的作用域和名字解析。而同一语言内也可能存在多种作用域,随实体的类型变化而不同。作用域类别影响变量的绑定方式,根据语言使用静态作用域还是动态作用域变量的取值可能会有不同的结果。
一缕殇流化隐半边冰霜
2018/08/29
8750
从 JavaScript 作用域说开去
JS底层运行机制
众所周知,计算机是有内存的,计算机会在内存中开辟一块空间去供js执行,这个空间我们称之为执行栈
子夜星辰
2022/11/15
1.9K0
JS学习系列 06 - 变量对象
上一节我们讨论了执行上下文,那么在上下文中到底有什么内容,为什么它会和作用域链扯上关系,JS 解释器又是怎么找到我们声明的函数和变量,看完这一节,相信大家就不会再迷惑了。
leocoder
2018/10/31
1.3K0
前端入门17-JavaScript进阶之作用域声明正文-作用域
作为一个前端小白,入门跟着这几个来源学习,感谢作者的分享,在其基础上,通过自己的理解,梳理出的知识点,或许有遗漏,或许有些理解是错误的,如有发现,欢迎指点下。
请叫我大苏
2018/12/24
5450
前端入门17-JavaScript进阶之作用域声明正文-作用域
由 JavaScript 的 with 引发的探索
1. 背景 某天吃饭的时候突然想到,都说 with 会有问题,那么是什么问题,是怎样导致的呢?知其然不知其所以然,在好奇心的驱使下,从 with 出发,一路追溯到 VO、AO。那么先来复习一下 with 是干嘛的吧。 2. with js 的 with 是为对象访问提供命名空间式的访问方式,with 创建一个对象的命名空间,在这个命名空间内你可以直接访问对象的属性,而不需要通过对象来访问: const o = { a: 1, b: 2 }; with (o) { console.log(a); /
用户1097444
2022/06/29
3190
由 JavaScript 的 with 引发的探索
深入理解变量对象、作用域链和闭包
执行上下文、执行栈、作用域链、闭包,这其实是一整套相关的东西,之前转载的文章也有讲到这些。下面两篇文章会更加详细地解释这些概念。
Chor
2019/11/07
7380
JS进阶:作用域和作用域链
作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。可能这两句话并不好理解,我们先来看个例子:
4O4
2022/04/25
2.6K0
JS进阶:作用域和作用域链
js常见错误总结
var声明的变量即是全局变量,也相当于给GO(window)设置了一个属性,而且两者建立映射机制
IT工作者
2021/12/30
1.9K0
三、变量对象
在JavaScript中,肯定不可避免的需要声明变量和函数,JS编译器是如何找到这些变量的呢?
用户6901603
2020/07/23
5660
函数与作用域
1.函数声明和函数表达式有什么区别 函数就是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。 JavaScript有三种方法,可以声明一个函数。 1.function命令 function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面传入函数的参数。函数体放在大括号里面。 function add(s) { console.log(s) } 上面的代码命名了一个print函数,以后使用print()这种形式,就可以调用相应
小胖
2018/06/27
8520
js堆栈内存详解
变量提升:当前上下文执行之前,会把var/function声明或者定义提升,带var的只声明,带function的声明+定义
花落花相惜
2021/12/15
1.9K0
JS学习系列 06 - 变量对象
上一节我们讨论了执行上下文,那么在上下文中到底有什么内容,为什么它会和作用域链扯上关系,JS 解释器又是怎么找到我们声明的函数和变量,看完这一节,相信大家就不会再迷惑了。
leocoder
2024/02/01
990
JS学习系列 06 - 变量对象
重学JS基础-作用域链和闭包
JS有一个全局对象,window,在全局声明的变量都属于window的属性,未使用声明符声明的属性也是window的属性。
Jou
2022/08/10
6040
【译】JS的执行上下文和环境栈是什么?
这篇文章中,我将深入探讨JavaScript中的一个最基本的部分,即执行上下文(或称环境)。读过本文后,你将更加清楚地了解到解释器尝试做什么,为什么在声明某些函数/变量之前,可以使用它们以及它们的值是如何确定的。
Jimmy_is_jimmy
2019/07/31
7830
手把手教会你JavaScript引擎如何执行JavaScript代码
JavaScript 在运行过程中与其他语言有所不一样,如果不理解 JavaScript 的词法环境、执行上下文等内容,很容易会在开发过程中产生 Bug,比如this指向和预期不一致、某个变量不知道为什么被改了,等等。所以今天我们就来聊一聊 JavaScript 代码的运行过程。
前端皮皮
2022/08/17
4430
手把手教会你JavaScript引擎如何执行JavaScript代码
JS入门难点解析9-闭包的深入解析
(注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!) (注2:更多内容请查看我的目录。)
love丁酥酥
2018/08/27
5360
[第18期] 一文搞清 Javascript 中的「上下文」
上下文是 Javascript 中的一个比较重要的概念, 可能很多朋友对这个概念并不是很熟悉, 那换成「作用域」 和 「闭包」呢?是不是就很亲切了。
皮小蛋
2020/03/02
4350
关于 JS 闭包看这一篇就够了
LHS (Left-hand Side) 和 RHS (Right-hand Side) ,是在代码执行阶段 JS 引擎操作变量的两种方式,字面理解就是当变量出现在赋值操作左侧时进行LHS查询,出现在右侧时进行RHS查询。更准确的来说,LHS是为了找到变量的容器本身从而可以进行赋值,而RHS则是获取某个变量的值。
用户8921923
2022/10/24
4430
JS词法环境和执行上下文
JavaScript是一门解释性动态语言,但同时它也是一门充满神秘感的语言。如果要成为一名优秀的JS开发者,那么对JavaScript程序的内部执行原理要有所了解。
hellocoder2029
2022/10/21
1.4K0
JS入门难点解析5-变量对象
(注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!) (注2:更多内容请查看我的目录。)
love丁酥酥
2018/08/27
1.1K0
相关推荐
从 JavaScript 作用域说开去
更多 >
LV.7
若尘科技有限公司开发
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文