JavaScript变量提升运行机制

简介: JavaScript的工作原理是,先解析代码,获取所有声明的变量或者函数,然后运行。这造成的结果就是所有声明的变量或者函数都被提升到代码的头部,这叫做声明提示提升。


JavaScript的工作原理是,先解析代码,获取所有声明的变量或者函数,然后运行。这造成的结果就是所有声明的变量或者函数都被提升到代码的头部,这叫做声明提示提升。


先看一个换汤不换药的经典例子:这个例子大家其实都知道发生了变量提升导致;结果是undefined。问题来了,这个提升的过程在哪里发送?怎么发生?


console.log(a);  // undefined
var a = 1;
b()              // 1
function b() {
    console.log(a)
}


一、JavaScript的执行过程


1. JavaScript在执行之前会经历一个编的过程(其他术语:创建、预解释过程)。


1.1 宏观的角度


  • 创建(包含变量对象,父级作用域上下文的变量对象)。
  • 创建(参数、内部变量、函数声明)。
  • 设置
PS:变量对象的创建过程
  • 根据函数的参数,创建并初始化argument object。
  • 扫描函数内部的代码,查找函数声明。对于所有找到的函数名和函数引用,都会放到变量对象中。如果之前已经存在同名的函数,会进行覆盖。
  • 扫描函数内部的代码,找到变量声明,对于所有找到的变量申明,都放到变量对象中,并初始化为undefined,变量名称跟已经声明的参数或者是函数相同,就会被忽略,不会干扰到之前的声明。


1.2 微观的角度


  • ,这个过程就是将字符串分解为有意义的代码块,这些代码块我们称为词法单元。
  • ,这个过程是将词法单位抽象为抽象语法树AST。
  • 。将AST生成可以执行的代码。


2. 编译完之后会进行一个执行阶段。


2.1 执行:变量的值、函数的引用、执行代码。


1、,例如在代码中var a,编译器会询问作用域中是否有一个该名称的变量存在。如果存在,就继续往下编译,如果不存在就会在当前作用域申明一个新的变量。

2、运行阶段,引擎发现代码a = 1的时候,首先会查询当前作用域是否是否有一个名为a的变量,如果存在就进行 赋值,。大多数情况下,编译发生在代码执行之前的几微秒(甚至更短)。


二、ES6 let、const


ES新增的变量声明语法let与const。我们用let作为例子。


function f() {
  console.log(a);
  let a = 2;
}
f(); // ReferenceError: a is not defined


这段代码直接报错a is not defined,let和const拥有类似的特征,阻止了变量提升,当执行console.log(a)的时候变量没有定义,所以报错了。 在阮一峰老师的网站也写到let和const都是不存在变量提升的。如下

,对此我也产生的疑问,发现在不同的权威机构有着不一样的解释。


  • MDN中写到:In ECMAScript 2015, let do not support Variable Hoisting, which means the declarations made using "let", do not move to the top of the execution block.

在MDN中认为let不存在变量提升

  • ECMA-262-13.3.1 Let and Const Declarations写到: let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated.

这说明即使是 block 最后一行的 let 声明,也会影响 block 的第一行。这就是提升(hoisting)

  • ECMA-262: 8.2.1.2 Runtime Semantics: EvalDeclarationInstantiation( body, varEnv, lexEnv, strict)写到: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts.

这句话也间接的证明 let hoisting 的存在。


那其实大家会有疑问,为什么上面的代码会报错。其实这并不是由于变量不提示导致的,而是由于TDZ(临时性死区)导致的。


在举个例子:


{
    a = 2;
    let a;
}


这段代码可以解释为:


{
    let a;// 变量提升
    "start TDZ"
    a = 2; // 这里在TDZ中间,所以会导致a = 2 报错
    a;
    "end TDZ"
}


所以破案了:let是存在变量提升。它“变量提升的行为”,是由于TDZ导致的。


