Js中的变量提升

更新日期: 2019-08-07阅读: 2k标签: 变量

为什么要提升

变量提升是 Javascript 中一个很有趣,也让很多人迷惑的特征。那么,Javascript 为什么要设计这个特征呢?

我来看 Javascript 创始人 Brendan Eich 的 twitter:

A bit more history: var hoisting was an implementation artifact. function hoisting was better motivated: https://twitter.com/BrendanEi....

函数的提升使用明确的理由的。但是变量的提升,只是再实现的“顺便”提升了。

那么函数为什么要提升呢?他也给出了理由:

yes, function declaration hoisting is for mutual recursion & generally to avoid painful bottom-up ML-like order

函数提升是为了可以再函数定义之前调用函数。只有这样才可能支持两个函数之间互相调用。同时,这样可以把程序的主要逻辑放在前部,而不是必须放在程序最后,是程序结果更加符合人的书写与阅读习惯。


如何提升

很多介绍变量提升的文章,提到变量提升可以这样理解:

/*... 一些代码 ...*/
var x = 1;

等价于

var x;
/*... 一些代码 ...*/
x = 1;

这很直观。但是 Javascript 编译、运行过程中,显然不会随便修改用户的代码。那么,变量提升在 Javascript 中具体是如何实现的呢?


Javascript 的运行过程

为了解决这个问题,我们先看一下 Javascript 的整理运行逻辑。

在 ECMA 262 里,对 ScriptEvaluationJob 如瑞啊的描述:

  1. Assert: sourceText is an ECMAScript source text (see clause 10).
  2. Let realm be the current Realm Record.
  3. Let s be ParseScript(sourceText, realm, hostDefined).
  4. If s is a List of errors, then

    1. Perform HostReportErrors(s).
    2. Return NormalCompletion(undefined).
  5. Return ? ScriptEvaluation(s).

对 Module 来说,有 TopLevelModuleEvaluationJob:

  1. Assert: sourceText is an ECMAScript source text (see clause 10).
  2. Let realm be the current Realm Record.
  3. Let m be ParseModule(sourceText, realm, hostDefined).
  4. If m is a List of errors, then

    1. Perform HostReportErrors(m).
    2. Return NormalCompletion(undefined).
  5. Perform ? m.Instantiate().
  6. Assert: All dependencies of m have been transitively resolved and m is ready for evaluation.
  7. Return ? m.Evaluate().

可以看到,Javascirpt 虽然是解释性执行的语言,但是它并不是边读取边解释边执行,而是一定要把整个脚本加载并解析完成(通过 ParseScript 或 ParseModule)之后,才开始执行。这样,在脚本开始执行的时候,就可以知道所有的变量与函数的声明的信息,即使还没有执行到变量或函数声明的地点。这就使得在 Javascript 里引用“还没有声明”的函数和变量成为可能。


不同作用域的变量提升

变量提升会在函数内,以及全局作用域发生。

全局(Script)

在 ECMA-262 中,通过 VarScopedDeclarations 来收集 Script 中的 var 变量定义,以及顶级函数定义,并在执行脚本之前,通过 GlobalDeclarationInstantiation 注册至全局环境。这些变量以及函数将被放在全局对象中。变量在此时将被初始化为 undefined ,而函数则是直接被初始化为函数本身(可以直接调用)。

Script 全局的 VarScopedDeclarations 将收集:

  1. var 声明的变量。包括 for(var ...)、for await (var ...) 中用 var 声明的变量。同时,在各种控制结构内部,以及 Block 内部的都会被一起收集。这一过程实在运行之前执行的,所以声明是否会提升与代码是否会被执行无关。如 if 等控制语句中,未被执行的代码中定义的变量也会被提升。但是,函数定义内部的 var 定义不会被收集,也就是说函数内部的 var 定义不会被提升至函数外。
  2. 顶级的函数声明。位于块内部,以及各种控制结构内部的函数声明不会被收集,也不会被提升。

说点细节的话,Script 的 VarScopedDeclarations 直接使用了其中的 StatementList 的 TopLevelVarScopedDeclarations 。StatementList 可以包含 Statement 与 Declaration 。StatementList 的 TopLevelVarScopedDeclarations 收集了 Statement 的 VarScopedDeclarations 与 Declaration 中的函数声明。

Statement 的 VarScopedDeclaration 与 Script 不同,直接递归收集了语句中(除函数定义体内部之外的)所有 var 声明。

因而对于函数声明,仅有顶级的会被提升。


全局(Module)

Module 的做法略有不同。它通过 VarScopedDeclarations 来收集 var 变量定义,通过 LexicallyScopedDeclarations来收集顶级函数定义,并在执行脚本之前,使用 InitializeEnvironment 注册至全局环境。Module 没有全局对象,全局变量是被放在全局的 Module 的 Lexical Scope 里的。不同的 Module 之间,不会互相影响。变量同样会被初始化为 undefined,而函数可以直接使用。


函数中

函数中的情形与 Script 类似。只不过它不通过 Script ,而是使用 FunctionBody 的 VarScopedDeclarations 来收集需要提升的定义,并在 FunctionDeclarationInstantiation 里注册到运行环境中。

但是,函数中的 var 变量定义并不保存在对象里。且 var 与 函数参数可以认为是出来同一个环境中的,所以,对于与函数参数同名的 var 变量,它的初始值将是函数的参数值,其它依然为 undefined 。


再说函数定义的提升

上面说了,var 是可以跨块提升的,但是函数声明不可以。除了顶级(全局的最外层,或函数定义的最外层)的函数意外,其他的函数声明将通过 LexicallyScopedDeclarations ,并在进入块的时候,通过 BlockDeclarationInstantiation 定义在一个新建的块级作用域中。

