深入理解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 相关文章推荐
extjs grid取到数据而不显示的解决
Dec 29 Javascript
javascript 运算数的求值顺序
Aug 23 Javascript
利用js判断浏览器类型(是否为IE,Firefox,Opera浏览器)
Nov 22 Javascript
js中setTimeout()与clearTimeout()用法实例浅析
May 12 Javascript
js编写当天简单日历效果【实现代码】
May 03 Javascript
JavaScript程序中实现继承特性的方式总结
Jun 24 Javascript
bootstrap提示标签、提示框实现代码
Dec 28 Javascript
react.js 获取真实的DOM节点实例(必看)
Apr 17 Javascript
微信小程序出现wx.getLocation再次授权问题的解决方法分析
Jan 16 Javascript
一文看懂如何简单实现节流函数和防抖函数
Sep 05 Javascript
详解关于Vue单元测试的几个坑
Apr 26 Javascript
vue使用element-ui实现表单验证
Dec 13 Vue.js
详解在网页上通过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
关于PHP堆栈与列队的学习
2013/06/21 PHP
解析如何通过PHP函数获取当前运行的环境 来进行判断执行逻辑(小技巧)
2013/06/25 PHP
php switch语句多个值匹配同一代码块的实现
2014/03/03 PHP
PHP连接MySQL的2种方法小结以及防止乱码
2014/03/11 PHP
php编写的一个E-mail验证类
2015/03/25 PHP
php文件压缩之PHPZip类用法实例
2015/06/18 PHP
php检查页面是否被百度收录
2015/10/28 PHP
关于Yii中模型场景的一些简单介绍
2019/09/22 PHP
javascript 框架小结 个人工作经验
2009/06/13 Javascript
浅析ajax请求json数据并用js解析(示例分析)
2013/07/13 Javascript
jQuery实现返回顶部功能适合不支持js的浏览器
2014/08/19 Javascript
简洁实用的BootStrap jQuery手风琴插件
2016/08/31 Javascript
Angular 中 select指令用法详解
2016/09/29 Javascript
JavaScript实现多栏目切换效果
2016/12/12 Javascript
bootstrap动态添加面包屑(breadcrumb)及其响应事件的方法
2017/05/25 Javascript
Angular4学习之Angular CLI的安装与使用教程
2018/01/04 Javascript
详解js模板引擎art template数组渲染的方法
2018/10/09 Javascript
Vue中Table组件Select的勾选和取消勾选事件详解
2019/03/19 Javascript
Moment.js实现多个同时倒计时
2019/08/26 Javascript
wxpython中利用线程防止假死的实现方法
2014/08/11 Python
Python实用日期时间处理方法汇总
2015/05/09 Python
Python函数式编程指南(四):生成器详解
2015/06/24 Python
一张图带我们入门Python基础教程
2017/02/05 Python
python引入导入自定义模块和外部文件的实例
2017/07/24 Python
基于Python的文件类型和字符串详解
2017/12/21 Python
Python连接Mssql基础教程之Python库pymssql
2018/09/16 Python
python中enumerate() 与zip()函数的使用比较实例分析
2019/09/03 Python
Selenium之模拟登录铁路12306的示例代码
2020/07/31 Python
四方通行旅游网:台湾订房、出国旅游
2017/09/20 全球购物
大学校运会广播稿
2014/02/03 职场文书
婚礼答谢宴主持词
2014/03/14 职场文书
党的群众路线教育实践活动个人对照检查材料(乡镇)
2014/11/05 职场文书
2015年大学组织委员个人工作总结
2015/10/23 职场文书
基于CSS3画一个iPhone
2021/04/21 HTML / CSS
python用tkinter开发的扫雷游戏
2021/06/01 Python
Python实现天气查询软件
2021/06/07 Python