还是从一个例子研究起,如下代码将会输出什么结果呢?
sayHello();
function sayHello() {
console.log(name);
console.log(gender);
var name = 'yulsh';
let gender = '男';
}
输出的结果是:
undefined
Uncaught ReferenceError: Cannot access 'gender' before initialization
在 javascript 中变量的声明会提升,因此上面的代码真正运行的过程是这样的:
var name; // 变量声明前置,初始化默认值 undefined
let gender; // 变量声明前置,没有被初始化
console.log(name);
console.log(gender);
name = 'yulsh';
gender = '男';
似乎忽略了一点,sayHello
在最上面也可以执行 ? 因为,函数声明的提升更加彻底,并且优先级高于变量
。
通过上面的说明,应该可以理解为什么是上面的输出结果了吧?
变量赋值的三个阶段
- 创建变量,在内存中开辟内存空间;
- 初始化变量,将变量初始化为
undefined
; - 变量赋值;
关于 var, let (const类似) , function 的差异:
- var 的 [创建] 和 [初始化] 都被提升了;
- let 的 [创建] 过程被提升了;
- function 的 [创建] [初始化] [赋值] 过程都被提升了;// sayHello 能运行的原因
验证let存在变量提升
ES6中 使用 let, const 来声明变量,为了验证上面的结论,且看看下面的例子:
let name = 'zhagnsan';
{
console.log(name);
let name = 'yulsh';
}
输出的结果:
Uncaught ReferenceError: Cannot access 'name' before initialization
在 {...}
中的 let 是被提升了,执行的过程应该是这样的:
let name = 'zhagnsan';
{
let name; // 没有被初始化
console.log(name);
name = 'yulsh';
}
使用 let / const 声明的变量是具有块作用域
的,因此,变量声明的时候在 { }
内部做了提升,但是没有初始化。这个过程产生了另外一个概念 暂时性死区
。有一篇文章详细的讨论了TDZ
。
再看一个例子
下面的代码将会输出怎样的结果呢? 看起来很迷惑,通过理解它,我们就可以彻底的理解变量提升和函数提升了。
console.log(a);
var a = 1;
console.log(a);
function a() { console.log(2); }
console.log(a);
var a = 3;
console.log(a);
function a() { console.log(3); }
a();
console.log(a);
输出:
ƒ a() { console.log(3); }
1
1
3
Uncaught TypeError: a is not a function
实际的执行过程是这样的:
function a() { console.log(3); } // 遇到多个同名的变量或者函数,以最后一个为准,并且函数提升的优先级高
console.log(a); // 输出 函数体
var a = 1; // 函数a被赋值为1, 从函数变成了变量
console.log(a); // 输出 1
console.log(a); // 输出 1
var a = 3; // 变量赋值为 3
console.log(a); // 输出 3
a(); // 执行出错,a已经不是函数,不能继续执行
console.log(a); // 不会执行
总结
浏览器引擎在运行js过程中,大致分为两个步骤,一是解析js,把变量/函数等在对应的作用域内做提升;二是执行JS。掌握了变量和函数的提升,才能对js执行过程了如指掌。