计算机科学中有个概念,叫做求值策略 Evaluation Strategy。它决定了变量之间,函数的实参与形参之间,值是如何传递的。求值策略主要有以下两种:
按值传递和按引用传递。
按值传递与按引用传递
- 按值传递(call by value)是最常用的求值策略,函数的形参是被调用时函数实参的副本,修改函数的形参不会影响实参。
- 按引用传递(call by reference)是另一种求值策略,函数的形参是被调用时函数实参的隐式引用,不再是函数实参的副本,修改函数的形参会相应地影响到实参,因为函数实参传递给函数形参是引用本身。
按引用传递会使函数调用的追踪更加困难,有时也会引起一些微妙的 BUG。按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低。两种传值方式都有各自的问题。首先明确一点,在 JavaScript 中,不存在按引用传递。那么,是不是 JavaScript 中,变量之间,函数形参与实参之间都是按值传递呢?这是一个有些争议的问题,可以近似认为,JavaScript 中的值都是按值传递。下面分基本类型的值和引用类型的值两种情况来看。
基本类型的值
对于基本类型的值,情况非常简单清晰,考虑以下代码:
1 | // 示例1 |
上面代码说明,基本类型按照值传递,函数实参只是函数形参的一个副本。也就是说,函数被调用时,引擎会分配一块新的内存来存放从函数实参复制来的函数形参,所以接下来函数内部对形参所做的所有操作都是基于这块新的内存,函数实参自然不会受到影响。
引用类型的值
对于引用类型的值,也就是对象,情况有些不一样。考虑以下代码:
1 | // 示例2 |
上面的代码说明,传入的对象 user
被函数 f
改变了,那么是否说明对于引用类型的值,是按照引用传递的吗?再考虑以下代码:
1 | // 示例3 |
如果 JavaScript 中的对象确实是按引用传递,传入的对象 user
的指向将被改变,那么上面代码最后一行应该打印 Sunny
。然而,实际打印出的仍然是 Bill
,这说明 JavaScript 中对象的值也不是按照引用传递的。事实上,对于对象而言,JavaScript 中函数实参传递给函数形参的是对象地址的一个副本,此时函数实参和形参都指向内存中同一块区域。所以在示例 2 中,修改 user.name
的值才会影响到原 user
对象。
总结
对于这种求值策略,一种观点认为本质上仍然是按值传递,另一种观点认为是按共享传递。叫什么术语其实不重要,重要是理解其内在机制,即调用函数传参时,函数实参传递给函数形参的,是对象的内存地址的副本,既不是按值传递的对象副本,也不是按引用传递的内存地址本身。