先看一道题
var a = 0;
if (true) {
a = 1;
function a() {};
a = 21;
console.log(a); // 21
}
console.log(a); // 1
如果没有if
呢
var a = 0;
a = 1;
function a() {};
a = 21;
console.log(a); // 21
console.log(a); // 21
添加并改变一下console.log
位置
var a = 0;
a = 1;
console.log(a); // 1
function a() {};
console.log(a); // 1
a = 21;
console.log(a); // 21
函数声明存在变量提升,上面的代码等同于下面的顺序:
function a(){};
var a ;
a = 0;
a = 1;
console.log(a); // 1
// function a() {};
console.log(a); // 1
a = 21;
console.log(a); //21
第一题代码的实际顺序是:
var a;
a = 0;
if (true) {
function a() {};
a = 1;
// function a() {};
a = 21;
console.log(a); // 21
}
console.log(a); // 1
var a
与 function a
究竟算不算一个变量呢?
var a;
a = 0;
if (true) {
function a() {};
console.log(a); // ƒ a() {}
}
console.log(a); // ƒ a() {}
再加一句console.log呢
var a;
a = 0;
if (true) {
function a() {};
a = 21;
console.log(a); // 21
}
console.log(a); //ƒ a() {}
全局环境的 a
与含有 function a() {}
的 if
条件内的 a
应该算是两个变量,但是引擎会把 function a() {}
之前的关于 a
变量的代码映射到全局环境上,但是之后的不会。
测试一下var a
在if
中是什么时候被改变的:
var _a = 0;
Object.defineProperty(window, 'a', {
get() {
return _a
},
set(val) {
console.log('set a =', val)
_a = val
},
enumerable: true,
configurable: true
})
eval(`
if (true) {
console.log(a, window.a) // function a () {} 0
a = 1;
console.log(a, window.a) // 1 0
function a () {} // set a = 1 //触发了set方法,将 window.a 的值更改了
console.log(a, window.a) // 1 1
a = 21;
console.log(a, window.a); // 21
}
console.log(a); // 1
`)
可以看MDN对这种方式的解释:非严格模式下的块级函数和有条件的创建函数,实际编写代码时,不要使用这种方式。
以下是另外搜到的一些信息(原文地址:www.zhihu.com/question/40… ):
这里会在非 strict 模式时,修正 FunctionDeclarationInstantiation 相关步骤,先InitializeBinding varEnvRec 内同名值为 undefined,然后 Block 内 FunctionDeclaration Evaluation 后才将 LexicalEnvironment 内的 funtion 给 varEnvRec 内的同名值。因此这种情况的 function 是“分身”在 var 和 let 里的。
对应到这个具体例子,与规范描述一致的解释是这样的:
var a; //评估时 InitializeBinding a undefined
if (true) {
function a () {}; // function a 被从 LexicalEnvironment 中找出给 var function a
a = 5; // 此时 LexicalEnvironment 中的 a 并没有被 evaluated 不能被访问,获取的是 varEnvRec 下 var a = 5;
function a () {}; // evaluated 完成 let a 可被访问
a = 0; // 块内有 let 先找到 let a = 0
console.log(a); // let a 值为 0
}
console.log(a); // var a 值为 5