已知声明一个变量有以下几种方法:
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 的区别了吗?