1. 例子

先来看两个个来自于 《JavaScript 高级程序设计》P70-P71 的两个例子。

1.1. 基本类型参数传递

function addTen(num) {num += 10;return num;
}var count = 20;
var result = addTen(count);
alert(count); // 20, 没有变化
alert(result); // 30

书上解释说,JavaScript 参数传递都是按值传参。

所以传递给 addTen 函数的值是 20 这个值,所以函数执行结束原始变量 count 并不会改变。

1.2. 引用类型参数传递

function setName(obj) {obj.name = 'Nicholas';obj = new Object();obj.name = 'Greg';
}var person = new Object();
setName(person);
alert(person.name); // Nicholas

为什么结果是 Nicholas 呢?

疑问:如果是传值,那应该是把 person 变量的值(也就是一个指向堆内存中对象的指针)传递到函数中,obj.name = 'Greg'; 改变了堆内存中对象的属性,为什么 person.name 还是 Nicholas

2. 传值还是传引用?

让我们再将上面两个例子综合为下面的例子:

function changeStuff(a, b, c) {a = a * 10;b.item = "changed";c = {item: "changed"};
}var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};changeStuff(num, obj1, obj2);console.log(num);
console.log(obj1.item);
console.log(obj2.item);

最终的输出结果是:

10
changed
unchanged

所以 JS 到底是传值调用还是传引用调用呢?要弄清楚这个问题,首先我们要明白到底什么是传值调用(Call-ny-value)传引用调用(Call-by-reference)

2.1. 传值调用(Pass by value)

在传值调用中,传递给函数参数是函数被调用时所传实参的拷贝。在传值调用中实际参数被求值,其值被绑定到函数中对应的变量上(通常是把值复制到新内存区域)。

changeStuff 的参数 a b cnum1 obj1 obj2 的拷贝。所以无论 a b c 怎么变化,num1 obj1 obj2 都保持不变。

问题就在于 obj1 变了。

2.2. 传引用调用(Pass by reference)

在传引用调用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。通常函数能够修改这些参数(比如赋值),而且改变对于调用者是可见的。

也就是说 changeStuff 函数内的 a b c 都分别与 num obj1 obj2 指向同一块内存,但不是其拷贝。函数内对 a b c 所做的任何修改,都将反映到 num obj1 obj2 上 。

问题就在于 numobj2 没变。

从上面的代码可以看出,JavaScript 中函数参数的传递方式既不是传值,也不是传引用。主要问题出在 JS 的引用类型上面。

JS 引用类型变量的值是一个指针,指向堆内存中的实际对象。

2.3. 传共享调用(Call by sharing)

还有一种求值策略叫做传共享调用(Call-by-sharing/Call by object/Call by object-sharing)。

传共享调用和传引用调用的不同之处是,该求值策略传递给函数的参数是对象的引用的拷贝,即对象变量指针的拷贝。

也就是说, a b c 三个变量的值是 num obj1 obj2 的指针的拷贝。 a b c 的值分别与 num obj1 obj2 的值指向同一个对象。函数内部可以对 a b c 进行修改可重新赋值。

function changeStuff(a, b, c) {a = a * 10; // 对 a 赋值,修改 a 的指向,新的值是 a * 10b.item = "changed"; // 因为 b 与 obj1 指向同一个对象,所以这里会修改原始对象 obj1.item 的内容c = {item: "changed"}; // 对 c 重新赋值,修改 c 的指向,其指向的对象内容是 {item: "changed"}
}

3 代码分析

接下来让我们再来分析一下代码。

3.1 变量初始化

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

3.2 调用函数

changeStuff(num, obj1, obj2);

可以看到,变量 a 的值就是 num 值的拷贝,变量 b c 分别是 obj1 obj2 的指针的拷贝。

函数的参数其实就是函数作用域内部的变量,函数执行完之后就会销毁。

3.3 执行函数体

a = a * 10;
b.item = "changed";
c = {item: "changed"};

如图所示,变量 a 的值的改变,并不会影响变量 num

b 因为和 obj1 是指向同一个对象,所以使用 b.item = "changed"; 修改对象的值,会造成 obj1 的值也随之改变。

由于是对 c 重新赋值了,所以修改 c 的对象的值,并不会影响到 obj2

4. 结论

从上面的例子可以看出,对于 JS 来说:

  • 基本类型是传值调用

  • 引用类型传共享调用

传值调用本质上传递的是变量的值的拷贝。

传共享调用本质上是传递对象的指针的拷贝,其指针也是变量的值。所以传共享调用也可以说是传值调用。

所以《JavaScript 高级程序设计》说 JavaScript 参数传递都是按值传参 也是有道理的。

