JavaScript变量提升

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


1、简介

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

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

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

一个例子:

// 声明
var myValue;
// 初始化
myValue = 100;
// 使用
alert(myValue);

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

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

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

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

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

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

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

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

2、函数作用域变量:var

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

// 声明变量num
var num;
console.log(num); // undefined
// 声明且初始化str
var str = 'Hello World';
console.log(str); // Hello World

提升与var

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

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

上面的代码在相当于:

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

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

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

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

上面的代码在相当于:

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的改进,它允许代码在块的级别是保持模块性和封装性:

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。

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

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 先声明,后使用

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

4、常量 const

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

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

提升与let

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

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

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

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

5、function(函数)声明

函数声明的一个例子:

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

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

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(定义子类)

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定义变量的提升效果相同

// Throws ReferenceError: Company is not defined
var apple = new Company('Apple');

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

var microsoft = new Company('Microsoft');

使用类声明表达式创建类

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

你可能感兴趣的:(JavaScript变量提升)