前言
在现代Web开发中,JavaScript的内存机制是一个至关重要却常常被忽视的主题。随着应用程序的复杂性增加,理解JavaScript如何管理内存变得愈加重要。内存机制不仅影响性能,还会在不当使用时导致内存泄漏和其他问题。本文将带大家探索JavaScript,让大家对JavaScript这门语言有更加深刻的认识。
在讲js内存机制之前我们先来认识下一门语言的类型
1.在使用前就要确定其变量的数据类型 ---------静态语言 例( c)
2.在运行时检查数据的类型 -------动态语言 js python
3.支持原始类型转换的语言 ------弱类型语言 js
4.不支持原始类型转换的语言 --------强类型语言 java
js是一门弱类型语言的动态语言,也就是说js支持原始类型转换,而且是在运行时检查数据的类型。
js的内存机制
在js中,它的内存空间主要是由三个部分所组成:代码空间、栈空间、堆空间。代码空间负责存放用户输入的代码,栈空间负责存放原始类型和函数执行上下文
而今天我们就要着重来讲解堆空间,用几段代码来带你们认识堆空间
let a = 1
let b = a
a = 2
console.log(b)
let c = {
name: 'zhangsan',
age: 18
}
let d = c
c.age = 20
console.log(d.age)
我们可以看到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的情况
内存空间
大部分空间都给了堆,栈存储原始类型不要太多空间
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,我们可以看到下图的结果
看到这里想必你对堆有些认识了,接下来我们要来点有难度的了,考验下你对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()的时候同理。
最后的输出是
小知识点
- 为什么栈的设计本身就很小?
因为如果栈设计的很大的话,那么栈中函数的上下文切换效率就会大大降低
- 原始类型的赋值是值的复制,引用类型的赋值,是引用地址的复制
const声明的内容是原始类型的话不能修改,要是声明的内容是引用类型,不能修改地址,可以修改内容
看到这里了就不妨动动手点个赞吧,谢谢大家