JavaScript变量提升

Author Avatar
chxpu 6月 22, 2018
  • 在其它设备中阅读本文章

JavaScript变量提升(转)

原文 :http://rainsoft.io/javascript-hoisting-in-details/?utm_source=javascriptweekly&utm_medium=email


1、简介

提升是一种将变量和函数的声明移到函数作用域(如果不存在任何函数内的话就是全局作用域)最顶部的机制。

提升影响了变量的生命周期,一个变量的生命周期包含3个阶段:

  • 声明 -> 初始化 -> 使用

    一个例子:

    1
    2
    3
    4
    5
    6
    // 声明
    var myValue;
    // 初始化
    myValue = 100;
    // 使用
    alert(myValue);

在JavaScript中,函数可以先声明,后使用。初始化被忽略了。一个例子:

1
2
3
4
5
6
// 声明
function sum(a, b) {
return a + b;
}
// 使用
sum(1, 2);

函数的使用可以在声明之前,例如:

1
2
3
4
5
6
// 使用
double(5);
// 声明
function double(num) {
return num * 2;
}

这是因为JavaScript中的函数声明会被提升到作用域的顶部。

变量提升在不同的方面的影响不同:

  • 变量声明:var, let或const关键字
  • 函数声明: function () {…}
  • 类声明:class关键字

以下分开说明三个的区别:

2、函数作用域变量:var

var声明在函数作用域内创建并初始化一个变量,声明但是未初始化的变量值是undefined。

1
2
3
4
5
6
// 声明变量num
var num;
console.log(num); // undefined
// 声明且初始化str
var str = 'Hello World';
console.log(str); // Hello World

提升与var

使用var 声明变量会被提升到所在作用域的顶部。如果在声明之前访问该变量,它的值是undefined

1
2
3
4
5
6
function double(num) {
console.log(myVariable); // undefined
var myVariable;
return num * 2;
}
double(3); // 6

上面的代码在相当于:

1
2
3
4
5
6
function double(num) {
var myVariable;var myVariable;
console.log(myVariable); // undefined
return num * 2;
}
double(3); // 6

声明会提升,赋值留在原地

1
2
3
4
5
6
7
function sum(a, b) {
console.log(myVariable); // undefined
var myVariable = 'Hello World';
console.log(myVariable); // Hello World
return a + b;
}
sum(1, 2);

声明会被提升,而赋值操作不受影响

上面的代码在相当于:

1
2
3
4
5
6
7
8
function sum(a, b) {
var myVariable; // 声明提升
console.log(myVariable); // undefined
myVariable = 'Hello World';
console.log(myVariable); // Hello World
return a + b;
}
sum(1, 2);

3、块级作用域变量: let

let声明在块级作用域内,默认情况下,声明但未初始化的变量的值是undefined

lets是ECMAScript 6的改进,它允许代码在块的级别是保持模块性和封装性:

1
2
3
4
5
6
7
8
9
if(true) {
// 声明块级变量
let _LetValue1;
console.log(_LetValue1); // undefined
let _LetValue2 = 'Hello World';
console.log(_LetValue2); // Hello World
var _VarValue = 'xxxx'
}
console.log(_LetValue2); // VM297:9 Uncaught ReferenceError: _LetValue2 is not defined

提升与let

使用let定义的变量会被提升到代码块的顶部。但是如果在声明前访问该变量,JavaScript会抛出异常ReferenceError:is not defined。

在声明语句一直到代码库的顶部,变量好像在一个临时死亡区间中一样。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function isTruthy(value) {
var myVariable = 'Value 1';
if (value) {
/**
* temporal dead zone for myVariable
*/
console.log(myVariable); // ReferenceError: myVariable is not defined
let myVariable = 'Value 2';
console.log(myVariable); // Value 2
return true
}
return false;
}
isTruthy(true); // true

从let myVariable一行一直到此块级的顶部,都是myVariable变量的临时死亡区间。如果在此区间访问该变量,JavaScript会抛出ReferenceError异常。

那么myVariable是否被提升了?
如果let定义的变量没有被提升,那么在临时死亡区间内myVariable的值就会是’Value 1’。由此我们可以确定块级变量确实有提升。

let 先声明,后使用

1
2
3
4
5
if(true) {
console.log(_LetValue); // Uncaught ReferenceError: _LetValue is not defined
// 声明块级变量
let _LetValue = 'xx';
}

4、常量 const

常量声明,当声明一个常量时,必须在同一语句中对该变量进行初始化。在声明与初始化之后,变量的值不能被修改。

1
2
3
const PI = 3.14;
console.log(PI); // Uncaught TypeError: Assignment to constant variable.
PI = 2.14;

提升与let

使用const定义的常量会被提升到代码块的顶部。

const声明常量提升效果与let什么变量相同。

1
2
3
4
5
6
7
8
function double(number) {
// 常量TWO的临时死亡区间
console.log(TWO); // Uncaught ReferenceError: TWO is not defined
const TWO = 2;
// 常量TWO的临时死亡区间结束
return number * TWO;
}
double(5);

常量始终要先声明初始化之后再使用。

5、function(函数)声明

函数声明的一个例子:

1
2
3
4
function isOdd(number) {
return number % 2 === 1;
}
isOdd(5); // true

需要注意的function(){…}和函数表达式var x = function(){…}的区别,两者都用于创建函数,但是提升机制不同。

1
2
3
4
5
6
7
8
9
10
addition(4 ,7);   // 11
substraction(7, 4); // Uncaught TypeError: substraction is not a function

function addition(num1, num2) {
return num1 + num2;
}

var substraction = function (num1, num2) {
return num2 - num1
}

还是var提升中的问题:声明会被提升,而赋值操作不受影响

6、class(类)声明

类声明使用提供的名称和参数创建一个构造函数。类是ECMAScript 6引入的。

类建立在JavaScript的原型继承之上并提供了方法如:super(访问父类) static(定义静态方法) extends(定义子类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
move(dX, dY) {
this.x += dX;
this.y += dY;
}
}
// 创建实例
var origin = new Point(0, 0);
// 调用实例方法
origin.move(50,60);

提升与class

class的提升与let定义变量的提升效果相同

1
2
3
4
5
6
7
8
9
10
// Throws ReferenceError: Company is not defined
var apple = new Company('Apple');

class Company {
constructor(name) {
this.name = name;
}
}

var microsoft = new Company('Microsoft');

使用类声明表达式创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(typeof Square);   // undefined
var mySquare = new Square(10); //Uncaught TypeError: Square is not a constructor

var Square = class {
constructor(sideLength) {
this.sideLength = sideLength;
}
getArea() {
return Math.pow(this.sideLength, 2);
}
};

var otherSquare = new Square(10);

7、总结

JavaScript的提升有多种形式,应该养成好习惯,按照声明->初始化->使用的顺序使用变量