问个 JS 变量提升,块作用域与重复声明的问题

2020-08-10 02:28:23 +08:00
 ZacharyM

先直接抛出问题:

(目测是受 ES6 块作用域 TDZ 的影响在 编译阶段 抛出的问题)

{
    var foo = 1;  // 该句报错,“foo 重复声明”
    function foo(){};
    console.log(typeof(foo));
}

小白的心路历程

(自学 js 中)最近在看《 you-dont-know-js 》—— js 的变量提升部分(一版,中文

作用域-函数优先部分有如下代码:

foo(); // "b"

var a = true;
if (a) {
   function foo() { console.log( "a" ); }
}
else {
   function foo() { console.log( "b" ); }
}

实际运行时发现 foo(); 一行报 TypeError: foo is not a function 错误。

思考之后觉得应该是 es6 块作用域的问题,导致 foo 的变量提升不如预期。遂更换 node 版本至 4.9 果然成功输出了 "b"

我的理解如下:

var foo;
var a;
foo();  // 此次相当于 foo 已声明,但未定义,暂为 undefined 。故报错
a = true;
if(a){
    foo = function(){console.log("a")};
}else{
    foo = function(){console.log("b")}
}

在理解上面这个出错问题的时候发现在块作用域下 var foo 变量声明和 foo 函数声明放在一起会报 重复声明 错。如下:

{
    var foo = 1;  // 该句报错,“foo 重复声明”
    function foo(){};
    console.log(typeof(foo));
}

已知 var 可重复声明,该情况(指同标识符的变量声明与函数声明)在全局、函数作用域下无 重复声明 问题。

(后测试同样的代码 node4.9 版本无此问题,个人考虑定位至块作用域特性相关问题,调试发现在还没执行下去的时候就已报错了,应该是 编译阶段 就抛出的问题。

个人基础较差,再往下就没啥头绪了,搜索"块作用域 var 函数声明 重复声明"相关字符也没找到结果。

希望各位 dalao 不吝赐教,指点以下该怎么分析这个问题(特性? 实际代码运行的时候是怎么样的情况?

3046 次点击
所在节点    JavaScript
17 条回复
dartabe
2020-08-10 02:55:48 +08:00
好像就是重复申明了变量啊 改了一下就对了

{
var foo = 1; // 该句报错,“foo 重复声明”
foo = function (){};
console.log(typeof(foo));
}
ZacharyM
2020-08-10 03:31:14 +08:00
@dartabe emm 问题不是怎么改,主要是想知道 js 具体是怎么处理和判断这个错误的。
另外有趣的是在 Edge(非 chrome 内核版本,Microsoft Edge 44.19041.1.0)、ios(13.5.1)上的 safari 以及 nodejs6.0.0 以下版本执行不报错,直接输出"number"。
Firefox,safari,chrome,nodejs6.0.0 及以上的才会报重复声明的错
dartabe
2020-08-10 03:32:20 +08:00
@ZacharyM
The function declaration in the block uses ES6 declaration semantics (like let or const), which does not allow redeclarations.

帮你在 stackOverFlow 上查了 还是多 google 好......
ZacharyM
2020-08-10 03:38:29 +08:00
@dartabe 这里用的是 var,不算 ES6 declaration semantics 吧。
dartabe
2020-08-10 04:16:47 +08:00
@ZacharyM

好像是 function declaration 的行为和 let const 一样 改为 let 有同样问题

{
var foo = 1; // 该句报错,“foo 重复声明”
let foo = function(){};
console.log(typeof(foo));
}
ianva
2020-08-10 05:45:08 +08:00
这个要看 ecma262 的规范,才比较好理解,推荐篇两篇文章,看懂执行模型就理解了,第一篇是 08 年的,不过讲的最清楚,不了解规范会有些复杂
https://www.cnblogs.com/RicCC/archive/2008/02/15/JavaScript-Object-Model-Execution-Model.html
https://juejin.im/post/6844903704466833421
ochatokori
2020-08-10 08:06:50 +08:00
你可以理解成 function xx 声明都会被改写成
var xx // 被提前到代码最开头
//执行你声明 function 前的代码
xx=function(){}

所以你写的代码执行顺序就是
var foo // function 声明的效果
var foo = 1; // 该句报错,“foo 重复声明”
foo=function(){};
console.log(typeof(foo));

其实没必要纠结这些,你也知道不同执行环境有不同执行结果,那就要避免写出这种代码,谁写生产环境写出这种代码那他会被捶死
Doracis
2020-08-10 09:27:52 +08:00
@ochatokori 大佬正解,最后一句高亮,一般这样的代码是真的不会放生产的,不符合代码规范不说,一旦出现 bug 会被 leader 锤死,老老实实搬砖不香吗
palmers
2020-08-10 09:37:09 +08:00
```js
{
var foo = 1; // 该句报错,“foo 重复声明”
function foo(){};
console.log(typeof(foo));
}
```
最后的代码 在 ES5 中应该是会报错的吧? 我想是因为函数声明提升和变量声明提升 但是函数声明提升优先于变量声明提升, 所以变成这样了:
```js
{
var foo = function() {}
var foo = 1;
console.log(typeof(foo));
}
```
所以在做提升的时候 也就是编译阶段就已经知道重复声明变量了 会提示 Identifier 'foo' has already been declared

我理解是这样
yaphets666
2020-08-10 09:46:06 +08:00
@ochatokori 好像是这样的
var foo // function 声明的效果
var foo // 该句报错,“foo 重复声明”
foo = 1;
foo=function(){};
console.log(typeof(foo));
krapnik
2020-08-10 10:26:16 +08:00
1.ES6 规定,块级作用域之中,函数声明语句的行为类似于 let ;
2.函数声明还会提升到所在的块级作用域的头部。
https://es6.ruanyifeng.com/#docs/let#块级作用域
dartabe
2020-08-10 10:52:09 +08:00
@krapnik 为什么楼上那么多瞎回答的还能得赞... V2EX 水平堪忧
Arrowing
2020-08-10 11:14:38 +08:00
本来呢,var 确实是可以重复声明。
但是你用块级作用域({}) + 函数声明语句( function a(){})之后,就不可以重复声明了。
ES6 规定,块级作用域之中,函数声明语句的行为类似于 let 。

只要使以上 2 个条件任意一个失效即可。
1 、块级作用域干掉,改为用 function 包裹的方式;
2 、用函数表达式声明函数,即 var foo = function(){} 。
frankkai
2020-08-10 11:18:02 +08:00
工作几年会发现 这是个无聊的问题。。
yzqtdu
2020-08-10 13:33:08 +08:00
这个问题跟 ECMAScript 语言定义有关,ES5 之前,块内的函数声明是未定义行为,具体表现跟旧浏览器的实现有关,总体来说,这种方式是不推荐的。ES6 之后引入了块级作用域,对块内函数声明的语义也进行了定义,可以理解为 let 或 const,因此会报错。https://www.ecma-international.org/ecma-262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics


为了历史兼容,其实块内函数声明会带来一些“bug”,具体见 https://www.zhihu.com/question/404772996
ChanKc
2020-08-19 12:41:57 +08:00
无聊的问题
养成习惯,手动提升,万事大吉
u823tg
2020-08-28 00:13:03 +08:00
这种问题没必要研究, 这是 js 缺陷问题。 除非考 js 各种奇淫巧计

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/696935

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX