JavaScript 属于描述型脚本语言, 在运行时由浏览器进行动态解析和执行, 在此过程中有一些特殊的行为模式, 合格的 JavaScript 程序员应该对此有清楚的了解.

预编译

预编译指: 变量和声明式函数的声明会在执行之前进行预编译,

变量提升
// 因为函数 testFunction 会被预编译, 所以以下语句可以正常运行
test('...');

function test(text) {
    console.log(text);
}

以上代码在执行前被预编译为:

function test(text) {
    console.log(text);
}

test('...');

要注意的是, 预编译只进行变量声明, 而不进行变量初始化, 因此:

/*
 * 因为变量 test 会被预编译, 所以以下语句可以正常运行, 但是
 * 变量被使用时尚未被初始化! 所以输出是 'undefined'
 */
console.log(test);

var test = '...';

// 正常输出 '...'
console.log(test);

以上代码在执行前被预编译为:

var test;

console.log(test); // 'undefined'

test = '...';

console.log(test); // '...'

另一个要注意的是, ES6 新引入的 let 和 const 限制了变量提升, 即:

// 出错, 因为 let 和 const 禁止在声明前使用
console.log(test);

let test = '...';
函数提升 (声明式函数与赋值式函数)

声明式函数与赋值式函数在使用时没有差别, 他们的区别在于: 只有声明式函数会被预编译.

// 正常运行, 因为函数提升
Function1();

// 无法运行, 因为赋值式函数虽然提升但未初始化
Function2(); 

// 声明式函数, function declaration
function Function1() {
    console.log('...');
}

// 赋值式函数, function expression
let Function2 = function() {
    console.log('...');
}

以上代码在执行前被预编译为:

// 仅提升了变量声明, 未初始化
let Function2;

// 提升了整个函数定义
function Function1() {
    console.log('...');
}

Function1();

Function2(); 

Function2 = function() {
    console.log('...');
}

这里要留心声明式函数提升的特点: 在严格模式下, 语句块中的函数不会被提升代码块顶部, 如:

'use strict';
if (condition) {
    function test() {
        console.log('...');
    }
} else {
    function test() {
        console.log('...');
    }
}

// Throws 'ReferenceError: ok is not defined'
test();

代码块的执行顺序

代码块是一个页面中由<script>标签包裹的代码段, 如:

<script>
    console.log('Block One');
</script>

一个页面中的 JavaScript 代码由若干代码块组成, 简单的页面通常只含一个代码块.

引入的外部 js 文件视为一个单独的代码块.

代码块的执行顺序和代码块出现顺序一致, 代码块间保持相互独立, 但不同代码块的全局变量和方法互相共享. 如:

<script>
    // 变量声明是预编译的, 所以此处可以正常运行
    console.log(test); 
</script>

<script>
    // notExist 未定义, 运行出错
    console.log(notExist);

    // 这部分代码到达不了
    console.log('...');

    // 变量声明会被预编译, 所以是有效的, 而且全局变量被共享了
    let test = 'Defined in block one'; 
</script>

<script>
    // 代码块间是相互独立, 但又共享全局变量的, 所以此处也可以正常运行
    console.log(test); 
</script>

 


REFERENCE

js的执行过程

javascript运行机制之执行顺序详解

Six ways to declare JavaScript functions

JavaScript系列文章:从let和const谈起