其他分享
首页 > 其他分享> > js块级作用域:为什么要引入let和const?

js块级作用域:为什么要引入let和const?

作者:互联网

前言

前面我已经讲了js的调用栈是怎么工作的,以及js引擎是如何解析和执行js代码的,正是由于这些前面所讲的一些特性.才引起了js的一些问题,或者说js的设计缺陷

前面我们说过,js只有全局作用域和函数作用域,这对于我们一些学过其他高级语言,比如java或者c语言的人来说,写js代码时总是会遇到一些我们直觉难以理解的bug,正是由于这种缺陷,es6之后通过引进letconst关键字并且设计了词法环境,来避免产生这种因缺陷引起的bug,但是js又必须向下兼容,所以很长时间段内,我们必须理解变量提升等特性,当然这也会提高你对js的理解

作用域

作用域:是指程序中定义变量的区域,该位置决定了变量的生命周期.通俗理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性与生命周期

es6之前,我们讲过函数只有两种作用域全局和函数

在es6之前,只有这两种作用域,而其他语言都普遍支持块级作用域.块级作用域就是用**{}**包起来的一段代码,比如函数,判断语句,循环语句,甚至单独的一个{}都能被看作块级作用域,如下:

if(){}
while(){}
function(){}
for(){}
{}

如果一种语言支持块级作用域,那么代码块内部定义的变量,外部是访问不到的,并且内部代码执行完之后,内部的变量会被销毁.
但是为什么js代码在es6之前不支持呢?因为js设计者的本人开发这门语言的时候只是为了应付公司的项目,当时并没有想过能火起来,开发js的时候无疑省去块级作用域是最方便的

var 关键字带来的一些问题

我们看看下面这段代码

1.变量偷偷提升,你半天看不出来

var myname = '凯隐'
function showName() {
	console.log(myname)
	if (0) {
		var myname = '拉亚斯特'
	}
	console.log(myname)
}
showName()

如果认真看完了我前面的博客留个心眼相信很容易就能回答出来
不过我们的直觉告诉我们这段应该打印什么呢?
应该打印凯隐
而事实是这两个打印语句打印的都是undefined
为什么呢?
因为showName函数中if块呢的代码变量提升了,所以这个函数作用域就先使用了这个函数作用域中的myname了,而内部的myname还没有赋值,是undefined
不过肯定好兄弟要问了:那为什么下面那个打印的还是undefined啊?不是if块中已经给myname赋值拉亚斯特了吗?
别急吗,这个等讲了词法环境,和js引擎查找变量的顺序之后再讲,先欠着

2.本该销毁的变量没有被销毁

function foo(){
	for(var i=0;i<7;i++){
	}
	console.log(i) //7
}

本该销毁的i变量并没有被销毁 这就造成了一些内存泄漏问题

基于以上问题,es6之后引进了let,const关键字,来避免这些涉及到这些设计上的错误

let,const的引入

关于let 和const,请看以下代码

let x = 5
const y = 7
x = 6
y = 8 //报错,const声明的变量不能修改

从上面的区别来看,我们可以简单的描述以下let和const
let声明的是变量,是能够更改的值
const声明的是一个常量,不能更该=改,当你更改时,js引擎会报错
但是这里要注意的一个点是

const obj = {
	a:123
	b:456
}
obj.a = 789 //能够修改 不会报错
obj = '修改了obj的引用' //报错

需要注意的是,用const声明的引用型对象.常量只是保存了引用地址,也就是说修改这个常量会报错,但是你修改常量保存的引用地址里的对象是不会报错的,就好像,我手里有一个绳子,拴着一条狗,但是这条狗怎么变化我是不管的,我只要保证我的绳子栓着的是这条狗就行

再来看一段代码

function varTest(){
	var x = 1
	if(true){
		var x = 2  //同样的x变量,引起变量提升
		console.log(x) //2
	}
	console.log(x) //2
}

