资讯专栏INFORMATION COLUMN

js中的值类型、引用类型、堆、栈、函数参数传递方式、连续赋值等概念的学习

hizengzeng / 1670人阅读

摘要:值类型基本类型和栈内存值类型也称为原始数据或原始值这类值存储在栈内存中基本类型的值不可以修改。目前中的基本类型一共有六种。堆的使用规则当创建数组时,就会在堆内存中创建一个数组对象,并且在栈内存中创建一个对数组的引用。

值类型(基本类型)和栈内存

值类型也称为原始数据或原始值(primitive value).这类值存储在栈(stack)内存中, 基本类型的值不可以修改。每当我们定义一个变量,并赋给它一个基本类型的值时,可以理解为,我们为这个变量绑定了一个内存空间,这个内存空间存放的就是变量的值。因此。基本类型数据是存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配。
目前js中的基本类型一共有六种:null,undefined,boolean,number,,string,symbol。其中symbol是es6中新加的数据类型。这些类型在内存中分别占用固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的、
让我们看一个基本类型的值不可以修改的示例

var a = 4;
a = 3; //注意,这里是覆盖,不是修改

var num1 = 5;
var num2 = num1;

num1+=1;
consol.log(num1);//6
console.log(num2);//5

从上面第二个例子可以看到,从一个变量向另一个变量复制基本类型的值,我们会在变量对象上重新创建一个新值,然后把值复制到新变量分配的位置上,这两个值是完全独立的,对着两个变量进行操作是互不影响的。

引用类型

这类值存储在堆内存中,堆是内存中的动态区域,相当于自留空间,在程序运行期间会动态分配给代码和堆栈。对中存储的一般都是对象,然后在栈内存中存储一个变量指针,计算机通过这个变量指针,找到堆中的数据块并进行操作。这种访问方式,我们叫它按引用访问。如图所示。

堆的使用规则
var fruit_1 = "apple";
var fruit_2 = "orange";
var fruit_3 = "banana";
var oArray = [fruit_1,fruit_2,fruit_3];
var newArray = oAarray;

当创建数组时,就会在堆内存中创建一个数组对象,并且在栈内存中创建一个对数组的引用。变量fruit_1、fruit_2、fruit_3为基本数据类型,他们的值直接存放在栈中;newArray、oArray为符合数据类型(引用类型),他们的引用变量存放在栈中,指向于存放在堆中的实际对象。
此时我们改变oAarray中的值,对应的newArray也会改变,因为它们的存储的指针指向同一个堆地址。

console.log(oArray[1]);// 返回 orange
newArray[1]="berry";
console.log(oArray[1]);// 返回 berry

例如,下面代码将newArray赋值为null:

newArray = null;

注意:接触一个值的引用并不意味着自动回收改值所占用的内存。解除引用的真正作用时让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

为什么会有栈内存和堆内存之分?

