ES6入门教程之let、const的使用方法


Posted in Javascript onApril 13, 2019

一、前提

解决ES5中只有全局作用域和函数作用域,没有块级作用域而带来的不合理的场景。

let

基本用法

用法和var 一样,只是let声明的变量只有在let命令所在的代码块有效

{
 let a = 10;
 var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

可以看出var 声明的变量在代码块之外也是可以调用,而let声明的则调用报错。所以let 声明只在它声明的当前代码块中才能调用。

变量提升

在使用 var 的时候会出现 “变量提升”的现象,即变量可以在声明之前使用,值为undefined。let 改变了这种现状,但是必须先声明在使用,如果在声明之前使用则会出现报错。如下:

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

暂时性死区

只要块级作用域内部存在 let 或者 const 命令,它所声明的变量就“绑定”在这个区域,不会受外部影响。且暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。如下:

var tmp = 123;

if (true) {
 tmp = 'abc'; // ReferenceError
 let tmp;
}

ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。如下:

if (true) {
 // TDZ开始
 tmp = 'abc'; // ReferenceError
 console.log(tmp); // ReferenceError

 let tmp; // TDZ结束
 console.log(tmp); // undefined

 tmp = 123;
 console.log(tmp); // 123
}

注意:

++使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。++

不允许重复声明

let 不允许在同一个作用域内声明同一个变量,如下:

// 报错
function func() {
 let a = 10;
 var a = 1;
}

// 报错
function func() {
 let a = 10;
 let a = 1;
}

或者如下:

function func(arg) {
 let arg;
}
func() // 报错

function func(arg) {
 {
 let arg;
 }
}
func() // 不报错

块级作用域

上面也提到过在es5中没有块级作用域的概念,只有函数作用域和全局作用域,那么就带来了一些问题,如下:

var tmp = new Date();

function f() {
 console.log(tmp);
 if (false) {
 var tmp = 'hello world';
 }
}

f(); // undefined


外层声明被内层声明所覆盖,内层使用的是外层的声明,内层变量提升导致 undefinded

第二种:计数循环全局泄露,如下:

var s = 'hello';

for (var i = 0; i < s.length; i++) {
 console.log(s[i]);
}

console.log(i); // 5

常见的面试题,最后输出不是预料中的 1 2 3 4 5 而全部是 5

ES6的块级作用域,实际上就是let 新增的,如下:

function f1() {
 let n = 5;
 if (true) {
 let n = 10;
 }
 console.log(n); // 5
}

内外层的 n 互不干扰

ES6中 允许作用域任意嵌套,并且互不干扰,如下:

内外层可以同名

{{{{
 let insane = 'Hello World';
 {let insane = 'Hello World'}
}}}};

或者

{{{{
 {let insane = 'Hello World'}
 console.log(insane); // 报错
}}}};

块级作用域的出现可以让以下立即执行函数的写法不必要,如下:

// IIFE 写法
(function () {
 var tmp = ...;
 ...
}());

// 块级作用域写法
{
 let tmp = ...;
 ...
}

块级作用域和函数声明

在ES5中,函数只能在顶层作用域和函数作用域中声明,不能在块级作用域中声明,但是浏览器为了兼容性,还是可以在块级作用域中声明,理论上在ES6中 块级作用域中声明的函数,在外部调用会报错,考虑环境的问题,应当避免在块级作用域中声明函数,如果需要也应当写成函数表达式的方式,而不是函数声明语句,如下:

// 函数声明语句
{
 let a = 'secret';
 function f() {
 return a;
 }
}

// 函数表达式
{
 let a = 'secret';
 let f = function () {
 return a;
 };
}

const

const声明的是一个常量 如下:

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

声明之后如果在赋值,将会报错,同时因为声明的是常量,即const声明后即要赋值不然也会报错
const 和 let 相同,声明也只在当前的块级作用域生效。同样也不会声明提升,也存在暂时死区,只能在声明之后使用,且和 let 一样不得重复声明,不能重新赋值。

重要:

const 所不能改变的并不是值,而是变量指向的那个内存地址所保存的值不能变动,对于简单类型(数值、字符串、布尔值),值就保存在变量所指向的内存地址中,因此等同于常量。而对于复合类型(数组、对象),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总指向一个固定的地址)。至于它指向的数据结构则是不能控制的 ,如下:

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,
即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

或者

const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错

常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错

如果真的想将声明对象冻结,不能在改变 则应该使用object.freeze()

const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

对象也可以冻结

var constantize = (obj) => {
 Object.freeze(obj);
 Object.keys(obj).forEach( (key, i) => {
 if ( typeof obj[key] === 'object' ) {
 constantize( obj[key] );
 }
 });
};

