速通JS内存机制

132 阅读5分钟

前言

在现代Web开发中,JavaScript的内存机制是一个至关重要却常常被忽视的主题。随着应用程序的复杂性增加,理解JavaScript如何管理内存变得愈加重要。内存机制不仅影响性能,还会在不当使用时导致内存泄漏和其他问题。本文将带大家探索JavaScript,让大家对JavaScript这门语言有更加深刻的认识。

在讲js内存机制之前我们先来认识下一门语言的类型

1.在使用前就要确定其变量的数据类型 ---------静态语言 例( c)

2.在运行时检查数据的类型 -------动态语言 js python

3.支持原始类型转换的语言 ------弱类型语言 js

4.不支持原始类型转换的语言 --------强类型语言 java

js是一门弱类型语言的动态语言,也就是说js支持原始类型转换,而且是在运行时检查数据的类型。

js的内存机制

在js中,它的内存空间主要是由三个部分所组成:代码空间栈空间堆空间。代码空间负责存放用户输入的代码,栈空间负责存放原始类型和函数执行上下文

不了解的小伙伴可以点这里:浅谈JavaScript执行机制

而今天我们就要着重来讲解堆空间,用几段代码来带你们认识堆空间

let a = 1
let b = a
a = 2
console.log(b)

image.png

let c = {
  name: 'zhangsan',
  age: 18
}
let d = c
c.age = 20
console.log(d.age)

image.png

我们可以看到b的值并没有因为a的修改而改变,而d中的值却因为c的改变一起改变了,为什么会出现这种情况,

function foo() {
  var a = 1
  var b = a
  var c = { name: 'zhangsan' }  //c存的是对象的地址
  var d = c
}
foo()

我们可以按照之前学习的js执行机制来画图,和之前不一样的是我们这次声明的变量里面有对象,在js的内部中它会把原始类型放进调用栈中,将引用类型放入堆中,左边的橙色框框是调用栈,右边的黑色框框是堆,每遇到一个引用类型的声明,js就会在堆中开辟一个位置给这个引用类型存放,而且有它的地址,用于查找这个引用类型,而在调用栈中存放的就是这个引用类型的地址,当执行d = c时,c会把它的地址传给d,就导致了c = #001,d=#001的情况

image.png

内存空间

大部分空间都给了堆,栈存储原始类型不要太多空间

1.栈:存储原始类型(原始类型的值一般都很小(占用空间小))

2.堆:存储引用类型(要占据的内存很大)


我们接下来看一个比较复杂的例子
function fn(person) {
  person.age = 19
  person = {
    name: '庆玲',
    age: 19
  }
  return person
}
const p1 = {
  name: '凤如',
  age: 18
}
const p2 = fn(p1)

console.log(p1);
console.log(p2);

我们直接开始分析,我们先在调用栈中创建一个全局执行上下文,里面有变量环境和词法环境,现在开始编译,先将用const声明的p1和p2放进词法环境中,值为undefined,然后再将函数fn的声明放进变量环境中,值为func······,全局上下文编译完成,全局上下文进入执行阶段,为p1在堆中开辟一片空间地址为#001,随后到p2,调用函数fn,将p1作为实参传入,此时我们又在调用栈中创建了一个fn执行上下文。

现在开始编译fn执行上下文,将形参person放进变量环境中,值为undefined,然后形实参统一,person的值为#001,至此person的编译环节结束了,进入执行阶段,看到这个person.age = 19,将19赋值给#001地址里面的age,此时#001中age为19,随后我们看到这边又凭空多了一个对象person覆盖了之前的person值,我们把它放进堆中,地址为#003,函数最后return person 意思就是调用函数返回的时person的值,此时person的值为#003,到这里函数fn执行完毕执行上下文销毁,p2被赋值#003,继续执行,输出p1和p2,我们可以看到下图的结果

Snipaste_2024-12-01_13-47-53.png

image.png


看到这里想必你对堆有些认识了,接下来我们要来点有难度的了,考验下你对js执行机制,闭包,js内存机制的综合了解

function foo() {
  var myname = '子俊哥哥'
  let test1 = 1
  const test2 = 2
  var innerBar = {
    setName: function (name) {
      myname = name
    },
    getName: function () {
      console.log(test1);
      return myname
    }
  }
  return innerBar
}
var bar = foo()
bar.setName('陈总')
console.log(bar.getName());

我提示几个重要的地方,foo函数执行完毕执行上下文下销毁,留下闭包 里面留存myname和test1,执行bar.setName('陈总')的时候想要找myname,但是在setName执行上下文中没有myname,他就会去它outer指向的地方,它outer指向foo,它就会去foo找,但是foo执行上下文被销毁,他就找到了foo留下的闭包里面有myname,把子俊哥哥改成了陈总,执行bar.getName()的时候同理。 image.png

最后的输出是

image.png

小知识点

  • 为什么栈的设计本身就很小?

因为如果栈设计的很大的话,那么栈中函数的上下文切换效率就会大大降低

  • 原始类型的赋值是值的复制,引用类型的赋值,是引用地址的复制

const声明的内容是原始类型的话不能修改,要是声明的内容是引用类型,不能修改地址,可以修改内容

看到这里了就不妨动动手点个赞吧,谢谢大家