讲师中心 微信公众号

工具推荐

视频效率加速
updream

一键生成高质量推广成片

Seko
Seko

AI短剧一键生成成片

剧云
剧云

中文短剧剧本制作平台

立刻MV
立刻MV

低成本歌曲 MV、视频生成

JS 函数参数传递机制 - 值传递与引用传递的误解与真相剖析

云芳同学_6328

云芳同学_6328

发布时间:2025-09-22 21:28:01

|

525人浏览过

|

来源于php中文网

原创

JavaScript函数参数传递本质是值传递,原始类型传值副本,对象类型传引用地址副本,因此修改对象属性会影响外部对象,但重新赋值参数不影响。

js 函数参数传递机制 - 值传递与引用传递的误解与真相剖析

JavaScript 的函数参数传递机制,核心就一句话:它永远是值传递。无论是原始类型(如数字、字符串)还是对象类型(包括数组、函数),传递的都是变量的值。只不过,对于对象而言,这个“值”是一个指向内存中实际对象的引用(或者说内存地址),而不是对象本身。所以,我们看到的“引用传递”现象,本质上是“引用地址的值传递”。

解决方案

理解 JavaScript 参数传递的关键在于区分原始值和引用值。当我们把一个变量作为参数传递给函数时,JavaScript 会创建这个参数的一个局部副本。

对于原始值类型(

number
,
string
,
boolean
,
null
,
undefined
,
symbol
,
bigint
),这个局部副本就是原始值本身的一个精确拷贝。这意味着,你在函数内部对这个参数的任何修改,都只会影响到这个局部副本,而不会触及到函数外部的原始变量。

function modifyPrimitive(num) {
  num = num + 10;
  console.log("Inside function (primitive):", num); // 20
}

let myNumber = 10;
modifyPrimitive(myNumber);
console.log("Outside function (primitive):", myNumber); // 10 (unaffected)

而对于对象类型(

Object
,
Array
,
Function
),情况就显得有些微妙了。此时传递的“值”并不是对象本身,而是存储在变量中的那个指向对象内存地址的“引用”。函数接收到的是这个引用地址的一个副本。这意味着,函数内部的参数和外部的原始变量现在都指向内存中的同一个对象。因此,如果你通过这个引用去修改对象的属性,那么外部的原始对象也会随之改变,因为它们指向的是同一块内存区域。

function modifyObjectProperties(obj) {
  obj.name = "Jane Doe";
  obj.age = 30;
  console.log("Inside function (object properties):", obj); // { name: 'Jane Doe', age: 30 }
}

let myObject = { name: "John Doe", age: 25 };
modifyObjectProperties(myObject);
console.log("Outside function (object properties):", myObject); // { name: 'Jane Doe', age: 30 } (affected)

然而,如果你在函数内部尝试将参数重新赋值为一个全新的对象,那么这个操作只会改变函数内部局部参数的指向,使其指向新的内存地址,而不会影响到函数外部的原始变量。因为原始变量依然持有它最初的那个引用地址。

function reassignObject(obj) {
  obj = { city: "New York" }; // obj现在指向了一个全新的对象
  console.log("Inside function (reassigned object):", obj); // { city: 'New York' }
}

let anotherObject = { country: "USA" };
reassignObject(anotherObject);
console.log("Outside function (reassigned object):", anotherObject); // { country: 'USA' } (unaffected)

这三个例子,在我看来,基本就涵盖了所有关于 JavaScript 参数传递的“真相”了。

为什么很多人会误解为“引用传递”?

说实话,这种误解真的太普遍了,甚至我刚开始接触 JavaScript 的时候也曾一度困惑。我想,这主要是因为当我们在函数内部修改了传入对象的属性时,函数外部的原始对象也确实发生了变化,这看起来简直就是“引用传递”的典型行为啊!在许多其他语言(比如 C++ 的引用)中,这种行为就是引用传递的标志。所以,把这种现象直接归结为“引用传递”,似乎是直观且“符合经验”的。

但问题在于,JavaScript 的“引用”和 C++ 里的“引用”是不同的概念。JavaScript 传递的那个“引用”,本身也是一个值。你可以把它想象成一个门牌号或者内存地址。当你把一个对象的门牌号传给函数时,函数拿到的只是这个门牌号的副本。函数内部和外部的变量,现在都有了同一个门牌号,所以它们都能找到同一栋房子。你通过门牌号副本去修改房子内部的装修,房子当然会变。但是,如果你在函数内部把这个门牌号副本换成了另一个新房子的门牌号,那只是你手里的门牌号变了,外面那个人手里的门牌号(指向老房子)可没变。

javascript-typescript-typescript-scaffold
javascript-typescript-typescript-scaffold

你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型

下载

这种“看起来像引用传递,但实际是值传递”的机制,其实是 JavaScript 设计哲学中的一个权衡。它既提供了操作共享对象的能力,又避免了像 C++ 那样直接操作内存地址可能带来的复杂性和风险。理解这一点,就能避开很多潜在的坑。

深入理解:原始值与对象在内存中的存储差异

要彻底搞清楚参数传递,我们不得不稍微深入一点,看看 JavaScript 在内存里是怎么“玩”的。这背后其实是原始值和对象在内存中存储方式的根本区别。

当我们声明一个原始值变量,比如

let a = 10;
,通常情况下(虽然现代 JavaScript 引擎有优化,但从概念上理解),这个
10
这个值是直接存储在变量
a
所占据的内存空间里的,我们称之为栈内存(Stack)。栈内存的特点是结构简单、存取速度快,但空间有限,主要用于存储原始值和函数调用的上下文。当你把
a
传递给函数时,函数参数会开辟一个新的内存空间,把
10
这个值复制一份放进去。两者之间再无瓜葛。

