参考文章
var和let/const的区别
阮一峰的let 和 const 命令
浅谈JS变量提升
var和 let / const 的区别
- 块级作用域
- 不存在变量提升
- 暂时性死区
- 不可重复声明
- let、const声明的全局变量不会挂在顶层对象下面
const命令两个注意点:
- const 声明之后必须马上赋值,否则会报错
- const 简单类型一旦声明就不能再更改,复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改。
一、块级作用域
为什么需要块级作用域?
ES5只有全局作用域和函数作用域,没有块级作用域。这带来很多不合理的场景:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
var tmp = new Date();
function f() {
console.log(tmp); // 想打印外层的时间作用域
if (false) {
var tmp = 'hello world'; // 这里声明的作用域为整个函数
}
}
f(); // undefined
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]); // i应该为此次for循环使用的变量
}
console.log(i); // 5 全局范围都可以读到
块级作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
console.log(n); // 10 内层的n
}
console.log(n); // 5 当前层的n
}
二、不存在变量提升
变量提升的现象:在同一作用域下,变量可以在声明之前使用,值为 undefined。ES5 时使用var声明变量,经常会出现变量提升的现象。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
三、暂时性死区:
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
var tmp = 123; // 声明
if (true) {
tmp = 'abc'; // 报错 因为本区域有tmp声明变量
let tmp; // 绑定if这个块级的作用域 不能出现tmp变量
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
暂时性死区和不能变量提升的意义在于:
为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
四、不允许重复声明变量
在测试时出现这种情况:var a= ‘声明’;const a = ‘不报错’,这种情况是因为babel在转化的时候,做了一些处理,在浏览器的控制台中测试,就成功报错
let、const不允许在相同作用域内,重复声明同一个变量
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错,因为这个不算相同作用域
}
}
五、let、const声明的全局变量不会挂在顶层对象下面
- 浏览器环境顶层对象是: window
- node环境顶层对象是: global
- var声明的全局变量会挂在顶层对象下面,而let、const不会挂在顶层对象下面。如下面这个栗子
var a = 1;
// 如果在 Node环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
const命令
- 一旦声明,必须马上赋值
let p; var p1; // 不报错
const p3 = '马上赋值'
const p3; // 报错 没有赋值
- const一旦声明值就不能改变
简单类型:不能改动
const p = '不能改变';
p = '报错'
复杂类型:变量指针不能变
考虑如下情况:
const p = ['不能改动']
const p2 = {
name: 'OBKoro1'
}
p[0] = '不报错'
p2.name = '不报错'
p = ['报错']
p2 = {
name: '报错'
}
const所说的一旦声明值就不能改变,实际上指的是:变量指向的那个内存地址所保存的数据不得改动
- 简单类型(number、string、boolean):内存地址就是值,即常量(一变就报错).
- 复杂类型(对象、数组等):地址保存的是一个指针,const只能保证指针是固定的(总是指向同一个地址),它内部的值是可以改变的(不要以为const就安全了!)
所以只要不重新赋值整个数组/对象,
因为保存的是一个指针,所以对数组使用的push、shift、splice等方法也是允许的,你就是把值一个一个全都删光了都不会报错。
复杂类型还有函数,正则等,这点也要注意一下。
let、const使用场景:
- let使用场景:变量,用以替代var。
- const使用场景:常量、声明匿名函数、箭头函数的时候。
补)浅谈变量提升
ES6之前我们一般使用var来声明变量,提升简单来说就是在
javascript
预解析阶段,把我们所写的类似于var a =123;这样的代码,声明提升到它所在作用域的顶端去执行,到我们代码所在的位置来赋值。
js
预解析(编译)阶段:
把所有的函数定义提前,所有的变量声明提前,变量的赋值不提前(预处理会跳过执行语句,只处理声明语句,同样也是按从上到下按顺序进行的。包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。即使声明是在调用的下方进行的,但浏览器仍然先声明再调用(执行),这个现象叫做“提升”。所以,即便一个函数的声明在下方,在前面仍然可以正常执行这个函数。赋值或其他逻辑运算是在执行阶段进行的,在预处理阶段会被忽略。 注意:
(1)函数声明的提升优先于变量声明的提升;
(2)重复的var声明会被忽略掉,但是重复的function声明会覆盖掉前面的声明。 在预处理阶段,声明的变量的初始值是undefined, 采用function声明的函数的初始内容就是函数体的内容。
1、普通变量提升
function test () {
console.log(a); //undefined
var a = 123;
};
test();
上述代码a的结果是undefined,它的实际执行顺序如下:
function test () {
var a;
console.log(a);
a = 123;
}
test();
再看一个:
a = 1;
var a;
console.log(a); //1
下面来看一道经典面试题:
console.log(v1);
var v1 = 100;
function foo() {
console.log(v1);
var v1 = 200;
console.log(v1);
}
foo();
console.log(v1);
输出的结果:
//undefined
//undefined
//200
//100
2. 函数提升
avascript中不仅仅是变量声明有提升的现象,函数的声明也是一样;具名函数的声明有两种方式:
- 函数声明式
- 函数字面量式(函数表达式)
//函数声明式
function bar () {}
//函数字面量式
var foo = function () {}
函数字面量式的声明和普通变量提升的结果是一样的,函数只是一个具体的值;
但是函数声明式的提升现象和变量提升略有不同
console.log(bar);
function bar () {
console.log(1);
}
//打印结果:ƒ bar () {
// console.log(1);
//}
执行顺序相当于:
function bar () {
console.log(1);
}
console.log(bar);
函数提升是整个代码块提升到它所在的作用域的最开始执行
思考下面的问题(看明白函数声明式提升下面的问题大概可以理解为什么输出1而不是2了吧):
foo(); //1
var foo;
function foo () {
console.log(1);
}
foo = function () {
console.log(2);
}
这就是函数优先规则。进入执行上下文时,首先会处理函数声明,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。(但是执行赋值操作还是会和正常一样改变了值)
下面这段代码,在低版本的浏览器中,函数提升不会被条件判断所控制,输出2;但是在高版本的浏览器中会报错,所以应该尽可能避免在块内部声明函数
foo(); //低版本:2 //高版本: Uncaught TypeError: foo is not a function
var a = true;
if(a){
function foo () { console.log(1); }
}else{
function foo () { console.log(2); }
}
小结
var str; //这个属于变量声明
str = "hhh"; //这个属于变量定义
另
var str2="fff";
这样的其实是两个过程,可以看成
var str2;
str2="fff";
而变量声明是会被提升的
我们习惯将var a = 2;看做是一个声明,而实际上javascript引擎并不这么认为。它将var a和a = 2看做是两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。
这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理,可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。