详细代码举例剖析var和let区别

593 阅读9分钟

0、配套视频教程地址

哔哩哔哩:10分钟剖析var和let-生动形象的代码演示-js小知识点第1期-中英双语字幕

1、总概

js中一共有三个声明量的方式,varletconst,包括变量和常量的声明。

  • var:旧版中声明变量的方式,当然也可以用来声明常量,只是这种常量只是一种约定,并没有语法上的支持。
  • let:新版本中声明变量的方式,和var会有很大的区别,解决了很多var遗留下来的问题,使js语言更加标准化,规范化。
  • const:这里简单提一下const,只需要记住两点就可以:
    1. const声明常量的时候就必须赋值。
    2. 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的声明是在小括号内部。

我们来看下小括号的作用,小括号一般情况在算术表达式当中可以用来提供运算的优先级,在这里则只是用来隔开关键字forvar,以及和后面大括号的语法结构。注意:小括号不是运算符,只是一种语法结构!

所以只用小括号隔开而声明小括号内部的变量就等同于全局变量,自然也是可以打印出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有块,禁多声,禁前调