注意,只有函数声明才会在相应的作用域引入一个函数对象。函数声明语句是以 function 关键字开始,整条语句仅声明一个函数。比如, var func = funciton() {} 格式的,不是函数声明,按照 var 的规则处理。

在 ECMAScript 将块级函数标准化之前,各家浏览器就已经各自实现了块内定义的函数。这就导致大家的实现各不相同,并且持续至今。这也包括块内声明的函数是如何提升的。因而,可以在浏览器里观察到与此处描述不同的块级定义的函数的提升行为。MDN 有不同浏览器块内函数提升的在不同浏览器中的对比。

let 与 const

let 与 const 一般被认为不提升。但是这也不太准确。他们与 var 有两点不同。

  1. let 与 const 不会跨块。他们定义的变量仅在块内(以及快内的嵌套块、函数内)可以访问。在块外不存在。(var 会提升到函数的顶层。)这与函数是一致的。并且与函数一样,它的定义是通过 LexicallyScopedDeclarations 收集的。
  2. 在 Script 全局,它们也不会被放进全局对象,而是一定处在一个 Lexical Enviroment 中。
  3. 他们在创建的时候,不会被初始化。而是一定要执行到定义的语句,才会被初始化。(var 变量在创建时,会立即被初始化为 undefined。)

在 Javascript ,使用未被初始化的变量会抛出异常。所以 var 提升之后,可以在定义之前使用(因为被初始化了),但是 let 与 const 在作用域之内,定义之前使用就会抛出异常(因为他们进入作用域就已经存在了,但是未被初始化)。但是,如果在作用域外使用,在非严格模式下,会导致在全局对象中创建一个同名变量(因为他们不存在!),反而不会出错。


node.js

这个为啥要拿出来说呢,因为 node.js 的(除 ECMAScript module 外)代码,都是会被放进一个函数运行的:

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
});

所以,在 node.js 里,无法在全局作用域写代码。因而 var 声明的“全局”变量,也不会进入全局对象。


小结

javascript 中所有变量、常量、函数声明,都是在进入相应的作用域时生成的。他们不是在声明语句所在的位置生成的,就是所谓“提升”

var 变量的作用域是其所在的函数(或全局作用域),let、const、函数的作用域是其所在的块。由于历史原因,在不同浏览器中,块级函数的表现会有所不同

在变量生成的时候,var 变量会被初始化为 undefined ;let、const 不会被初始化;函数则直接被初始化为实际的函数对象。如果 var 变量与函数参数重名,则会被初始化为函数参数的值,使用未被初始化的变量会引发运行时错误(所以,在 let、const 变量的作用域中,定义语句之前使用他们会发生错误)

在变量的实际声明处,var 变量仅执行 Initializer 的赋值(如果没有 Initializer,则什么也不做);let、const 变量将初始化为 Initializer 的值(如没有 Initializer,初始化为 undefined );函数声明处则什么也不做。


链接: https://www.fly63.com/article/detial/5186

css自定义变量_初次接触CSS变量

本文的目的主要是展示CSS变量是如何工作的。随着Web应用程序变得越来越大,CSS变得越来越大,越来越多,而且很多时候都很乱,在良好的上下文中使用CSS变量,为您提供重用和轻松更改重复出现的CSS属性的机制。

CSS变量(自定义属性)实践指南

Sass和Less这样的预处理器,让我们的CSS代码保持良好的结构和可维护性。像变量、混合(mixins)、循环控制等特性,增强了动态编写CSS的能力,从而减少重复代码,也加快了我们开发速度。

css变量_原生css 中变量的使用

原生css 中变量的使用,这个重要的 CSS 新功能,所有主要浏览器已经都支持了。本文全面介绍如何使用它,你会发现原生 CSS 从此变得异常强大。声明变量的时候,变量名前面要加两根连词线(--),var()函数用于读取变量。

理解var let const区别

JavaScript中var、let、const区别?js中let和const都是es5版本新的命名规范,在此之前定定义一个变量只能用var。我们可以把let和const看做是为了弥补var的一些不足而新设计出来的

一个例子,变量提升和函数提升就是这么简单!

引擎在读取js代码的过程中,分为两步。第一个步骤是整个js代码的解析读取,第二个步骤是执行。在JS代码执行之前,浏览器的解析器在遇到 var 变量名 和function 整个函数 提升到当前作用域的最前面。

CSS变量使用解析

很早直接就了解到CSS变量相关的内容,奈何之前使用价值不高(很多主流浏览器不兼容),最近发现主流浏览器都已经支持了这一变化,CSS变量就像JS的变量,每个类名或者花括号就像一个function,里面的变量只有上下文以内可以获取,这就让CSS有了更多可能性。

let与var的区别,为什么要用let?

var是全局声明,let是块级作用的,只适用于当前代码块;var变量会发生变量提升,let则不会进行变量提升;var 会造成重复赋值,循环里的赋值可能会造成变量泄露至全局

ES6-变量的解构赋值

解构赋值官方解释:按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。变量的解构赋值就是一种写法,掌握了这种写法可以让我们在书写 javascript 代码时可以更加的简单,迅捷。

JS中变量的存储

JS中的变量是保存在栈内存中的:1.基本数据类型的值直接在栈内存中存储;2.值与值之间是独立存在的,修改一个变量不会影响其他变量;对象是保存到堆内存中的,每创建一个新的对象

js 交换变量值的方法总汇

这篇文章总结七种办法来交换a和b的变量值 。最最最简单的办法就是使用一个临时变量了 ,最后我的方案是利用了ES6的解构赋值语法 ,它允许我们提取数组和对象的值,对变量进行赋值

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!