为什么这不起作用?
function getLogger(arg) {
function logger() {
console.log(arg)
}
return logger
}
let fruit = 'raspberry'
const logFruit = getLogger(fruit)
logFruit() // "raspberry"
fruit = 'peach'
logFruit() // "raspberry" Wait what!? Why is this not "peach"?
因此,要谈一谈这里发生了什么,我正在创建一个名为fruit 的变量,并将其分配给一个字符串'raspberry' ,然后我将fruit 传递给一个函数,该函数创建并返回一个名为logger 的函数,该函数在调用时应记录fruit 。当我调用该函数时,我得到了一个console.log ,输出为'raspberry' ,如预期。
但随后我把fruit 重新分配给'peach' ,并再次调用logger 。但我没有得到fruit 的新值console.log ,而是得到了fruit 的旧值!
我可以通过再次调用getLogger 来获得一个新的记录器来回避这个问题:
const logFruit2 = getLogger(fruit)
logFruit2() // "peach" what a relief...
但是为什么我不能直接改变变量的值,而让logger 来记录最新的值呢?
答案是,在JavaScript中,当你调用一个带参数的函数时,你所传递的参数是通过值传递的,而不是通过引用。让我简单地描述一下这里发生的事情:
function getLogger(arg) {
function logger() {
console.log(arg)
}
return logger
}
// side-note, this could be written like this too
// and it wouldn't make any difference whatsoever:
// const getLogger = arg => () => console.log(arg)
// I just decided to go more verbose to keep it simple
当getLogger 被调用时,logger 函数被创建。这是一个全新的函数。当一个全新的函数被创建时,它寻找所有它可以访问的变量,并 "关闭 "它们以形成所谓的 "闭合"。这意味着,只要这个logger 函数存在,它就可以访问其父函数中的变量和其他模块级的变量。
那么,当logger 创建时,它可以访问哪些变量?再看一下这个例子,它可以访问fruit,getLogger,arg, 和logger (它自己)。再次阅读这个列表,因为它对代码的工作方式至关重要。你注意到什么了吗?fruit 和arg 都被列出来了,尽管它们是完全相同的值。
仅仅因为两个变量被分配了相同的值,并不意味着它们是同一个变量。下面是这个概念的一个简化例子:
let a = 1
let b = a
console.log(a, b) // 1, 1
a = 2
console.log(a, b) // 2, 1 ‼️
请注意,尽管我们让b 指向变量a 的值,但我们能够改变变量a ,而指向的值b 却没有变化。这是因为我们并没有把b 指向a 本身。我们把b 指向了a 当时所指向的值!
我喜欢把变量想象成指向计算机内存中的小箭头。因此,当我们说let a = 1 ,我们在说。"嘿,JavaScript引擎,我希望你在内存中创建一个数值为1 的地方,然后创建一个叫做a 的箭头(变量),指向内存中的那个地方。"
然后,当我们说。let b = a的时候,我们是在说:"嘿,JavaScript引擎,我想让你创建一个叫做b 的箭头(变量),指向此刻a 所指向的同一个地方。"
以同样的方式,当你调用一个函数时,JavaScript引擎为函数参数创建一个新变量。在我们的例子中,我们调用了getLogger(fruit) ,JavaScript引擎基本上是这样做的:
let arg = fruit
所以,后来我们在做fruit = 'peach' ,对arg 没有影响,因为它们是完全不同的变量。
无论你认为这是一个限制还是一个特点,事实是,这就是它的工作方式。如果你想让两个变量彼此保持同步,有一个方法可以做到这一点!嗯,差不多吧。这个想法是这样的:你可以改变箭头(变量)指向的地方,而不是改变它们所指向的地方!这就是为什么你要改变箭头。比如说:
let a = {current: 1}
let b = a
console.log(a.current, b.current) // 1, 1
a.current = 2
console.log(a.current, b.current) // 2, 2 🎉
在这个例子中,我们不是重新赋值a,而是改变a 所指向的值。而由于b 恰好指向同一个东西,它们都得到了更新。
所以,让我们把这个解决方案应用于我们的logger 问题:
function getLatestLogger(argRef) {
function logger() {
console.log(argRef.current)
}
return logger
}
const fruitRef = {current: 'raspberry'}
const latestLogger = getLatestLogger(fruitRef)
latestLogger() // "raspberry"
fruitRef.current = 'peach'
latestLogger() // "peach" 🎉
Ref 后缀是 "引用 "的简称,这就是说,变量所指向的值只是用来引用另一个值(在我们的例子中是一个对象的current 属性)。
这样做自然会有取舍,但我很高兴JavaScript规范要求函数参数通过值而不是引用来传递。当你有需要的时候,这个变通方法也不会太麻烦(这是很罕见的,因为可变性使得程序通常更难理解)。希望这对你有帮助!祝您好运!