变量声明

在ES5之前,变量声明通常用关键词var或者function,ES6新引入了letconst两种声明方式,本章内容将介绍变量声明,几种方式声明变量的区别以及变量提升。

1
2
3
4
var i;
let j;
const z;
function q(){};

浏览器运行JavaScript代码时,会在计算机中分配出一块内存,这个就是栈内存,也称为Stack,或ECStack(执行环境栈)。同时,为了区分代码是在哪个区域执行的,会在代码执行前形成一个执行上下文EC(Execute Content),全局下的代码上下文为全局上下文EC(G),全局上下文中,VO(G) variable object 存储全局变量对象。

Var/let/const区别

  1. 变量提升,当前上下文代码执行之前,会把所有带var/fucntion关键字的变量进行提前声明或定义,var是提前声明,function是提前声明加定义。const/let不存在变量提升
  2. 不能基于let重复声明,即使先基于let声明,再基于var声明也不可以
  3. “全局上下文”中,基于var声明变量,也相当于给全局对象window添加一个属性,并且任何一个值改变,另一个也会跟着变化(映射机制);基于let声明变量就是全局变量,和GO没有关系
  4. let/const/function 都会产生块级私有上下文,而var不会
  5. const声明变量,一旦赋值,就不允许重新指向。

undefined:此处有值,但还没有定义;常见场景有:变量声明没有赋值,函数调用的返回值,对象属性没有赋值,函数调用的参数。

null:没有对象,这里不应该有值;常见场景有:对象原型链的终点

作用域

代码执行会形成上下文,全局下代码执行形成的是全局上下文;局部下代码执行形成的是私有上下文,也称为块级上下文。块级上下文通常分为以下几种情况:

  1. 函数执行形成私有上下文
  2. 对象的大括号是私有上下文
  3. 判断体/循环体/代码块 都有可能产生私有上下文/块级上下文、

[ 1 ] 例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可以看到scope里,m在Block里,而n则在Global里
// var声明的变量会扩散到全局,let不会,也可以理解为基于let/const/function声明的变量都会产生全局上下文
// 在代码之前,浏览器就会自己处理很多事情:词法分析、变量提升
// 词法分析阶段,如果有发现基于let/const并且重复声明的变量操作,则直接报语法错误
debugger; // 开启断点调试,
{
var n = 12; // n是全局上下文的,代码块不会对他有任何限制
console.log(n); // 12

let m = 13; // m 是块级上下文私有的
console.log(m); // 13
}
console.log(n); // 12
console.log(m); // ReferenceError: m is not defined

变量提升

  • 在代码执行之前,浏览器会把带VAR和FUNCTION的变量进行提前的声明和定义,带VAR的变量提前声明,带FUNCTION的变量提前声明加定义。
  • 基于VAR和FUNCTION在全局上下文中声明过的变量,都会给window对象加一个属性,可以使用'key' in window来检测
  • 条件判断的情况下:老版本浏览器下带function的变量会提前声明加定义;新版本浏览器下只会提前声明
  • 对于已经声明或者定义的语句,浏览器不会重复执行
  • 除此之外,全局上下文中,不论判断条件是否成立,都会进行变量提升,条件中带function的在新版本的浏览器中只会提前声明,不会提前赋值了
1
2
3
4
5
6
7
8
9
10
if(!a){
var a = 10;
}

等价于:

var a;
if(a){
a= 10;
}

[ 1 ] 例 变量提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn();
function fn(){ console.log(1) }; // 会提前声明+赋值
fn();
function fn(){ console.log(2) }; // 不会再次提前声明,但是会赋值
fn();
var fn = function(){ console.log(3) }; // 不会再次提前声明,执行时赋值
fn();
function fn(){ console.log(4) }; // 不会再次提前声明,但是会再次赋值
fn();
function fn(){ console.log(5) }; // 不会再次提前声明,但是会再次赋值
fn();

变量提升结果是,全局上下文中有一个全局变量fn(5),然后1处和2处都不在赋值了,所以输出都为5;直到3处,再次赋值之后,输出变为3
执行的时候:5 -> 5 -> 5 -> 3 -> 3 -> 3

[ 2 ] 例 变量提升

1
2
3
4
5
6
7
8
var foo = 1;
function bar(){ // var foo; foo = undefined
if(!foo){ // !undefined = true
var foo = 10; / foo = 10
}
console.log(foo); //=> 10
}
bar();