准确的叫法为:变量声明提升,函数声明提升

一、变量提升与函数提升

1、变量提升

  1. var a = 1;
  2. function print() {
  3. console.log(a); // undefined
  4. var a = 2;
  5. }
  6. print();

undefined:表示未赋值 null:赋值了,只不过赋的是 null

原因是代码的实际执行顺序书写顺序是不同的,实际执行顺序如下

function print() {
  var a = null;
  console.log(a); // undefined
  a = 2;
}

2、函数提升

a();
function a() {};
// 即可以先调用,再声明

区分以下代码:

b();    // error
var b = function() {};

此时 var b为一个变量,但内容为函数,其遵守变量提升的规则,因此内容为 null,因此报错

3、优先级

  • 变量提升
  • 函数提升
    var a = function(){};
    var a;
    console.log(typeof a);    // 'function'
    
    若函数提升在前,则 var a会覆盖前者,导致输出为 undefined,然而实际输出却不是这样,说明变量提升在前

4、易混淆的例子

var a = 1;
function a() { 
  console.log(123);
}
a(); // error

执行后会报错,是由于其实际的执行顺序如下

var a;
function a() { 
  console.log(123);
}
a = 1;
a(); // error

其中,前四行代码:

var a;
function a() { 
  console.log(123);
}

属于变量提升与函数提升,因此实际执行代码过程中,这四行代码不会执行(在代码执行前,这四行就已经被编译了),因此程序的入口其实是第5行代码 a = 1,此时a不再是函数了,因此会报错


二、执行上下文的概念

1、概念

  • 分为 全局上下文函数上下文
  • 全局上下文(window) 永远处于栈底,不会被删除
  • 函数上下文只有在函数被调用时才会产生,调用完毕后自动删除,若只定义了函数而未调用,则不会产生该函数的上下文
  • 上下文的执行顺序严格按照栈,先进后出

如下,共有三个上下文:

// 分别为 window, a, b
function a() {...};
function b() {...};
a();
b();

2、和作用域的区别

  • 作用域分为 全局作用域函数作用域ES6 后出现了块级作用域(if语句)
  • 作用域代码编译时自动生成(无论是什么作用域),而执行上下文代码执行时才生成(即使是全局上下文,也要等代码执行时才能生成)
  • 作用域是静态的,永远不会改变,如下代码 ```javascript var a = 10;

function way1() { console.log(a); }

function way2(f) { var a = 20; f(); } way2(way1); // 10

` way1() `的作用域早已确定,不会因其在 ` way2() `内被调用而改变,因此其仍会输出10

<a name="gvsum"></a>
#### 3、作用域与对象属性的关系
```javascript
var a = {
  way: function() {    // 1、首先在内部 function 中寻找,失败
    console.log(way);
  }
}
void 0;      // 2、随后来外部作用域中找 way,仍然失败

a.way();     // error

作用域只有 函数作用域 和 全局作用域,当 function中找不到 way 时,就会去外面(全局作用域)找,仍找不到,报错
正确的写法如下

way:function() {
  console.log(this.way);
}

三、上下文与栈的关系

当前执行的程序,永远处于栈顶,如下

void 0;                     // 1、进入全局上下文
var i = 1;
var a = function() {
  console.log('a()');
  b();                      // 3、进入 b 函数上下文
}

var b = function() {
  console.log('b()');
}

a();                        // 2、进入 a 函数上下文

输出结果为

a()
b()

更复杂的例子

console.log("i-begin: " + i);
var i = 1;
print(i);
function print(j) {
  if(j == 4) return;
  console.log("j-begin: " + j);
  print(j + 1);
  console.log("j-end: " + j);
}
console.log("i-end: " + i);

其共产生了5个上下文,输出顺序为

j-begin: 1
j-begin: 2
j-begin: 3
j-end: 3
j-end: 2
j-end: 1
i-end: 1