分析以上代码,会发现这个函数的执行上下文入栈之后,var声明的x变量发生了变声==变量提升,if块里面的也会,然后赋值操作x=1,再次赋值操作x=2,最后打印的也都是2,因为打印的x都是变量环境里面的x

但是引入了关键字le之后就不同了

function varTest(){
	let x = 1
	if(true){
		let x = 2  //不同的x变量
		console.log(x) //2
	}
	console.log(x) //1
}

从上面代码来看,使用了let之后js里面产生了块级作用域
没错,es6之后确实产生了块级作用域,但是块级作用域又是怎么实现的?
这里可能很少会有人去关注,我之前也没怎么关注,会用就行了,但是本着打破砂锅问到底的原则,还是去git,知乎,甚至外网上去找了以下,然后加上自己的理解给大家整合出来

首先我们得理解什么是词法环境
(这里提一嘴哈,没有证明存在词法环境和变量环境存在或者不存在,这只是一个抽象的说法,因为js引擎编译js代码是非常复杂得过程,一般为了方便理解才引入了这两种抽象说法,其实我觉得只要大家理解之后能去验证自己的理解是对的,什么词法环境或者变量环境自己起名叫阿猫阿狗也可以,也希望大家学习的时候,能多问问自己为什么)

词法环境和变量环境一样,也是js引擎编译js代码时候存放变量和常量的一块区域,只不过变量环境存储的是var声明的变量,词法环境保存的是let和const声明的变量和常量

可以理解为一个执行上下文包括:变量环境,词法环境,this(这个之后说)

js是怎么支持块级作用域的?

同一段代码中,js如何支持变量提升和块级作用域的呢?

那么我们就站在执行上下文的角度来讲讲,首先通过一段代码来讲解执行流程

function foo() {
	var a = 1
	let b = 2
	{
		let b = 3
		var c = 4
		let d = 5
		console.log(a)
		console.log(b)
	}
	console.log(b)
	console.log(c)
	console.log(d)
}

先编译创建执行上下文和可执行代码,然后快速扫面代码,对于var声明的变量,变量提升阶段放入变量环境
此时变量环境

VariableEnviroment:{
	a = undefined
	c = undefined
}

当前词法环境:

LexicalEnviroment:{
	b:uninitialized
}

通过上面的词法环境和变量环境,你有什么发现没有?
没错,let声明的b也变量提升了!
这时肯定有人说了:‘哎呀,这不对啊,不是说了let,const和没有变量提升吗?之前网上都是真么说的啊’
没错,网上大部分为了简便记忆,确实一直说let和const声明的变量没有变量提升,但是编译的时候内部是进行了变量提升的

不过有个特点就是,let声明的变量在你显示赋值之前,是不能使用的,简单的来说就是,let b声明了b之后,b进行了提升,这时候js引擎知道了,表示"我已经知道了你用let声明了一个变量b,我也在内存中为b预留了一个内存位置,但是在你显示赋值b具体内容之前,你不能使用它" 这就叫做暂存死区

这时候大家知道了吧,没错,let和const声明的量也是存在变量提升的,不过这种变量提升和var声明的变量提升不一样,在你没有对它显示赋值之前,你使用它是会报错的

所以在表现形式上来说,let和const声明的量表现得没有变量提升,所以一般就直接得说他们没有变量提升了

好了 言归正传
以上代码流程

首先

通过我上面所说的两者结合,js就同时实现了变量提升和块级作用域了

总结

其实这篇挺简单的,由于var的变量提升存在变量覆盖,变量污染和内存泄漏等问题,于是引入了let,const关键字,同时js内部引入了词法环境这个概念来实现块级作用域.

下一篇博客我会讲作用域和闭包,这其实也是变量环境和词法环境的知识点

标签:块级,const,变量,作用域,js,词法,let
来源: https://blog.csdn.net/abcsxc258/article/details/122279496