预编译

预编译

运行过程

  1. 通篇的检查是否有语法错误,有则直接抛错,不执行任何语句
  2. 预编译
  3. 解释一行,执行一行

变量提升

  • 变量只把声明提升,赋值不提升
  • 函数
    • 函数声明:整体提升
    • 函数表达式:只提升变量,字面量不提升
1
2
3
4
5
6
7
test(); // 1
function test() {
console.log(1);
}

console.log(a); // undefined
var a;

暗示全局变量 imply global variable

  • 没有声明(没写var)就赋值的变量
  • 全局声明或者不声明(没写var)得变量都会挂在 window 上
1
2
3
4
5
6
7
8
9
function test() {
var a = b = 1;
}

test();
console.log(b); // 1 (b挂在了window,因为没有var声明)
console.log(window.b); // 1
console.log(a); // ReferenceError: a is not defined
console.log(window.a); // undefined

🌈AO(activation object)活跃对象,函数上下文

  1. 寻找形参和变量声明没用var声明的变量不提升变量名,不声明var的变量被修改,当前作用域有此变量的,优先修改此作用域内的变量
  2. 实参值赋值给形参
  3. 寻找函数声明并赋值
  4. 执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function test(a) {
console.log(a);
var a = 1;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
test(2);
// function a() {}
// 1
// 1
// function() {}
AO = {
a: undefined ->
2 ->,
function a(){} ->
1,

b: undefined ->
function() {},

d: function d() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function test(a, b) {
console.log(a);
c = 0;
var c;
a = 5;
b = 6;
console.log(b);
function b() {}
function d() {}
console.log(b);
}
test(1, 2);
// 1
// 6
// 6
AO = {
a: undefined ->
1 ->
5,

b: undefined ->
2 ->
function b() {} ->
6,
c: undefined ->
0,
d: function d() {}
}
  • 没用var声明的变量不会有变量提升
1
2
3
4
5
6
7
8
9
10
11
console.log(a);
a = 2;

// ReferenceError: a is not defined

// vs

console.log(a);
var a = 2;

// undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function test() {
console.log(a);
a = 2;
}
test();

// ReferenceError: a is not defined


// vs

function test() {
console.log(a);
var a = 2;
}
test();

// undefined
  • 代码块、if 语句中,通过 var 声明的变量,会提升到外层作用域(也就是里外共享var声明的变量)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
console.log(a);
if (true) {
a = 2;
}

// ReferenceError: a is not defined

// vs

console.log(a);
if (true) {
var a = 2;
}
// undefined



// ----------

var a = 1;

{
a = 3;
console.log(a); // 3
var a;
}

console.log(a); // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function test() {
console.log(a);
if (true) {
a = 2;
}
}
test();
// ReferenceError: a is not defined


// vs

function test() {
console.log(a);
if (true) {
var a = 2;
}
}
test();
// undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
a = 3;
}

console.log(a); // 3

// vs

{
var a = 3;
}

console.log(a); // 3

// ----------------------

if (true) {
a = 3;
}
console.log(a); // 3

// vs

if (false) {
a = 3;
}
console.log(a); // Uncaught ReferenceError: a is not defined

// ----------------------

if (true) {
var a = 3;
}
console.log(a); // 3

// vs

if (false) {
var a = 3;
}
console.log(a); // undefined

GO(global object)全局上下文

  1. 寻找变量
  2. 寻找函数声明
  3. 执行

GO === window

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 1;
function a() {
console.log(2);
}
console.log(a);

// 1

GO = {
a: undefiend ->
function a(){} ->
1
}
1
2
3
4
5
6
7
8
9
console.log(a, b);
function a() {}
var b = function() {};

