一、前言:
要说他们的区别,首先需要了解作用域的概念
作用域永远都是任何一门编程语言中的重中之重,因为它控制着变量与参数的可见性与生命周期。js中的作用域一共有两种(块级作用域与函数作用域)首先先理解下这两个概念
-
块级作用域:任何一对花括号 {} 中的语句都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。比如if(){} ,for(){}中的花括号都是块级作用域
-
函数作用域:很明显是function(){}的形式,定义在函数中的参数和变量在函数外部是不可见的
关于这块的总结:
var:var是忽略块级作用域的,意思就是说,在块级作用域中用var定义的变量,在块级作用域外依然可以访问,var只有在函数作用域中声明,外部才不能访问;
let/const:这两种方式是在es6时提出的,所以这两种方式都是有块级作用域一说的
对于这块,我们可以来看几个Demo:
Demo1:
/*---------在块级作用域中,分别用var/const/let三种方式定义的变量a---------*/
if( true ) {
var a=1;
console.log(a) //当然是1
}
console.log(a) //答案也是1,因为对var来说没有块级作用域
--------------------------------------------------------------------------
if( true ) {
const a=1;
console.log(a) //当然是1
}
console.log(a) //答案是Uncaught ReferenceError: a is not defined,用const定义的变量a只能在块级作用域内部访问,外部访问不了
--------------------------------------------------------------------------
if( true ) {
let a=1;
console.log(a) //同样也是1
}
console.log(a) //答案是Uncaught ReferenceError: a is not defined,用let定义的变量a只能在块级作用域内部访问,外部访问不了
/*---------在函数作用域中,再来分别用var/const/let三种方式定义的变量a---------*/
function add () {
var a=2
console.log(a)
}
add() //打印2
console.log(a) //答案是Uncaught ReferenceError: a is not defined,因为var定义的变量遵循函数作用域的规则(所以外部不能访问函数内部的变量)
--------------------------------------------------------------------------
function add () {
const a=2
console.log(a)
}
add() //打印2
console.log(a) //答案是Uncaught ReferenceError: a is not defined,因为const定义的变量遵循函数作用域的规则(所以外部不能访问函数内部的变量)
--------------------------------------------------------------------------
function add () {
let a=2
console.log(a)
}
add() //打印2
console.log(a) //答案是Uncaught ReferenceError: a is not defined,因为let 定义的变量遵循函数作用域的规则(所以外部不能访问函数内部的变量)
我相信上面这个例子对于大家来说肯定是过于简单了,但是能快速帮助大家理解上述的定论,现在再来看一个经典面视题…
Demo2:
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // 5 5 5 5 5
}, 300);
}
console.log(i)
for (let j = 0; j < 5; j++) {
setTimeout(function () {
console.log(j); // 0 1 2 3 4
}, 300);
}
console.log(i)
解析:为什么上面会是这样的结果呢?这里还涉及到了js的执行机制知识点,要两者结合才ok
第一个例子中用var 定义的i变量,在for(){}循环中属于块级作用域,但是var没有块级作用域一说,所以此时的i相当于是全局变量,整个循环的执行过程应该是这样的,首先定义了变量i,赋值为0,然后判断i是否小于5,成立走循环体内的代码,js执行遇到setTimeout语句时,因为它是宏任务,所以会将其放置到宏任务事件队列中,然后继续执行代码,i++,第一次循环结束,然后再次判断i是否小于5,又成立,再走循环内容,还是放到宏任务事件队列中,。。。以此类推,最终在宏任务事件队列中就有5个回调函数,但此时i已经变为了5,所以这5次打印都是5;
第二个例子中用let定义变量,这里我给你看张图你应该就明白了
这张图是通过babel打包后的代码原理,执行过程还是和第一个例子是相同的,一开始不执行函数,只是放到了事件队列中,但是会将每次循环的变量值都当成参数传给回调函数,所以最后回调函数执行时的变量j就是当时传过来的值,所以结果就是0,1,2,3,4了,这样你看明白了吗?
二、var
- 可重复定义
var a=1;
var a=2;
console.log(a) //2
- 声明时可以不赋值
var a;
console.log(a) //undefined
- 声明提前
console.log(a) //undefined
var a=10;
console.log(a) //10
//上述代码相当于
var a;
console.log(a) //undefined
a=10;
console.log(a) //10
- 无法定义块级作用域变量,在块级作用域中定义相当于定义了一个全局变量
for(var a=0;a<10;a++){
}
console.log(a) //10
三、const
- 当声明时不赋值报错
const a; //不赋值会报错
- 改变const常量值时报错
const a=1;
a=2;
- 在同一作用域中重复定义报错
const a=1;
const a=2;
- 不会被声明提前(与var有区别)
console.log(a)
const a=2;
- 可以声明块级作用域的变量,块级作用域外无法访问内部变量!
if(true){
const a=1;
}
console.log(a)
四、let
- 可以声明时不赋值
let a;
a=1;
console.log(a)
- 在同一作用域中不可以重复定义
let a;
let a=1;
console.log(a)
或者
let a=1;
var a=1;
console.log(a)
- 不会被声明提前(与var有区别)
console.log(a)
let a=1;
- 可以声明块级作用域的变量,块级作用域外无法访问内部变量!
if(true){
let a=1;
}
console.log(a)