深入理解es6块级作用域的使用


Posted in Javascript onMarch 28, 2019

一.var 声明与变量提升机制

在JavaScript中使用var定义一个变量,无论是定义在全局作用域函数函数的局部作用域中,都会被提升到其作用域的顶部,这也是JavaScript定义变量的一个令人困惑的地方。由于es5没有像其它类C语言一样的块级作用域,因此es6增加了let定义变量,用来创建块级作用域。

我们来看一个var定义变量的示例:

function  setName(){
  if(condition){
    var name = 'loho';
    console.log(name);
  }else{
    console.log(name);
  }
}

var student = 'eveningwater';
setName();

以上代码可以理解成如下:

var student;
function setName(){
  var name;
  if(condition){
    name = 'loho';
    console.log(name);//loho
  }else{
   console.log(name);//undefined
  }
}
student = 'eveningwater';
setName();

二.块级声明

块级声明意在指定一个块级作用域,使得块级作用域中所定义的变量无法再全局被访问到,块级作用域也被称为词法作用域。

块级作用域存在于两个地方:

  1. 函数内部。
  2. 指定代码块中。(即"{"和"}"之间的区域)

1.let 声明

let声明同var声明用法一致,唯一的区别在于,let声明将变量限制在一个块内,这样就形成了一个块级作用域,因此也就不会存在变量的提升了。

例如前面的示例,我们可以写成如下:

let stundent = 'eveningwater';
function setName(){
  if(condition){
    let name = 'loho';
    console.log(name);//loho
  }else{
    //如果条件为false执行到这里
    console.log(name);//不返回值
  }
}
setName();

2.禁止重声明

在使用let定义变量之前如果已经声明了相同的变量,就会报错。因此不能重复声明变量。如以下示例:

var name = 'eveningwater';
//报错,重复声明
let name = 'loho';

当然这两个变量必须是在同一个作用域中,如果是不同作用域中,则不会报错。但有可能会遮蔽第一次声明的变量。如以下示例:

var name = 'eveningwater';
if(condition){
  //不会报错
  let name = 'loho';
}

3.const声明

使用const标识符所声明的变量必须要初始化,因此这个声明的就是一个常量。如下例:

const name='eveningwater';//正确
const name;//错误,未初始化

const声明同let声明一样,也是创建了一个块级作用域,在这个块级作用域之外是无法访问到所声明的变量的。换句话说,就是const所声明的变量不会有变量提升机制。如下例:

if(condition){
    const name = 'eveningwater';
    console.log(name);//'eveningwater'
}
//错误
console.log(name);

同样的const也不能重复声明,如下例:

var name = 'eveningwater';
//错误,不能重复声明
const name = 'loho';

但也可以在不同作用域中重复声明,如下例:

var name = 'eveningwater';
if(condition){
  const name = 'loho';
  console.log(name);//loho,屏蔽全局定义的变量
}

尽管const声明与let声明有太多相似的地方,但const声明也有一处与let声明不同,那就是const声明的变量不能被赋值,无论是在非严格模式下还是在严格模式下,都不能对const声明的变量进行赋值。如下例:

const name = 'eveningwater';
//错误
name = 'loho';

不过,如果定义的是一个对象,可以对对象的值进行修改,如下例:

const student = {
  name:'eveningwater'
}
student.name = 'loho';//没问题
//错误,相当于赋值修改对象
student = {
  name:'loho'
}

4.临时死区

前面提到let和const声明的变量都不会提升到作用域的顶部,因此在使用这两个标识符声明之前访问会报错,即使是typeof操作符也会触发引用错误。如下例:

console.log(typeof name);//报错
const name = 'eveningwater';

由于第一行代码就报错了,因此后续的声明变量语句不会执行,此时就出现了JavaScript社区所谓的"临时死区"(temporal dead zone).虽然这里示例是const声明,但let声明也是一样的。

但如果在const或let声明的变量的作用域之外使用typeof操作符监测却不会报错,只不过会返回undefined。如下例:

console.log(typeof name);//undefined
if(condition){
  let name = 'eveningwater';
}

5.循环中的块级作用域绑定

我们在使用var声明变量的时候,总会遇到这样的情况,如下:

for(var i = 0;i < 100;i++){
  //执行某些操作
}
//这里也能访问到变量i
console.log(i);//100

