一文详解JavaScript中的按值传递和按引用传递

来自:网络
时间:2024-06-10
阅读:

JavaScript中几乎都是按值传递

编程语言中,把一个变量的值赋值给另一个变量,或者给函数调用传递参数有两种方式:按值传递按引用传递

JavaScript中几乎都是按值传递。我们看一个例子:

let a = 1
let b = a
b = 2
console.log(a) // 1
  • 上面的代码,声明了一个变量a,赋值为1;
  • 然后又声明了一个变量b,将变量a的值1赋值给变量b,此时变量b的值也是1;
  • 接着我们将变量b的值修改为2;
  • 此时打印变量a的值应该仍然是1,而不是2。

这就是按值传递,我们把变量a的值赋值给变量b的时候,只是把1这个值复制了一份给变量b,变量b的值的修改并不会影响到变量a的值。

很好,到目前为止,我们说上面的代码是按值传递很好理解,很符合我们的直觉。

上面是基元值的情况,如果换成引用类型的值呢?看下面的代码。

const foo = {
    a: 1
}
const bar = foo
bar.a = 2
console.log(foo.a) // 2
  • 上面的代码声明了一个变量foo,给它赋值了一个对象;
  • 然后又声明了一个变量bar,把变量foo指向的对象赋值给变量bar
  • 接着我们通过bar.a把对象的属性a修改为2;
  • 我们发现foo.a也被修改了!

说好的按值传递呢?如果是按值传递,修改bar.a不应该导致foo.a被修改啊,这好像不太符合直觉啊?难道引用类型的值是按引用传递吗?

并不是。JavaScript中引用类型的值也是按值传递的,只不过这个传递的值是对象在堆内存中的地址。

一文详解JavaScript中的按值传递和按引用传递

看上面的图片可以更清楚地理解这个过程,假设对象在堆内存中的地址是0x100,那么按值传递的就是0x100这个地址。bar.a修改了对象里属性的值,但是foobar仍都然都指向地址0x100,所以通过bar.a修改对象属性值会反应到foo.a上。

上面的图只是一个粗略的方便理解的图,下面的图可能更符合代码实际的内存分布。

一文详解JavaScript中的按值传递和按引用传递

另外,仍然是上面的代码,如果我们稍加改动,给变量bar赋值一个新的对象,那么变量foo和变量bar就指向不同的内存地址了,修改变量bar将不再导致变量foo被修改。有些支持按引用传递的语言,类似的操作会导致变量foo也被修改,这个我不太了解,所以就不展开了。

const foo = {
    a: 1
}
let bar = foo
bar = {
    a: 2
}
console.log(foo.a) // 1

一文详解JavaScript中的按值传递和按引用传递

ES Module中的live bindings

前面我们说了,JavaScript中几乎都是按值传递,这样说通常都有例外。ES Module中export导出的变量被称为live bindings(实时绑定),这是JavaScript中唯一按引用传递的情况。

// a.js
export let count = 1
export function increment() {
    count++
}
// b.js
import { count, increment } from './a.js'
console.log(count) // 1
// count = 2 // import的变量是只读的,不能修改,尝试修改会报错 TypeError: Assignment to constant variable.
increment()
console.log(count) // ?

让我们暂停下来,思考一下,上面的代码中,第二个console.log(count)会输出什么?

答案是2。ES Module中export的变量,其它模块import进来之后是只读的,尝试修改会报错。但是export变量的模块可以另外导出一个方法用来修改这个变量,变量的修改会同步反应在两个模块中,这种情况被称为live bindings,是按引用传递的。

上面类似的代码在CommonJs中的执行结果截然不同。

// a.js
let count = 1
function increment() {
    count++
}
module.exports = {
    count,
    increment
}
const { count, increment } = require('./a.js')
console.log(count) // 1
count = 2  // 可以修改
console.log(2) // 2
increment()
console.log(count) // 是2而不是3

require导入的变量是可以被修改的,上面的代码中最后的console.log(count)的值是2而不是3,因为这里count是按值传递的。

总结

  • JavaScript中几乎都是按值传递。
  • ES Module中export导出的变量是JavaScript中唯一的按引用传递,这被称作live bindings。另外export导出的变量是只读的,在模块外部不允许修改它的值,通常可以额外导出一个方法用来修改这个变量。

以上就是一文详解JavaScript中的按值传递和按引用传递的详细内容,更多关于JavaScript按值和按引用传递的资料请关注其它相关文章!

返回顶部
顶部