0、配套视频教程地址
哔哩哔哩:10分钟剖析var和let-生动形象的代码演示-js小知识点第1期-中英双语字幕
1、总概
js中一共有三个声明量的方式,
var、let和const,包括变量和常量的声明。
var:旧版中声明变量的方式,当然也可以用来声明常量,只是这种常量只是一种约定,并没有语法上的支持。let:新版本中声明变量的方式,和var会有很大的区别,解决了很多var遗留下来的问题,使js语言更加标准化,规范化。const:这里简单提一下const,只需要记住两点就可以:const声明常量的时候就必须赋值。const声明的常量不能被再次赋值。别的规则就和let完全一样了。
2、 var和let的区别简介
2.1、 var
- 只有全局作用域和函数作用域,没有块级作用域
- 同一作用域下,可以多次声明同一变量
- 可以在声明语句前调用变量
2.2、 let
- 不仅有全局作用域和函数作用域,还有块级作用域
- 同一作用域下,不可多次声明同一变量
- 不可在声明语句前调用变量 这样一比较二者的区别就显而易见了,只需要重点关注上面加粗的部分即可。下面通过具体的例子一一说明。
3、 var和let的区别代码举例
3.1、 var只有全局作用域和函数作用域,没有块级作用域
这里提到了块级作用域,让我们先来看一下什么是块级作用域。从代码的体现形式上来说,除了函数体形成的一对大括号以外,像循环语句、判断语句,甚至直接书写一对大括号,这些情况下所形成的一对大括号所包裹的区域就是块级作用域。但是语言是否支持就另说了。
早期的js当中用var声明的变量就会无视这种形式的块级作用域,而只对全局作用域和函数作用域敏感。
通俗来讲,由var声明的变量,在函数体内外所声明的相同名称的变量会区别对待,而对是否在块作用域内外通过var声明的相同名称的变量会作为同一变量对待。
3.1.1、 循环语句中var声明的变量
for (var i = 0; i < 10; i++) {
var one = 1;
}
console.log(one);
console.log(i);
结果:
> 1
> 10
在循环体外依然可以打印变量one的值。就是因为循环体并未形成块级作用域而将变量隔开。
这里还要重点说一下i变量,i的声明是在小括号内部。
我们来看下小括号的作用,小括号一般情况在算术表达式当中可以用来提供运算的优先级,在这里则只是用来隔开关键字
for与var,以及和后面大括号的语法结构。注意:小括号不是运算符,只是一种语法结构!
所以只用小括号隔开而声明小括号内部的变量就等同于全局变量,自然也是可以打印出i的最后值的,当然由于循环10次i的值已经变成了10了。也说明每次循环都是共用了一个全局变量i。这点很重要,在进行DOM操作的时候也是会带来意想不到问题之所在。有机会另发布文章来单独讨论这个问题。
3.1.2、 判断语句中var声明的变量
if (false) {
var one = 1;
}
console.log(one);
结果:
> undefined
我们知道如果打印一个从来没有声明过的变量的时候,一定会报错,解释器会告诉我们某个变量没有定义过。而从上面代码来看代码是不能进入到if语句内部执行的,因为条件为false。
而这里确实打印出了undefined,说明one这个变量肯定被声明过了,而代码当中通过var来声明变量one的语句并不会被执行。
所以声明变量one的事情其实是解释器的预编译过程帮我们做了,解释器在执行某个文件的时候,会总体先进行扫描,把所有的变量的声明语句给提取出来放到代码的最上面,叫做变量的声明提升,而变量赋值语句保持位置不变。相当于下面的代码。
var one
if (false) {
one = 1;
}
console.log(one);
结果:
> undefined
总结起来也就是预编译帮我们声明了one变量,但是one变量并没有被赋值为1。而只声明没有被赋值的变量的默认值就是undefined。
当然如果把判断条件改成true,结果就会变成1,因为赋值语句被执行了,1已经覆盖掉了undefined。
if (true) {
var one = 1;
}
console.log(one);
结果:
> 1
如果把判断语句内部的var语句给注释掉的话,依然会报变量one未定义的错误。因为如果代码文件中根本不存在声明one变量这个动作的话,那么解释器为何会帮我们去声明这个变量呢?而变量提升就更无从谈起了。
if (false) {
// var one = 1;
}
console.log(one);
结果:
> ReferenceError: one is not defined
3.1.3、 函数加判断语句中var声明的变量
虽然var对块级作用域会视而不见,但是对函数作用域还是敏感的,下面来看块级作用域和函数作用域在一起的例子。
function printOne() {
if (true) {
var one = 1;
}
console.log(one);
}
printOne();
console.log(one);
结果:
> 1
> ReferenceError: one is not defined
在函数的内部打印one,由于判断语句的并没有形成真正的块级作用域,所以完全可以打印出one的值1。
而在函数的外部再来打印one的值,由于被函数作用域所阻挡,所以是无法获取到one的值的,而在全局作用域也找不到one变量,自然就报错说one并未声明。
3.2、 let不仅有全局作用域和函数作用域,还有块级作用域
3.2.1、 循环语句中let声明的变量
for (let i = 0; i < 10; i++) {
let one = 1;
}
console.log(i);
console.log(one);
结果:
ReferenceError: i is not defined
ReferenceError: one is not defined
两个变量都会提示未定义,当然这里需要一个个的打印,因为报错之后就不会继续执行下面的代码了。
之所以找不到这两个变量的原因,就是因为let关键字对块级作用域是敏感的,所以通过let声明的变量只能在循环体的大括号内有效,在全局是看不到这两个变量的,因此就报错了。
3.2.2、 判断语句中let声明的变量
if (false) {
let one = 1;
}
console.log(one);
结果:
ReferenceError: one is not defined
将条件改成true
if (true) {
let one = 1;
}
console.log(one);
结果:
ReferenceError: one is not defined
无论条件是否成立,结果都是找不到变量one,还是因为块级作用域的原因。
3.2.3、 函数加判断语句中let声明的变量
function printOne() {
if (true) {
let one = 1;
}
console.log(one);
}
printOne();
console.log(one);
结果:
ReferenceError: one is not defined
ReferenceError: one is not defined
因为one的声明在if判断语句的块作用域里面,所以无论是在函数中,还是在全局都是读取不到one的值的,当然上面的函数执行和打印也是要分开执行,因为报错了。
3.3、 var在同一作用域下可以多次声明同一变量
3.3.1、 全局作用域通过var多次声明同一变量
var user = 'xiaoming';
var user = 'xiaohua';
console.log(user);
结果:
xiaohua
正常打印,并不会报错。
3.3.2、 函数作用域通过var多次声明同一变量
function printUser() {
var user = 'xiaoming';
var user = 'xiaohua';
console.log(user);
}
printUser();
结果:
xiaohua
正常打印,并不会报错。
3.3.3、 全局作用域通过let多次声明同一变量
let user = 'xiaoming';
let user = 'xiaohua';
console.log(user);
结果:
SyntaxError: Identifier 'user' has already been declared
报错了,提示user变量已经声明过了。
3.3.4、 函数作用域通过let多次声明同一变量
function printUser() {
let user = 'xiaoming';
let user = 'xiaohua';
console.log(user);
}
printUser();
结果:
SyntaxError: Identifier 'user' has already been declared
报错了,提示user变量已经声明过了。
3.4、 var声明的变量,可以在声明语句前调用
3.4.1 全局作用域中在变量声明前调用
console.log(user);
var user = 'xiaoming';
结果:
undefined
在变量前面调用,并没有报错,但是打印的结果却是undefined,原因是js解释器在执行代码之前,会有一个预编译的过程,会扫描代码文件当中所有的变量声明语句,然后将变量的声明提升到代码的最前面,而变量的赋值语句保持位置不变,叫做变量声明提升。所以上面代码就等同于下面代码。结果必然为undefined
var user;
console.log(user);
user = 'xiaoming';
结果:
undefined
3.4.2 函数作用域中在变量声明前调用
function printUser() {
console.log(user);
var user = 'xiaoming';
}
printUser();
结果:
undefined
道理是一样的,等同于下面的代码,当然这里的变量只是提升到函数内部的最前面。
function printUser() {
var user;
console.log(user);
user = 'xiaoming';
}
printUser();
结果:
undefined
3.4.3 即使在条件为假的判断语句当中声明变量
function printUser() {
console.log(user);
if(false) {
var user = 'xiaoming';
}
}
printUser();
结果:
undefined
上面代码看似user的声明语句无法执行,但是js解释器不会去关注判断条件,只寻找声明语句,有则提升到函数的最上面。
3.5、 let声明的变量,不可在声明语句前调用
3.5.1 全局作用域中在变量声明前调用
console.log(user);
let user = 'xiaoming';
结果:
ReferenceError: Cannot access 'user' before initialization
报错提示:在user变量初始化之前不能访问。
3.5.2 函数作用域中在变量声明前调用
function printUser() {
console.log(user);
let user = 'xiaoming';
}
printUser();
结果:
ReferenceError: Cannot access 'user' before initialization
报错和上面一样,函数内部也是同样的道理。
4、总结
最后总结一个简单易记的顺口溜
- var无块,可多声,可前调
- let有块,禁多声,禁前调