let关键字:加强版的var关键字
本文首发于个人网站:let关键字:加强版的var关键字
你好,今天大叔想和你唠扯唠扯 ES6 新增的关键字 —— let
。再说 let
的具体用法之前,大叔想先和你说说大叔自己对 let
的感受 —— let
其实就是加强版的 var
。为啥这么说呢?别急,且听大叔慢慢道来。
首先,let
和 var
的作用是一样一样滴,都是用来声明变量。看到这儿,你可能会有个问题啦,既然作用一样,为啥还要再搞个什么新特性出来?
想要回答这个问题,就要说到 let
和 var
的不同之处了。比方说 var
声明的全局变量会自动添加到顶级对象中作为属性,而 let
就不会。再比方说 var
允许声明提升或者重复声明,而 let
就不允许这样做。当然了,它们之间的不同可不止这些,大叔也只是举个栗子而已。
如果你没了解过 ES6 的内容,看到这儿可能有点懵。没关系啊~ 别往心里去,因为接下来大叔就是要和你唠扯唠扯 let
的具体用法。
声明的全局变量不是顶级对象的属性
在整明白 let
和 var
第一点不同之前,大叔要先和你唠扯唠扯 var
这个关键字的一些用法。为啥?!var
你要是都整不明白的话,你还想整明白 let
,那就是一个美丽的扯!
首先,咱们都知道其实声明一个全局变量,是既可以使用 var
进行声明,也可以不使用 var
进行声明的。比方说像下面这段代码一样:
var a = 'a'
console.log(a)
b = 'b'
console.log(b)
上面这段代码不用大叔多扯,想必你也知道打印的结果是个啥 —— 打印 a 和 b 嘛。别急,这才是个开始,咱不点慢慢来不是~
接下来呢,大叔要用 delete
这个运算符来做个骚操作了 —— 先用 delete
删除上面的两个变量 a
和 b
,然后呢再分别打印这两个变量的值。
你寻思一下这个时候应该打印的结果是啥呢?对啦!变量 a
的值会正常输出 a,但变量 b
会报错 b is not defined
。那为啥又是这样一个结果呐?
大叔觉得你应该知道 delete
运算符的作用是用来删除对象的属性,但是 delete
是无法删除变量的。对啦!你想的没错,这就说明上面声明的 a
是变量但不是对象的属性,而是 b
是对象的属性但不是变量。
大叔这话说的有点绕,给你带入一个场景吧。比如上面这段代码是在一个 HTML 页面中定义的 JavaScript 代码,那 a
就是一个全局变量,b
就是向 window
对象添加了一个属性。所以,delete
运算符可以删除 b
,但不能删除 a
的原因了。
那也就是说使用 var
关键字声明的是变量,不使用 var
关键字声明的是 window
对象的属性呗。话唠叨这儿,大叔还得来个骚操作。咱再看一段代码:
var a = 'a'
console.log(window.a)
var b = 'b'
console.log(window.b)
这段代码如果按照上面的结论,打印的结果就应该是 undefined 和 b。但是~ 你真实运行一下这段代码,就应该知道实际上打印的结果是 a 和 b!
这咋和上面的结论不一样呢?!是不是又有点懵?哈哈~ 别先急着懵逼,这个问题实际上是 JavaScript 的作者 Brendan Eich 当年在设计 JavaScript 这门语言时的一个小失误:在全局作用域中声明的变量同时会被作为属性添加到顶级对象中。
可能唠扯到这儿,你会满屏的吐槽弹幕:这尼玛谁不知道?!但大叔真正想和你唠扯的就是这一点,这个小小的失误,就导致了使用 var
关键字声明的全局变量会污染全局对象的问题。
而 ES6 新增的 let
就很好滴弥补了这个问题!也就是说,使用 let
关键字声明的全局变量不会污染全局对象。不信咱可以来试试嘛~ 还是刚才那个场景,在一个 HTML 页面中定义 JavaScript 代码,仅仅把 var
改成 let
:
let a = 'a'
console.log(a)
console.log(window.a)
这段代码实际的运行结果就是 a 和 undefined。事实证明 let
有效滴解决了 var
的问题,所以你知道为啥 ES6 要新增一个关键字来完成和 var
一样的事儿了吧?!
不允许重复声明
但是,但可是,可但是~ let
就这么一点点和 var
的区别吗?答案肯定不是滴。咱们还是先来唠扯唠扯 var
关键字,使用 var
声明的变量是允许反复滴重复声明的,就像下面这段代码:
var a = 'a'
var a = 'aa'
console.log(a)
这段代码最终打印的结果是 aa,原因就在于 var
声明的变量是允许重复声明的。可能这会儿你又会问了,这我也知道啊,有啥子问题吗?
问题肯定是有滴,要是没有大叔花这么多口舌和你在这儿叨逼叨干啥啊~ 大叔还是给你带入一个场景,比方说你定义了一个 JS 文件是需要被其他小伙伴导入使用滴,那你在这个文件里面声明的变量在人家那分分钟被重新声明了,你内心是个啥感受?
当然了,大叔就是举个栗子,你也别太当真啦~ 总而言之,就是说咱们在真实开发时对变量的命名肯定是有规划的,不能随意就被重新声明使用,这样会让命名空间很乱很乱滴。
你可能有想问了,这个问题要怎么解决呢?答案其实很简单,就是使用 ES6 新增的这个 let
关键字。因为 let
关键字声明的变量是不允许被重复声明,否则会报错滴。不信你也可以看看嘛:
let a = 'a'
let a = 'aa'
console.log(a)
仅仅只是把 var
改成 let
,这个结果就是报错了,报错的内容是:SyntaxError: Identifier 'a' has already been declared
,大概的意思就是变量 a 已经被声明过了。
所以,你看,let
可不是仅仅那么一点点的区别呢!
不允许声明提前
这会儿你是不是又想问 let
和 var
之间还有没有其他区别啊?大叔也不藏着掖着了,干脆一口气都和你说了吧!你知道使用 var
关键字声明的变量是允许声明提前的吗?啥?不知道!没事儿,这个简单,啥叫声明提前,来看段代码:
console.log(a)
var a = 'a'
你运行一下这段代码,看看打印的结果是啥?没错~ 结果就是 undefined。为啥不是报错呢?原因就是使用 var
关键字声明的变量允许声明提前。还是说人话吧,也就是说,上面这段代码和下面这段代码本质上是没区别的:
var a
console.log(a)
a = 'a'
这样婶儿写你可能就明白了为啥打印的结果是 undefined 而不是报错了吧!但是,嘿嘿~ 咱们又得唠扯唠扯 let
了,因为 let
声明的变量就不允许声明提前。不信的话还是给你看段代码先:
console.log(a)
let a = 'a'
这段代码运行之后打印的结果就是报错,报错的内容是:ReferenceError: Cannot access 'c' before initialization
,大概的意思就是无法在声明变量 c
之前访问变量 c
。
暂时性死区(TDZ)
let
是不是挺屌的吧?!那你想不想知道 let
声明的变量又为啥不允许声明提前呢?嘿嘿~ 这是因为使用 let
声明变量的过程中存在一个叫做暂时性死区(Temporal dead zone,简称 TDZ)的概念。
是不是觉得挺高深的?哈哈~ 其实没啥高深的,大叔就给你唠扯明白这个事儿。规矩不变,咱还是先看段代码再说:
if (true) {
console.log(a)
let a;
console.log(a)
a = "a";
console.log(a)
}
大叔想先问问你这段代码里面三处打印的结果分别是啥?你得认真的寻思寻思哈~ 这可都是大叔刚和你唠过的内容。
- 第一处打印的结果是报错,报错内容就是
ReferenceError: Cannot access 'c' before initialization
- 第二处打印的结果是 undefined
- 第三处打印的结果是 b
对于这样的结果,大叔估计你应该会明白,毕竟都是刚唠过的内容。接下来,你得认真的看了,因为大叔要和你来唠扯有关暂时性死区的概念了~
所谓的暂时性死区,就是说使用 let
关键字声明的变量直到执行定义语句时才会被初始化。也就是说,从代码从顶部开始执行直到变量的定义语句执行,这个过程中这个变量都是不能被访问的,而这个过程就被叫做暂时性死区。
具体到上面这段代码的话,实际上暂时性死区的开始和结束位置就像下面这段代码标注的一样婶儿:
if (true) {
// 暂时性死区开始
console.log(a); // 报错,ReferenceError: Cannot access 'a' before initialization
let a;
// 暂时性死区结束
console.log(a); // 输出undefined
a = "a";
console.log(a); // 输出a
}
捞到这会儿,大叔相信你应该可以明白啥子是暂时性死区了。其实啊,一些新的概念也没啥难理解的,主要是你理解的角度和方式的问题。
typeof
运算符也不再安全
总体上来说,let
关键字要比 var
关键字严格了许多,导致我们开发时遇到的问题相应会减少许多。但 let
就没有任何问题了吗?答案显然不是滴,大叔一直信奉一句话:任何技术都没有最优,只有最适合。
ES6 新增的 let
关键字也是如此,就比方说刚才咱们捞的暂时性死区的内容,其实就有问题。啥问题呢?你还记得 JS 里面有个运算符叫做 typeof
吧,就是用来判断原始数据类型的。这个运算符在 let
出现之前相对是比较安全的,说白了就是不容易报错。但在 let
出现之后就不一定了,比方说如果你把它用在刚才说的暂时性死区里面,它就会报错了:
if (true) {
console.log(typeof c)
let c;
}
这段代码最终打印的结果同样是报错,报错内容同样是:ReferenceError: Cannot access 'c' before initialization
。
块级作用域
关于 let
关键字咱们捞到这会儿,其实基本上已经唠完了。但是,但可是,可但是~ 嘿嘿~ let
还有一个最重要的特性大叔还没和你唠呢,这重量级的都得最后出场不是?!
那这个最重要的特性就是啥呢?叫做块级作用域。唠到作用域想必你应该知道在 ES5 中存在两个:全局作用域和函数作用域,但在 ES6 中又新增了一个块级作用域。
为什么需要块级作用域
想唠明白什么是块级作用域,咱就得从为啥需要块级作用域唠起啊~ 规矩不变,还是先看段代码:
var a = "a"
function fn() {
console.log(a)
if (false) {
var a = "b"
}
}
fn()
你觉得这段代码运行之后打印的结果应该是啥?是 a?是 b?还是... ...?其实结果是 undefined。当然了,这个结果不难得出,你运行一下就能看到。关键在于,为啥是这么个结果?!
因为就在于 ES5 只有全局作用域和函数作用域,而上面这段代码的结果产生的原因就在于局部变量覆盖了全局变量。当然了,还有比这更麻烦的问题呢,比方说咱们再看下面这段代码:
for (var i = 0; i < 5; i++) {
console.log("循环内:" + i)
}
console.log("循环外:" + i)
是不是无比地熟悉吧?!不就是个 for
循环嘛!关键在哪?关键在于 for
循环结束之后,你会发现依旧能访问到变量 i
。这说明啥?说明变量 i
现在是一个全局变量。当然了,你可能会说这没啥问题,毕竟之前一直不都是这个样子的嘛。
什么是块级作用域
但是,大叔要和你说的是,现在不一样了啊,现在有块级作用域啦!啥是块级作用域?还是看段代码先:
if (true) {
let b = "b"
}
console.log(b)
这段代码运行之后打印的结果是报错,报错的内容是:SyntaxError: Lexical declaration cannot appear in a single-statement context
。
这说明啥?这就说明现在你使用 let
声明的变量在全局作用域中访问不到了,原因就是因为使用 let
声明的变量具有块级作用域。
接下来你的问题可能就是这个块级作用域在哪呢吧?其实这个块级作用域就是在花括号({}
)里面。比方说,咱们现在把上面那个 for
循环的代码用 let
改造一下再看看:
for (let i = 0; i < 5; i++) {
console.log("循环内:" + i)
}
console.log("循环外:" + i)
改造完的这段代码运行之后的结果就是在循环结束后的打印结果是报错,报错内容大叔就不说了,因为都一个样。
块级作用域的注意事项
整明白了啥是块级作用域,接下来大叔就得和你唠叨唠叨需要注意的事儿了。就是在使用 let
关键字声明块级作用域的变量时可必须在这对 {}
里面啊,不然同样也会报错滴。
比方说,咱们经常在使用 if
语句时爱把 {}
省略,但是如果 if
语句里面是使用 let
声明变量的话就不行了。不信来看段代码吧:
if (true) let c = 'c'
这段代码的运行结果同样是报错,而且报错内容都是一样的。可是不能忘记哦~
块级作用域的作用
好了,整明白啥是块级作用域了,也唠清楚需要注意的了,你是不是想问问这块级作用域有啥子用处啊?大叔都想你心里面去了,嘿嘿~
你知道匿名自调函数吧?还记得怎么写一个匿名自调函数吗?是不是这样婶儿的:
(function(){
var msg = 'this is IIFE.'
console.log(msg)
})()
还记得匿名自调函数的作用不?是不是就是为了定义的变量和函数不污染全局命名空间?!有了 let
,有了块级作用域,上面这段匿名自调函数就可以写成这样婶儿的:
{
let msg = 'this is IIFE.'
console.log(msg)
}
简化了不少吧?!
写在最后的话
好了,整到这儿,ES6 新增的 let
关键字所有大叔想和你唠扯的内容都唠扯完了,也希望能对你有所帮助。最后再说一句:我是不想成熟的大叔,为前端学习不再枯燥、困难和迷茫而努力。你觉得这样学习前端技术有趣吗?有什么感受、想法,和好的建议可以在下面给大叔留言哦~
let关键字:加强版的var关键字的更多相关文章
- JavaScript var关键字、变量的状态、异常处理、命名规范等介绍
本篇主要介绍var关键字.变量的undefined和null状态.异常处理.命名规范. 目录 1. var 关键字:介绍var关键字的使用. 2. 变量的状态:介绍变量的未定义.已定义未赋值.已定义已 ...
- JavaScript中var关键字的使用详解
作用 声明作用:如声明个变量. 语法 ? 1 var c = 1; 省略var 在javascript中,若省略var关键字而直接赋值,那么这个变量为全局变量,哪怕是在function里定义的. ? ...
- Linq专题之var关键字
在c#1.0,c#2.0中声明一个变量时,总是要指定变量的类型,如果不指定变量类型编译器就会报错,产生编译错误.在c#3.0中我们可以不指定变量的具体类型,而使用一个新的关键字"var&qu ...
- c#中var关键字用法
Technorati 标签: C# 转载自csdn:http://blog.csdn.net/robingaoxb/article/details/6175533 var关键字是C# 3.0开始新 ...
- C#中var关键字【转】
[转]http://blog.csdn.net/courageously/article/details/5695626 var关键字是C# 3.0开始新增的特性,称为推断类型 . 可以赋予局部变量推 ...
- 浅谈 var 关键字
提起 var关键子,程序员的第一反应就是JavaScript, 事实上这个关键子在其他语言中也有被采用. 比如说C#, 比如说kotlin, 用法和JavaScript中使用差不多,作为要声明变量的前 ...
- 什么时候用var关键字
C#关键字是伴随这.NET 3.5以后,伴随着匿名函数.LINQ而来, 由编译器帮我们推断具体的类型.总体来说,当一个变量是局部变量(不包括类级别的变量),并且在声明的时候初始化,是使用var关键字的 ...
- 关于var关键字的详解
var 在很多语言中都比较常见,到底var是什么,如何应用,下面就笔者常用的javascript.c#对var进行说明: var 是 variable(变量,可变物)的简写.在多种计算机编程语言中,v ...
- C#中var关键字用法分析
原文连接 本文实例分析了C#中var关键字用法.分享给大家供大家参考.具体方法如下: C#关键字是伴随着.NET 3.5以后,伴随着匿名函数.LINQ而来, 由编译器帮我们推断具体的类型.总体来说,当 ...
随机推荐
- 面试 16-01.MVVM
16-01.MVVM #前言 MVVM的常见问题: 如何理解MVVM 如何实现MVVM 是否解读过Vue的源码 题目: 说一下使用 jQuery 和使用框架的区别 说一下对 MVVM 的理解 vue ...
- 个人微信公众号搭建Python实现 -个人公众号搭建-构想(14.3.1)
@ 目录 1.需求 2.怎么做 关于作者 1.需求 个人便捷工具 2.怎么做 针对个人未认证订阅号拥有以下权限 以及微信网页的一些权限,但是由于开发微信网页有限制 可定制功能只有被动回复,以及这个素材 ...
- Docker 在搭建私有仓库配置镜像时候报错
今天搞私有镜像报了个错 ,看了,好久原来是 多了个空格 服务失败,因为控制进程退出时带有错误代码.参见"systemctl状态docker".详细信息参见"服务" ...
- Springboot接入RabbitMQ详细教程
本文适用于对 RabbitMQ 有所了解的人,在此不讨论MQ的原理,只讨论如何接入.其实Spring Boot 集成 RabbitMQ 非常简单,本文章使用的是Spring Boot 提供了sprin ...
- Mysql实现定时清空一张表的旧数据并保留几条数据
要达到如下目的: Mysql数据库会每隔一段时间(可以是2小时,也可以是一天,这个可以自定义),定时对一张库中的表做一个判断,如果这张表的数据超过了20条(这个数据也是自定义的,也可以是200条),就 ...
- AndroidSDK安装选项说明
前言:本文的目的在于了解AndroidSDK相关安装选项,正确根据自身需要选择性安装,避免安装过多无用的东西导致硬盘爆满. 1. AndroidSDK安装选项说明,如上图. 2. 实际游戏打包使用到A ...
- MySQL中的排序
在编写SQL 语句时常常会用到 order by 进行排序,那么排序过程是什么样的?为什么有些排序执行比较快,有些排序执行很慢?又该如何去优化? 索引排序 索引排序指的是在通过索引查询时就完成了排序, ...
- Java学习日报8.2
package user;import java.util.*; public class User { private String kouling; private String u; priva ...
- 解析STM32的库函数
意法半导体在推出STM32微控制器之初,也同时提供了一套完整细致的固件开发包,里面包含了在STM32开发过程中所涉及到的所有底层操作.通过在程序开发中引入这样的固件开发包,可以使开发人员从复杂冗余的底 ...
- reactor模式:多线程的reactor模式
上文说到单线程的reactor模式 reactor模式:单线程的reactor模式 单线程的reactor模式并没有解决IO和CPU处理速度不匹配问题,所以多线程的reactor模式引入线程池的概念, ...