ES6 let、var、const区别、作用域

97 阅读4分钟

已知声明一个变量有以下几种方法:

a=1
var a=1
//以上两种是ES3,以下是ES6
let a=1
const a=1

为什么ES6要重新给声明变量一个新的语法?很显然以前的var并不好用,不然干嘛要出新的。

先看a=1,可能有教材会告诉你,a=1是在声明一个全局变量。这句话当然是错的!它只是有时会是全局变量。比如:

a=1
console.log(window.a)   //1
这时它确实是一个全局变量。
var a
function fn(){
  a=1
  console.log(window.a)
}
fn()  //1
这时它也是一个全局变量。
var a
function fn(){
  var a
  function fn2(){
    a=1
    console.log(window.a)
  }
  
}
fn()  //undefined
因为这里的a=1赋值给了fn()里的a,所以此处并非全局变量

由此看出,在没有函数,或只有一层函数的时候,a=1确实是全局变量。再细讲,如果a=1时当前fn()里有var a,就直接利用这个a,变成局部变量。如果当前fn没有var a,才去声明全局变量a。或者,在没有a这个变量的情况下,a=1才会去声明全局变量。(但a=1这种写法依然不建议使用)

var a=1 这种写法避免了上面出现的问题,但是var a不管是写在哪里,它都会变量提升。我们来举一个极端的例子:

function fn(){
  if(true){
   console.log(1)
   }else{
   console.log(2)
   }
}
fn()  //1   正常情况下else是不会执行的。
function fn(){
  if(true){
   console.log(a)
   }else{
   var a
   console.log(2)
   }
}
fn()  //undefined 但不报错

这里的 var a 其实它会提升到上面,相当于写在 if 前,即使 else 不会执行。这样写有时会给代码带来一些不符合逻辑的地方。

还有另外一个问题,如果我只想暴露 fn 一个全局变量,但我又要使用这个a,这个 a 要怎么写?一般做法的话。。。

function x(){
 var a=1
 window.fn = function(){
   console.log(a)
  }
 }
 现在虽然隐藏了a,但是fn是一个全局变量,x也是

但我就是想只暴露 fn,于是只能把它变成立即执行函数:

(function (){
 var a=1
 window.fn = function(){
   console.log(a)
  }
 }())

写了这么多代码只是为了只暴露一个变量,害!多麻烦。所以后来才有了let用法,为了方便使用局部变量。

我们都知道 let 只会作用在花括号内,不会超出范围。

{
  let a=1
  console.log(a)  //1
  {
   let a=2
   console.log(a)  //2
   }
}

但let会有一个临时死区的特点,即如果你在a声明前就使用a,那么就会报错,且let前到最近一个花括号之间的代码都不会执行,这个区域内就叫临时死区。

{
  let a=1
  {
   console.log(a)  //报错 a is not defined
   let a=2
   }
}


const是个常量,赋值之后就不要改动值了,再赋值就会报错。常用在声明一些不会变动的值如:const PI=3.14159


小总结:
1、不要作a=1这样的声明
2、已不建议使用var
3、let作用域在最近的{}之间,不会超出范围。
4、如果你在let之前使用a,则报错。
5、如果重复用let 声明a,就会报错。
6、const 123点同上,const只有一次赋值机会,且一开始声明就要赋值,否则报错。


下面是一些陷阱( 非面试题 ):

1、以下代码a打印出什么?

var a=1
function fn(){
  console.log(a)
  }
xxxxx(此处有一行看不见的代码)
fn()

答案:在xxxxx还不知道是什么的情况下,a未必是1。很简单,如果看不见的地方写的是a=2,那么打印出来的a肯定是2咯。但如果看不见的代码是写在fn()下面,这时打印出来的才是1。

2、以下代码打印出什么?

for(var i=0;i<6;i++){
}
console.log(i)

注意,这里写的是var,无论何时var都会变量提升,就相当于是以下写法:

var i
for(i=0;i<6;i++){
}
console.log(i)

所以打印出6。

3、以下代码打印出什么?假设最简单的xxxxx处是fn(),那么会打印出0,1,2,3,4,5
但现在是一行看不见的代码,这时还会打印出0,1,2,3,4,5 吗?

var i
for(i=0;i<6;i++){
  function fn(){
    console.log(i)
  }
  xxxxx //此处会执行fn
}

未必。如果xxx处写的是button.onclick = fn,那么 i必然是6


4、以下代码打印出什么?

html
<ul>
 <li>导航1</li>
 <li>导航2</li>
 <li>导航3</li>
 <li>导航4</li>
 <li>导航5</li>
 <li>导航6</li>
</ul>

JS
var liTags = document.querySelectorAll('li')
for(var i=0;i<liTags.length;i++){
  liTags[i].onclick = function(){
   console.log(i)
   }
 }

你是否以为点到哪个导航,就是导航x呢?这题和上题一样,答案是6。为什么是6呢,因为这里至始至终只有一个 i 呀。那么这题我如何才能破解?给它6个 i,且用 let!

var liTags = document.querySelectorAll('li')
for(var i=0;i<liTags.length;i++){
  let j=i
  liTags[j].onclick = function(){
   console.log(j)
   }
 }

把每个 i 都存到 j 里,第 j 个,打印出 j ,6次循环就有6个 j 。这样你点击某个导航时,才能打印出对应的序号。

非要用 var 怎么办呢???只能用立即执行函数咯!

var liTags = document.querySelectorAll('li')
for(var i=0;i<liTags.length;i++){
  (function (){
     var j = arguments[0]
     liTags[j].onclick = function(){
     console.log(j)
     }
  })(i)
 }

现在点击任一导航时,会得出相应的序号。但是这种做法是多么蛋疼~

看到这里的你,也许会想,为什么for循环里不直接用let i ?那现在就来分析一下吧。

var liTags = document.querySelectorAll('li')
for(let i=0;i<liTags.length;i++){
     liTags[i].onclick = function(){
     console.log(i)
     }
 }

这串代码会得到和上面代码一样的结果。点击任一导航时,会得出相应的序号。但这段代码到底是如何运作的,你知道多少?这里全程有几个 i ?
ES6在发明 let 时就已想到了你会使用 let ,而一般情况下应该是这样用的:

for(let i=0;i<liTags.length;i++){
     let j = i
     liTags[i].onclick = function(){
     console.log(i)
     }
 }

这块代码中,i 的作用域,只在 () 内,不在块内,{}里是访问不到 i 的。那我们为什么能在 {} 里也可以访问到呢,是因为for每次进入循环后它会帮你在块中创建一个 i,而这个 i 等于外面 () 内 i 的值。为了好区分好理解,这里改写下 i:

for(let _i=0;_i<liTags.length;_i++){
     let i = _i   //这行是JS自动加的,你看不见
     liTags[i].onclick = function(){
     console.log(i)
     }
 }

进入 {} 后,js 会创建一个同名同姓的变量 i ,所以 liTags[i] 这里的 i 是 _i=0 那一瞬间的拷贝,JS把 _i 赋值给 i,你在 {} 里操作的值最终会返回给下一次循环的 i 。如下:

for(let i=0;i<liTags.length;i++){
     //块里的i = 圆括号里的i的值
     liTags[i].onclick = function(){
     console.log(i)
     }
     //圆括号里的i的值 = 块里的i
 }

等这个代码结束了,它会再把现在 i 的值返回给圆括里的 i,下次圆括号里的 i 就会继承到这个值。

现在一共多少个 i ? 7个。
它的值有0,1,2,3,4,5,退出循环变成6。共6次循环,每次循环分别是i0 i1 i2 i3 i4 i5,他们分别代表0,1,2,3,4,5,但他们都叫i。

所以,现在你知道 var 和 let 的区别了吗?