Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【ES基础】let和作用域

【ES基础】let和作用域

原创
作者头像
前端达人
修改于 2019-01-29 17:35:46
修改于 2019-01-29 17:35:46
86500
代码可运行
举报
文章被收录于专栏:前端达人前端达人
运行总次数:0
代码可运行

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)以JavaScript为基础制定的一种脚本语言标准。目前,该标准基本上每年发布一次新的ES规范标准,目前最新的标准是ECMAScript 2018(ES9),由于前端开发的应用场景日益复杂,自从费时六年之久ES6(ECMAScript 2015)的出现,增加了很多新的特性,让JavaScript语言更加标准化和工程化。因此我们有必要重新学习JavaScript,这样才能适应前端日新月异的发展。

从今天开始,小编将会介绍ES的内容,为了便于理解和学习,每篇文章尽量简短。本篇文章小编将带着大家一起学习如何使用新的语法声明变量——let。

本篇文章阅读时间预计10分钟。

你将会学到以下内容:

  • let基本介绍
  • 作用域介绍
    • 作用域
    • 全局作用域和函数作用域
    • 块级作用域
  • var和let的区别
  • 重复定义变量的问题
  • 提升概念的问题

let介绍

ES6引入了let,用let声明变量,解决了JavaScript没有块级作用域的问题(注:ES3的catch分句会产生块作用域)。

有其它语言背景的比如JAVA,C#开发者来说,这个概念并不难以理解,反而ES6之前,JavaScript没有块级作用域,对于新手而言,使用var声明变量,会让JavaScript不易懂和难以调试,用不好,甚至会有内存泄露的可能性。为什么会这样,主要是没有清楚作用域的概念,接下来我们首先了解下什么是作用域。

作用域

作用域简单的来说,就是一套寻找变量的规则,用于确定在何处以及如何查找变量。说直白点:这些变量在哪里?它们存储在哪里?编译器如何找到它们?ES6代码之前,只有全局作用域或函数作用域。

当一个块或函数嵌套在另一个函数时,就发生了作用域嵌套。如图所示,就有三个嵌套作用域:

  1. 全局作用域,其中有一个标识符:foo(整个绿色区域)
  2. foo创建的函数作用域,其中有三个标识符:a,bar和b(整个黄色区域)
  3. bar创建的函数作用域,其中有一个标识符:c(蓝色区域)

如何在嵌套作用域中寻找变量呢:引擎从当前作用域开始查找变量,如果找不到,就会向上一级继续查找。当抵达最外层全局作用域时,无论找到还是没有找到,查找过程中都会停止。

全局作用域和函数作用域

如何理解全局作用域和函数作用域呢,使用var声明变量时,如果在函数外声明,就是全局变量,任何函数都可以进行使用,这就是全局作用域查找。如果在函数内使用var声明变量,就是函数作用域查找,只能在函数内部进行访问,外部不能进行访问,如下段代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var a = 12; // 全局作用域都能访问
 function myFunction() {
   console.log(a); // alerts 12
   var b = 13;
   if(true) {
     var c = 14; // 函数内部可以访问
     alert(b); // alerts 13
   }
   alert(c); // alerts 14
 }
 myFunction();
 alert(b); // alerts undefined

为什么b变量访问不到?因为变量b是在函数内进行声明的,因此函数执行完后,由于垃圾数据回收机制的存在,引擎认为函数执行完了,变量应该进行销毁。

如果你在函数内忘记写了b标识前忘记写了var,引擎就会自作聪明,在函数外全局作用域为你自动声明变量b,这样在函数外就能访问b变量了(全局作用域)。

因此使用var进行声明时,如果一不小心,你就会声明一个全局作用域的变量,更糟糕的情况还有可能污染一个同名的变量,因此产生的BUG就很难查找。

以下这个例子会更加明显,也是开发者经常会出现的问题,i变量会绑定到外部作用域(函数或全局作用域),污染整个外部作用域:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for(var i=0;i<10;i++){
	console.log(i); //依次输出1到9
}
console.log(i);//10

