了解JavaScript中的吊装

In this tutorial, we'll investigate how the famed hoisting mechanism occurs in JavaScript. Before we dive in, let's get to grips with what hoisting is.

在本教程中,我们将研究著名的提升机制在JavaScript中是如何发生的。 在我们深入之前,让我们先掌握一下起重是什么。

Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.

提升是一种JavaScript机制,在执行代码之前,将变量和函数声明移至其作用域的顶部。

Inevitably, this means that no matter where functions and variables are declared, they are moved to the top of their scope regardless of whether their scope is global or local.

不可避免地,这意味着无论在何处声明函数和变量,无论它们的作用域是全局的还是局部的,它们都将移至其作用域的顶部。

Of note however, is the fact that the hoisting mechanism only moves the declaration. The assignments are left in place.

但是,值得注意的是,升降机构仅移动声明。 作业留在原地。

If you've ever wondered why you were able to call functions before you wrote them in your code, then read on!

如果您曾经想知道为什么在代码中编写函数之前就能够调用它们,那么请继续阅读!

未定义vs ReferenceError ( undefined vs ReferenceError )

Before we begin in earnest, let's deliberate on a few things.

在我们认真开始之前,让我们仔细考虑一些事情。

console.log(typeof variable); // Output: undefined

This brings us to our first point of note:

这使我们想到了第一点:

In JavaScript, an undeclared variable is assigned the value undefined at execution and is also of type undefined.

在JavaScript中,未声明的变量在执行时被赋予未定义的值,并且也是未定义的类型。

Our second point is:

我们的第二点是:

console.log(variable); // Output: ReferenceError: variable is not defined

In JavaScript, a ReferenceError is thrown when trying to access a previously undeclared variable.

在JavaScript中,尝试访问以前未声明的变量时将抛出ReferenceError。

The behaviour of JavaScript when handling variables becomes nuanced because of hoisting. We'll look at this in depth in subsequent sections.

当处理变量时,JavaScript的行为由于提升而变得细微差别。 我们将在后续部分中对此进行深入研究。

起重变量 ( Hoisting variables )

The following is the JavaScript lifecycle and indicative of the sequence in which variable declaration and initialisation occurs.

以下是JavaScript的生命周期,并指示发生变量声明和初始化的顺序。

variable-hoisting

However, since JavaScript allows us to both declare and initialise our variables simultaneously, this is the most used pattern:

但是,由于JavaScript允许我们同时声明和初始化变量,所以这是最常用的模式:

var a = 100;

It is however important to remember that in the background, JavaScript is religiously declaring then initialising our variables.

但是重要的是要记住,在后台,JavaScript会虔诚地声明然后初始化变量。

As we mentioned before, all variable and function declarations are hoisted to the top of their scope. I should also add that variable declarations are processed before any code is executed.

如前所述,所有变量和函数声明都悬挂在其作用域的顶部 。 我还应该补充一点,在执行任何代码之前都要先处理变量声明

However, in contrast, undeclared variables do not exist until code assigning them is executed. Therefore, assigning a value to an undeclared variable implicitly creates it as a global variable when the assignment is executed. This means that, all undeclared variables are global variables.

但是,相反, 未声明的变量在执行分配它们的代码之前不存在。 因此,在执行赋值时,将值分配给未声明的变量会隐式将其创建为全局变量。 这意味着, 所有未声明的变量都是全局变量。

To demonstrate this behaviour, have a look at the following:

为演示此行为,请查看以下内容:

function hoist() {
  a = 20;
  var b = 100;
}

hoist();

console.log(a); 
/* 
Accessible as a global variable outside hoist() function
Output: 20
*/

console.log(b); 
/*
Since it was declared, it is confined to the hoist() function scope.
We can't print it out outside the confines of the hoist() function.
Output: ReferenceError: b is not defined
*/

Since this is one of the eccentricities of how JavaScript handles variables, it is recommended to always declare variables regardless of whether they are in a function or global scope. This clearly delineates how the interpreter should handle them at run time.