而当我们声明一个对象变量,比如

let obj = { name: "Alice" };
,事情就完全不同了。
{ name: "Alice" }
这个实际的对象数据,是存储在堆内存(Heap)中的。堆内存的特点是空间大、灵活,可以存储任意大小的复杂数据结构,但存取速度相对较慢。变量
obj
本身在栈内存中存储的,并不是对象的所有数据,而是一个指向堆内存中那个实际对象数据的“地址”(或者叫引用)。

所以,当

obj
作为参数传递给函数时,函数接收到的,正是这个“地址”的副本。函数内部的参数变量,现在也拥有了同一个地址。它们就像两把钥匙,都能打开同一扇门,进入同一间屋子。你通过其中一把钥匙进入屋子,改变了屋子里的家具摆设,另一把钥匙再进去时,看到的就是改变后的样子。但如果你用其中一把钥匙去配了一把新屋子的钥匙,那和老屋子就没关系了。这种内存模型,是理解所有 JavaScript 引用行为的基础。

实际开发中如何避免参数传递带来的“意外”?

在日常开发中,参数传递带来的“意外”通常是指函数内部对对象参数的修改,不经意间影响了函数外部的原始对象,导致了难以追踪的副作用。要避免这种问题,有几个策略非常实用:

  1. 明确意图:是否需要修改原对象? 在编写函数时,首先要问自己:这个函数是否应该修改传入的对象?如果答案是“不应该”,那么就必须采取措施来保护原始对象。如果答案是“应该”,那么就需要在函数签名或文档中明确指出这一点,让调用者知道函数会产生副作用。

  2. 创建浅拷贝(Shallow Copy) 当你只需要修改对象的第一层属性,且不希望影响原始对象时,浅拷贝是一个简单有效的办法。

    • 对象: 使用
      Object.assign({}, originalObject)
      或 ES6 的展开运算符
      {...originalObject}
      。
      function processUser(user) {
        const newUser = { ...user }; // 创建一个浅拷贝
        newUser.status = "active";
        return newUser;
      }
      let userProfile = { id: 1, name: "Bob" };
      let activeUser = processUser(userProfile);
      console.log(userProfile); // { id: 1, name: 'Bob' } (未被修改)
      console.log(activeUser);  // { id: 1, name: 'Bob', status: 'active' }
    • 数组: 使用
      Array.prototype.slice()
      或展开运算符
      [...originalArray]
      。
      function addId(items) {
        const newItems = [...items]; // 创建一个浅拷贝
        newItems.push(newItems.length + 1);
        return newItems;
      }
      let myArr = [10, 20];
      let updatedArr = addId(myArr);
      console.log(myArr);       // [10, 20] (未被修改)
      console.log(updatedArr);  // [10, 20, 3]

      需要注意的是,浅拷贝只复制了对象的第一层。如果对象内部嵌套了其他对象,那么这些嵌套对象仍然是共享的引用。

  3. 创建深拷贝(Deep Copy) 如果你的对象包含多层嵌套,并且你希望完全独立地操作这个对象,那么你需要进行深拷贝。

    • JSON.parse(JSON.stringify(object))
      :
      这是最常见也最简单的深拷贝方法,但它有局限性。它不能拷贝函数、
      undefined
      、
      symbol
      、
      Date
      对象(会变成字符串)、正则表达式等。对于只包含基本数据类型和普通对象的复杂数据结构,它通常足够用。
      function processComplexObject(data) {
        const newData = JSON.parse(JSON.stringify(data));
        newData.details.age = 40;
        return newData;
      }
      let complexObj = { name: "Charlie", details: { age: 35, city: "London" } };
      let processedObj = processComplexObject(complexObj);
      console.log(complexObj.details.age); // 35
      console.log(processedObj.details.age); // 40
    • 第三方库: 对于更复杂的场景,例如需要拷贝函数、循环引用等,Lodash 的
      _.cloneDeep()
      等专业库是更好的选择。
  4. 采用函数式编程思想:纯函数 尽量编写“纯函数”(Pure Function)。纯函数有两个特点:

    • 给定相同的输入,总是返回相同的输出。
    • 不会产生任何副作用,即不修改函数外部的任何状态(包括传入的对象参数)。 遵循这个原则,可以大大减少代码中的“意外”和 bugs。

通过这些实践,我们不仅能更清晰地理解 JavaScript 的参数传递机制,也能在实际开发中写出更健壮、更可维护的代码。毕竟,代码的健壮性往往比一时的“简洁”更重要。

热门AI工具

更多
PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

创客贴AI

创客贴AI是一款AI图像与设计工具,AI辅助的智能在线设计工具。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

函子Zion

一款支持AI搭建、可视化编辑和多端发布的全栈无代码开发平台。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

2335

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

3822

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

1256

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

4439

2025.09.10

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

4976

2023.06.20

正则表达式不包含
正则表达式不包含

正则表达式,又称规则表达式,,是一种文本模式,包括普通字符和特殊字符,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式的文本。php中文网给大家带来了有关正则表达式的相关教程以及文章,希望对大家能有所帮助。

3421

2023.07.05

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

9582

2023.07.05

java正则表达式匹配字符串
java正则表达式匹配字符串

在Java中,我们可以使用正则表达式来匹配字符串。本专题为大家带来java正则表达式匹配字符串的相关内容,帮助大家解决问题。

832

2023.08.11

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

0

2026.11.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP基础入门课程
PHP基础入门课程

共33课时 | 3.5万人学习

React中文开发手册
React中文开发手册

共0课时 | 1人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn