前言
通过前面的文章我们对js的一步一步的深度了解js这门语言,我们已经到了入门的阶段因为我们已经了解到了里面到底有哪些机制了,我们今天就来聊一聊在js中的一个底层的知识——js的内存机制
内存机制
当我们提到内存机制,其实在我们日常的学习中会对这个概念进行忽视,但如果不了解js中的内存机制的话我们就绝对不可能说自己对这门语言精通掌握,让我们先以代码的方式来了解一下这个概念 让我我们来看一下下面两段代码
let a = 1
let b = a
a = 2
console.log(b);
//输出1
let a = {name:'zyx',age:18}
let b = a
a.age = 19
console.log(b.age);
//输出19
我们先看一下第一段代码:我们定义了一个变量a=1,定义了一个b=a,之后又将a的值改为2那我们发现b的值并没有发生改变,没何的影响,
相比之下我们再看一下第二段代码,我们定义了一个对象a 也将a的值赋给了b,我们同样将a.age进行更改,我们发现b中的age受到了a修改的影响。
为了弄明白这个个问题我们就要先从js是一种什么样子的语言,那就让我们来了解一下语言的类型
1.语言类型
我们在学习C语言,Java的学习中,当我们要定义一个具体变量要敲定好变量的具体类型,相比之下我们在学习js里我们都并没有上面的操作,我们都是直接用var, let ,conts来定义变量,我破门根据这两个区别来对语言进行分类
- 静态语言:在使用前就需要确定其变量的数据类型
- 动态语言:在运行的过程中检查数据的类型
显然js就是动态语言,js定义的变量是通过在代码运行的过程中v8引擎来检查数据的具体类型来进行的。
其实在语言的分类中还有一种分类,它会根据在语言执行的过程中是否能进行隐式类型转换,(将不同类型的值之间进行转换)分为了:弱类型语言和非弱类型语言
- 支持隐式类型转换的语言 —— 弱类型语言
- 不支持隐式类型转换的语言 —— 弱类型语言
现在我们就知道了Javascript是一个弱类型的动态的语言,这也就意味着我们写代码并不需要告诉这个引擎变量的类型,它会在执行规程中自己识别出来,同时我们也可以使用同一个变量来保存不同的数据类型。
当我们在js中想要查看一个变量的具体类型时可以使用typeof + 变量 来进行查看。当我们使用该方法来查看对象的具体类型时会发现在产看基础数据类型时会显示其类型名,再查看引用类型时会出现object,而其中存在两个特例,当我们查看函数类型会输出function,这是函数中独有特征,而当我们识别null是也会出现object这一个bug这就是因为该方法在v8进行对变量汇集那个这些变量进行二进制转换而当读取到000开头的二进制变量时会将其默认识别成为object而null则全都是0则被识别为对象。
2.内存空间
我们要了解js在运行过程中数据是如何存储的,我们就必须先要搞明白存储空间的种类
当v8引擎在执行我们们所写的代码过程当中,就会在我们的运行空间中占用一部分,而这片空间就会被划分成上面图片所示的三部分空间。代码空间自然是来存储代码的,而堆空间和栈空间的存储让我们来根据一份代码来看一下
function foo(){
var a = 1;
var b = a;
var c = {name:'zyx'}
var d = c
}
foo()
让我们一起来看一下上面代码是如何存储的吧
在之前js的运行机制中我们只讲到基础类型和let、const定义的变量分别存放在变量环境和词法环境中,根据上图可以看到旁边有个名字叫堆的存储空间,而之前没讲到的引用类型正是存放在堆中。引用类型在堆中的存放和数组中的元素存放差不多,可以看成键值对的样子,每个地址用来储存不同的数据。
在了解完了堆的一些基础认识后,我们来看看代码是如何执行的:我们直接从foo部分来看,在编译完成后,foo中四个变量都是un,然后我们执行foo,a、b这个大家肯定都知道,那么当执行到var c = {name: 'zyx'}的时候,c会被赋值成堆中的一个地址,可以用来访问该地址中存放的数据,就类似于c语言中的指针一样。然后var d = c会将d的值同样赋值成一个地址,由于c已经被赋值成了#001,那么d也会被赋值成#001用来储存地址以此用来访问数据。
-- 栈的设计本身就很小:因为如果栈设计的很大的话,那么栈中的函数的上下文切换效率就会大大降低
-- 原始类型的赋值是值得复制,引用类型的赋值是引用地址的复制
能力提升
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);
我们来看一下上面代码如何存储并与并运行的。
根据调用栈中的运行过程我们可以看到
person首先被赋值成了#001,然后随着fn的执行person中的age:18被修改成了age:19,从而导致堆中#001的地址中的age被修改成了19,然后person又被赋值成了一个新对象,而新对象在堆中存放的地址是#002,此时person存放的是地址#002,所以最后p1输出的是age被修改之后#001的值,而p2则是获得了函数返回的person = #002。
最后让我们来看一个思考题结合上下文和我们之前所学习的知识点来思考一下代码运行结果以及存储结构应该为什么样子的吧。
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());