干货分享—var、let与const之间如何选择

前言

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

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

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

var和let的区别

块级作用域

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

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

Hoisting(变量提升)

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

console.log(val);//undefined
var val = 0;
console.log(val);//Cannot access 'val' before initialization
let val = 0;

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

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

全局作用域下赋值

var i = 1;//使用var关键字
j = 2;//不使用任何关键字  这种情况在严格模式不适用  会报j is not defined
let num = 3;//使用let关键字

如上所述,进行输出:

console.log('i : ' + i);//i : 1
console.log('j : ' + j);//j : 2
console.log('num : ' + num);//num : 3
console.log('window.i : ' + window.i);//window.i : 1

console.log('window.j : ' + window.j);//window.j : 2

console.log('window.num : ' + window.num);//window.num : undefined

再使用delete关键:

delete i;
delete j;
delete num;
console.log('i : ' + i);//i : 1
console.log('j : ' + j);//报错 被删除了
console.log('num : ' + num);//num : 3
delete window.i;
delete window.j;
delete window.num;
console.log('window.i : ' + window.i);//window.i : 1
console.log('window.j : ' + window.j);//window.j : undefined
console.log('window.num : ' + window.num);//window.num : undefined

由此我们可以得出结论,在全局作用域下赋值(只适用于非严格模式):
1.在使用var关键字的情况下会同时赋值到window下,并且不能通过delete关键字删除;
2.在不使用用关键字的情况下也会同时赋值到window下,并且能通过delete关键字删除;
3.在使用let关键字的情况下不会赋值到window下;

扩展

例1:

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

arr[1]();//1

例2:

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

arr[1]();//10

注意两段代码的唯一差别就是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:

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

arr[1]();//10

例3虽然还是let,但是我已经把它放到for循环的{}外面了,所以效果与例2一致

接下来还有例4
例4:

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

arr[1];//1

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

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

        arr.push(i);
    }

例5
这里的知识点涉及了闭包,如果对闭包暂时还不是很了解的可以先去了解下 一篇文章带你弄懂闭包

function outside(num){
    function inside(){
        return num;
    }
    return inside;
}
var arr = [];
for(var i = 0,length = 10;i<length;i++){
    arr.push(outside(i));
}
arr[1]();//1

例5是非常有意思。babel为了兼容浏览器,在进行代码以及语法的转换时,是会把像例1中for循环中的let替换成var的(毕竟现在还不是所有浏览器都兼容es6),如果是直接替换,像例2,是肯定不对的,但是像例5,不用块级作用域,也能达到例1的效果。
babel做兼容转换代码还有一点也非常有意思,比如像下面的代码:

{
	let num = 100;
}
console.log('num : ' + num);

如果只是替换声明变量的关键字:

{
	var num = 100;
}
console.log('num : ' + num);

根本就达不到实用let的效果,babel中的做法是:

{
	var _num = 100;//不单单替换关键字,还会改变变量名
}
console.log('num : ' + num);

是不是非常有意思?

const和let的区别

不变的变量

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

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

const j = 0;
j++;
console.log('j:'+j);//Assignment to constant variable.

const一旦赋值过后是不能重新赋值的,但是这时会出现一种情况:

const obj = {};
obj.name = 'object';
console.log(obj);//{name: "object"}
const obj = {};
obj = {};
console.log(obj);//Assignment to constant variable.

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

扩展

babel对const关键字的转化也非常的有意思,const具有和let一样的块级作用域,所以一些地方的转化和let是一样的,但是const还有一个不能修改的特性,对于这一点,我们继续往下面看:

const num = 10;
num = 20;

如果是单纯的替换声明变量的关键字:

var num = 10;
num = 20;

同样的达不到我们需要的效果,babel中是怎么去做的呢?

function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }

var num = 10;
num = (_readOnlyError("num"), 20);

看到这里是不是觉得越看越有意思?是不是就算不使用let和const关键字也能自己写出想要的效果?

总结

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

结尾

这篇博客写的挺久的,可能看完10分钟左右吧,但是我写完这个应该是花了我挺长时间的,为什么我会花费我的时间去写这个呢?首先是因为对自己学过的知识的总结,再者是希望帮助一些刚刚学习JavaScript的人,当然,这些我在开篇就已经说过了。不过其实我还有一点最重要的没有说,有时看一些博客,要是作者使用var来用作变量申明的关键字,而不去使用let,下面的留言中总有一些“云大佬”在线指点,要使用let,不要去使用var,但是仅此而已,没有下文了。我个人的理解是,一定要结合当前的情况来判断到底使用什么关键字。如果项目中没使用babel(或者其它功能相似),又要兼容ES5,这个时候还一股脑的追求使用let和const,这不是在搞笑吗?JavaScript是一门很有魅力的语言,就如同我上面写的let扩展中的例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 ↩︎

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值