由于这是JavaScript处理变量的方式之一,因此建议始终声明变量,而不管它们是在函数范围内还是在全局范围内 。 这清楚地说明了解释器在运行时应如何处理它们。

ES5 ( ES5 )

变种 (var)

The scope of a variable declared with the keyword var is its current execution context. This is either the enclosing function or for variables declared outside any function, global. Let's look at a few examples to identify what this means:

用关键字var声明的变量的范围是其当前的执行上下文 。 这是封闭函数,或者是在任何函数global外部声明的变量。 让我们看几个例子来确定这意味着什么:

全局变量 (global variables)
console.log(hoist); // Output: undefined

var hoist = 'The variable has been hoisted.';

We expected the result of the log to be: ReferenceError: hoist is not defined, but instead, its output is undefined.

我们期望该日志的结果为: ReferenceError: hoist is not defined ,但其输出undefined

Why has this happened?

为什么会这样呢?

This discovery brings us closer to wrangling our prey.

这一发现使我们更接近于对猎物进行争吵。

JavaScript has hoisted the variable declaration. This is what the code above looks like to the interpreter:

JavaScript悬挂了变量声明。 这是上面的代码对解释器的外观:

var hoist;

console.log(hoist); // Output: undefined
hoist = 'The variable has been hoisted.';

Because of this, we can use variables before we declare them. However, we have to be careful because the hoisted variable is initialised with a value of undefined. The best option would be to declare and initialise our variable before use.

因此,我们可以在声明变量之前使用变量。 但是,由于提升变量的初始值未定义,所以我们必须小心。 最好的选择是在使用前声明并初始化变量。

函数范围变量 (Function scoped variables)

As we've seen above, variables within a global scope are hoisted to the top of the scope. Next, let's look at how function scoped variables are hoisted.

正如我们在上面看到的,全局范围内的变量被提升到范围的顶部。 接下来,让我们看看如何提升函数作用域变量。

function hoist() {
  console.log(message);
  var message='Hoisting is all the rage!'
}

hoist();

Take an educated guess as to what our output might be.

对我们的输出进行有根据的猜测。

If you guessed, undefined you're right. If you didn't, worry not, we'll soon get to the bottom of this.

如果您猜到了, undefined就对。 如果您不这样做,请不用担心,我们很快就会深入浅出。

This is how the interpreter views the above code:

这就是解释器如何查看以上代码的方式:

function hoist() {
  var message;
  console.log(message);
  message='Hoisting is all the rage!'
}

hoist(); // Ouput: undefined

The variable declaration, var message whose scope is the function hoist(), is hoisted to the top of the function.

范围为函数hoist()的变量声明var message悬挂在函数的顶部。

To avoid this pitfall, we would make sure to declare and initialise the variable before we use it:

为了避免这种陷阱,我们将在使用变量之前确保对其进行声明初始化

function hoist() {
  var message='Hoisting is all the rage!'
  return (message);
}

hoist(); // Ouput: Hoisting is all the rage!

严格模式 (Strict Mode)

Thanks to a utility of the es5 version of JavaScript known as strict-mode, we can be more careful about how we declare our variables. By enabling strict mode, we opt into a restricted variant of JavaScript that will not tolerate the usage of variables before they are declared.

得益于es5版本JavaScript实用程序(称为strict-mode),我们可以更加谨慎地声明变量。 通过启用严格模式 ,我们选择了JavaScript的受限变体,该变体在声明变量之前不会容忍使用变量。

Running our code in strict mode:

在严格模式下运行我们的代码:

  1. Eliminates some silent JavaScript errors by changing them to explicit throw errors which will be spit out by the interpreter.

    通过将一些无提示JavaScript错误更改为显式的抛出错误,这些错误将被解释程序吐出,从而消除了这些错误。
  2. Fixes mistakes that make it difficult for JavaScript engines to perform optimisations.

    修复了使JavaScript引擎难以执行优化的错误。
  3. Prohibits some syntax likely to be defined in future versions of JavaScript.

    禁止将来JavaScript版本中可能定义的某些语法。

We enable strict mode by prefacing our file or function with