本文同步于我的博客 https://github.com/nodejh/nodejh.github.io/issues/32

JavaScript 是传值调用还是传引用调用?相关推荐

  1. Java中的形参和实参的区别以及传值调用和传引用调用

    名词解析: 1.形参:用来接收调用该方法时传递的参数.只有在被调用的时候才分配内存空间,一旦调用结束,就释放内存空间.因此仅仅在方法内有效. 2.实参:传递给被调用方法的值,预先创建并赋予确定值. 3 ...

  2. 简单了解函数的传值调用与传址调用(C语言)

    一.首先需要引入函数中的实际参数与形式参数 实际参数(实参)是指真实传给函数的参数.实参可以是常量.变量.表达式.函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值 ...

  3. 【 C 】函数参数通过传值调用还是传址调用?

    C 函数的所有参数均以 "传值调用" 方式进行传递,这意味着函数将获得参数值的一份拷贝.这样函数就可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数. 既然调用函数 ...

  4. 手写C语言之函数概念-函数分类-实参与形参-传值调用与传址调用介绍(11)

    目录 函数是什么? C语言中函数的分类 库函数 自定义函数 写一个函数可以找出两个整数中的最大值. 交换整型变量的函数 函数的参数 实际参数(实参) 形式参数(形参) 函数的调用 传值调用 传址调用 ...

  5. 函数的调用(传值调用和传址调用)

    传值调用 函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参. 传址调用 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式. 这种传参方式可以让函数和函数外边的变量建 ...

  6. 函数学习笔记(一) 传值调用、传址调用.

    前言  函数是程序的一个子程序,自己定义的函数与主函数的地位·相同. 函数分为: 库函数.自定义函数 一.库函数 在学习函数之前我们就有接触到了函数--库函数 比如说printf.scanf.getc ...

  7. php中什么时候用传值,php中传值与传引用的区别。什么时候传值什么时候传引用?...

    java中的this与super的区别 java中的this与super的区别 1. 子类的构造函数如果要引用super的话,必须把super放在函数的首位 代码如下: class Base { Ba ...

  8. 传值调用 与传地址调用(传引用)

    转自:http://myturn.blog.hexun.com/15584978_d.html #include <iostream> using namespace std ; void ...

  9. Java的形参、实参与传值调用、传地址调用

    形参:方法声明时小括号内声明的变量,如下面的 int i: void fun(int i){i=i+1; } 是为了声明此方法需要传入的参数类型.在方法被调用时创建,在方法结束时销毁,只作用于方法内部 ...

最新文章

  1. linux c 编译错误 conflicting types for 的解决办法
  2. linkText()的用法
  3. 几种常见窗函数及其matlab程序实现,几种常见窗函数及其MATLAB程序实现(20200911110057).pdf...
  4. .Net Core3.0使用gRPC
  5. java 调用 mahout_java – 运行Mahout本地获取MahoutDriver的ClassNotFoundException
  6. 空白DirectX11应用程序
  7. 谈一下ACM的入门书籍及方法
  8. 最近,华为应用市场上线了一个服务
  9. oracle 高速保存数据,教你怎样在Oracle数据库中高速导出/导入(一)
  10. LMS自适应滤波器的FPGA实现
  11. 计算机cpu天体图,电脑cpu天梯图2019|最新Intel/AMD处理器性能排行2019
  12. 微信小程序图书借阅系统+后台管理系统
  13. 【Python】unittest中执行用例通过但是报错:OSError: [WinError 6] 句柄无效。
  14. micropython stm32f107_stm32f107 USART3数据接收错误问题
  15. java模拟魔兽世界武器掉落
  16. idea的java项目怎么连数据库_idea 使用Java连接SQL Server数据库教程
  17. python 学习过程中所收藏博客原文链接666666
  18. JAVA实现Excel照相机功能_Excel如何将工作表转换为图片,并随着数据的变化自动更新?...
  19. 你还在用if-else吗?
  20. 修复计算机黑屏,笔记本电脑黑屏怎么修复 笔记本电脑黑屏修复方法【详解】...

热门文章

  1. 局部类型 之 部分方法
  2. Linux下的字符集问题
  3. 用户、组以及相关文件说明
  4. Tomcat : IOException while loading persisted sessions: java.io.EOFException
  5. CCNA实验(9) -- Frame Relay
  6. Ruby BigDecimal库拒绝服务漏洞
  7. matplotlib - 极坐标上的散点图
  8. 将 gitblog 的博客内容搬迁到 CSDN
  9. 【二】Drupal 入门之新建主题
  10. Mysql中字段类型不一致导致索引无效