前两天遇到的问题, 经过很多网友的深刻讨论, 终于有一个相对可以解释的通的逻辑了, 然后我仔细研究了一下相关的点, 顺带研究了一下 JS 中的隐式变量.
以下文章中提到的隐式变量都是指没有用 var,let,const 等关键字定义的变量.
以下文章中提到的 var 变量都是指用 var 声明定义的变量.
一遇到隐式变量, 我们去百度一下, 都会看见这样一句话, 隐式变量是全局变量, 在函数中用隐式变量也是全局变量, 但是在函数中用 var 变量是局部变量, 那我们来具体看下隐式变量到底与 var 变量有什么区别, 下面我们来通过一些代码来探究隐式变量与 var 变量的区别.
1. 隐式变量与 var 变量的区别
代码 1:
var 变量:
- console.log(a);//undefined
- var a = 100;
代码 2:
隐式变量:
- console.log(a);//Uncaught ReferenceError: a is not defined
- a = 100;
看上面的代码我们发现 var 变量 a 是存在变量声明提升的, 也就是代码 1 相当于下面的代码:
- var a;
- console.log(a);
- a = 100;
这与我们以前了解的那个 var 变量是一样的, 变量声明提升还是那个变量声明提升, 并不会改变, 这里如果想详细了解变量提升的, 推荐这篇文章大家可以看一下. 然后我们继续看代码 2, 隐式变量在前面打印变量 a 时会报错, 那这里我们是不是可以猜测, 隐式变量是不是不存在变量提升, 当然以前好像也没有人说过隐式变量存在声明提升, 可能我一厢情愿的认为隐式变量是全局变量就存在声明提升. 然后我们继续看在函数中, 代码块中声明的隐式变量是不是跟上面全局定义的隐式变量一样, 不存在声明提升.
代码 3:
函数中的 var 变量:
- console.log(a);//Uncaught ReferenceError: a is not defined
- function b() {
- console.log(a);//undefined
- var a = 100;
- }
- b();
代码 4:
函数中的隐式变量:
- console.log(a);//Uncaught ReferenceError: a is not defined
- function b() {
- console.log(a);//Uncaught ReferenceError: a is not defined
- a = 100;
- }
- b();
我们来看上面的代码 3 和代码 4, 我们发现代码 3, 在第一行就报错, 因为 var 在函数内部定义的变量属于局部变量, 全局是访问不到的, 然后把第一行注释掉再运行, 发现第三行打印出的 a 是 undefined, 证明 var 变量在函数中进行了变量声明提升. 我们来看代码 4, 在函数内定义了一个隐式变量, 然后第一行就会报错, a 是未定义, 然后把第一行注释掉再运行, 发现第三行也还是报错 a 未定义, 这里是不是就说明隐式变量并不会有变量声明提升这种操作, 换一句话说, 就是隐式变量类似于函数一样, 声明和定义是一起的, 只有执行了这行代码, 才能引用访问这个变量, 这里推荐一篇关于变量生命周期的文章, 可以细读一下, 然后我们再看一下代码块中定义的 var 变量和隐式变量:
代码 5:
代码块中的 var 变量:
- console.log(a);//undefined
- {
- console.log(a);//undefined
- var a = 100;
- }
代码 6:
代码块中的隐式变量:
- console.log(a);//Uncaught ReferenceError: a is not defined
- {
- console.log(a);//Uncaught ReferenceError: a is not defined
- a = 100;
- }
然后我们看代码块中的 var 变量, 是存在声明提升的, 然后隐式变量是不存在声明提升的, 所以上面访问都会报错.
结论: 根据上面的一系列的探究我们基本可以确定一个结论, 那就是隐式变量是不存在变量声明提升的.
2. 代码块中的函数声明提升
上面探究完隐式变量我们再稍微看下代码块中的函数声明提升:
代码 7:
- console.log(a);//undefined
- {
- function a(){};
- console.log(a);//ƒ a(){}
- }
- console.log(a);//ƒ a(){}
我们看上面的代码, 我们都知道函数是 JS 中的 "一等公民", 优先级是最高的, 那在上面这个代码中, 函数是否提升到了块作用域的外面呢, 如果我说提升了, 那你从块作用域最外面调用你会发现报错, a 不是一个方法:
- a();//Uncaught TypeError: a is not a function
- {
- function a(){};
- }
然后这样之前的我就得出一个结论, 函数声明提升并没有将函数提升到最外层, 那就要问那上面我们打印的 a 为 undefined, 为什么不是报错 a 未定义呢, 根据阮一峰老师的这篇文章 http://es6.ruanyifeng.com/#docs/let 的描述, 我们可以得出一个结论:
块作用域内定义的函数在块作用域内会进行函数提升, 提升到最上面, 然后在最外层类似于 var 声明一个同名变量, 默认值为 undefined.
3. 代码块中存在同名的隐式变量与函数的情况
然后我们再回过头来看代码块中同时存在同名的隐式变量和函数时会怎样:
首先我们来看上面这个代码, 此时如果我们再最外层的上面打印 a 和 b 你会发现会打印出来是 undefined:
代码 8:
- console.log(a,b);//undefined undefined
- {
- console.log(a);//ƒ a() {}
- function a() {};
- a = 50;
- console.log(a);//50
- }
- console.log(a);//ƒ a() {}
- {
- console.log(b);//ƒ b() {}
- b = 50;
- function b() {};
- console.log(b);//50
- }
- console.log(b);//50
我们看上面的代码, 你会发现最前面打印 a 和 b 都是 undefined, 通过前面对隐式变量和函数声明的探究我们可以知道此时外面的 a 和 b 都是函数声明定义的, 另外我们从侧面也可以看出这一点, vscode 有一个功能, 是可以查看变量是谁定义的, 那我们来看一下:
此时我们发现 vscode 分析出来最上面的 a 和 b 都是 a 和 b 函数定义的, 根据我们上面的探究, 既然隐式变量不存在变量声明提升, 那只能函数定义的, 然后通过对函数的探究可以证实这一点, 同时配上 vscode 分析出来的结果, 也证实了这一点, 首先可以确定, 最外层的全局的 a 和 b 都是函数定义的. 然后我们再继续往下看, 最开始的 a 和 b 打印 undefined 没问题,
然后我们来看下上面图片上第 16 行打印的结果为什么是 a 方法, 通过我上篇文章一行一行的调试你会发现, 此时代码块中的 a 其实是被限制在块作用域里面的, 并不是全局的变量, 此时其实 a = 50 这行代码不是隐式变量, 因为 a 已经被函数定义过了, 那 a = 50 也就是对之前定义过的变量赋值了, 所以此时
a = 50 不是隐式变量, 然后对之前定义的 a 赋值, 在代码块中打印出来为 50, 然后出了块作用域打印出来的结果为方法 a, 通过代码一步步的调试你会发现全局的 a, 只有在执行 a 方法的代码时才会把块作用域赋的值同步到全局.
第一步: 此时全局的 a 为 undefined, 此时 a 是代码块中函数定义的:
第二步: 此时我们发现出来了一个块作用域, 因为代码块中的函数提前了, 此时代码块中的 a 的值为 a 方法, 而全局的 a 还是 undefined, 没什么问题
第三步: 此时我们会发现当函数 a 这一行代码走完之后, 块作用域里面的 a 跟全局的 a 都变成了 a 方法, 也就是说想要让块作用域中的 a 的值同步到全局, 必须让代码执行到定义 a 方法的下一行才可以, 要不然代码块中的 a 的值是不会同步到代码块外面的
第四步: 到这一步我们会发现块作用域中的 a 变成了 50, 而全局的 a 还是 a 方法, 根据上一步得到的结论, 此时只要在 a = 50 这行代码后面再执行一次方法 a,a = 50 就会被同步到全局, 此时验证一下也确实是这样
然后出块作用域你会发现打印 a 为 a 方法, 此时你就不会好奇为什么打印结果是 a 方法了, 因为块作用域中的 a 并没有同步到全局, 而 b 打印 50, 是因为 b = 50 后面执行了一行 b 方法, 将 b 同步到了全局. 到这里我们也就了解为什么两个代码只是换了一下函数的位置打印结果过就完全不同了, 然后看文章的时候如果发现有什么错误或写的不好的地方, 还请指正, 我会立即做出修改.
来源: https://www.cnblogs.com/yukixing/p/11617354.html