本文首发于个人网站:let关键字:加强版的var关键字

你好,今天大叔想和你唠扯唠扯 ES6 新增的关键字 —— let。再说 let 的具体用法之前,大叔想先和你说说大叔自己对 let 的感受 —— let 其实就是加强版的 var。为啥这么说呢?别急,且听大叔慢慢道来。

首先,letvar 的作用是一样一样滴,都是用来声明变量。看到这儿,你可能会有个问题啦,既然作用一样,为啥还要再搞个什么新特性出来?

想要回答这个问题,就要说到 letvar 的不同之处了。比方说 var 声明的全局变量会自动添加到顶级对象中作为属性,而 let 就不会。再比方说 var 允许声明提升或者重复声明,而 let 就不允许这样做。当然了,它们之间的不同可不止这些,大叔也只是举个栗子而已。

如果你没了解过 ES6 的内容,看到这儿可能有点懵。没关系啊~ 别往心里去,因为接下来大叔就是要和你唠扯唠扯 let 的具体用法。

声明的全局变量不是顶级对象的属性

在整明白 letvar 第一点不同之前,大叔要先和你唠扯唠扯 var 这个关键字的一些用法。为啥?!var 你要是都整不明白的话,你还想整明白 let,那就是一个美丽的扯!

首先,咱们都知道其实声明一个全局变量,是既可以使用 var 进行声明,也可以不使用 var 进行声明的。比方说像下面这段代码一样:

var a = 'a'
console.log(a)
b = 'b'
console.log(b)

上面这段代码不用大叔多扯,想必你也知道打印的结果是个啥 —— 打印 a 和 b 嘛。别急,这才是个开始,咱不点慢慢来不是~

接下来呢,大叔要用 delete 这个运算符来做个骚操作了 —— 先用 delete 删除上面的两个变量 ab,然后呢再分别打印这两个变量的值。

你寻思一下这个时候应该打印的结果是啥呢?对啦!变量 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 可不是仅仅那么一点点的区别呢!

不允许声明提前

这会儿你是不是又想问 letvar 之间还有没有其他区别啊?大叔也不藏着掖着了,干脆一口气都和你说了吧!你知道使用 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关键字的更多相关文章

  1. JavaScript var关键字、变量的状态、异常处理、命名规范等介绍

    本篇主要介绍var关键字.变量的undefined和null状态.异常处理.命名规范. 目录 1. var 关键字:介绍var关键字的使用. 2. 变量的状态:介绍变量的未定义.已定义未赋值.已定义已 ...

  2. JavaScript中var关键字的使用详解

    作用 声明作用:如声明个变量. 语法 ? 1 var c = 1; 省略var 在javascript中,若省略var关键字而直接赋值,那么这个变量为全局变量,哪怕是在function里定义的. ? ...

  3. Linq专题之var关键字

    在c#1.0,c#2.0中声明一个变量时,总是要指定变量的类型,如果不指定变量类型编译器就会报错,产生编译错误.在c#3.0中我们可以不指定变量的具体类型,而使用一个新的关键字"var&qu ...

  4. c#中var关键字用法

    Technorati 标签: C# 转载自csdn:http://blog.csdn.net/robingaoxb/article/details/6175533   var关键字是C# 3.0开始新 ...

  5. C#中var关键字【转】

    [转]http://blog.csdn.net/courageously/article/details/5695626 var关键字是C# 3.0开始新增的特性,称为推断类型 . 可以赋予局部变量推 ...

  6. 浅谈 var 关键字

    提起 var关键子,程序员的第一反应就是JavaScript, 事实上这个关键子在其他语言中也有被采用. 比如说C#, 比如说kotlin, 用法和JavaScript中使用差不多,作为要声明变量的前 ...

  7. 什么时候用var关键字

    C#关键字是伴随这.NET 3.5以后,伴随着匿名函数.LINQ而来, 由编译器帮我们推断具体的类型.总体来说,当一个变量是局部变量(不包括类级别的变量),并且在声明的时候初始化,是使用var关键字的 ...

  8. 关于var关键字的详解

    var 在很多语言中都比较常见,到底var是什么,如何应用,下面就笔者常用的javascript.c#对var进行说明: var 是 variable(变量,可变物)的简写.在多种计算机编程语言中,v ...

  9. C#中var关键字用法分析

    原文连接 本文实例分析了C#中var关键字用法.分享给大家供大家参考.具体方法如下: C#关键字是伴随着.NET 3.5以后,伴随着匿名函数.LINQ而来, 由编译器帮我们推断具体的类型.总体来说,当 ...

  10. php中的var关键字的用法总结(var在类外用报错)(类属性必须带限定词)

    php中的var关键字的用法总结(var在类外用报错)(类属性必须带限定词) 一.总结 1.var在类外用报错:如果不是在类中,用var定义变量是错的. 2.类属性必须带限定词:php中类属性必须定义 ...

随机推荐

  1. 开发人员看测试之细说JBehave

    上篇我们说到如何从Github上clone出一个JBehave项目,既是为了学习JBehava,也是为了熟悉下Github.从clone下来的项目看来,基本没什么问题,稍微捋一捋就可以运行,但是就cl ...

  2. 【高级算法】模拟退火算法解决3SAT问题(C++实现)

    转载请注明出处:http://blog.csdn.net/zhoubin1992/article/details/46453761 ---------------------------------- ...

  3. 用开源软件建垂直搜索引擎 转载 http://news.cnblogs.com/n/60041/

    用Solr.Nutch等开源软件来构建电子元器件垂直搜索引擎涉及很多实现细节,本文结合实际应用系统对数据采集.中文搜索.结果输出.分页处理.整合数据库等重点问题提出了切实可行的解决方法. 用开源软件建 ...

  4. ES(二): Build ES Cluster on Azure VM

    目录: 系统环境准备 安装ES集群 安装Kibana 安装x-pack 安装head 系统环境准备 参见: HDP2.4安装(二):Centos7配置 修改network: 修改hosts: 配置ss ...

  5. 产品经理学Python:条件控制

    条件控制其实就是if...else...(如果...条件是成立的,就做...:反之,就做...)的使用,其基本结构是: 具体看下面这个例子: def account_login(): # 定义函数 p ...

  6. MySQL1-安装

    MySQL 什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库, 每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据. 我们也可以将数据 ...

  7. 关于springboot整合配置pagehelper插件的方法

    一,java代码配置法 这种方法个人感觉比较繁琐不是很推荐,而且也不怎么符合springboot的理念,但是胜在也能够用,所以就列起来,万一以后接手的代码是用这种方式的也方便自己维护. 首先引入jar ...

  8. kubernetes 实战4_命令_Configure Pods and Containers

    Configure Service Accounts for Pods A service account provides an identity for processes that run in ...

  9. 微信小游戏 修改appid

    微信开发者工具中,当你使用一个公众号开发一个项目,有需求切换到另外一个公众号继续开发时,需要修改appid. 修改微信小游戏 project.config.json 文件的appid

  10. slice层解析

    如果说之前的Concat是将多个bottom合并成一个top的话,那么这篇博客的slice层则完全相反,是把一个bottom分解成多个top,这带来了一个问题,为什么要这么做呢?为什么要把一个低层的切 ...