我们通过在文件或函数前添加严格模式来启用严格模式

'use strict';

// OR
"use strict";

Let's test it out.

让我们测试一下。

'use strict';

console.log(hoist); // Output: ReferenceError: hoist is not defined
hoist = 'Hoisted'; 

We can see that instead of assuming that we missed out on declaring our variable, use strict has stopped us in our tracks by explicitly throwing a Reference error. Try it out without use strict and see what happens.

我们可以看到,而不是假设我们错过了声明变量的机会,而use strict却通过显式抛出Reference error阻止了我们。 在不严格使用的情况下尝试一下,看看会发生什么。

Strict mode behaves differently in different browsers however, so it's advisable to perform feature testing thoroughly before relying on it in production.

但是,严格模式在不同的浏览器中的行为会有所不同,因此建议在生产中完全依赖功能测试之前再进行依赖测试。

ES6 ( ES6 )

ECMAScript 6, ECMAScript 2015 also known as ES6 is the latest version of the ECMAScript standard, as the writing of this article, Jan 2017 and introduces a few changes to es5.

ECMAScript 6 ,ECMAScript 2015(也称为ES6)是ECMAScript标准的最新版本,撰写本文时为2017年1月 ,介绍了es5的一些更改。

Of interest to us is how changes in the standard affect the declaration and initialisation of JavaScript variables.

我们感兴趣的是标准的更改如何影响JavaScript变量的声明和初始化。

(let)

Before we start, to be noted is the fact that variables declared with the keyword let are block scoped and not function scoped. That's significant, but it shouldn't trouble us here. Briefly, however, it just means that the variable's scope is bound to the block in which it is declared and not the function in which it is declared.

在开始之前,需要注意的事实是,用关键字let声明的变量是块范围的,而不是函数范围的。 这很重要,但这不应该在这里困扰我们。 但是,简单地说,这仅意味着变量的作用域绑定到了声明变量的块,而不是声明变量的函数。

Let's start by looking at the let keyword's behaviour.

让我们从查看let关键字的行为开始。

console.log(hoist); // Output: ReferenceError: hoist is not defined ...
let hoist = 'The variable has been hoisted.';

Like before, for the var keyword, we expect the output of the log to be undefined. However, since the es6 let doesn't take kindly on us using undeclared variables, the interpreter explicitly spits out a Reference error.

像以前一样,对于var关键字,我们期望日志的输出是undefined 。 但是,由于es6 let不会使用未声明的变量来友好地对待我们,因此解释器会明确吐出Reference错误。

This ensures that we always declare our variables first.

这样可以确保我们始终先声明变量。

However, we still have to be careful here. An implementation like the following will result in an ouput of undefined instead of a Reference error.

但是,在这里我们仍然要小心。 如下所示的实现将导致输出undefined而不是Reference error

let hoist;

console.log(hoist); // Output: undefined
hoist = 'Hoisted'

Hence, to err on the side of caution, we should declare then assign our variables to a value before using them.

因此,为了谨慎起见,我们应该声明然后在使用变量之前将其赋值

const (const)

The const keyword was introduced in es6 to allow immutable variables. That is, variables whose value cannot be modified once assigned.

es6中引入了const关键字以允许不可变的变量 。 即,赋值后不能修改其值的变量。

With const, just as with let, the variable is hoisted to the top of the block.

使用const ,就像使用let ,将变量提升到块的顶部。

Let's see what happens if we try to reassign the value attached to a const variable.

让我们看看如果尝试重新分配附加到const变量的值会发生什么。

const PI = 3.142;

PI = 22/7; // Let's reassign the value of PI

console.log(PI); // Output: TypeError: Assignment to constant variable.

How does const alter variable declaration? Let's take a look.

const如何改变变量声明? 让我们来看看。

console.log(hoist); // Output: ReferenceError: hoist is not defined
const hoist = 'The variable has been hoisted.';

Much like the let keyword, instead of silently exiting with an undefined, the interpreter saves us by explicitly throwing a Reference error.

