你也许不知道,块作用域内的函数声明如此神奇

引言

做了这么多年前端,我觉得对于块作用域这个神奇的魔法,我还是知之甚少;打开 MDN,总结一下块作用域的特点:

  1. var 声明的变量不会产生块作用域,即使是在大括号中声明的
  2. let 和 const 在大括号中声明变量会产生块作用域,但是块内声明的变量只能在块内访问
  3. 使用 let 声明的变量在块级作用域内能强制执行更新变量
  4. 函数声明同样被限制在声明他的语句块内

1-3都是众所周知的,举 MDN 上的几个例子一笔带过:

js 复制代码
// demo1
var x = 1;
{
  var x = 2;
}
console.log(x); // 输出 2

// demo2
let x = 1;
{
  let x = 2;
}
console.log(x); // 输出 1

// demo3
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 10
a[1](); // 10
a[6](); // 10

/********************/

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 0
a[1](); // 1
a[6](); // 6

块作用域内的函数声明

重点来看一看第四点,先上 MDN 的案例,先不看答案,请大家自己做一做依次打印出内容:

js 复制代码
console.log("outside top",foo);
{
  foo('inside');
  function foo(location) {
   console.log('foo is called ' + location);
  }
  foo('inside');
  foo = 2;
  console.log("inside",foo);
}
console.log("outside bottom",foo)

查看答案:

js 复制代码
console.log("outside top",foo); // outside undefined
{
  foo('inside'); // 正常工作并且打印 'foo is called inside'
  function foo(location) {
   console.log('foo is called ' + location);
  }
  foo('inside'); // 正常工作并且打印 'foo is called inside'
  foo = 2;
  console.log("inside",foo); // inside 2
}
console.log("outside bottom",foo) // outside bottom function foo(){}

看了答案之后其实我还是一头雾水,我还是不知道这一段代码是如何执行的,是时候亮出我的"杀手锏"了:debug,可以通过 debug 来看一看执行时的作用域链,有助于分析问题的本质;在第一行代码添加一个 debug 然后在浏览器中执行:

全局作用中竟然声明了一个 foo 变量!此时作用域为 Global: { foo:undefined } 继续往下调试:

块作用域顶层能够访问到 foo 函数声明!而且此时全局作用域上的 foo 为 undefined!此时整体的作用域为

js 复制代码
Block:{
    foo:function foo(){}
}
Global:{
    foo:undefined
}

继续往下,foo 函数声明之后,在 foo('inside') 代码之前,全局作用域上的 foo 值变为函数了!此时作用域变为:

js 复制代码
Block:{
    foo:function foo(){}
}
Global:{
    foo:function foo(){}
}

然后块内 foo 被赋值为 2,Block 作用域上的 foo 值为 2,此时作用域变为:

js 复制代码
Block:{
    foo:2
}
Global:{
    foo:function foo(){}
}

此时搜索 foo 的值会先找最近的块作用域,找到 foo 对应的值为 2,那么就返回 2。

块作用域结束后打印 foo 变量,此时访问的是全局作用域上的 foo,自然就是 foo 函数声明了。现在从作用域的角度搞清楚了整体的执行逻辑,但是函数到底是怎么提升的呢?

块作用域内的函数提升

我直接说我的猜想(可能不一定正确,如果有不对之处敬请大家指出来):

js 复制代码
var foo;
console.log("outside top",foo);
{
  let foo = function foo(location) {
   console.log('foo is called ' + location);
  }
  foo('inside');
  window.foo = foo;
  foo('inside');
  foo = 2;
  console.log("inside",foo);
}
console.log("outside bottom",foo)
  1. 首先将 foo 变量提升到全局作用域,但是不赋值;
  2. 其次将 foo 函数声明提升到块作用域顶层
  3. 在原函数声明位置,对 window 上的 foo 变量赋值为 foo 函数

注意

讲了这么多,最终要落实到代码中来,那就是尽量不要在块中声明函数,这么多怪异的操作会导致程序产生无法预测的 bug

相关阅读

相关推荐
凤头百灵鸟1 天前
Python语法进阶篇 --- 单例模式、魔法方法
javascript·python·单例模式
2501_913061341 天前
JVM虚拟机——面试中的八股文
java·jvm·面试
敲代码的彭于晏1 天前
感谢掘金,我的书又出版了
前端·vue.js·react.js
龙猫里的小梅啊1 天前
CSS(五)CSS盒模型
前端·css·html
一袋米扛几楼981 天前
【前端开发】基于TypeScript打破 React 黑盒——组件的“工厂心智模型”与源码解剖
javascript·react.js·typescript
明月_清风1 天前
Nginx 生产环境配置完全指南:从安全加固到性能调优
前端·nginx
用户600071819101 天前
【翻译】用 Reanimated CSS 动画为 TextInput 添加发光效果
前端
李剑一1 天前
前后端命名冲突?驼峰与下划线的统一方案(附可直接复用代码)
前端
用户11481867894841 天前
Git Stash 丢失后的完整找回指南
前端·git
代码不加糖1 天前
2026 React 面试“通关秘籍”:高频 12 问 + 深度解析(含Hooks源码思想)
前端·react.js·面试