基本类型值和引用类型值

作者 糖一瓶 日期 2018-02-19
基本类型值和引用类型值

基本概念:

基本类型值:number、 string、 boolean、 undefined、 null
引用类型值:Object、 Array、 Function、 RegExp、 Math、 date

它们的区别:
基本类型值在内存中都是”栈内存”,引用类型是”堆内存”
从一个变量赋值另一个变量的时候,若赋值的基本类型,会创建这个值的副本。
从一个变量赋值另一个变量的时候,若赋值的是引用类型值,赋值的其实是指针,因此两个变量指向的是同一个对象。

面试题详解

  • 面试题1

    var a = {x:1};
    var b = a ;
    a.x = 2
    b.x; //2

    a = {x:3};
    b.x ; //2
    a.x = 4;
    b.x; //2

详解每一个b.x的值
首先定义了一个对象a,有一个属性x,值为1.接着让b=a,这一步的结果就是a和b指向了同一个对象。
在栈里会保存两份1,分别赋值给a和b。修改a或b,对另一个变量不会有影响。
如下图:
1
对象则不然,变量a和b如果被赋值对象,a和b实际上保存的只是对象的地址,而且a和b还是被存储在栈里,同时a和b的地址是相同的。但对象是在堆里保存,且只保存一份,对象的地址就是a和b的值,a和b都指向同一个对象。这与C里面的指针类似,修改指向同一个对象的任何一个变量,与之引用同一对象变量很快就会发生同样的变化。如下图:
2
所以现在的情况就是,a和b都指向了堆中的一个对象,这个对象的属性x值是1。那么a.x = 1,b.x自然也等于1
1 a.x = 2
接下来发生一件事情,a修改了对象的x属性为2,这个变化反映到了堆中:
3
看,a和b还是指向了同一个对象,只不过对象中的x属性值变成了2。这一变化b很快就发现了,所以你再去访问b.x,实际上就是访问堆中的对象的x属性,也就是2。
1 a = {“x”:3};
再后来,为a赋值了一个新的对象,虽然它也有一个属性x,但它确实是一个新对象!那么内存堆中发生了什么呢?首先,堆中原有的对象(x = 2的那个)还在那里。因为新建了一个对象(x = 3的),堆中就会出现一个新的对象,与原来的对象毫无关系。同时,b并没有变化,它还指向原有的对象(x = 2),但a指向原来的对象的地址却发生了变化,它指向了x = 3的这个新对象。堆中情况如下:
4
如上图,a的地址变了,同时a和原来的对象也没有指向关系了,它指向了新的对象,这个新对象的x = 3。而b对象没有任何变化,它还坚守着自己的对象,对象的x属性是2。
1 a.x = 4;
接着修改了a的x属性为4,参考前面的描述,我们可以知道堆的变化如下:
5
瞧,a的x属性变成了4,但b和a已经没有关系了,所以b的x属性还是2。

PS:以上地址什么的,都是随便写的,实际内存中肯定不是这些数字。

  • 面试题2

    var a = {n:1}; 
    var b = a;
    a.x = a = {n:2};
    console.log(a.x);// --> undefined
    console.log(b.x);// --> [object Object]

上面的例子看似简单,但结果并不好了解,很容易把人们给想绕了——“a.x不是指向对象a了么?为啥log(a.x)是undefined?”、“b.x不是应该跟a.x是一样的么?为啥log出来居然有2个对象”
当然各位可以先自行理解一下,若能看出其中的原因和工作机理自然就无须继续往下看啦。
下面来分析下这段简单代码的工作步骤,从而进一步理解js引用类型“赋值”的工作方式。
首先是
var a = {n:1};
var b = a;
在这里a指向了一个对象{n:1}(我们姑且称它为对象A),b指向了a所指向的对象,也就是说,在这时候a和b都是指向对象A的:
6
这一步很好理解,接着继续看下一行非常重要的代码:
a.x = a = {n:2};
我们知道js的赋值运算顺序永远都是从右往左的,不过由于“.”是优先级最高的运算符,所以这行代码先“计算”了a.x。
这时候发生了这个事情——a指向的对象{n:1}新增了属性x(虽然这个x是undefined的):
7
从图上可以看到,由于b跟a一样是指向对象A的,要表示A的x属性除了用a.x,自然也可以使用b.x来表示了。
接着,依循“从右往左”的赋值运算顺序先执行 a={n:2} ,这时候,a指向的对象发生了改变,变成了新对象{n:2}(我们称为对象B):
8
接着继续执行 a.x=a,很多人会认为这里是“对象B也新增了一个属性x,并指向对象B自己”
但实际上并非如此,由于( . 运算符最先计算)一开始js已经先计算了a.x,便已经解析了这个a.x是对象A的x,所以在同一条公式的情况下再回来给a.x赋值,也不会说重新解析这个a.x为对象B的x。
所以 a.x=a 应理解为对象A的属性x指向了对象B:
9
那么这时候结果就显而易见了。当console.log(a.x)的时候,a是指向对象B的,但对象B没有属性x。没关系,当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。但当查找到达原型链的顶部 - 也就是 Object.prototype - 仍然没有找到指定的属性B.prototype.x,自然也就输出undefined;
而在console.log(b.x)的时候,由于b.x表示对象A的x属性,该属性是指向对象B,自然也输出了[object Object]了,注意这里的[object Object]可不是2个对象的意思,对象的字符串形式,是隐式调用了Object对象的toString()方法,形式是:”[object Object]”。所以[object Object]表示的就只是一个对象罢了