文章目录
  1. 1. 问题的起源
  2. 2. 数据类型
  3. 3. 声明变量时不同的内存分配
  4. 4. 不同的内存分配机制也带来了不同的访问机制
  5. 5. 复制变量时的不同
  6. 6. 函数参数传递的不同
  7. 7. 特殊的类型—String
    1. 7.1. 什么是字符串的不可变?

问题的起源

其实这个问题来源于C和C++,因为C或C++里都有一个特殊的数据类型—-指针,那时候所谓的传值和传引用是对指针来说的。那么针对指针来说,什么是传值,又什么是传引用呢?首先,指针作为一种数据类型,其本身肯定是占用一定的内存空间,而且指针同时还要指向另一块内存空间。

image5.png
针对指针来说,指针作为一种数据类型,指针的标识符也就是其在内存中所在的地址,指针的值就是其所指向的地址。也就是说,指针值是地址。
那么一个指针在作为函数的参数的时候,传给参数的到底是指针的地址还是指针的值呢? —-这就是传值和传引用问题的起源。

数据类型

在 javascript 中数据类型可以分为两类:

  • 原始数据类型 primitive type,比如Undefined、Null、Boolean、Number、String(除外)。
  • 引用类型 Object type,比如Object、Array、Function、Date等。

PS:本文所说的原始数据类型也称为简单数据类型,引用类型也称为复杂数据类型。

声明变量时不同的内存分配

原始值: 存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 - 栈中。这样存储便于迅速查寻变量的值。String类型除外。
引用值 :存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
image1.jpg

不同的内存分配机制也带来了不同的访问机制

在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。而原始类型的值则是可以直接访问到的。

复制变量时的不同

原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。
image2.jpg
引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)
image3.jpg

函数参数传递的不同

首先我们应该明确一点: ECMAScript中所有函数的参数都是按值来传递的。但是为什么涉及到原始类型与引用类型的值时仍然有区别呢,还不就是因为内存分配时的差别。
原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。
引用值:对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象呀。或许我这么说了以后你对书上的例子还是有点不太理解,那么请看图吧:
image4.jpg
image13.png
所以,如果是按引用传递的话,是把第二格中的内容(也就是变量本身)整个传递进去(就不会有第四格的存在了)。但事实是变量把它里面的值传递(复制)给了参数,让这个参数也指向原对象。因此如果在函数内部给这个参数赋值另一个对象时,这个参数就会更改它的值为新对象的内存地址指向新的对象,但此时原来的变量仍然指向原来的对象,这时候他们是相互独立的;但如果这个参数是改变对象内部的属性的话,这个改变会体现在外部,因为他们共同指向的这个对象被修改了呀!
image14.png
对于传引用来说,形参的值是指针的地址,那么每次对形参的改变,其实改变的都是指针所指向的地址。而如果向取得结构体的地址,也需要通过实参的地址。

特殊的类型—String

字符串也是一种数据类型,那么字符串的值保存在哪里呢? 其作为函数参数的时候,传值是如何进行的呢?
字符串虽然也是一种基本数据类型,但因为其大小不固定,所以,其一般其更像与引用数据类型。但其又有其特殊性—-不可变性。

什么是字符串的不可变?

1
2
3
4
5
var str = "";
for (var i = 0; i < 3; i++) {
str += i;
}
console.log(str); //012

看上图程序,字符串是可以改变的。那为什么还要说,字符串不可变呢,先别急,我们来分析一下程序运行时内存结构图。
image7.png
image8.png
image9.png
image10.png
看到没有,每一次字符串值的改变,其所指向的地址都会跟着改变一次。其所说的不可变,是跟引用数据类型相比来说,引用数据类型值的改变一般是对象本身的改变,而其指向是不变的,而字符串值的改变是其指向地址的改变。所以,字符串的每一次改变都会产生垃圾,此垃圾过一段时间会被垃圾回收机制回收。

了解了上述的情况,所以字符传在作为传值的时候,为了节省空间,只是复制了字符串所指向的地址给形参,而形参的值如果改变了的话,因为字符串具有不可变特性,所以会重新开辟一份空间给形参。如下:

1
2
3
4
5
6
var str = '你是谁?';
function change(str) {
str = '我就是我了';
}
change(str);
console.log(str); //你是谁

image11.png image12.png

字符串在Java C++中均作为比较特殊的一种类型,JS中虽然将其当作一种基本数据类型,但是其使用时更偏向于引用数据类型,但是其又具有不可变型,才促使我们可以将其当作基本数据类型使用。

转自:http://www.th7.cn/web/js/201503/90277.shtml
转自:http://fehacker.com/2014/12/19/call-by-sharing/

文章目录
  1. 1. 问题的起源
  2. 2. 数据类型
  3. 3. 声明变量时不同的内存分配
  4. 4. 不同的内存分配机制也带来了不同的访问机制
  5. 5. 复制变量时的不同
  6. 6. 函数参数传递的不同
  7. 7. 特殊的类型—String
    1. 7.1. 什么是字符串的不可变?