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中引用类型的值也是按值传递的,只不过这个传递的值是对象在堆内存中的地址。
看上面的图片可以更清楚地理解这个过程,假设对象在堆内存中的地址是0x100,那么按值传递的就是0x100这个地址。bar.a
修改了对象里属性的值,但是foo
和bar
仍都然都指向地址0x100,所以通过bar.a
修改对象属性值会反应到foo.a
上。
上面的图只是一个粗略的方便理解的图,下面的图可能更符合代码实际的内存分布。
另外,仍然是上面的代码,如果我们稍加改动,给变量bar
赋值一个新的对象,那么变量foo
和变量bar
就指向不同的内存地址了,修改变量bar
将不再导致变量foo
被修改。有些支持按引用传递的语言,类似的操作会导致变量foo
也被修改,这个我不太了解,所以就不展开了。
const foo = { a: 1 } let bar = foo bar = { a: 2 } console.log(foo.a) // 1
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按值和按引用传递的资料请关注其它相关文章!