let关键字很像,解释器不是通过undefined的方式静默退出,而是通过显式抛出Reference error节省我们的时间。

The same occurs when using const within functions.

在函数中使用const时也会发生同样的情况。

function getCircumference(radius) {
  console.log(circumference)
  circumference = PI*radius*2;
  const PI = 22/7;
}

getCircumference(2) // ReferenceError: circumference is not defined

With const , es6 goes further. The interpreter throws an error if we use a constant before declaring and initialising it.

使用const ,es6更进一步。 如果我们在声明和初始化常量之前使用常量,则解释器将引发错误。

Our linter is also quick to inform us of this felony:

我们的短毛猫也会很快通知我们这一重罪:

PI was used before it was declared, which is illegal for const variables.

Globally,

在全球范围内

const PI;
console.log(PI); // Ouput: SyntaxError: Missing initializer in const declaration
PI=3.142;

Therefore, a constant variable must be both declared and initialised before use.

因此,必须在使用前声明和初始化常量变量。

As a prologue to this section, it's important to note that indeed, JavaScript hoists variables declared with es6 let and const. The difference in this case is how it initialises them. Variables declared with let and const remain uninitialised at the beginning of execution whilst variables declared with var are initialised with a value of undefined.

作为本节的序言,必须注意的是,JavaScript确实使用了es6 let和const声明的变量。 在这种情况下,不同之处在于如何初始化它们。 在执行开始时,用letconst声明的变量将保持未初始化状态 ,而用var声明的变量将使用undefined值进行初始化。

起重功能 ( Hoisting functions )

JavaScript functions can be loosely classified as the following:

JavaScript函数可以大致分为以下几类:

  1. Function declarations

    函数声明
  2. Function expressions

    函数表达式

We'll investigate how hoisting is affected by both function types.

我们将研究两种功能类型如何影响起重。

函数声明 (Function declarations)

These are of the following form and are hoisted completely to the top. Now, we can understand why JavaScript enable us to invoke a function seemingly before declaring it.

它们具有以下形式,并完全吊在顶部。 现在,我们可以理解为什么JavaScript使我们可以在声明函数之前先调用它了。

hoisted(); // Output: "This function has been hoisted."

function hoisted() {
  console.log('This function has been hoisted.');
};

函数表达式 (Function expressions)

Function expressions, however are not hoisted.

但是,函数表达式不会被悬挂。

expression(); //Output: "TypeError: expression is not a function

var expression = function() {
  console.log('Will this work?');
};

Let's try the combination of a function declaration and expression.

让我们尝试将函数声明和表达式结合起来。

expression(); // Ouput: TypeError: expression is not a function

var expression = function hoisting() {
  console.log('Will this work?');
};

As we can see above, the variable declaration var expression is hoisted but it's assignment to a function is not. Therefore, the intepreter throws a TypeError since it sees expression as a variable and not a function.

正如我们在上面看到的,变量声明var expression被吊起,但是它没有分配给函数。 因此,解释器会引发TypeError因为它将expression视为变量而非函数

优先顺序 ( Order of precedence )

It's important to keep a few things in mind when declaring JavaScript functions and variables.

在声明JavaScript函数和变量时,请记住一些重要事项。

  1. Variable assignment takes precedence over function declaration

    变量赋值优先于函数声明
  2. Function declarations take precedence over variable declarations

    函数声明优先于变量声明

Function declarations are hoisted over variable declarations but not over variable assignments.

函数声明悬挂在变量声明之上,而不悬挂在变量分配之上。

Let's take a look at what implications this behaviour has.

让我们看一下这种行为的含义。

通过函数声明进行变量分配 (Variable assignment over function declaration)

var double = 22;

function double(num) {
  return (num*2);
}

console.log(typeof double); // Output: number

函数声明超过变量声明 (Function declarations over variable declarations)

var double;

function double(num) {
  return (num*2);
}

console.log(typeof double); // Output: function

Even if we reversed the position of the declarations, the JavaScript interpreter would still consider double a function.

即使我们颠倒了声明的位置,JavaScript解释器仍然会考虑将函数double

