JavaScript變量提升

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


1、簡(jiǎn)介

提升是一種將變量和函數(shù)的聲明移到函數(shù)作用域(如果不存在任何函數(shù)內(nèi)的話就是全局作用域)最頂部的機(jī)制。

提升影響了變量的生命周期,一個(gè)變量的生命周期包含3個(gè)階段:

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

一個(gè)例子:

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

在JavaScript中,函數(shù)可以先聲明,后使用。初始化被忽略了。一個(gè)例子:

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

函數(shù)的使用可以在聲明之前,例如:

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

這是因?yàn)镴avaScript中的函數(shù)聲明會(huì)被提升到作用域的頂部。

變量提升在不同的方面的影響不同:

  • 變量聲明:var, let或const關(guān)鍵字
  • 函數(shù)聲明: function () {...}
  • 類聲明:class關(guān)鍵字

以下分開說明三個(gè)的區(qū)別:

2、函數(shù)作用域變量:var

var聲明在函數(shù)作用域內(nèi)創(chuàng)建并初始化一個(gè)變量,聲明但是未初始化的變量值是undefined。

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

提升與var

使用var 聲明變量會(huì)被提升到所在作用域的頂部。如果在聲明之前訪問該變量,它的值是undefined。

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

上面的代碼在相當(dāng)于:

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

聲明會(huì)提升,賦值留在原地

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

聲明會(huì)被提升,而賦值操作不受影響

上面的代碼在相當(dāng)于:

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、塊級(jí)作用域變量: let

let聲明在塊級(jí)作用域內(nèi),默認(rèn)情況下,聲明但未初始化的變量的值是undefined。

lets是ECMAScript 6的改進(jìn),它允許代碼在塊的級(jí)別是保持模塊性和封裝性:

if(true) {
   // 聲明塊級(jí)變量
   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定義的變量會(huì)被提升到代碼塊的頂部。但是如果在聲明前訪問該變量,JavaScript會(huì)拋出異常ReferenceError:is not defined。

在聲明語句一直到代碼庫的頂部,變量好像在一個(gè)臨時(shí)死亡區(qū)間中一樣。例如:

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一行一直到此塊級(jí)的頂部,都是myVariable變量的臨時(shí)死亡區(qū)間。如果在此區(qū)間訪問該變量,JavaScript會(huì)拋出ReferenceError異常。

那么myVariable是否被提升了?
如果let定義的變量沒有被提升,那么在臨時(shí)死亡區(qū)間內(nèi)myVariable的值就會(huì)是'Value 1'。由此我們可以確定塊級(jí)變量確實(shí)有提升。

let 先聲明,后使用

if(true) {
   console.log(_LetValue); // Uncaught ReferenceError: _LetValue is not defined
   // 聲明塊級(jí)變量
   let _LetValue = 'xx';
}

4、常量 const

常量聲明,當(dāng)聲明一個(gè)常量時(shí),必須在同一語句中對(duì)該變量進(jìn)行初始化。在聲明與初始化之后,變量的值不能被修改。

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

提升與let

使用const定義的常量會(huì)被提升到代碼塊的頂部。

const聲明常量提升效果與let什么變量相同。

function double(number) {
   // 常量TWO的臨時(shí)死亡區(qū)間
   console.log(TWO); // Uncaught ReferenceError: TWO is not defined
   const TWO = 2;
   // 常量TWO的臨時(shí)死亡區(qū)間結(jié)束
   return number * TWO;
}
double(5);

常量始終要先聲明初始化之后再使用。

5、function(函數(shù))聲明

函數(shù)聲明的一個(gè)例子:

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

需要注意的function(){...}和函數(shù)表達(dá)式var x = function(){...}的區(qū)別,兩者都用于創(chuàng)建函數(shù),但是提升機(jī)制不同。

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提升中的問題:聲明會(huì)被提升,而賦值操作不受影響

6、class(類)聲明

類聲明使用提供的名稱和參數(shù)創(chuàng)建一個(gè)構(gòu)造函數(shù)。類是ECMAScript 6引入的。

類建立在JavaScript的原型繼承之上并提供了方法如:super(訪問父類) static(定義靜態(tài)方法) extends(定義子類)

class Point {
   constructor(x, y) {
      this.x = x;
      this.y = y;
   }
   move(dX, dY) {
      this.x += dX;
      this.y += dY;
   }
}
// 創(chuàng)建實(shí)例
var origin = new Point(0, 0);
// 調(diào)用實(shí)例方法
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');

使用類聲明表達(dá)式創(chuàng)建類

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、總結(jié)

JavaScript的提升有多種形式,應(yīng)該養(yǎng)成好習(xí)慣,按照聲明->初始化->使用的順序使用變量

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容