JavaScript 程序的运行阶段分为 预编译阶段 和 执行阶段。
在预编译阶段,JavaScript 引擎会做一件事情,那就是读取 变量的定义
并 确定其作用域
即生效范围。
var
或 let
关键字定义的变量,在未赋值的情况下,该变量的值是 undefined
const
关键字定义变量却不赋值,将会抛出错误在 JavaScript 中,如果变量或函数没有声明就被使用,会引致错误的。
console.log(a);// Uncaught ReferenceError: a is not defined
声明提升 包括 变量声明提升 和 函数声明提升:
var
、let
和 const
声明的变量在代码执行之前被 JavaScript 引擎提升到当前作用域的顶部JavaScript 的代码在生成前,会先对代码进行编译,编译的一部分工作就是找到所有的声明,然后建立作用域将其关联起来,因此,在 当前作用域内 包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
注意这里是 声明 会被提前处理,赋值 并没有, 定义声明是在编译阶段进行的,而赋值是在执行阶段进行的 。也就是说声明提升了,赋值还留着原地,等待执行。
下面展示了标准的变量声明提升。
console.log(a);var a = 2;console.log(a);
等价于:
var a;// 变量声明 默认赋值 undefinedcomsole.log(a);// 输出变量a undefineda = 2;// 给a赋值2console.log(a);// 输出变量 a 为 2
这里就用到了我们上面的结论声明提升了,赋值还留着原地。
函数的两种创建方式:
🌰 代码示例:函数声明
foo();// 输出 'bar'function foo() {console.log('bar');}
🌰 代码示例:函数表达式
foo();// 报错:foo is not a functionvar foo = function () {console.log('bar');};
解析:同样地先执行函数,后创建函数,结果却是不一样。原因在于,通过函数声明的方式,该 函数声明(包括定义)会被提升至作用域的顶部,而表达式的创建方式则只提升了变量 foo
至作用域的顶部,此时的 foo
其值为undefined
,调用 foo
自然报错:foo
不是一个方法。
再来看一个示例:
var foo = function () {console.log('1');};function foo() {console.log('2');}foo();// '1'
预编译阶段进行变量声明提升和函数声明提升后,上述代码执行效果等同于:
// 变量声明提升const foo;// 函数声明提升function foo(){console.log('2');}// 变量赋值保持原位执行,foo 函数被覆盖foo = function(){console.log('1');};foo();// '1'
总结:
函数声明和变量声明都会被提升。但是,函数声明会覆盖变量声明。
🌰 代码示例:
var a;function a() {}console.log(a);// 'function a(){}'
但是,如果变量存在赋值操作,则最终的值为变量的值。
var a = 1;function a() {}console.log(a);// 'function a(){}'var a;function a() {}console.log(a);// 'function a(){}'a = 1;console.log(a);// 1
变量的重复声明是无用的,但函数的重复声明会覆盖前面的声明(无论是变量还是函数声明)。
var a = 1;var a;console.log(a);
输出结果为 1,以上代码等同于:
// 此时 a 的默认值为 undefinedvar a;a = 1;console.log(a);// 1
由于函数声明提升优先于变量声明提升,所以变量的声明无效。
var a;function a() {console.log(1);}a();// 1
后面的函数声明会覆盖前面的函数声明。
a();// 2function a() {console.log(1);}function a() {console.log(2);}
所以,应该避免在同一作用域中重复声明。