块级作用域

幸好es6引入了let,避免了有var声明变量的一些问题,让变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常是{...}内部),有一点需要强调,在块级作用域定义的变量,块级作用域外是无法访问的,如下段代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let a = 12; // 全局作用域,可以访问
function myFunction() {
   console.log(a); // alerts 12
   let b = 13;
   if(true) {
     let c = 14; // this is NOT accessible throughout the function!
     alert(b); // alerts 13
   }
   alert(c); // alerts undefined  {}外,因此无法访问
 }
 myFunction();
 alert(b); // alerts undefined  {}外,因此无法访问

在for循环体,使用var和let的区别更加明显,一个是在全局作用域进行查找变量,一个是在块级作用域查找变量,块级作用域每一次执行都会产生一个作用域。首先在for循环里,使用var声明变量,如下段代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for(var i=0;i<5;i++){
	setTimeout(function() {
		console.log(i); 
	}, 1000);
}
// 输出 5 5 5 5 5

由于JavaScript是单线程,事件循环机制的存在(不太了解事件循环机制的,大家可以查看《JavaScript基础——你真的清楚JavaScript是什么吗?》),主线程执行for循环后,才会执行SetTimeOut里的函数,由于使用var声明的变量,作用域会绑定for循环的上一层作用域,由于for循环执行完后,i的变量自然就等于5,因此setTimeOut在执行内部函数时,查找i变量的值,才会输出5。如图所示变量寻找路径:

将var替换let,将会输出什么结果,如下段代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for(let i=0;i<5;i++){
	setTimeout(function() {
		console.log(i);
	}, 1000);
}
// 输出 0,1,2,3,4

由于块级作用域的存在,每次循环,就会产生一个循环体块级作用域,因此才会达到预期的输出。如图所示变量寻找路径:

var和let的比较

对比项

let

var

声明变量

可以被释放

可以被提升

重复定义检查

可被用于块状作用域

重复定义变量问题

用var在同一个作用域重复定义变量,后者将会覆盖前者声明的变量的值,如下段代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var a = 0;
var a = 1;
alert(a); // alerts 1
function myFunction() {
 var b = 2;
 var b = 3;
 alert(b); // alerts 3
}
myFunction();

使用let在同一作用域下重复定义变量,将会产生SyntaxError的错误,如下段代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let a = 0;
let a = 1; // SyntaxError
function myFunction() {
 let b = 2;
 let b = 3; // SyntaxError
 if(true) {
	let c = 4;
	let c = 5; // SyntaxError
 }
}
myFunction();

如果你在嵌套作用域里进行重新定义变量,虽然变量名相同,但是不是同一变量,如下段代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var a = 1;
let b = 2;
function myFunction() {
	var a = 3; // different variable
	let b = 4; // different variable
	if(true) {
	  var a = 5; // overwritten
	  let b = 6; // different variable
	  console.log(a); // 5
	  console.log(b); // 6
}
	  console.log(a); // 5
	  console.log(b); // 4
}
myFunction();
console.log(a);
console.log(b);

提升概念的问题

初学JavaScript的同学,直觉上会认为编译器会由上到下一行行的执行,其实并不正确,函数声明和变量声明都会被提升(使用var声明变量,let声明变量将不会被提升)。函数首先会被提升,然后才是变量提升。

首先我们看下段函数提升的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bookName("ES8 Concepts");
function bookName(name) {
	console.log("I'm reading " + name);//I'm reading ES8 Concepts
}

正常输出,由于函数会提升至执行语句前。在来看以下代码,使用变量的方式声明函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bookName("ES8 Concepts"); //TypeError: bookName is not a function
var bookName = function(name) {
	console.log("I'm reading " + name);
}

为什么会这样呢,JavaScript引擎只会先提升函数,在提升变量声明,引擎将会对上述代码这样调整,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var bookName; // 变量声明提升至最上面
bookName("ES8 Concepts"); // bookName is not function 
								  // because bookName is undefined
