JS问题记录:async箭头函数 对 var 变量提升影响

8 阅读4分钟

JS问题记录:async箭头函数 对 var 变量提升影响

 ee ={ qaz:{
            mylog:()=>{ console.log(" 1 e.index.mylog.........",ee) ;}
  }  },
  S = async () => {
         console.log(" inner1 e.index.mylog.........",ee)
          let i = await (async () => {
                  var ee;
                  console.log("let i = await (async () => {}.........",ee);
                   return true;
           })();
          console.log(" inner2 e.index.mylog.........",ee)
            let c =[
                   {isShow:true,itemName:"xx"},
                   {isShow:true,itemName:"yy"}
               ];
        for (let ee of c)  { 
            console.log(" for (let e of c).........",ee);
        }
        console.log("  last e.index.mylog.........",ee)
        ee.qaz.mylog();
   };

S();

 vendor.js? [sm]:1 TypeError: Cannot read property 'mylog' of undefined

晕啊???

出现 TypeError: Cannot read property 'mylog' of undefined 错误的原因同样是变量作用域和变量覆盖问题,下面为你详细分析。

错误原因分析

1. 内层 async 函数中的变量声明

在 S 函数内部的内层 async 函数里,使用 var ee 声明了一个变量 ee

javascript

let i = await (async () => {
    var ee;
    console.log("let i = await (async () => {}.........", ee);
    return true;
})();

转存失败,建议直接上传图片文件

这里使用 var 声明的 ee 会被提升到该内层函数的顶部,不过在赋值之前其值为 undefined。不过这并不是导致最终错误的直接原因。

在 JavaScript 里,使用 var 声明的变量会发生 “提升” 现象。这意味着,变量的声明会被提升到其所在作用域的顶部,不过变量的赋值操作不会被提升。在变量声明语句之前访问该变量,其值为 undefined

因为let i  函数的 await -async 此时的ee作用域被提升到S() 函数内的顶部??

2. for...of 循环中的变量覆盖

在 for...of 循环中,使用 let ee 声明了一个新的循环变量 ee

javascript

for (let ee of c) { 
    console.log(" for (let e of c).........", ee);
}

转存失败,建议直接上传图片文件

let 关键字声明的变量具有块级作用域,for...of 循环中的 ee 仅在该循环块内有效,并且会覆盖掉外部定义的 ee 变量。当循环结束后,外部的 ee 变量已经被循环内的 ee 覆盖,此时外部的 ee 指向的是 c 数组的最后一个元素。

3. 调用 mylog 方法时出错

在循环结束后,执行 ee.qaz.mylog() 语句,由于此时 ee 是 c 数组的最后一个元素,而 c 数组中的元素是 {isShow: true, itemName: "xx"} 这种形式的对象,它们并没有 qaz 属性,所以 ee.qaz 为 undefined,调用 ee.qaz.mylog() 就会抛出 TypeError: Cannot read property 'mylog' of undefined 错误。

解决方案

为避免变量覆盖问题,你可以给循环变量换一个名称,确保外部定义的 ee 对象不会被覆盖。以下是修正后的代码:

javascript

const ee = { 
    qaz: {
        mylog: () => { 
            console.log("  ee.qaz.mylog.........", ee);
        }
    }  
};

const S = async () => {
    let i = await (async () => {
        var ee;
        console.log("let i = await (async () => {}.........", ee);
        return true;
    })();

    let c = [
        { isShow: true, itemName: "xx" },
        { isShow: true, itemName: "yy" }
    ];

    // 更换循环变量名称,避免覆盖外部的 ee
    for (let item of c) {
        console.log(" for (let item of c).........", item);
    }

    console.log("  ee.qaz.mylog.........", ee);
    ee.qaz.mylog();
};

S();

转存失败,建议直接上传图片文件

总结

这个错误是由于变量覆盖导致的。在 for...of 循环中使用相同的变量名 ee 覆盖了外部定义的 ee 对象。通过将循环变量名改为 item,可以避免变量覆盖,保证 ee.qaz.mylog() 能够正常调用。

################

原错误原因回顾

在之前的代码里,错误的根源是 for...of 循环中的变量 ee 覆盖了外部定义的 ee 对象。循环结束后,外部的 ee 指向的是 c 数组的最后一个元素,而该元素并没有 qaz 属性,所以调用 ee.qaz.mylog() 时就会报错。

修改后代码分析

1. 内层函数修改

你把内层的 async 函数调用改成了普通函数调用:

javascript

let i =  ( () => {
    var ee;
    console.log("let i = await (async () => {}.........", ee);
    return true;
})();

转存失败,建议直接上传图片文件

这里使用 var 声明的 ee 会被提升到该函数的顶部,不过它仅在这个内层函数作用域内有效,不会影响到外部的 ee 对象。

2. for...of 循环

javascript

for (let ee of c)  { console.log(" for (let e of c).........", ee);}

转存失败,建议直接上传图片文件

此 for...of 循环中使用 let 声明的 ee 具有块级作用域,它只会在循环块内部有效。循环结束后,这个 ee 变量就会超出其作用域,不会影响到外部定义的 ee 对象。

3. 调用 mylog 方法

javascript

console.log(" 1111e.index.mylog.........", ee);
ee.qaz.mylog();

转存失败,建议直接上传图片文件

由于循环结束后,外部的 ee 对象没有被覆盖,它仍然指向最初定义的 { qaz: { mylog: () => { console.log("222 e.index.mylog.........", ee) ; } } } 对象,所以 ee.qaz 是有值的,调用 ee.qaz.mylog() 不会出现 Cannot read property 'mylog' of undefined 错误。

总结

修改后的代码不会出现之前的错误,是因为 for...of 循环里的 ee 变量具有块级作用域,在循环结束后就会超出作用域,不会覆盖外部定义的 ee 对象。因此,后续调用 ee.qaz.mylog() 时,ee 仍然是最初定义的对象,能够正常调用 mylog 方法。