与垃圾回收机制有关,为了使程序运行时占用的内存最小。
当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;
当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复理由(因为对象的创建成本通常比较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。

函数中参数传递方式,按值传递和按引用传递

按值传递:函数的形参时被调用时所传实参的副本,修改形参并不会影响实参。

var num = 10;
function change(num){
    num = num * 10;
}
change(num)
console.log(num);       // 2

可以看到这里的变量num在运行完函数以后,值并没有发生改变。

按引用传递:函数的形参接收实参的内存地址,而不再是副本。这意味着函数形参的值如果被修改,实参也会被修改。

var ab={
    x:1,
    y:2
}

function foo(obj){
    obj.x = 2
}
foo(ab)

console.log(ab);    //{x:2,y:2}

可以看到,原来的ab对象,在函数foo调用之后,其中的对象属性发生变化。由上面的两个例子,我们是不是可以推断在js中,对于基本类型的数据,在函数传递过程中使用的时按值传递,而对于引用类型数据,在函数传递过程中使用的时是按引用传递方式呢?让我们再看另外一个例子。

var ab={
    x:1,
    y:2
}

function foo(obj){
    obj = {
        x:2,
        y:3
    }
}
foo(ab)

console.log(ab);    //{x:1,y:2}

这个示例,如果按照我们刚刚说的结论,这里的函数运行后,输出的对象ab的值应该是{x:2,y:3}。但是真正的结果确实{x:1,y:2}。这就奇怪了,到底js中的函数参数的传递方式是按什么样的方式呢。我在网上找了很多资料,发现有个说法叫按共享传递。简单来说,就是对于基本类型,是按值传递,对于对象而言,直接修改形参对实参没有效果,而修改形参的属性却可以同时修改实参的属性。而我的理解是这样的。看一张图。

由我们上面对引用类型与堆内存的介绍,我们知道,当我们定义一个ab对象时,系统会在堆中开出一个空间用来存储改对象,对应的在栈内存中开出一个空间,用来存储指向对象的地址。如上图,根据按引用传递的方式,我们知道,foo函数内部的obj。在函数编译的时候,是指向ab所指向的对象的,二者共用一个地址。当函数执行后,obj指向了新的对象地址,但是之前的ab所指向的对象属性,依旧被ab所引用,没有任何改变。所以,这里输出的ab的值依旧未变。
而对于函数内部,改变形参的属性值这个情况,我们也可以很容易的清楚,因为函数内部的obj和ab对象共同指向同一个对象空间,所以改变前者的对象属性的值,自然会影响后者

一道经典的面试题
var a = {n:1};
var b = a;

a.x = a = {n:2};
console.log(a.x);
console.log(b.x)

这道题考察了很多东西,js中的运算符的优先级,比如赋值运算,是从右到左的。js中的对象存储的问题。但是这道题很容易根据赋值运算是从右到左的顺序运行的来得到错误的结果

a = {n:2}
a.x = a

然后得到了错误的答案。a.x = {n:2}.实际上,要解出这道题,我们至少要知道两个,第一个就是运算符的优先级问题,点运算符的优先级高于赋值运算级。所以这里的执行顺序第一步肯定是a.x,然后才是从右到左的赋值运算;第二个,我们要知道的是,js对象在内存中是如何存储的。知道这两点,那这道题就不难解决了。同样的,我们继续看几张图。

通过该题的前两行代码的声明a和b,结合上面所说的对象存储的原理,可以很容易看明白。a和b指向同一个对象空间。

a.x = a = {n:2}

这行代码,首先会运行a.x。这样便会在{n:1}对象所存储的空间上添加一个x属性名,并且等待赋值。即原来的{n:1}变成{n:1,x:undefined}。然后,按照赋值运算的顺序,先将变量a的指向变为{n:2},但是这里要注意,{n:1,x:undefined}由于被b引用,所以依旧存在在内存当中。然后执行 ax.x = a,这里要注意,这里的a已经是{n:2}了。而a.x则是b指向的{n:1,x:undefined}中的x属性,所以最终b指向的{n:1,x:undefined}变为{n:1,x:{n:2}}。
所以最后的结果。看下图。

console.log(a.x);   //undefined
console.log(b.x);   //{n:2}


参考链接:
https://www.imooc.com/article...
https://blog.csdn.net/lxiang2...
https://blog.csdn.net/xdd1991...
https://segmentfault.com/a/11...
https://blog.csdn.net/u012860...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97561.html

相关文章

  • 第二章 一切都是对象

    摘要:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。 一、对象和类的存储 根据java虚拟机规范第七版的规定,Java虚拟机所管理的内存将包括以下几个运行时数据区域:程序计数器、方法区、堆、虚拟机栈、本地方法栈。(详见深入理解jav...

    孙吉亮 评论0 收藏0
  • 前端进击巨人(二):、队列、内存空间

    摘要:中有三种数据结构栈堆队列。前端进击的巨人一执行上下文与执行栈,变量对象中解释执行栈时,举了一个乒乓球盒子的例子,来演示栈的存取方式,这里再举个栗子搭积木。对于基本类型,栈中存储的就是它自身的值,所以新内存空间存储的也是一个值。 面试经常遇到的深浅拷贝,事件轮询,函数调用栈,闭包等容易出错的题目,究其原因,都是跟JavaScript基础知识不牢固有关,下层地基没打好,上层就是豆腐渣工程,...

    edgardeng 评论0 收藏0
  • ​搞不懂JS赋值·浅拷贝·深拷贝请看这里

    showImg(https://segmentfault.com/img/bVbvpCA); 前言 为什么写拷贝这篇文章?同事有一天提到了拷贝,他说赋值就是一种浅拷贝方式,另一个同事说赋值和浅拷贝并不相同。我也有些疑惑,于是我去MDN搜一下拷贝相关内容,发现并没有关于拷贝的实质概念,没有办法只能通过实践了,同时去看一些前辈们的文章总结了这篇关于拷贝的内容,本文也属于公众号【程序员成长指北】学习路线...

    lauren_liuling 评论0 收藏0
  • JavaScript学习总结(一)基础部分

    摘要:前缀规范每个局部变量都需要有一个类型前缀,按照类型可以分为表示字符串。例如,表示以上未涉及到的其他对象,例如,表示全局变量,例如,是一种区分大小写的语言。布尔值与字符串相加将布尔值强制转换为字符串。 基本概念 javascript是一门解释型的语言,浏览器充当解释器。js执行时,在同一个作用域内是先解释再执行。解释的时候会编译function和var这两个关键词定义的变量,编译完成后从...

    AlanKeene 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<