bookName = function(name) { // 变量赋值不会被提升
	console.log("I'm reading " + name);
}

如果使用let替换var声明函数呢?将会有什么提示输出呢?如下段代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bookName("ES8 Concepts"); // ReferenceError: bookName is not defined
let bookName = function(name) {
	console.log("I'm reading " + name);
}

从中可以看出,使用let声明的变量将不会产生变量声明提升。这样的好处就是,让我们更好的按照由上到下的常规方式书写代码,尽量避免提升问题产生的难以查找的问题。

小节

今天的文章就到这里,从中我们可以看出let可以说是var的进化版,为了避免产生奇奇怪怪的问题,让我们能按照大多数高级语言书写代码的思维方式,在绝大部分情况下,我们应该使用let声明变量,让我们的代码更加易于维护和使用。

本文部分内容参:《你不知道的JavaScript》

多精彩内容,请微信关注”前端达人”公众号!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
HashMap与Hashtable的区别是面试中经常遇到的一个问题。
HashMap与Hashtable的区别是面试中经常遇到的一个问题。这个问题看似简单,但如果深究进去,也能了解到不少知识。本文对两者从来源,特性,算法等多个方面进行对比总结。力争多角度,全方位的展示二者的不同,做到此问题的终结版。
黑泽君
2018/10/11
1.6K0
HashMap与Hashtable的区别是面试中经常遇到的一个问题。
HashMap和HashTable的理解与区别
Hashtable是java一开始发布时就提供的键值映射的数据结构,而HashMap产生于JDK1.2。虽然Hashtable比HashMap出现的早一些,但是现在Hashtable基本上已经被弃用了。而HashMap已经成为应用最为广泛的一种数据类型了。造成这样的原因一方面是因为Hashtable是线程安全的,效率比较低。也可能是Hashtable开始设计的时候没有遵循驼峰命名法(手动笑哭)。
全栈程序员站长
2022/07/22
2250
HashMap和Hashtable的联系与区别
HashMap继承自AbstractMap类,而HashTable继承自Dictionary类。它们都同时实现了Map(图)、Cloneable(可克隆)、Serializable(可序列化)这三个接口。Dictionary类现已被弃用,父类已被弃用,自然没有人使用它的子类Hashtable。
VIBE
2022/12/02
7440
面试必问之HashMap VS HashTable
HashMap和HashTable有什么不同?在面试和被面试的过程中,我问过也被问过这个问题,也见过了不少回答,今天决定写一写自己心目中的理想答案。
技术zhai
2019/02/15
4260
Java中的HashMap和HashTable到底哪不同?
HashMap和HashTable有什么不同?在面试和被面试的过程中,我问过也被问过这个问题,也见过了不少回答,今天决定写一写自己心目中的理想答案。
哲洛不闹
2018/09/18
6920
Java中的HashMap和HashTable到底哪不同?
HashMap常见面试题_java面试题大汇总
大家好,又见面了,我是你们的朋友全栈君。 目录 1.HashMap的数据结构? 2.HashMap的工作原理? 3.当两个对象的hashCode相同会发生什么? 4.你知道hash的实现吗?为什么要这
全栈程序员站长
2022/09/22
3900
HashMap常见面试题_java面试题大汇总
内含扩容源码的面试题,目标是手写HashMap!
    推荐在单线程环境下使用HashMap替代,如果需要多线程使用则用ConcurrentHashMap。
上分如喝水
2021/08/16
3920
内含扩容源码的面试题,目标是手写HashMap!
Java集合详解4:一文读懂HashMap和HashTable的区别以及常见面试题
《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始写的新系列。
Java技术江湖
2019/10/11
9010
hashmap和hashtable的区别,说法错误的是_javamap的用法
HashMap和Hashtable的区别 一、HashMap简介 HashMap是在JDK1.2中引入的Map的实现类。 1.HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。
全栈程序员站长
2022/10/02
3730
hashmap和hashtable的区别,说法错误的是_javamap的用法
Java集合容器面试题(2020最新版)
Java面试总结汇总,整理了包括Java基础知识,集合容器,并发编程,JVM,常用开源框架Spring,MyBatis,数据库,中间件等,包含了作为一个Java工程师在面试中需要用到或者可能用到的绝大部分知识。欢迎大家阅读,本人见识有限,写的博客难免有错误或者疏忽的地方,还望各位大佬指点,在此表示感激不尽。文章持续更新中…
Java架构师必看
2020/04/10
1.2K0
Hashtable全面使用-Java快速入门教程
Java中的Hashtable类是抽象字典类(Dictionary )的具体实现。
jack.yang
2025/04/05
1120
Hashtable全面使用-Java快速入门教程
轻松看懂Hashtable源码以及与HashMap的区别
上一篇我们认识了什么是Map、Hash,了解了Hash处理哈希冲突的几种常用方法(拉链法、开放定址法),以及分析了JDK1.8版本的HashMap源码,对Java集合框架有了初步的认识,我们本篇继续分析JDK1.8版本的Hashtable源码,最后比较HashMap和Hashtable的区别。
烂猪皮
2019/04/25
4200
轻松看懂Hashtable源码以及与HashMap的区别
HashMap面试题,看这一篇就够了!
在后端的日常开发工作中,集合是使用频率相当高的一个工具,而其中的HashMap,则更是我们用以处理业务逻辑的好帮手,同时HashMap的底层实现和原理,也成了面试题中的常客。
天之痕苏
2019/12/18
1.2K0
HashMap面试题,看这一篇就够了!
聊聊java中的哪些Map:(五)HashTable与HashMap的区别
既然聊到了HashMap,那么HashTable、ConcurrentHashMap等这都是绕不开的话题。做为ConcurrentHashMap在并发场景下高效性能的一个反例,HashTable究竟是怎么实现的呢,本章将对HashTable的源码进行分析。
冬天里的懒猫
2020/08/26
6270
聊聊java中的哪些Map:(五)HashTable与HashMap的区别
面试题:HashMap和HashTable的区别
1、两者父类不同 HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。
向着百万年薪努力的小赵
2022/12/02
1300
HashMap&ConcurrentHashMap&HashTable
JDK1.8以前Hashmap底层是数组和链表结合在一起使用,也就是散列链表。hashmap的key的hashcode()扰动函数处理后得到hash值,然后通过(n-1)& hash 判断当前元素存放的位置,如果当前位置存在元素的话,就判断当前位置存在的元素是否与之相同,相同则直接覆盖,不相同就通过拉链法解决冲突。
Tim在路上
2020/08/04
4310
大厂必问的Java集合面试题
本文已经收录到github仓库,此仓库用于分享互联网大厂高频面试题、Java核心知识总结,包括Java基础、并发、MySQL、Springboot、MyBatis、Redis、RabbitMQ等等,面试必备!欢迎大家star!
程序员大彬
2022/01/06
1.5K0
大厂必问的Java集合面试题
Java集合必会14问(精选面试题整理)
答:通过分析源码我们知道了HashMap通过resize()方法进行扩容或者初始化的操作,下面是对源码进行的一些简单分析:
我没有三颗心脏
2018/08/13
4590
Java集合必会14问(精选面试题整理)
面试常被问到的 Java 集合知识点(详细)
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,需要将对象进行存储,集合就是存储对象最常用的一种方式,也叫容器。
程序员小猿
2021/01/19
8840
面试常被问到的 Java 集合知识点(详细)
Java 8系列之重新认识HashMap
作者:美团点评技术团队 链接:https://zhuanlan.zhihu.com/p/21673805 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
bear_fish
2018/09/19
1.2K0
Java 8系列之重新认识HashMap
推荐阅读
相关推荐
HashMap与Hashtable的区别是面试中经常遇到的一个问题。
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验