ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-01-03 16:30:57  阅读:226  来源: 互联网

标签:块级 const 变量 作用域 js 词法 let


前言

前面我已经讲了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声明的量表现得没有变量提升,所以一般就直接得说他们没有变量提升了

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

  • 函数内部var声明得变量,编译阶段阶段,经过变量提升全部放入了变量环境中并赋值undefined
  • 通过let声明得变量也提升了放入了词法环境,此时它没有值,而是一个状态uninitiated,表示预留了内存空间
  • 注意,{}内的let声明的变量和没有参与变量提升,这就说明,let和const声明的变量只在当前块内进行一个提升,
  • 接下来,就是执行可执行代码了

首先

  • 赋值操作a = 1,b= 2
  • 然后执行到{}里面的内容,这时候词法环境又会为这个{}里的内容划分出一个新的区域(这区域也是个对象,其实这里也有个专属名词,不过我不记得了,理解了就好,这个区域可以理解为是保存b变量的对象的子域)
  • 此时词法环境里又出现了一个新的区域,保存着这个块里的b,d变量,然后赋值操作,b=3,变量环境中的c赋值c=4,d=5,打印a = 1,b=3
  • {}里的面代码执行完之后,为词法环境中为b,d创建的那块区域被销毁,打印b = 2,c=4(c是变量环境中的,只在函数执行完出栈时才会销毁),这时遇到了d,因为保存d的区域词法环境已经给销毁了,所以d会报错d is not defined

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

总结

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

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

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有