我们可以使用let声明将变量i限制在循环中,此时再在循环作用域之外访问变量i就会报错了,因为let声明已经为循环创建了一个块级作用域。如下:

for(let i = 0;i < 100;i++){
  //执行某些操作
}
//报错
console.log(i);

6.循环中的创建函数

在使用var声明变量的循环中,创建一个函数非常的困难,如下例:

var func = [];
for(var i = 0;i < 5;i++){
  func.push(function(){
    console.log(i);
  })
}
func.forEach(function(func){
  func();
});

你可能预期想的是打印从0到5之间,即0,1,2,3,4的数字,但实际上答案并不是如此。由于函数有自己的作用域,因此在向数组中添加函数的时候,实际上循环已经运行完成,因此每次打印变量i的值都相当于是在全局中访问变量i的值,即i = 5这个值,因此实际上答案最终会返回5次5.

在es5中,我们可以使用函数表达式(IIFE)来解决这个问题,因为函数表达式会创建一个自己的块级作用域。如下:

var func = [];
for(var i = 0;i < 5;i++){
  (function(i){
    func.push(function(){
      console.log(i);
    })
  })(i)
}
func.forEach(function(func){
  func();//这就是你想要的答案,输出0,1,2,3,4
});

但事实上有了es6的let声明,我们不必如此麻烦,只需要将var变成let声明就可以了,如下:

var func = [];
for(let i = 0;i < 5;i++){
  func.push(function(){
    console.log(i);
  })
}
func.forEach(function(func){
  func();//输出0,1,2,3,4
})

但是这里不能使用const声明,因为前面提到过,const声明并初始化了一个常量之后是不能被修改的,只能在对象中被修改值。如以下示例就会报错:

//在执行循环i++条件的时候就会报错
for(const i = 0;i < len;i++){
  console.log(i);
}

因为i++这个语句就是在尝试修改常量i的值,因此不能将const声明用在for循环中,但可以将const声明用在for-in或者for-of循环中。如下:

var func = [];
var obj = {
  name:'eveningwater',
  age:22
}
for(let key in obj){
 func.push(function(){
   console.log(key)
 })
}
func.forEach(function(func){
  func();//name,age
});
//以下也没问题
var func = [];
var obj = {
  name:'eveningwater',
  age:22
}
for(const key in obj){
  func.push(function(){
    console.log(key)
  })
}
func.forEach(function(func){
 func();//name,age
});

这里并没有修改key的值,因此使用const和let声明都可以,同理for-of循环也是一样的道理。for-of循环是es6的新增的循坏。。

7.全局作用域绑定

let,const声明与var声明还有一个区别就是三者在全局作用域中的行为。当使用var声明一个变量时,会在全局作用域(通常情况下是浏览器window对象)中创建一个全局属性,这也就意味着可能会覆盖window对象中已经存在的一个全局变量。如下例:

console.log(window.Array);//应该返回创建数组的构造函数,即f Array(){}
var Array = '这是数组';
console.log(window.Array);//返回'这是数组';

从上例,我们可以知道即使全局作用域中已经定义了Array变量或者已经存在了Array属性,但我们之后定义的Array变量则会覆盖之前已经定义好的或者已经存在的Array变量(属性)。

但是es6的let和const声明则不会出现这种情况,let和const声明会创建一个新的绑定,也就是说不会成为window对象的属性。换句话说,就是所声明的变量不会覆盖全局变量,而只会遮蔽它。如下例:

let Array = '这是数组';
console.log(Array);//'这是数组‘;
console.log(window.Array);//应该返回创建数组的构造函数,即f Array(){}

这也就是说window.Array !== Array这个等式返回布尔值true。

8.块级绑定的最佳实践

在使用es6块级声明变量中,最佳实践是如果确定后续不会改变这个变量的值,用const声明,如果确定要改变这个变量的值,则用let声明。因为预料外的变量值的改变时很多bug出现的源头。如下示例:

function eveningWater(){};
eveningWater.prototype.name = 'eveningwater';
let ew = new eveningWater();
//定义不能被修改的变量,也就是用于判断实例类型的属性
const _constructor = ew.constructor;
//可以改变自定义的名字属性
let name = ew.name;
if(_constructor === eveningWater || _constuctor === Object){
 console.log(_constructor);
}else{
  name = 'loho';
  console.log(name)
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
纯js分页代码(简洁实用)
Nov 05 Javascript
jquery中键盘事件小结
Feb 24 Javascript
JavaScript笔记之数据属性和存储器属性
Mar 31 Javascript
原生JS实现在线问卷调查投票特效
Jan 03 Javascript
JavaScript动态绑定详解
Sep 14 Javascript
详解react关于事件绑定this的四种方式
Mar 09 Javascript
Vue-cropper 图片裁剪的基本原理及思路讲解
Apr 17 Javascript
electron制作仿制qq聊天界面的示例代码
Nov 26 Javascript
使用layui监听器监听select下拉框,事件绑定不成功的解决方法
Sep 28 Javascript
js判断浏览器的环境(pc端,移动端,还是微信浏览器)
Dec 24 Javascript
Node.js实现批量下载图片简单操作示例
Jan 18 Javascript
windows下create-react-app 升级至3.3.1版本踩坑记
Feb 17 Javascript
详解在网页上通过JS实现文本的语音朗读
Mar 28 #Javascript
详解React服务端渲染从入门到精通
Mar 28 #Javascript
微信小程序学习笔记之函数定义、页面渲染图文详解
Mar 28 #Javascript
JavaScript刷新页面的几种方法总结
Mar 28 #Javascript
Vue批量图片显示时遇到的路径被解析问题
Mar 28 #Javascript
微信小程序学习笔记之目录结构、基本配置图文详解
Mar 28 #Javascript
vue-cli中使用高德地图的方法示例
Mar 28 #Javascript
You might like
DOTA2 探索永无止境 玩家自创强悍插眼攻略
2020/04/20 DOTA
PHP 程序授权验证开发思路
2009/07/09 PHP
写php分页时出现的Fatal error的解决方法
2011/04/18 PHP
php array_filter除去数组中的空字符元素
2020/06/21 PHP
php-cli简介(不会Shell语言一样用Shell)
2013/06/03 PHP
Laravel框架表单验证详解
2014/09/04 PHP
PHP处理数组和XML之间的互相转换
2016/06/02 PHP
js静态作用域的功能。
2006/12/25 Javascript
网站接入QQ登录的两种方法
2014/07/22 Javascript
基于javascript代码检测访问网页的浏览器呈现引擎、平台、Windows操作系统、移动设备和游戏系统
2015/12/03 Javascript
jQuery简单实现iframe的高度根据页面内容自适应的方法
2016/08/01 Javascript
浅谈JS获取元素的N种方法及其动静态讨论
2017/08/25 Javascript
详解ionic本地相册、拍照、裁剪、上传(单图完全版)
2017/10/10 Javascript
vue2中的keep-alive使用总结及注意事项
2017/12/21 Javascript
关于redux-saga中take使用方法详解
2018/02/27 Javascript
JavaScript 五大常见函数
2018/03/23 Javascript
bootstrap datepicker的基本使用教程
2019/07/09 Javascript
layui框架与SSM前后台交互的方法
2019/09/12 Javascript
基于JavaScript实现猜数字游戏代码实例
2020/07/30 Javascript
antd vue 刷新保留当前页面路由,保留选中菜单,保留menu选中操作
2020/08/06 Javascript
vue 路由meta 设置导航隐藏与显示功能的示例代码
2020/09/04 Javascript
js定时器出现第一次延迟的原因及解决方法
2021/01/04 Javascript
[03:46]DAC趣味视频-中文考试.mp4
2017/04/02 DOTA
[00:50]深扒TI7聊天轮盘语音出处6
2017/05/11 DOTA
详细解析Python中的变量的数据类型
2015/05/13 Python
Python3如何判断三角形的类型
2020/04/12 Python
parser.add_argument中的action使用
2020/04/20 Python
Python实现初始化不同的变量类型为空值
2020/06/02 Python
推荐一些比较有用的css3新属性
2014/11/11 HTML / CSS
CSS3 :default伪类选择器使用简介
2018/03/15 HTML / CSS
欧洲高端品牌直销店:Fashionesta
2016/08/31 全球购物
两则小学生的自我评价分享
2013/11/14 职场文书
CAD制图设计师自荐信
2014/01/29 职场文书
考生诚信考试承诺书
2015/04/29 职场文书
买卖合同纠纷代理词
2015/05/25 职场文书
《中国机长》观后感:敬畏生命,敬畏职责
2019/11/12 职场文书