原文 :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í)慣,按照聲明->初始化->使用的順序使用變量