js变量提升

作者: 魂斗驴 | 来源:发表于2021-03-21 10:32 被阅读0次

许多JavaScript程序员将提升解释为JavaScript将声明(变量和函数)移至其当前作用域(函数或全局)顶部的行为。 好像它们实际上已经移到了代码的顶部一样,事实并非如此。例如:

console.log(a);
var a= 'Hello World!';

他们会说,上面的代码将在提升后转换为以下代码:

var a; 
console.log(a); 
a ='Hello World!';

尽管这似乎正在发生(因为代码可以正常工作),但实际上并没有发生,您的代码也无处可去。JavaScript引擎实际上并没有移动您的代码,您的代码停留在您键入代码的位置。

那么,什么是提升呢?

在编译阶段,即在代码执行前的几微秒内,将对其进行扫描以查找函数和变量声明。所有这些函数和变量声明都被添加到称为词汇环境(Lexical Environment) 的JavaScript数据结构内部的内存中。这样,即使在源代码中实际声明它们之前也可以使用它们。

什么是词汇环境?

词法环境 是一个数据结构,具有标识符变量映射。(这里的标识符是指变量/函数的名称,而变量是对实际对象[包括函数对象]或原始值的引用)。

词汇环境在概念上是这样的:

LexicalEnvironment = {
  Identifier:  <value>,
  Identifier:  <function object>
}

简而言之,词汇环境是程序执行过程中变量和函数存在的地方。

现在我们知道了提升实际上是什么,让我们来看看如何提升发生的函数和变量(varletconst)声明。

提升函数声明:

helloWorld();  // prints 'Hello World!' to the console
function helloWorld(){
  console.log('Hello World!');
}

我们已经知道函数声明是在编译阶段添加到内存中的,因此我们可以在实际函数声明之前在代码中对其进行访问。

因此,上述代码的词法环境如下所示:

lexicalEnvironment = { 
 helloWorld:<func> 
}

因此,当JavaScript引擎遇到对的调用时helloWorld(),它将查看词法环境,找到该函数并能够执行它。

提升函数表达式

JavaScript仅提升函数声明,不提升函数表达式。例如:此代码将不起作用。

helloWorld();  // TypeError: helloWorld is not a function
var helloWorld = function(){
  console.log('Hello World!');
}

由于JavaScript仅提升声明,而不初始化(赋值),因此helloWorld会被视为变量,而不是函数。因为helloWorldvar变量,所以引擎将undefined在提升过程中为其分配值。

因此,此代码将起作用。

var helloWorld = function(){
  console.log('Hello World!');  prints 'Hello World!'
}
helloWorld();

提升var变量:

让我们看一些示例,以了解在var变量情况下的提升。

console.log(a); // outputs 'undefined'
var a = 3;

我们期望,3但是得到了undefined。为什么?

请记住,JavaScript仅是提升声明,而不是初始化。也就是说,在编译期间,JavaScript仅将函数和变量声明存储在内存中,而不存储它们的分配(值)。

但是为什么undefined呢?

当JavaScript引擎var在编译阶段找到变量声明时,它将将该变量添加到词法环境中,并使用进行初始化,undefined并在执行过程中稍后到达代码中实际分配的行时,它将分配该值到变量。

因此,以上代码的初始词汇环境如下所示:

lexicalEnvironment = {
  a: undefined
}

因此,我们得到undefined而不是3。当引擎到达完成实际分配的行(在执行过程中)时,它将在其词法环境中更新变量的值。因此,赋值后的词法环境将如下所示:

lexicalEnvironment = { 
 a:3 
}

提升let和const变量:

让我们先来看一些例子:

console.log(a);
let a = 3;

输出:

ReferenceError: a is not defined

那么letconst变量没有被提升吗?

答案要复杂得多。

所有声明(函数,var,let,const和类)都使用JavaScript进行了提升,而 var 声明用初始化 undefined, 但 let 和 const 声明保持未初始化

只有在运行时由JavaScript引擎评估其词法绑定(赋值)时,它们才会被初始化。这意味着您无法在引擎在源代码中声明的位置评估其值之前访问该变量。这就是我们所谓的“时间死区”,即从变量创建到其无法访问它们的初始化之间的时间跨度。

如果JavaScript引擎在声明它们的行上仍找不到let或的值const,它将为它们分配的值undefined或返回错误(如果是const)。

让我们来看更多示例:

let a;
console.log(a); // outputs undefined
a = 5;