吊装课程 ( Hoisting classes )

JavaScript classes too can be loosely classified either as:

JavaScript类也可以粗略地分类为:

  1. Class declarations

    类声明
  2. Class expressions

    类表达式

类声明 (Class declarations)

Much like their function counterparts, JavaScript class declarations are hoisted. However, they remain uninitialised until evaluation. This effectively means that you have to declare a class before you can use it.

就像它们的函数对应物一样,悬挂了JavaScript类声明。 但是,它们直到评估之前仍未初始化。 这实际上意味着您必须先声明一个类,然后才能使用它。

var Frodo = new Hobbit();
Frodo.height = 100;
Frodo.weight = 300;
console.log(Frodo); // Output: ReferenceError: Hobbit is not defined

class Hobbit {
  constructor(height, weight) {
    this.height = height;
    this.weight = weight;
  }
}

I'm sure you've noticed that instead of getting an undefined we get a Reference error. That evidence lends claim to our position that class declarations are hoisted.

我确定您已经注意到,除了得到undefined我们还获得了Reference error 。 这些证据证明我们的立场是悬挂了阶级声明。

If you're paying attention to your linter, it supplies us with a handy tip.

如果您关注您的短绒棉,它可以为我们提供方便的提示。

Hobbit was used before it is declared, which is illegal for class variables

So, as far as class declarations go, to access the class declaration, you have to declare first.

因此,就类声明而言,要访问类声明,必须先声明。

class Hobbit {
  constructor(height, weight) {
    this.height = height;
    this.weight = weight;
  }
}

var Frodo = new Hobbit();
Frodo.height = 100;
Frodo.weight = 300;
console.log(Frodo); // Output: { height: 100, weight: 300 }

类表达式 (Class expressions)

Much like their function counterparts, class expressions are not hoisted.

就像它们的函数对应物一样,类表达式也不会悬挂。

Here's an example with the un-named or anonymous variant of the class expression.

这是类表达式的未命名或匿名变体的示例。

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square); // Output: TypeError: Polygon is not a constructor

var Polygon = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

Here's an example with a named class expression.

这是一个带有命名类表达式的示例。

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square); // Output: TypeError: Polygon is not a constructor


var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

The correct way to do it is like this:

正确的方法是这样的:

var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square);

警告 ( Caveat )

There's a bit of an argument to be made as to whether Javascript es6 let, const variables and classes are actually hoisted, roughly hoisted or not hoisted. Some argue that they are actually hoisted but uninitialised whilst some argue that they are not hoisted at all.

关于实际上是否提升,大致提升或不提升Javascript es6 let,const变量和类,存在一些争论。 有些人认为它们实际上是吊起的,但尚未初始化,而有些人则认为它们根本没有吊起。

结论 ( Conclusion )

Let's summarise what we've learned so far:

让我们总结一下到目前为止所学到的内容:

  1. While using es5 var, trying to use undeclared variables will lead to the variable being assigned a value of undefined upon hoisting.

    在使用es5 var时 ,尝试使用未声明的变量将导致在提升时为该变量分配一个未定义的值。
  2. While using es6 let and const, using undeclared variables will lead to a Reference Error because the variable remains uninitialised at execution.

    在使用es6 letconst时 ,使用未声明的变量将导致引用错误,因为该变量在执行时仍未初始化。

Therefore,

因此,

  1. We should make it a habit to declare and initialise JavaScript variables before use.

    我们应该养成使用前声明和初始化JavaScript变量的习惯。
  2. Using strict mode in JavaScript es5 can help expose undeclared variables.

    在JavaScript es5中使用严格模式可以帮助公开未声明的变量。

I hope this article will serve as a good introduction to the concept of hoisting in JavaScript and spur your interest regarding the subtleties of the JavaScript language.

我希望本文能很好地介绍JavaScript中的吊装概念,并激发您对JavaScript语言的精妙之处的兴趣。

The code we've discussed can be found here.

我们已经讨论过的代码可以在这里找到。

翻译自: https://scotch.io/tutorials/understanding-hoisting-in-javascript

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值