一篇文章带你弄懂var、let与const

前言

我与JavaScript的相遇很巧合,在我第一次使用JavaScript前,我从来没有或者说从来没有机会去学习它。在那之前,我主要是进行android应用的开发,主语言是Java,后面为了迎合团队的技术栈,不得不使用JavaScript进行开发。我现在还记得,当时我的leader三令五申,只能使用var(为什么只使用var?我想使用let怎么办?),为此我经历了莫名其妙的bug,我写这篇文章,最大的目的,除了是对自己经验的总结,还希望能够帮助一些刚刚学习JavaScript的人(正如当年的我一样),要是正在看这篇文章的你发现了错误或者有什么不同的意见,也希望能够指出,不至于让错误的总结去误导更多的人。

为什么只使用var?我想使用let怎么办?

这里我出示一张图1,从中可以看到各大浏览器厂商对let的支持性(这里没提const了,感兴趣的自己去看一下)
在这里插入图片描述
浏览器厂商对let的支持限制了当时的我只能使用var,这一切直到我使用了 babel。关于babel如何将let转化为var,如果感兴趣可以自己去看看。

var和let的区别

块级作用域

这里是我当初用var最懵比的地方,我以前用过c和Java,而这两款语言的变量声明的作用域都是被锁定在方括号里面的,而var是没有块级作用域2

{
	var val = "我是用var声明的变量";
}
console.log(val);

在这里插入图片描述

{
	let val = "我是用let声明的变量";
}
console.log(val);

在这里插入图片描述

Hoisting(变量提升)

从概念的字面意义上说,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。3

console.log(val);
var val = 0;

在这里插入图片描述

console.log(val);
let val = 0;

在这里插入图片描述
造成这样结果的原因就是因为使用var声明的变量会比使用let的变量多一个变量提升的操作,它的效果更像下面的代码(这里的代码单独理解变量提升是没问题的,但是并不等于这种效果):

let val;
console.log(val);
val = 0;

在这里插入图片描述

扩展

这里的知识点涉及了闭包,如果对闭包暂时还不是很了解的可以先去了解下闭包
例1:

const func = (() => {
    var arr = [];
    for(let i = 0,length = 10;i<length;i++){
        arr.push((() => {
            return i;
        }));
    }
    return arr;
})

func()[1]();

在这里插入图片描述
例2:

const func = (() => {
    var arr = [];
    for(var i = 0,length = 10;i<length;i++){
        arr.push((() => {
            return i;
        }));
    }
    return arr;
})
func()[1]();

在这里插入图片描述
注意两段代码的唯一差别就是for循环中例1使用的是let,而例2中使用的是var,但是输出的结果却是天壤之别,为什么会造成这种效果呢?我们来分析一下:

let是有块级作用域的,所以for循环里面的代码可以视作:

	{
		//i是在for循环的{}里面的 现在循环的长度是10  就有10个这样的块级作用域
		//这10个块级作用域的i是互不影响的
	   i = 1;
	   arr.push((() => {
	       return i;
	   }));
	}

var是没有块级作用域的 它只有函数作用域和全局作用域,所以里面的代码可以视作:

	//没有块级作用域,所以已经提升到for循环的{}外面
	//i是在for循环的{}外面的 现在的循环长度是10 虽然有这样的10个块级作用域
	//但是只有一个i变量 当循环完后  i==10
	//这时  arr.push(function) 这里的function是还没执行的 所以不管怎么样
	//输出都是10  因为i只有一个
	i = 1;
	{
	    arr.push((() => {
	        return i;
	    }));
	}

请仔细查看我在上面代码中加的注释,为了更方便理解,特意加上例3的代码

例3:

const func = (() => {
    var arr = [];
    let i = 0,length = 10;
    for(;i<length;i++){
        arr.push((() => {
            return i;
        }));
    }
    return arr;
})

func()[1]();

在这里插入图片描述
例3虽然还是let,但是我已经把它放到for循环的{}外面了,所以效果与例2一致

接下来还有例4
例4:

const func = (() => {
    var arr = [];
    for(var i = 0,length = 10;i<length;i++){
        arr.push((() => {
            return i;
        })());
    }
    return arr;
})

func()[1];

在这里插入图片描述
例4虽然输出和例1一致,但是原理是完全不一样的。在例4中,arr.push(function) functon立即执行,虽然i没在for循环的{}内,但是在i变化的过程中立马调用了i的返回
实际效果:

	i = 1;
    {
        // arr.push((() => {
        //     return i;
        // })());

        arr.push(i);
    }

const和let的区别

不变的变量

我是这样理解const的,加强版的let,因为它除了拥有let的一些特性之外,还有一个特性,就是不可修改,这里扯远一点,就像加了final(JavaScript是没有这个关键字的)的let。

let i = 0;
i++;
console.log('i:'+i);

const j = 0;
j++;
console.log('j:'+j);

在这里插入图片描述
const一旦赋值过后是不能重新赋值的,但是这时会出现一种情况:

const obj = {};
obj.name = 'object';
console.log(obj);

在这里插入图片描述

const obj = {};
obj = {};
console.log(obj);

在这里插入图片描述
如果你看到这里,对上面的两张输出结果带有疑惑的话,个人建议多看看JavaScript中基础类型和引用类型

总结

作用域有全局作用域、函数作用域、块级作用域;
var的作用域是不受块级作用域影响的;
let是会受快级作用域影响的;
let是在编译的时候才会初始化;按道理 我理解这里的编译器是逐句编译的
var在声明的时候会变量提升;let是在编译的时候才会初始化就是说它不会变量提升
常见的提升还有函数声明的提升,这也是函数声明和函数表达式的区别之一
const就是加了final的let;

我为什么写这个

这篇博客写的挺久的,可能看完10分钟左右吧,但是我写完这个应该是花了我挺长时间的,为什么我会花费我的时间去写这个呢?首先是因为对自己学过的知识的总结,再者是希望帮助一些刚刚学习JavaScript的人,当然,这些我在开篇就已经说过了。不过其实我还有一点最重要的没有说,有时看一些博客,要是作者使用var来用作变量申明的关键字,而不去使用let,下面的留言中总有一些“云大佬”在线指点,要使用let,不要去使用var,但是仅此而已,没有下文了。我个人的理解是,一定要结合当前的情况来判断到底使用什么关键字,JavaScript是一门很有魅力的语言,就如同我上面写的例1和例2一样,稍微变动一点点,输出的结果大大不同。


  1. 来源:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let ↩︎

  2. 来源:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/block ↩︎

  3. 来源:https://developer.mozilla.org/zh-CN/docs/Glossary/Hoisting ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章