js 变量提升与函数提升
1. 现象
var str = '123'; console.log(str); // 123
console.log(str); // undefined var str = '123';
var val = '123'; // 立即执行函数 (function(){ console.log(val); // 123 })();
var val = '123'; // 立即执行函数 (function(){ console.log(val); // undefined var val = '测试'; })();
2. 预解析
2.1 核心:预解析
当看到 var a = 2;
时,可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个声明 var a;
和 a = 2;
。
- 第一个定义声明是在编译阶段进行的;
- 第二个赋值声明会被留在原地等待执行阶段;
即代码是这样写的:
// 我们看到的代码: var name = '123';
但Js会将它解析成:
// 声明(Declaration) var name; // 声明但未初始化,所以分配 undefined // 初始化(Initialization) name = '123'; // 初始化(赋值)
2.2 只有声明被提升了
只有声明会被提升,而赋值和其他代码逻辑会在执行到代码的位置时才会生效。所以才会有下面的问题:
foo(); function foo() { console.log(name); // undefined var name = '123'; }
注:函数被提升了,自然可以正常执行,但变量仅仅是声明被提升了。
2.3 每个作用域都会进行提升操作
上面的代码实际在编译时是这样的:
function foo(){ var name; // 声明 console.log(name); // undefined name = '123'; // 初始化 } foo(); // 函数执行
3. 提升之间的优先级
3.1 函数会首先被提升,然后才是变量
3.2 函数字面量不会进行函数提升
最直观的例子,就是在函数字面量前调用该函数:
foo(); var foo = function(){ console.log(1); } // TypeError: foo is not a function
在上面这段程序中:
- 变量标识符
foo
被提升并分配给所在作用域(在这里是全局作用域),因此在执行 foo() 时不会导致ReferenceError(), 而是会提示foo is not a function
。 - 然后就是执行 foo,foo 此时并没有赋值(注意变量被提升了)。由于对 undefined 值进行函数调用而导致非法操作,因此抛出 TypeError 异常。
4. ES6和小结
ES6 新增了两个命令 let 和 const,用来声明变量,变量声明在它们身上不会存在。
4.1 变量提升是可以规避的
let 命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
// var 的情况 console.log(foo); // 输出undefined var foo = 2; // let 的情况 console.log(bar); // 报错ReferenceError:Cannot access 'bar' before initialization let bar = 2;
变量 bar 用 let 命令声明,不会发生变量提升。这表明在声明它之前,变量 bar 是不存在的,这时如果用到它,就会抛出一个错误。
4.2 小结
- 变量提升:函数声明和变量声明总是会被解释器悄悄地被“提升”到方法的最顶部,但变量的初始化不会提升;
- 函数提升:函数声明可以被看做是函数的整体被提升到了代码的顶部,但函数字面量表达式并不会引发函数提升;
- 函数提升优先于变量提升;
- le t和 const 可以有效的规避变量提升;