ES6 声明变量的六种方法

1. function
2. var
3. let
4. const
5. import
6. class

顶层对象的属性

在浏览器环境指的是window对象,在 Node 指的是global对象,ES5 之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1

a = 2;
window.a // 2

global 对象

ES5 的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。

  1. 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
  2. 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
  3. Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

  1. 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
  2. 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
  3. 不管是严格模式,还是普通模式,new Function('return this')() ,总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript 动态将数字金额转化为中文大写金额
May 14 Javascript
jquery实现页面图片等比例放大缩小功能
Feb 12 Javascript
JavaScript中的console.assert()函数介绍
Dec 29 Javascript
ArtEditor富文本编辑器增加表单提交功能
Apr 18 Javascript
js选择器全面解析
Jun 27 Javascript
HTML5+JS+JQuery+ECharts实现异步加载问题
Dec 16 jQuery
基于Vue的ajax公共方法(详解)
Jan 20 Javascript
js实现控制文件拖拽并获取拖拽内容功能
Feb 17 Javascript
Angular中的ng-template及angular 使用ngTemplateOutlet 指令的方法
Aug 08 Javascript
浅谈Webpack核心模块tapable解析
Sep 11 Javascript
重学 JS:为啥 await 不能用在 forEach 中详解
Apr 15 Javascript
JS实现移动端双指缩放和旋转方法
Dec 13 Javascript
ES6入门教程之变量的解构赋值详解
Apr 13 #Javascript
微信小程序template模版的使用方法
Apr 13 #Javascript
vue基于viewer实现的图片查看器功能
Apr 12 #Javascript
js form表单input框限制20个字符,10个汉字代码实例
Apr 12 #Javascript
详解js创建对象的几种方法及继承
Apr 12 #Javascript
详解JQuery基础动画操作
Apr 12 #jQuery
React中阻止事件冒泡的问题详析
Apr 12 #Javascript
You might like
php for 循环语句使用方法详细说明
2010/05/09 PHP
基于PHPExcel的常用方法总结
2013/06/13 PHP
PHP内置的Math函数效率测试
2014/12/01 PHP
基于jquery的button默认enter事件(回车事件)。
2011/05/18 Javascript
JQuery的Ajax跨域请求原理概述及实例
2013/04/26 Javascript
ExtJS如何设置与获取radio控件的选取状态
2014/01/22 Javascript
JavaScript中的索引数组、关联数组和静态数组、动态数组讲解
2014/11/08 Javascript
javascript浏览器窗口之间传递数据的方法
2015/01/20 Javascript
JavaScript获得url查询参数的方法
2015/07/02 Javascript
基于jQuery实现拖拽图标到回收站并删除功能
2015/11/25 Javascript
倾力总结40条常见的移动端Web页面问题解决方案
2016/05/24 Javascript
无阻塞加载js,防止因js加载不了影响页面显示的问题
2016/12/18 Javascript
作为老司机使用 React 总结的 11 个经验教训
2017/04/08 Javascript
vue 子组件向父组件传值方法
2018/02/26 Javascript
vue中如何让子组件修改父组件数据
2018/06/14 Javascript
VeeValidate 的使用场景以及配置详解
2019/01/11 Javascript
js实现固定区域内的不重叠随机圆
2019/10/24 Javascript
js blob类型url的视频下载问题的解决
2019/11/29 Javascript
Vue-cli3多页面配置详解
2020/03/22 Javascript
springboot+vue+对接支付宝接口+二维码扫描支付功能(沙箱环境)
2020/10/15 Javascript
Python入门篇之面向对象
2014/10/20 Python
Python实现读取目录所有文件的文件名并保存到txt文件代码
2014/11/22 Python
Collatz 序列、逗号代码、字符图网格实例
2017/06/22 Python
Python3用tkinter和PIL实现看图工具
2018/06/21 Python
Python将字符串常量转化为变量方法总结
2019/03/17 Python
Django 配置多站点多域名的实现步骤
2019/05/17 Python
使用Python opencv实现视频与图片的相互转换
2019/07/08 Python
tensorflow中tf.slice和tf.gather切片函数的使用
2020/01/19 Python
文明之星事迹材料
2014/05/09 职场文书
学雷锋演讲稿汇总
2014/05/10 职场文书
产品售后服务承诺书
2014/05/21 职场文书
党的群众路线教育实践活动制度建设计划
2014/11/03 职场文书
2016大学生诚信考试承诺书
2016/03/25 职场文书
成功的商业计划书这样写才最靠谱
2019/07/12 职场文书
解析MySQL索引的作用
2022/03/03 MySQL
Java 超详细讲解IO操作字节流与字符流
2022/03/25 Java/Android