javascript函数参数是值传递还是引用传递

suoniao 2021-01-20
需要:0索币

先来看两个个来自于 《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 c 是 num1 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 上 。

问题就在于 num 和 obj2 没变。

从上面的代码可以看出,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 * 10
  b.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

回帖
  • 消灭零回复
广告位招租 100元/月 广告位招租 100元/月
相关主题
javascript数组相关函数length属性、delete关键字、pop()栈方法、shift() 0
javascript实现去除左右空白的trim方法 0
javascript函数参数是值传递还是引用传递 0
javascript面向对象基础 0
React.js入门与实战开发适配PC端及移动端新闻头条平台 0
web前端开发项目商品秒杀界面开发 0
ECMAScript6编程艺术27章 0
JAVASCRIPT 基于ES6语法实战彩票项目 0
javascript AJAX实战课 0
html5加css3和javascript实战之仿腾讯网首页 0
HTML5和CSS3加JAVASCRIPT前端仿凤凰网实战含源码 0
web前端培训javascript教程 js教程完全攻略 0
WEB前端JavaScript面试技巧全套 0
设计模式Javascript版本讲解教程 0
JAVASCRIPT高级教程 0
JS实现按回车键提交数据 回车点击事件监听 0
js函数深入讲解,js实现计算器,js面向对象 0
web前端面试题javascript面试题讲解视频 0
JavaScript高级+项目实战+设计模式 0
相关主题
原来EUC-CN是gb2312编码的一种表示方法 0
索鸟快传2.1.0版本发布 0
golang网络数据读取timeout的处理 – 以SetReadDeadline为例 0
索鸟网广告位招租 0
golang利用通道chan实现一个通用的TCP连接池 0
frpc和frps的交互流程详解了解了frp是如何进行TCP代理的 0
golang实现内存池 go语言字节池byte pool实现代码 0
golang网络编程之基于TCP协议实现长连接 golang心跳检测 0
国产光刻机产业链最新核心材料ArF 193nm光刻胶通过用户认证 0
javascript数组相关函数length属性、delete关键字、pop()栈方法、shift() 0
Qt为我们提供了几个可以用于线程Sleep的函数msleep和usleep 0
QT程序闪退错误捕获教程利用DbgHelp 错误调试技术 0
简单快速修改Qtcreator项目工程的名称 0
golang 的interface接口类型断言 0
golang类型断言type的使用 0
golang依赖包管理 mod使用教程 0
golang表单验证库validator 0
如何使用Go语言实现一个简单的异步任务框架呢?生产者消费者模型 0
内网穿透反向代理工具frp实现TCP协议代理源码分析 0
内网穿透反向代理库frp的实现原理分析一 0