JS-有条件的创建函数

140 阅读2分钟

先看一道题

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 afunction 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 aif中是什么时候被改变的:

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