「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
今天遇到了一道关于闭包特别有意思的题。
给大家一点时间,思考一下这道题输出的结果是什么?
现在我和大家一起看一下这道题应该怎样输出
首先我们要明确的是整个f1()函数是一个闭包,这个问题在我上一个闭包有关的问题里有提及。确定其是闭包以后我们就会知道,闭包每一次执行之后都会生成新的执行环境,所以三次log不会互相影响。
第二步我们先看第一个log中console.log(+f1());,+ ()的执行顺序是什么。
因此我特意查了一下MDN的运算符优先级表。
由此可知,函数调用的优先级要高于一元加法,所以在遇到+和函数调用符()时,我们应该优先运算函数运算。
下面我们先对f1()函数进行划分
当我们执行f1()函数时,我们得到的是其返回值f2
所以最后console.log(f1())得到的是console.log(f2),再和之前的+组合成了console.log(+f2)
现在我们得到了console.log(+f2),我们就要知道+会发生什么。
根据红宝书中的一元运算符中所写
一元运算符运算到非数值时,对象会调用它们的valueOf和/或 toString() 方法以得到可以转换的值。
所以console.log(+f2)会使f2强制变成数值,所以要执行其valueOf函数。
最后得到的便是console.log(sum),由于我们并没有执行f2函数,所以f1函数内部的sum并没有实现累加操作,因此我们所得到的便是0
经过我们对第一个打印的分析,其实后面两个也就迎刃而解了。
console.log(+f1()());
console.log(+f1()()());
对于第二个打印console.log(+f1()());我们可以将其看做console.log(+f2());
还是由于运算符优先级的关系,我们会先执行f2()
function f2 () {
sum++
return f2
}
根据上面代码,我们会将sum累加一次,然后再返回f2,此时的打印依旧是console.log(+f2)
执行f2.valueOf()函数。
f2.valueOf = function () {
return sum
}
依旧返回的是sum,console.log(sum)。但是此时的sum已经经过了一次累加,得到的是1。
同理,第三个打印console.log(+f1()()());,只不过是多执行了一次f2(),sum多进行了一次累加。得到的应该是2。
function f1 () {
var sum = 0
function f2 () {
sum++
return f2
}
f2.valueOf = function () {
return sum
}
f2.toString = function () {
return sum++
}
return f2
}
console.log(+f1()); // 0
console.log(+f1()()); // 1
console.log(+f1()()()); // 2
那么既然我们每次执行的都是valueOf()那我们什么时候会执行toString()呢。
还是上面红宝书中所写,既然valueOf()是将非数字转换成数字,那么我们应该能联想到,toString()就应该是将非字符转换成字符。所以我们只需要一步操作,就会将执行f2.valueOf()转变成了执行f2.toString()
function f1 () {
var sum = 0
function f2 () {
sum++
return f2
}
f2.valueOf = function () {
return sum
}
f2.toString = function () {
return sum++
}
return f2
}
console.log(""+f1()); // "0"
console.log(""+f1()()); // "1"
console.log(""+f1()()()); // "2"
只需要将+前面加上一个空的字符串,那么JS就会默认将f2当做字符串去匹配,此时就会默认执行toString方法了。
由上面可知,当只使用+时,得到的是数字的0,1,2当使用””+时得到的便是字符串形式的0,1,2。
以上便是我对这道题的全部理解。
今天有个朋友说我对valueOf和toString的理解不对,他指出valueOf和toString同时存在时valueOf都会执行,而不是有“”时,只执行toString,是我写的不严谨了,再次改正。