思考的问题:为什么他可以工作以及他是如何工作的,为什么他可以工作
function getLogger(arg){
function logger(){
console.log(arg)
}
return logger
}
let fruit='raspberry'
const logFruit=getLogger(fruit)
logFruit() // 'raspberry'
fruit='peach'
logFruit()// 'raspberry' 这里打印出来的内容为什么不是peach 大家心里有这个疑惑吗
首先,来说下上面这个代码块的执行发生了什么,我建立一个fruit的变量并且给他赋值了'rasberry',然后我传递给此函数,在创建并返回一个名字叫logger,当其被调用时会打印出fruit这个变量的函数。我首次调用这个函数,我们可以看到控制台打印出我们所期待的'rapberry'值。
但是当我给fruit变量重新赋值为'peach',然后再次调用logger函数时。但是控制台log出来的数据不是我刚刚赋的新值,而是一个之前第一次赋的值。
我可以解决这问题,我们可以再次调用getLogger这个函数得到一个新的logger:
const LogFruit2=getLogger(fruit)
logFruit2() // 'peach'
但是为什么我只能是改变变量的值并且得到一个新的logger函数后才能log出一个最新的值?
原因是在JavaScript中,当你调用一个带有参数的函数时。这个参数会被当做是一个value传递,不是一个引用。下面用代码来简单的描述这里究竟是如何运行的:
function getLogger(arg){
function logger(){
console.log(arg)
}
return logger
}
当getLogger函数被调用时,这个logger的函数被创建了。他就是一个新的函数,当一个新的函数被创建时,这个看起来所有能够访问的变量形成一个关闭的状态这样形势被称为一个闭包。这意味只要这个logger函数存在,他将访问到变量在他的父级函数或者其他模块级别的函数变量。
因此什么变量logger函数被创建之后就能够被访问?我们再来看一下这个例子,它能够访问到fruit,getLogger,arg和logger,这些变量对于代码为何是如此运行有着至关重要的作用。你可以发现,arg和fruit都被列举出来了,尽管他们是一样的值。
只是因为两个变量都被赋上相同的值,但是并不意味着他们是同一个变量。下面是一个简单的的例子关于这个概念:
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,就相当于在告诉js引擎,此刻我想要创建一个名叫b的指针指向与a指针指向同一块内存区域。
也就是说,当我们调用一个函数时,js引擎将会创建一个新的变量为了函数的参数。在上面提到的例子中,我们调用了getLogger(fruit),然后js引擎基本上做了如下的操作。
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函数log值的问题:
function getLastestLogger(argRef){
function logger(){
console.log(argRef)
}
return logger
}
const fruitRef={current:'raspberry'}
const latestLogger=getLastestLogger(fruitRef)
latestLogger()// 'raspberry'
fruitRef.current='peach'
latestLogger() //'peacch'
这个Ref的后缀对于引用单词的缩写,也就是变量的值只是用来引用另一个值(但是在我们的例子当中,current只是一个对象的属性)。
结论
这是一个利弊权衡的问题,但是很高兴JavaScript的规范标准更倾向于传递值而不是一个引用对象在调用函数时。但是这个解决办法也不是很麻烦,当你真正有需求的时候(这个其实是非常罕见,因为可变性和动态性将会使得程序变得难以理解)。
写在最后
本文翻译与外国以为开发工程师的文章,如有不正确的地方,欢迎指正。
原文地址