so...总结一下

  • let 声明会提升到块顶部
  • 从块顶部到该变量的初始化语句,这块区域叫做 TDZ(临时死区)
  • 如果你在 TDZ 内使用该变量,JS 就会报错,注意TDZ 跟 hoisting不等价。
目录
相关文章
|
12天前
|
前端开发 JavaScript UED
深入理解JavaScript中的事件循环机制
JavaScript中的事件循环机制是其异步编程的核心,深入理解该机制对于开发高效、流畅的前端应用至关重要。本文将介绍事件循环的工作原理、常见的事件循环模型,以及如何利用这些知识解决前端开发中的常见问题。
|
5天前
|
缓存 移动开发 JavaScript
WKWebView对网页和js,css,png等资源文件的缓存机制及如何刷新缓存
WKWebView对网页和js,css,png等资源文件的缓存机制及如何刷新缓存
17 1
|
12天前
|
开发框架 JavaScript 前端开发
JavaScript的事件循环机制是其非阻塞I/O的关键
【5月更文挑战第13天】JavaScript的事件循环机制是其非阻塞I/O的关键,由调用栈、事件队列和Web APIs构成。当异步操作完成,回调函数进入事件队列,待调用栈空时,事件循环取队列中的任务执行。在游戏开发中,事件循环驱动游戏循环更新,包括输入处理、游戏逻辑更新和渲染。示例代码展示了如何模拟游戏循环,实际开发中则常使用游戏框架进行抽象处理。
40 4
|
12天前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包机制
闭包是JavaScript中一个重要且常被误解的概念。本文将深入探讨闭包的本质、工作原理以及在实际开发中的应用。通过详细解析闭包的定义、作用域链、内存管理等方面,读者将对闭包有更清晰的理解,并能够运用闭包解决实际开发中的问题。
|
12天前
|
消息中间件 存储 前端开发
理解JavaScript事件循环机制
理解JavaScript事件循环机制
14 1
|
12天前
|
前端开发 JavaScript UED
JavaScript 的事件循环机制是其非阻塞 I/O 模型的核心
【5月更文挑战第9天】JavaScript的事件循环机制是其非阻塞I/O的关键,通过单线程的调用栈和任务队列处理异步任务。当调用栈空时,事件循环从任务队列取出一个任务执行,形成循环。异步操作完成后,回调函数进入任务队列,等待被事件循环处理。微任务如Promise回调在每个宏任务结束后执行。此机制确保JavaScript能高效处理异步操作,不阻塞主线程,提供流畅的用户体验。
19 2
|
12天前
|
JavaScript 前端开发 开发者
【Web 前端】什么是JS变量提升?
【5月更文挑战第1天】【Web 前端】什么是JS变量提升?
【Web 前端】什么是JS变量提升?
|
12天前
|
JavaScript 前端开发 开发者
【JavaScript技术专栏】JavaScript事件处理机制详解
【4月更文挑战第30天】本文探讨JavaScript中的事件处理机制,涉及事件类型(如click、mouseover)、事件流(冒泡型、捕获型及目标阶段)、事件处理函数(内联与addEventListener方法)以及事件委托(用于优化内存和处理动态元素)。此外,还介绍了事件取消,通过`preventDefault()`和`stopPropagation()`控制事件行为。理解这些概念对构建交互式Web应用至关重要。
|
12天前
|
JavaScript 前端开发
Node.js中的错误处理机制
【4月更文挑战第30天】本文介绍了Node.js的错误处理机制,包括Error对象、try-catch、错误事件监听及Promise和async/await的错误处理。错误通常封装在Error对象中,可自定义错误类型。try-catch用于捕获异常,但不适用于异步错误。事件监听器处理对象发出的'error'事件,防止应用崩溃。Promise的.catch()和async/await结合try-catch用于处理异步错误。良好的错误处理是保证应用健壮性和可靠性的关键。
|
12天前
|
消息中间件 存储 前端开发
理解JavaScript事件循环机制
理解JavaScript事件循环机制