深入理解 JavaScript 函数调用机制:值传递与共享对象

作者:代码拾荒

时间:2026-10-21

浏览:533

本文深入解析 JavaScript 中的函数参数传递机制,澄清值传递与引用传递的误区。通过对比 C++ 概念,引入 Call by Sharing(共享传递)模型,并结合 Google V8 源码分析对象引用、严格相等判断及局部句柄实现,阐明变量共享指针而非修改指针本身的特性。

文章目录

JavaScript 函数调用机制概述

定义

在 JavaScript 学习过程中,许多开发者对函数参数的传递方式感到困惑。为了探究本质,我们尝试从源码中寻找答案。在此之前,需要明确几个核心概念,暂时摒弃传统的“值传递”或“引用传递”等固有叫法,回归英文术语进行探讨。

call by reference && call by value && call by sharing

前两者分别对应我们熟悉的 C++ 中的引用传递和值传递。第三种概念较为晦涩,官方解释为“接收对象引用的副本”。用通俗的话来说:

可以将 Object 理解为键(key)的集合,Object 对 key 指向的数据具有引用性质(此处不深究是指针实现还是 C++ 引用实现)。函数接收的是一个变量的副本,该变量包含了 Object 的引用,这本质上是一种值传递。

因此,在函数传参时,接收到的对象形参实际上是实参的副本。直接更改形参的指向是不可行的;但由于 Object 内部的 key 本身都是引用,所以修改 key 所指向的数据是可行的。

通过代码验证参数传递行为

证明

通过几段简单的代码即可验证上述观点。

Code 1: 函数能修改 key 指向的数据


let func = obj => { obj.name = 'Dosk' };
let obj = {name : 'Alxw'};
console.log(obj); //{ name: 'Alxw' }
func(obj)
console.log(obj); //{ name: 'Dosk' }

Code 2: 函数不能修改 obj


let func = obj => { obj = {} };
let obj = {name : 'Alxw'};
console.log(obj); //{ name: 'Alxw' }
func(obj)
console.log(obj); //{ name: 'Alxw' }

Code 3: 内部 obj 和外部 === 结果相等


let def = {name : 'Alxw'};
let func = obj => { console.log(obj === def) };
func(def); //true

第三段代码可能引发疑问:既然 obj 是 def 的副本,为什么 === 操作的结果为真?通常认为 === 对于 Object 比较的是内存地址,如果是复制应该返回 false。

为了解答这一疑问,我们需要回到 Google V8 的源码中查看具体实现。

深入 Google V8 源码分析

深入 Google V8

查看源码中关于严格等于操作的代码部分:


bool Object::StrictEquals(Object* that) {
 if (this->IsNumber()) {
  if (!that->IsNumber()) return false;
  return NumberEquals(this, that);
 } else if (this->IsString()) {
  if (!that->IsString()) return false;
  return String::cast(this)->Equals(String::cast(that));
 } else if (this->IsSimd128Value()) {
  if (!that->IsSimd128Value()) return false;
  return Simd128Value::cast(this)->Equals(Simd128Value::cast(that));
 }
 return this == that;
}

从逻辑上看,这似乎对应最后一种情况。理论上,如果 def 和 obj 是不同的对象,应该返回 false,这是否推翻了前文所述?其实不然,这里忽略了一个关键点:Google V8 在实例化 Object 时是动态实例化的。在编译型语言中,动态实例化只能在堆内存上进行,即只能通过指针引用。证明这一结论涉及 Local、Handle 等类的实现细节,较为繁琐。一个更简单的证明方式是:在源码中搜索所有调用 Object::StrictEquals 的地方,发现都是直接传入而没有取地址操作。

有人可能会问:既然值传递的变量包含 Object 的引用,理论上应该能修改 Object 才对,为什么第三段代码不能修改 obj 本身?

道理很简单:我们在 JavaScript 语言逻辑层面的操作,实际上只是调用 Google V8 实例方法而已,无法直接操作到这一底层层面(当然,潜在的 BUG 除外)。

重新定义 Call by Sharing

重新定义

至此,我们可以对 call by sharing 进行重新解释:

传递时确实是值传递,但内容包含了 Object 的指针,且无法修改这个指针,它是多个变量共享的。

基于源码接口的补充证明

另一种简单的证明

让我们查看源码。


V8_DEPRECATE_SOON("Use maybe version",
         Local<Value> Call(Local<Value> recv, int argc,
                  Local<Value> argv[]));
V8_WARN_UNUSED_RESULT MaybeLocal<Value> Call(Local<Context> context,
                       Local<Value> recv, int argc,
                       Local<Value> argv[]);

上面的是即将弃用的接口,碰巧我看到的这个版本代码包含大量的这种即将弃用的代码,看看就好。重点是第二个接口,它是函数唯一的调用接口。其中的 Local<Value> 最终会调用 C++ 的位复制,因此可以简单证明这是值传递。

可能是重点

别忘了,我们定义的变量都是类似 Handle<Object> 这种形式的,所以它们之间的对象才是共享的。我们所说的 JavaScript 里面的变量并不直接指的是 Object 的实例。

最后的最后

总之,理解起来可能很费劲甚至可能有错误,但在 JavaScript 语言层次上能够确定这一特性,这才是重要的。

以上所述是小编给大家介绍的JavaScript中的函数调用,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章