从C语言开始

有时候讲一些细节或是底层的东西,我喜欢用C语言来讲,因为用C更方便来描述内存里面的东西。先举一个例子,swap函数,相信有一些编程经验的人都见识过,声明如下,函数体我就不写了,各位脑补一下。
  1. void swap1(int a, int b);
  2. void swap2(int* a, int* b)

这里swap1是不能交换两个数的值的,swap2可以。那为什么呢?有教材会说,第一个是值传递,第二个是引用传递,传递的是指针,所以第二个可以。好吧,这个解释和没说一样,那下面我就来解释一下,调用这两个函数的时候,到底发生了什么,为什么一个可以交换,另一个不可以。为了方便描述,我把这两个函数的调用代码也写出来

  1. int main() {
  2. int a = 3;
  3. int b = 4;
  4. swap1(a, b);    //此时a = 3, b = 4;
  5. int* pa = &a;
  6. int* pb = &b;     //为了方便解释,增加这两个临时变量,否则直接写swap2(&a, &b)的话,这行代码做的事情太多,不好解释。
  7. swap2(pa, pb);    //此时a = 4, b = 3;
  8. return 0;
  9. }

函数的执行是在栈中,下图描述了swap1执行开始和结束的时候,栈中的情况。

左图为执行前,右图为执行后。当main函数调用swap1函数的时候,将两个入参a,b压栈,压栈采用的是复制的方式,当swap1执行的时候,修改了swap1栈空间的两个值,但是main函数中的两个值没有受影响。这就是值传递。把入参的值复制压栈来传入参数。下面来看swap2的情况
左图为执行前,右图为执行后。其中最左一列为内存地址。这里地址,压栈方向,地址顺序均为示例。各位看到没有,所谓引用传递,其实还是值传递,传递的时候还是采用复制压栈,只是传递的“值”是个地址。swap2执行的时候,执行*a = temp.给*a 赋值,这行语句的意思是修改a这个地址指向的内存的值,由于这个地址指向的位置在main方法的栈空间中,所以实现了修改原来值。
 
以上就是C语言在实现swap函数时候内存的细节,下面我们来探讨一下JS中调用函数的情况。由于JS中没有&这个取地址符号,那么JS中传递的到底是什么呢?

回到JS

 
JS中的数据类型有数字,布尔,数组,字符串,对象,null, undefined. 那么当用这些数据类型来作为函数的参数的时候,到底是引用传递还是值传递呢?先说结论:布尔,数字是值传递,字符串,数组,对象是引用传递。事实上字符串,数组也可以作为对象。null和undefined在传递的时候到底是什么,我不清楚。如果有熟悉的大神请帮忙解释一下。在这里小弟先谢了。
 
布尔,数字在作为参数传递的时候,其实现和C语言一样,这里不做赘述。也是将调用者的局部变量复制压栈,传递给被调用者。下面我来详细的描述一下对象是如何传递的。以一个函数来举例,假设你需要实现一个函数,将一个传入的数组反序,reverse,下面有两个实现,请各位来看一下有什么问题:
  1. function reverse1(array) {
  2. var temp = [];
  3. for (var i = array.length - 1; i > -1; i--) {
  4. temp.push(array[i]);
  5. }
  6. array = temp;
  7. }
  8. function reverse2(array) {
  9. var temp = [];
  10. for (var i = array.length - 1; i > -1; i--) {
  11. temp.push(array[i]);
  12. }
  13. for (var i = 0; i < array.length; i++) {
  14. array[i] = temp[i]
  15. }
  16. }

这两个函数都是先将一个反序完成的数组存储在temp里面,然后赋值给入参array,就是赋值的方式有所不同。这个不同的赋值方式也导致了结果的不同,结果就是reverse1无法完成工作,reverse2可以。为了解答这个问题,我先讲一下JS里面,内存中对象是如何存储的。当一行代码 var temp = [] 被运行的时候,内存中是这样的:

其中蓝色的是栈,黑框的是堆,用来动态分配内存,最右绿色的表示这段堆的起始地址。也就是说当声明一个对象的时候,栈中保存的内容只是一个指针,真正的内容在堆中。以此为基础,我们再来看一下当函数reverse1执行的时候,内存中如何实现的。为方便举例,假设传入的数组为[1,2,3];

上图为执行前,下图为执行后。当函数reverse1执行时,in作为参数传入。传入参数时,类似C语言的引用传递,将地址复制了一份,压栈传到子函数中。所以两个函数中的变量是指向同一个位置的。当reverse1执行时,temp中存储了array的反序,最后一行赋值的时候,你就看到了如下面的图表示的那样,reverse1中的array确实指向了新的反序数组,但是调用者中的局部变量in却丝毫未动。所以导致了reverse1无法完成反序功能。

那么我们再看reverse2. reverse2中的第二个循环逐个给数组的内容复制,其实它操纵的内存空间就是array指向的区域,我们又知道array和in指向了同一个区域,所以in指向的区域也被改变了。

总结一下以上所说的,

  1. JS中布尔,数字为基本数据类型,是值传递。无法作为引用传递。所以JS中无法实现基本数据类型的swap函数。
  2. 对象是引用传递。当传递对象给子函数时,传递的是地址。子函数使用这个地址来操作修改传入的对象。但是如果在子函数修改该地址指向的位置时,这个改变将无法作用于调用者。
  3. 引用传递其实还是值传递,只是传入的值是个地址,并且该地址指向了一段保存了对象数据的内存。这点和C中的引用传递类似。

特别说一下String

String是JS的内置对象,所以根据上文所说,它是引用传递。那么下面我请你写一个函数,将传入的String修改,给它两头加上引号。所以很明显,下面这样的函数就是错误的了
  1. function foo(s) {
  2. s = "\"" + s + "\""
  3. }

那么正确的函数应该怎写呢?你可能会想,应该使用String对象的函数来修改String的内容。这么想是对的,但是很不幸,JS提供的String没有任何一个可以修改String内容的函数。有人说不对,比如字符串连接函数,concat,转大小写函数toUpperCase,toLowerCase。事实上这两个函数只是返回了一个新的String对象,其原本的值兵没有改动。这个你可以去做实验看看。所以String对象被建立好之后,就再也无法改动了,所以无法用一个子函数来修改它的值。又由于String可以用 == 来判断其内容是否相等,所以它的各方面特性都很像基本数据类型。但是还有一点不一样,请看下面的例子:

  1. var a = 1
  2. var b = 1
  3. a == b            //true
  4. a === b           //true
  5. var s1 = "sdf"
  6. var s2 = "sdf"
  7. s1 == s2          //true
  8. s1 === s2         //true
  9. s3 = new String("sdf")
  10. s1 === s3         //false

对于数字,估计各位没有疑问吧。那么对于字符串来说,== 比较的是两个字符串的内容,这个应该也没有疑问。那么===呢?并且为什么s1===s2为true,s1===s3为false呢?

 
当用===来比较字符串的时候,事实上比较的是两个对象的地址。s1的值“sdf”这个字符串的地址,s3则是一个新的对象的地址。他们不相等,这个很好理解。那么s1 和 s2如何解释呢?这因为JS引擎有一个静态字符串存储区,当声明一个字符串常量的时候,会先去该存储区查找有没有相同的字符串,如果有就返回该字符串,没有再在静态字符串区重新初始化一个字符串对象。这就解释了为什么s1 === s2.
 
顺便说一句,就是字符串的不可变性,以及常量字符串区这两个特性,Java和JS是一样的。然而C++的STL中的std::string是可变的。

注:本文中的JS执行时的内存示例图并不是真正的JS引擎执行时候物理内存的样子。物理内存的实现取决于JS引擎。

最新文章

  1. [IOS 开发]TableView如何刷新指定的cell 或section
  2. Saddest&#39;s polar bear Pizza offered new YorkShire home
  3. codeforces 630KIndivisibility(容斥原理)
  4. ubuntu12.10可用更新源
  5. HttpServlet请求重定向
  6. HTML总结1
  7. JavaScript基础(一)
  8. POJ1789 Truck History 【最小生成树Prim】
  9. Easyui布局
  10. java自旋锁
  11. 用IFeatureWorkspaceAnno.CreateAnnotationClass 创建注记图层时报“The application is not licensed to modify or create schema”的错误的解决方案。
  12. webpack----webpack4尝鲜
  13. 【原创】Java基础之Session机制
  14. luogu P4183 [USACO18JAN]Cow at Large P
  15. 洛谷 P4515 [COCI2009-2010#6] XOR
  16. php 的 PHPExcel1.8.0 使用教程
  17. 域名(Domain Name)
  18. OpenCV颜色转换和皮肤检测
  19. 【Mac】安装 tesserocr 遇到的一些坑(‘cinttypes&#39; file not found)
  20. SQL Server 错误(待补充)

热门文章

  1. MyEclipse10+Jdk1.7+OSGI+MySql实现CRUD数据库
  2. 使用CAShapeLayer来实现圆形图片加载动画[译]
  3. IAR之工程配置
  4. ZOJ 2967 Colorful Rainbows 【Stack】
  5. USACO Cow Pedigrees 【Dp】
  6. 基于visual Studio2013解决算法导论之046广度优先搜索
  7. 一步一步重写 CodeIgniter 框架 (7) —— Controller执行时将 Model获得的数据传入View中,实现MVC
  8. java生产者消费者问题代码分析
  9. Android性能检测--traceview工具各个参数的意思
  10. C 语言学习 之搭建环境和熟悉命令