// function a() {} undefined
GO = {
b: undefined,
a: function a() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function test() {
var a = b = 1;
console.log(a);
}

test();

GO = {
b: 1
}

AO = {
a: undefined ->
1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var b = 3;
console.log(a);
function a(a) {
console.log(a);
var a = 2;
console.log(a);
function a() {}
var b = 5;
console.log(b);
}

a(1);

// function a(a) {}
// function a() {}
// 2
// 5

GO = {
b: 3
a: function a() {}
}

AO = {
a: undefined ->
1 ->
function a() {} ->
2,
b: undefined ->
5
}

AO = {
b: undefined ->
5
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a = 1;
function test() {
console.log(a);
a = 2;
console.log(a);
var a = 3;
console.log(a);
}
test();

//undefined
// 2
// 3

GO = {
a: undefined ->
1,
test: function test() {}
}
AO = {
a: undefined ->
2 ->
3
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function test() {
console.log(b);
if (a) {
var b = 2;
}
c = 3;
console.log(c);
}
var a;
test();
a = 1;
console.log(a);

// undefined
// 3
// 1

GO = {
test: function test() {},
a: undefined ->
1,
c: 3
}

AO = {
b: undefined
}

🌈 if代码块陷进

假定 if 中有一个函数声明 function a() {}

  1. if 未执行时:
    1. if 里的函数声明首先把变量名 a **提升到当前作用域(不是块作用域)的最顶部** a = undefined
  2. if 执行时:
    1. 函数声明整体会被提升到块作用域顶部
    2. 执行到函数声明语句时,会把块作用域里的 a 赋值到当前作用域同名变量 a
1
2
3
4
5
6
7
8
9
10
11
console.log(b);
if (true) {
console.log(b);
function b() {}
}

// 因为if中,function b() {} 会被看做函数表达式
// 所以会把函数名 b 变量提升到当前作用域的最顶部(也就是全局执行器上下文),b 定义没值 undefined

// if 在执行时,会首先把函数声明提升到 if 代码块内部的顶端
// 所以内部打印 function b
1
2
3
4
5
6
7
8
9
var f = function() {return true;};
var g = function() {return false;};
(function() {
console.log(g);
if (g() && [] == ![]) {
f = function f() {return false;};
function g() {return true;};
}
})();

分析:

  • if代码块中的 var 变量会提升,但函数声明并不会整体提升,而只会提升函数名到当前作用于顶端。
  • 赋值的执行一定会在if执行的过程中做。

上面打断点会发现,function g的g变量是提升到if外边了,但此刻还是undefined。而iife外边的f,g此刻在iife中是可以访问的。但由于iife中有了同名g,所以还是undefined,然后if判断是g()由于g是undefined不是函数所以报错了。另外if中的f前面由于没有var ,所以改的是iife外边的f

练习

1
2
3
4
5
6
7
function test() {
return a;
a = 1;
function a() {}
var a = 2;
}
console.log(test());
1
2
3
4
5
6
7
function test() {
a = 1;
function a() {}
var a = 2;
return a;
}
console.log(test());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
a = 1;
function test(e) {
function e() {}
arguments[0] = 2;
console.log(e);
if (a) {
var b = 3;
}
var c;
a = 4;
var a;
console.log(b);
f = 5;
console.log(c);
console.log(a);
}
var a;
test(1);

go = {
a: undefined -> 1,
test: function test() {},
f: undefined -> 5
}

ao = {
a: undefined ->
4,
b: undefined,
c: undefined,
e: undefined ->
1 ->
function e() {} ->
2
}

面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var a = false + 1;
console.log(a); // 1 隐式转换,没有字符串,把非字符串转基本类型,再to number

var b = false == 1;
console.log(b); // false

// string -1 NaN
if (typeof(a) && (-true) + (+ undefined) + '') {
console.log('通过'); // 执行
} else {
console.log('未通过');
}
// hint: if 中 => "undefined" && (-1 + NaN) + '' =>
// "undefined" && NaN + '' =>
// "undefined" && 'NaN' => true

// 1 15
if(1 + 5 * '3' === 16) {
console.log('通过'); // 执行
} else {
consoel.log('未通过');
}
// hint: 乘法,会把非数字转数字



// true false false
🍒 console.log(!!' ' + !!'' - !!false || '通过了'); // 1
// hint: ' ': 不是非空串,为真,两次取反,等于没反,最后为真

window.a || (window.a = '1');
console.log(window.a); // '1'
// ()运算优先级高,所以先运行 window.a = '1' , 然后 window.a 就是真
1
2
3
4
5
6
7
8
function test() {
console.log(a); // undefined
if (false) {
function a () {}
}
}
test();
console.log(a); // 报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var a = 0;

console.log('No.1', a);

{
console.log('No.2', a);
a = 1;
console.log('No.3', a);
function a () {}
console.log('No.4', a);
a = 2;
console.log('No.5', a);
}

console.log('No.6', a);

// 1. {} 块作用域中 function a 的 a 变量提前到当前作用域(也就是全局作用域)
// 2. 开始执行 a = 0, 所以 No.1 的 a 是 0
// 3. 开始执行 {} 代码块中的内容,首先把 function a() {} 整体提升到块作用域顶部
// ,此刻块作用域中有了变量 a,所以 No.2 的 a 是 function a() {}
// 4. 接着 a = 1, 就把块作用域中的 function a 改成了 1,所以 No.3 的 a 是 1
// 5. 执行到 function a() {} 这一步时,
// 会把块作用域里的 a 赋值到全局同名变量 a,此刻全局 a 变成了 1
// 6. 接着 a = 2 ,把块作用域的 a 变成了 2,所以 No.5 的 a 是 2
// 7. No.6 打印全局 a ,是 1

/**
* go: {
* a: undefined -> 0 -> 1
* }
*
* ao: {
* a: function a -> 1
* }
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let a = 0;

console.log(a);

if (true) {
a = 1;

console.log(a);

function a() {}

a = 2;

console.log(a);
}

console.log(a);

// 这一题有点不一样,外部 a 是用 let 声明的 ,无法被 function a 覆盖
1
2
3
4
5
6
{
let a = 1;
function a() {}
}

// 报错,暂时性死区