JavaScript变量提高运行机制

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

先看一个换汤不换药的经典例子:这个例子你们其实都知道发生了变量提高致使\color{red}{console.log(a)};结果是undefined。问题来了,这个提高的过程在哪里发送?怎么发生?函数

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

1、JavaScript的执行过程

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

1.1 宏观的角度

  • 建立\color{red}{Scope chain}(包含变量对象,父级做用域上下文的变量对象)。
  • 建立\color{red}{variableObject}(参数、内部变量、函数声明)。
  • 设置\color{red}{this}
PS:变量对象的建立过程
  • 根据函数的参数,建立并初始化argument object。
  • 扫描函数内部的代码,查找函数声明。对于全部找到的函数名和函数引用,都会放到变量对象中。若是以前已经存在同名的函数,会进行覆盖。
  • 扫描函数内部的代码,找到变量声明,对于全部找到的变量申明,都放到变量对象中,并初始化为undefined,变量名称跟已经声明的参数或者是函数相同,就会被忽略,不会干扰到以前的声明。

1.2 微观的角度

  • \color{red}{分词/词法分析},这个过程就是将字符串分解为有意义的代码块,这些代码块咱们称为词法单元。
  • \color{red}{解析/语法分析},这个过程是将词法单位抽象为抽象语法树AST。
  • \color{red}{代码生成}。将AST生成能够执行的代码。

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

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

一、\color{red}{变量的提高发生在编译的代码生成阶段析},例如在代码中var a,编译器会询问做用域中是否有一个该名称的变量存在。若是存在,就继续往下编译,若是不存在就会在当前做用域申明一个新的变量。网站

二、运行阶段,引擎发现代码a = 1的时候,首先会查询当前做用域是不是否有一个名为a的变量,若是存在就进行 赋值,\color{red}{若是没有就向上级执行,这个查询过程是在做用域链中完成}。大多数状况下,编译发生在代码执行以前的几微秒(甚至更短)。ui

2、ES6 let、const

ES新增的变量声明语法let与const。咱们用let做为例子。this

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都是不存在变量提高的。以下 lua

\color{red}{可是在最近的阅读中发现,有人对let真的不存在变量提高提出了疑问。},对此我也产生的疑问,发如今不一样的权威机构有着不同的解释。spa

  • 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不存在变量提高3d

  • 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)code

  • 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 的存在。\color{red}{在 ECMAScript 2015中, let 也会提高到语句块的顶部。可是,在这个语句块中,在变量声明以前引用这个变量会致使一个 ReferenceError的结果。}cdn

那其实你们会有疑问,为何上面的代码会报错。其实这并非因为变量不提示致使的,而是因为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不等价。
相关文章
相关标签/搜索