技术解析

f 等于几,为什么?
0
2021-08-10 01:37:07
idczone
(() => {
	{
		function f() { 'A' }
		f = 1;
		f = 2;
		function f() { 'B' }
		f = 3;
	}
	console.log('f =', f);
})();

[] - []; // ?
{} - {}; // ?
[] - {}; // ?
{} - []; // ?

找出会返回 -0 的那一项.

f=2,因为我在 console 跑过了,

我感觉应该是 undefined 才对,是因为没开 strict mode 么。

我觉得类似这个问题:
https://stackoverflow.com/questions/63948741/javascript-function-hoisting-inside-block-statement
https://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6
第二次定义 function 导致跨作用域的变量工作方式变了。

我们在先在每次为 f 赋值之后输出以下 f 的值
```js
(() => {
{
function f() { 'A' }
console.log('f_1 =', f); // f_1 = ƒ f() { 'B' }
f = 1;
console.log('f_2 =', f); // f_2 = 1
f = 2;
console.log('f_3 =', f); // f_3 = 2
function f() { 'B' }
console.log('f_4 =', f); // f_4 = 2
f = 3;
console.log('f_5 =', f); // f_5 = 3
}
console.log('f_out =', f); // f_out = 2
})();
```
好了,那么有疑惑的点只会在 f_1 = ƒ f() { 'B' } 、f_4 = 2 和 f_out = 2 上对不对,其它的都是符合直觉的。
首先来看为什么 f_1 = ƒ f() { 'B' } 与 f_4 = 2
其实正确的书写方式应该是 1.声明函数; 2.声明变量; 3.业务逻辑;
所以简单改正之后其实类似这样
```js
(() => {
var f
{
f = function () { 'A' }
f = function () { 'B' }
console.log('f_1 =', f); // f_1 = ƒ f() { 'B' }
f = 1;
console.log('f_2 =', f); // f_2 = 1
f = 2;
console.log('f_3 =', f); // f_3 = 2
console.log('f_4 =', f); // f_4 = 2
f = 3;
console.log('f_5 =', f); // f_5 = 3
}
console.log('f_out =', f); // f_out = 2
})();
```
那么就剩下一个疑惑了,就是为什么 最后的 f_out = 2
明明之前已经输出了 f_5 = 3,这个我就不知道原理了,只能从 block scope 和 local scope 和你说
我把外部 local scope 中的 f 继续声明成 f , 而内部 block scope 中的 f 的声明成 _f:
```js
(() => {
var f
{
var _f
f = _f = function () { 'A' }
f = _f = function () { 'B' }
console.log('f_1 =', _f); // f_1 = ƒ f() { 'B' }
_f = 1;
console.log('f_2 =', _f); // f_2 = 1
_f = 2;
console.log('f_3 =', _f); // f_3 = 2
f = _f
console.log('f_4 =', _f); // f_4 = 2
_f = 3;
console.log('f_5 =', _f); // f_5 = 3
}
console.log('f_out =', f); // f_out = 2
})();
```
具体为什么可能需要大佬来解释了。

可以逐行调试,然后看每一行执行完后,f 在不同作用域下的值是什么:
// 此时没有 f
(() => {
f = -1;
// 函数块内:-1
{
f = 1;
// 块内:1 函数块内:-1
function f() { 'A' }
// 块内:f() 函数块内:f()
f = 1;
// 块内:1 函数块内:f()
f = 2;
// 块内:2 函数块内:f()
function f() { 'B' }
// 块内:2 函数块内:2
f = 3;
// 块内:3 函数块内:2
}
console.log('f =', f); // 输出函数块内的 f 值,即 2
})();
可以看出来,把操作分成两类,一类是赋值,另一类是函数声明。
赋值的逻辑很简单,就是直接影响当前块内作用域的 f 值,执行到哪就赋值成啥。
比较反常识的是函数声明,可以看到函数声明的时候其实只把当前块内作用域的 f 复制一份放到函数块内,第一次和第二次函数声明都是这样的,如果当前块内作用域块内没有 f,那么就会在当前块内作用域声明一个 f 变量,再把函数赋值上去,紧接着再把块内的 f 复制一份到函数块内作用域。
以上是我根据行为做出的推断,实际上还是得看 ES 标准以及引擎的实现方案。
不过这个说白了还是老生常谈的 ES5 作用域提升的特性,在严格模式+块级作用域下是不会有这种现象的,而且如今的商业开发也极少会考虑保留作用域提升特性,拥抱 ES6+或使用 TS 都在团队开发效率方面有很高的回报。

上面代码编辑有问题,更新一个正确的版本:
// 此时没有 f
(() => {
f = -1;
// 函数块内:-1
{
f = 1;
// 块内:1 函数块内:-1
function f() { 'A' }
// 块内:1 函数块内:1
f = 1;
// 块内:1 函数块内:f()
f = 2;
// 块内:2 函数块内:f()
function f() { 'B' }
// 块内:2 函数块内:2
f = 3;
// 块内:3 函数块内:2
}
console.log('f =', f); // 输出函数块内的 f 值,即 2
})();
第一次函数声明的时候就是做了值的复制,所以内外都是 1

win 10 ie11 的结果为 f = function f () { 'B' }
win 10 ie11 用 ie10 兼容模式跑结果为 f = 3
使用 browserstack 测试 chrome 30/firefox 30/safari 9.1 跑结果为 f = 3
chrome/firefox/safari 最新版测试,结果为 f = 2
严格模式下直接报错

数据地带为您的网站提供全球顶级IDC资源
在线咨询
专属客服