在这里,在编译阶段,JavaScript引擎会遇到该变量a并将其存储在词法环境中,但是由于它是一个let变量,因此引擎不会使用任何值对其进行初始化。因此,在编译阶段,词法环境将如下所示:

lexicalEnvironment = {
  a: <uninitialized>
}

现在,如果我们尝试在声明变量之前访问变量,则JavaScript引擎将尝试从词法环境中获取变量的值,因为该变量未初始化,因此将引发引用错误。

在执行期间,当引擎到达声明该变量的行时,它将尝试评估其绑定(值),因为该变量没有与之关联的值,因此它将对其进行分配undefined

因此,执行第一行后,词法环境将如下所示:

lexicalEnvironment = {
  a: undefined
}

undefined将被记录到控制台,之后5将被分配给它的词法环境将得到更新,以包含的价值a,以5undefined

注意 只要在变量声明之前不执行该代码,我们甚至可以在声明它们之前在代码(例如,函数主体)中引用letconst变量。

例如,此代码是完全有效的。

function foo () {
  console.log(a);
}
let a = 20;
foo();  // This is perfectly valid

但下面的例子会产生引用错误。

function foo() {
 console.log(a); // ReferenceError: a is not defined
}
foo(); // This is not valid
let a = 20;

提升等级声明

正如letconst声明,在JavaScript类也提升,同样letconst声明,他们仍然未初始化状态的评价。因此,它们也受到“临时死区”的影响。例如:

let peter = new Person('Peter', 25); // ReferenceError: Person is  
                                     // not defined
console.log(peter);
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

因此,要访问这些类,您必须先声明它们。例如:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
let peter = new Person('Peter', 25); 
console.log(peter);
// Person { name: 'Peter', age: 25 }

因此,再次在编译阶段,上述代码的词法环境将如下所示:

lexicalEnvironment = {
  Person: <uninitialized>
}

当引擎评估了class语句后,它将使用该值初始化该类。

lexicalEnvironment = {
  Person: <Person object>
}

提升类表达式

就像函数表达式一样,类表达式也不会被提升。例如,此代码将不起作用。

let peter = new Person('Peter', 25); // ReferenceError: Person is  
                                     // not defined
console.log(peter);
let Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

正确的方法是这样的:

let Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
let peter = new Person('Peter', 25); 
console.log(peter);
// Person { name: 'Peter', age: 25 }

结论

因此,现在我们知道,在提升过程中,JavaScript引擎不会实际移动代码。正确理解提升机制将有助于您避免将来因提升而引起的任何错误和混乱。为避免未定义的变量或参考错误等提升的副作用,请始终尝试在变量的各自作用域顶部声明变量,并始终在声明变量时尝试初始化变量。

参考

Hoisting in Modern JavaScript — let, const, and var

相关文章

  • JS中的提升

    JS中包含两种提升,变量提升和函数提升。 变量提升 变量提升只能是var或者function声明的变量或者函数,l...

  • 浏览器学习笔记-JS执行

    变量提升 变量提升原理浏览器对js是先编译后执行,在编译过程中,js中的变量声明会被提升到代码段落前面。函数声明和...

  • JavaScript 大纲

    js 语法基础 JavaScript 介绍 js 输出 注释 变量,变量声明提升,全局变量,常量 7 大数据类型 ...

  • Javascript 变量执行过程 和 数组 & 对象的区别

    变量提升 JS执行过程1. 先提升(先提升函数,再提升变量,如果名字一样,变量提升会覆盖函数提升)2. 再执行,如...

  • 变量提升和函数提升

    JS不像C语言,C语言是先声明后使用,否则会报错。但JS中,有变量提升现象,可以先使用后声明。 JS存在变量提升,...

  • 前端经典面试题合集(一)

    1.谈谈变量提升 考察点:js基础知识,js执行机制,变量的提升答:执行js代码时,会生成执行环境,在函数中的代码...

  • 变量声明提升

    变量声明提升是JS中一个基础的问题,同时也是对JS词法作用域认识的一个提升。在JS面试题中,关于变量声明提升的问题...

  • JS变量提升

    废话不多说,直接看以下例子,代码地址: 函数声明和函数表达式 很明显,这个大家都知道这个答案,就是很常见的作用域提...

  • js变量提升

    JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的...

  • js变量提升

    对于大多数js开发者来说,变量提升可以说是一个非常常见的问题,但是可能很多人对其不是特别的了解。所以在此,我想来讲...

网友评论

    本文标题:js变量提升

    本文链接:https://www.haomeiwen.com/subject/vbcfcltx.html