JavaScript变量的生命周期:let为什么不被提升
2018-03-16 09:56
302 查看
原文链接
提升实际上是把变量和函数定义移动到作用域顶部的过程,通常发生在变量声明
当
ES2015给
我们去看看这个过程中更多的细节。
1.容易出错的
有时候我会看见一个奇怪的实践,变量
函数
这就是典型的提升。
事实证明,先使用后声明变量或函数可能会造成混乱。假设您在滚动查看一个大文件,突然看到一个未声明的变量...它是怎么出现在这里,又是在哪里定义的?
当然一个经验丰富的JavaScript开发者不会用这种方式写代码,但是在成千上万的JavaScript GitHub仓库中很有可能会面对这样的代码。
即使看着上面给出的代码示例,也很难理解代码中的声明流程。
自然地,首先声明或描述一个未知的词语,然后才使用它来编写短语。
声明阶段:在作用域中注册一个变量
初始化阶段:分配内存,给作用域中的变量创建绑定。在这个阶段,变量自动地被初始化为
赋值阶段:给已经初始化过的变量赋值
通过声明阶段但是没有到达初始化阶段的变量是处于未定义的状态。
注意,根据变量的生命周期,声明阶段和一般说的变量声明是不同的术语。简言之,引擎在三个阶段处理变量声明:声明阶段、初始化阶段和赋值阶段。
3.
熟悉了生命周期阶段,我们用它们来描述引擎是怎样处理
假设一个场景,当JavaScript遇到一个作用域里有一个
在声明和初始化之后,赋值阶段之前,变量值为
在赋值阶段,
严格来说,提升的概念是在函数作用域的顶部声明和初始化变量。在声明和初始化阶段之间没有间隙。
来研究一个例子。下面的代码创建了一个带有
语句
声明、初始化、赋值阶段在函数作用域的开始立刻执行(只有一步)。
下面的代码示例展示了函数提升:
5.
现在假设解释器进入了一个块级作用域,作用域包含一个
接着解释器一行一行的解析语句。
如果在这个阶段尝试访问变量
当解释器到达
变量退出了暂时性死区。
稍后,当赋值语句
如果JavaScript遇到
来看一个例子。在块级作用域里面用
后来,语句
赋值语句
5.1 为什么提升在
如上所述,提升就是变量在作用域顶部进行声明和初始化。但是
两个阶段中间的间隙创建了暂时性死区,在这里,变量不能被访问。
以一种科幻的风格,在
由于声明和定义两个阶段解耦,提升对于一个
保持稳定的变量声明,有如下建议:
声明、初始化,然后再使用变量。这个流程正确并且容易遵守;
尽可能隐藏变量。变量暴露的越少,代码就越模块化。
链接:https://juejin.im/post/5aa631ab5188255587233214
来源:掘金
提升实际上是把变量和函数定义移动到作用域顶部的过程,通常发生在变量声明
var或函数声明
function fun() {...}。
当
let(包括和
let有同样声明行为的
const和
class)被ES2015提出来的时候,包括我在内的许多开发人员都使用提升来描述变量是如何被访问的。但是在对这个问题进行更多的搜索之后,让我惊讶的是,提升并不是描述
let变量初始化和可用性的正确术语。
ES2015给
let提供了不同并且改善的机制。它要求更严格的变量声明实践(在定义之前不能使用),因此会有更高质量的代码。
我们去看看这个过程中更多的细节。
1.容易出错的var
提升
有时候我会看见一个奇怪的实践,变量var varname和函数
function funcName() {...}在作用域任意地方声明:
// var hoisting num; // => undefined var num; num = 10; num; // => 10 // function hoisting getPi; // => function getPi() {...} getPi(); // => 3.14 function getPi() { return 3.14; }
num变量在
var num声明之前被访问,所以被认定为
undefined。
函数
function getPi() {...}被定义在末尾。由于它被提升到作用域的顶部,所以函数能在定义
getPi()之前被调用。
这就是典型的提升。
事实证明,先使用后声明变量或函数可能会造成混乱。假设您在滚动查看一个大文件,突然看到一个未声明的变量...它是怎么出现在这里,又是在哪里定义的?
当然一个经验丰富的JavaScript开发者不会用这种方式写代码,但是在成千上万的JavaScript GitHub仓库中很有可能会面对这样的代码。
即使看着上面给出的代码示例,也很难理解代码中的声明流程。
自然地,首先声明或描述一个未知的词语,然后才使用它来编写短语。
let鼓励遵循这种方式来使用变量。
2.探索底层:变量的生命周期
当引擎访问变量的时候,它们的生命周期包括下面几个阶段:声明阶段:在作用域中注册一个变量
初始化阶段:分配内存,给作用域中的变量创建绑定。在这个阶段,变量自动地被初始化为
undefined
赋值阶段:给已经初始化过的变量赋值
通过声明阶段但是没有到达初始化阶段的变量是处于未定义的状态。
注意,根据变量的生命周期,声明阶段和一般说的变量声明是不同的术语。简言之,引擎在三个阶段处理变量声明:声明阶段、初始化阶段和赋值阶段。
3.var
变量的生命周期
熟悉了生命周期阶段,我们用它们来描述引擎是怎样处理var变量的。
假设一个场景,当JavaScript遇到一个作用域里有一个
var声明的变量的函数。在任何语句执行之前,这个变量就通过了声明阶段和初始化阶段(第一步)。
var variable在函数作用域中声明的位置不影响声明和初始化阶段。
在声明和初始化之后,赋值阶段之前,变量值为
undefined并且可以被访问使用。
在赋值阶段,
variable = 'value',变量会得到它的初始值(第二步)。
严格来说,提升的概念是在函数作用域的顶部声明和初始化变量。在声明和初始化阶段之间没有间隙。
来研究一个例子。下面的代码创建了一个带有
var变量的函数:
function multiplyByTen(number) { console.log(ten); // => undefined var ten; ten = 10; console.log(ten); // => 10 return number * ten; } multiplyByTen(4); // => 40当JavaScript开始执行
multipleByTen(4)的时候,它就进入了
multipleByTen的函数作用域,在第一条语句之前,变量
ten完成了声明和初始化阶段。所以调用
console.log(ten)会打印出
undefined。
语句
ten = 10分配了一个初始值。分配之后,
console.log(ten)正确地输出了10。
4.函数声明的生命周期
假设一个函数声明语句function funName() {...},这更加容易。
声明、初始化、赋值阶段在函数作用域的开始立刻执行(只有一步)。
funcName()可以在该作用域的任何地方调用,不依赖于声明语句的位置(甚至可以在最后)。
下面的代码示例展示了函数提升:
function sumArray(array) { return array.reduce(sum); function sum(a, b) { return a + b; } } sumArray([5, 10, 8]); // => 23当JavaScript调用
sumArray([5, 10, 8])的时候,它就进入了
sumArray的函数作用域。在作用域里面,在所有语句执行之前,
sum通过了三个阶段:声明、初始化、赋值。这种方式,
array.reduce(sum)甚至可以在声明语句
function sum(a, b) {...}之前使用
sum。
5.let
变量的生命周期
let变量的处理方式和
var不同。最主要的区别就是声明和初始化阶段被分开了。
现在假设解释器进入了一个块级作用域,作用域包含一个
let variable语句。变量立刻通过了声明阶段,在作用域注册了它的名字(步骤 1)。
接着解释器一行一行的解析语句。
如果在这个阶段尝试访问变量
variable,JavaScript会抛出
ReferenceError: variable is not defined。因为变量的状态是未定义的,
variable在暂时性死区。
当解释器到达
let variable语句的时候,初始化阶段通过(步骤 2)。现在变量的状态是已定义并且访问它会得到
undefined。
变量退出了暂时性死区。
稍后,当赋值语句
variable = 'value'出现,就通过了赋值阶段(步骤 3)。
如果JavaScript遇到
let variable = 'value',那么初始化和赋值会发生在这一条语句上。
来看一个例子。在块级作用域里面用
let声明一个变量
variable:
let condition = true; // console.log(number); // => Throws ReferenceError let< 4000 /span> number; console.log(number); // => undefined number = 5; console.log(number); // => 5 }当JavaScript进入
if (condition) {...}块级作用域的时候,
number立刻通过了声明阶段。因为
number处于未定义的状态,处于暂时性死区,试图访问这个变量会抛出
ReferenceError: number is not defined。
后来,语句
let number完成了初始化。现在这个变量可以访问了,但是它的值是
undefined。
赋值语句
number = 5当然就是完成了赋值阶段。
const和
class类型和
let有相同的生命周期,除了赋值只能发生一次。
5.1 为什么提升在let
的生命周期里无效
如上所述,提升就是变量在作用域顶部进行声明和初始化。但是let的生命周期将声明和初始化两个阶段解耦了。解耦让提升这个术语失效了。
两个阶段中间的间隙创建了暂时性死区,在这里,变量不能被访问。
以一种科幻的风格,在
let生命周期中,提升这个术语的崩塌创造了暂时性死区。
6.结论
随意使用var声明变量容易犯错。基于这个教训,ES2015创建了
let。它使用一种改进的算法来声明变量,并且使用块级作用域。
由于声明和定义两个阶段解耦,提升对于一个
let声明的变量(包括包括
const和
class)无效。在初始化之前,变量处于暂时性死区并且不可以访问。
保持稳定的变量声明,有如下建议:
声明、初始化,然后再使用变量。这个流程正确并且容易遵守;
尽可能隐藏变量。变量暴露的越少,代码就越模块化。
链接:https://juejin.im/post/5aa631ab5188255587233214
来源:掘金
相关文章推荐
- 为什么你那么努力,却一直还得不到提升?
- 为什么 APM 能提升 IT 团队工作质量?
- 20170128C语言提升01_数据本质和static和生命周期和作用域
- javascript变量声明提升(hoisting)
- javascript变量声明提升(hoisting)
- 【词汇详解】“生命周期”之为什么线程(或者安卓的activity等)要有生命周期
- 20170128C语言提升01_数据本质和static和生命周期和作用域
- javascript变量声明提升
- Javascript变量与函数的声明与提升
- 制造信息化:生命周期管理提升制造水平
- javascript变量声明提升(hoisting)
- 20170128C语言提升01_数据本质和static和生命周期和作用域
- 连载《一个程序猿的生命周期》- 前言 (注:为什么写“一个程序猿的生命周期”)
- 为什么做的报表领导不满意,如何提升报表的价值?
- [知其然不知其所以然-3] 为什么在高负载下cpu的温度没有显著提升
- 20170128C语言提升01_数据本质和static和生命周期和作用域
- JavaScript变量声明提升和函数声明提升
- OpenMp并行提升时间为什么不是线性的?
- javascript变量声明提升(hoisting)
- 程序员提升之留学-1我为什么用留学