目录
前言
前面我们学习了关于函数和对象的内容,为了更好的理解这两个内容,关于他们存储数据的内容,形参和实参的种种,我们可以通过今天的博客来加深对他们的了解,也能通过数据结构的知识来理解这些内容,更多以理论为主。
一、简单类型和复杂类型
在了解简单类型和复杂类型之前,我们先来了解值类型和引用类型是什么意思:
- 值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型,比如string ,number,boolean,undefined,null
- 引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型通过 new 关键字创建的对象(系统对象、自定义对象),如 Object、Array、Date等
简单类型又叫做基本数据类型或者值类型,复杂类型又叫做引用类型。
二、栈和堆
简单类型和复杂类型,必然是拥有自己的存储空间的,他们是怎么存储的呢?我们通过栈和堆的概念来理解。
- 栈(操作系统):由操作系统自动分配释放存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈;简单数据类型存放到栈里面
- 堆(操作系统):存储复杂类型(对象),一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型存放到堆里面
注意:JavaScript中没有堆栈的概念,通过堆栈的方式,可以让大家更容易理解代码的一些执行方式,便于将来学习其他语言。
三、内存分配
简单类型和复杂类型的存储方式有一些不一样,我们分别来根据图看看他们的区别在哪里:
1.简单类型的内存分配
- 值类型(简单数据类型):string,number,boolean,undefined,null
- 值类型变量的数据直接存放在变量(栈空间)中
2.复杂类型的内存分配
- 引用类型(复杂数据类型):通过 new 关键字创建的对象(系统对象、自定义对象),如 Object、Array、Date等
- 引用类型变量(栈空间)里存放的是地址,真正的对象实例存放在堆空间中
四、传参
根据这两个类型的传递参数的方式,我们可以看出来他们在存储空间做的变化,以及数据传导的过程,可以方便我们来观察代码容易犯错的地方和如何优化代码的结构和存储。
1.简单类型传参
关于简单类型的传参,函数的形参也可以看做是一个变量,当我们把一个值类型变量作为参数传给函数的形参时,其实是把变量在栈空间里的值复制了一份给形参,那么在方法内部对形参做任何修改,都不会影响到的外部变量。
function fn(a) {
a++;
console.log(a);
}
var x = 10;
fn(x);
console.log(x);
我们通过调用函数赋予x新的数值,然而最终的结果是11 10,因为x=10,x并没有变化,函数传递的形参内经过函数虽然变化为11了,但是x还是=10,这个存储空间还是存在的。
2.复杂类型传参
关于复杂类型的传参,函数的形参也可以看做是一个变量,当我们把引用类型变量传给形参时,其实是把变量在栈空间里保存的堆地址复制给了形参,形参和实参其实保存的是同一个堆地址,所以操作的是同一个对象。我们来看看代码的演示:
function Person(name) {
this.name = name;
}
function f1(x) { // x = p
console.log(x.name); // 2. 这个输出什么 ?
x.name = "张学友";
console.log(x.name); // 3. 这个输出什么 ?
}
var p = new Person("刘德华");
console.log(p.name); // 1. 这个输出什么 ?
f1(p);
console.log(p.name); // 4. 这个输出什么 ?
最后的结果:
为什么会使这样的结果? 根据函数的调用和引用类型的传递方式,我们知道,最先找到的是序号为1的问题,会先输出已经定义好的对象之后,再执行下面的调用函数,所以第一个是刘德华。函数开始运行,因为之前已经定义了p的参数,p的参数传递给了x,将x的地址指向了p,于是第一个log输出就是指向了刘德华的地址。而函数里的第二个log,因为前面重新定义了新的变量,所以log输出了新的x变量。第四个为什么是张学友不是刘德华呢?因为在栈空间里保存的堆地址复制给了形参,所以根据操作同一个对象的原因,让原本的地址变成了新定义的变量张学友,导致最后打印出来的,是指向张学友地址的内容,而不是最开始定义的刘德华。
总结
理解简单类型和复杂类型的存储方式,他们传参的方式,来理解这个代码的流程是如何运行的,这样可以帮助我们去更好的理解代码,理论更多的是需要我们去认真的实践和理解他们。希望大家能够认真的学习这些理论来完善自己对代码的理解。