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

引言

做了这么多年前端,我觉得对于块作用域这个神奇的魔法,我还是知之甚少;打开 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

相关阅读

相关推荐
大前端爱好者43 分钟前
React 19 新特性详解
前端
随云6321 小时前
WebGL编程指南之着色器语言GLSL ES(入门GLSL ES这篇就够了)
前端·webgl
J老熊1 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
我爱学Python!2 小时前
面试问我LLM中的RAG,秒过!!!
人工智能·面试·llm·prompt·ai大模型·rag·大模型应用
OLDERHARD2 小时前
Java - LeetCode面试经典150题 - 矩阵 (四)
java·leetcode·面试
寻找09之夏2 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js
非著名架构师2 小时前
js混淆的方式方法
开发语言·javascript·ecmascript
银氨溶液3 小时前
MySql数据引擎InnoDB引起的锁问题
数据库·mysql·面试·求职
多多米10053 小时前
初学Vue(2)
前端·javascript·vue.js
敏编程3 小时前
网页前端开发之Javascript入门篇(5/9):函数
开发语言·javascript