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