提升和它背后的机制

Posted by Rimin on 2019-04-19

变量提升

var

变量提升即将变量声明提升到它所在作用域的最开始的部分(这里就要先搞清楚它的作用域)

1
2
3
4
5
6
7
8
9
10
11
12
(function(){
var a='One';
var b='Two';
var c='Three';
})();
//实际上它是这样子的
(function(){
var a,b,c;
a='One';
b='Two';
c='Three';
})();
1
2
3
4
5
6
7
var scope="global";  
function t(){
console.log(scope); //undefinded , scope声明覆盖了全局的scope,但是由于变量提升还没有赋值
var scope="local";
console.log(scope); //local
}
t();

由于有变量提升的问题,在写js code 的时候,我们需要把变量放在函数级作用域的顶端。

注意: 使用 “use strict” 如果将值分配给一个未声明的变量会自动创建该名称的全局变量,则会报错。
let
  1. 不存在变量提升

  2. 存在块级作用域

    1
    2
    3
    4
    5
    6
    {
    var a = 10;
    let b = 20;
    }
    console.log(a); //10
    console.log(b); // ReferenceError: b is not defined
  3. let不允许在同一作用域内重复声明同一个变量

  4. 可以先声明后初始化

const
  1. 不存在变量提升
  2. 一旦声明常量,就必须同时初始化。不能先声明,后初始化
  3. 不允许在同一作用域内重复声明同一个变量
  4. 声明的常量不能改变,而修改对象的属性,并不会改变对象的地址,因此用const声明对象的属性是可以修改的

⚠️⚠️注意 var 在全局作用域下声明的变量会挂载到全局window上,但是let 和 const 的不会!!

1
2
3
4
let len = 10;
function fn() {
console.log(this.len);
}

函数提升

1
2
3
4
5
6
7
function myTest(){
foo();
function foo(){ //函数提升
console.log("我来自 foo");
}
}
myTest();

但是如果是这样则会产生错误:

1
2
3
4
console.log(sum(10,10));
var sum = function(num1,num2){
return num1+num2;
};

因为函数位于一个初始化的语句中,是函数声明,而不是函数声明

同时也要记住,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用:

1
2
3
4
5
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
// ...
};

这个代码片段经过提升后,实际上会被理解为以下形式:

1
2
3
4
5
6
7
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = ...self...
// ...
}

var let const 如何选择

var < let < const
即为了避免代码的不规范及全局变量污染,优先使用 const,,其次是 let ,最后才是 var

函数优先

函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个
“重复”声明的代码中)是函数会首先被提升,然后才是变量。
看一下代码:

1
2
3
console.log(foo); //ƒ foo(){} 
var foo = 2;
function foo(){};

由此可以看出,同样会有提升,但是函数的提升会优先

背后的原理

为什么会存在变量的提升呢?内部的运行机制是怎么处理的?
在真正解释执行之前,JavaScript解释器会预解析代码,将变量、函数声明部分提前解释:

  1. 运行代码来调用函数
  2. 在执行代码函数执行,先去创建上下文.
  3. 进入创建阶段
    1. 初始化作用域链
    2. 创建变量对象(VO)
      • 创建arguments对象,检查当前上下文的参数,初始化名称和值并创建引用副本
      • 扫描当前上下文的函数声明
        • 扫描到来函数声明,那就在VO(variable object)里创建他的名称,并且指针是指向内存中函数引用的地址
        • 如果扫描的函数声明已存在,那就覆盖VO中的引用指针
      • 扫描当前上下文的变量声明
        • 扫到变量声明,在VO中添加变量名称,并且初始化值是undefined
        • 如果VO中存在变量名称,那就不做处理,继续扫描
    3. 确定当前上下文的this指向
  4. 激活/执行阶段

比如:

1
2
3
4
5
6
7
8
9
10
11
12
// 上下文
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope: undefined,
f: reference to function f(){}
},
Scope: [AO, globalContext.VO],
this: undefined
}

可以看出为什么会出现提升且为什么函数可以优先提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function() {

console.log(typeof foo);
console.log(typeof bar);
var bar = function() {
return 'world';
};

function foo() {
return 'hello';
}
var foo = 'hello';

}());

最后,欢迎大家围观指正