let 命令

let命令用于声明变量,但是与传统var命令的不同之处在于拥有以下特性:

  1. 使用let命令声明的变量只在let命令所在的代码块内有效(我将之称为变量绑定);
  2. 不存在变量提升;
  3. 存在暂时性死区;
  4. 不允许重复声明;
  5. 在全局声明,但不是全局对象的属性;

下面依次对这几个特性进行解释说明,并尝试探讨这几个特性的意义所在。

01 变量绑定

众所周知,在ES5规范中,JavaScript只有两种作用域:全局作用域与词法作用域(又称函数作用域或局部作用域)。也就是说,JavaScript中的变量要么被暴露在外,所有人都可以对其进行修改,要么就要被封装在函数体内,这样变量中的值就只有在函数内才可以获取或修正(除非使用闭包)。在漫长的开发实践中,开发者们逐渐发现基于以上两种作用域的变量绑定模式存在以下问题:

  1. function 关键字的过度使用(块级作用域);

有时候,我们的初心仅仅是想要安静的声明一些变量以供我们在某段代码中使用,并且不想冒着变量泄露全局的风险(这使我们的变量有可能覆盖到其他人的变量,或者被后来的其他人覆盖掉我们的变量,同时我们也不想维护有关变量名的长长的文档,把他们都变成一个个“关键字”)。在ES5规范中,我们的最佳实践是使用IIFE函数解决这一问题:

(function() {

var a = 1;

})();

console.log(1); // err

但是承认吧,这样的写法其实并不优雅不是吗,毕竟函数是“可执行的代码块”,而IIFE函数的唯一目的只是为了创建一个词法作用域环境。

于是,在ES6规范中,引入了块级作用域这个概念,写法只是简单的使用‘{}’表示,于是原先我们的IIFE函数就可以被替换为:

{
let a = 1;
}; console.log(a); // err

是不是优雅了很多?其实ES6规范包含了许多这样使我们的代码更加简洁,优雅的语法糖。有关块级作用域的其他知识,请见我的另一篇文章[谈谈块级作用域]。

而let关键字即是用来将变量”绑定“至所在的块级作用域,也就是说,在所在的块级作用域外,我们无法读取,修改使用let关键字声明的变量。

  1. var关键字的声明的变量具有”变量提升“效果,这导致了一些问题:

    var tmp = new Date();

    function foo() {

    console.log(tmp);

    if (false) {

    var tmp = 'hello world';

    }

    }

    foo(); // undefined

由于没有块级作用域,var关键字声明的tmp变量在代码初始化的过程中被提升至foo函数作用域顶端,覆盖了全局作用域的tmp变量,因此输出的tmp值为undefined,这显然不是我们想要的结果。而有了块级作用域后,tmp改用let关键字声明,该变量就会乖巧的待在自身的块级作用域内,console.log则可以正确的输出我们想要的结果。

  1. 用来计数的循环变量会泄露为全局变量

    var s = 'hello';

    for (var i = 0; i<s.length; i++ ) {

    console.log(s[i]);

    }

    console.log(i); // 5

在本段代码中,函数的实际解析过程是这样的:

var s = 'hello';
var i = 0; while ( i<s.length) {
console.log(s[i]);
i++;
} console.log(i); // 5

发现了吗,我们无意中创建了一个全局变量i,这样的结果可能是我们不想要的。

因此ES6使用了块级作用域与let关键字解决这个问题:

var s = 'hello';

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

看到了吗,原先的变量i不复存在,怎么做到的,我认为上面代码被解析为如下代码:

var s = 'hello';

{
let i = 0;
while ( i<s.length) {
console.log(s[i]);
i++;
}
} console.log(i); // undefined

值得注意的是,let关键字声明的变量仅在每次循环中有效,即每循环一次,都会重新创建一个全新的块级作用域并且它享有自己的let变量,利用这个特点,我们可以实现以往通过闭包才能实现的效果:

var arr = [];

for (let i=0; i<10; i++) {
a[i] = function() {
console.log(i);
};
}; a[6](); // 6

02 不存在变量提升

let关键字声明的变量不存在var关键字声明的变量那样会出现”变量提升“现象。所有变量都需要先声明后使用,否则会报错(这也意味着使用typeof关键字不再安全)。

也许,变量提升使我们的代码能够更加灵活,但是这种灵活性却带来了很多潜在的问题,let关键字拒绝了这种灵活性,却使我们的代码更加健壮,规范。

先声明后使用啊,朋友,let如是说。

03 暂时性死区

我已经出生(存在)了,只是还没有长大(声明),等我长大(声明)之后,你才可以娶(获取)我。这就是暂时性锁区的含义,在块级作用域内,let关键字绑定的变量名在声明之前不可获取,赋值,修改。这看起来似乎更加合理,很奇怪ES5规范中为什么没有这样规定。

04 不允许重复声明

“嘿,你没必要给我取两次相同的名字!“如果你视图在一个块级作用域内多次使用let声明一个变量,你就能听到let关键字冲着屏幕外的你向你怒吼,是的,你真的没有必要那样做。醒醒吧。ES6规范增加这一条这是为了给你一个友善的忠告。

05 虽然在全局声明但不是全局对象的属性

全局对象是最顶层的对象,在浏览器环境值的是window对象,在Node.js中值的是global对象,在ES5中,全局对象的属性与全局变量是等价的。但在ES6中,使用let关键字, const命令,class命令声明的全局变量将不再属于全局对象的属性。

为什么这样做?道理很简单,为了保护变量不会被意外的修改,但随之而来的问题是,在全局声明的变量去了哪里?

let b = 1;
window.b; // undefined

const 命令

const命令用来声明常量,一旦声明,其值就不能改变。这也就意味着,const一旦声明常量,就必须立即初始化。

除此之外,const的作用域与let命令相同,也拥有let命令的五大特性(仅在所在代码块内有效,暂时性死区,不存在变量提升,不允许重复声明, 在全局内声明不是全局对象的属性)。

最后值得一提的是,就像变量存储引用类型值时,存储的实际上是一个指向内存地址的”指针“,const实际上也是如此,因此其”值不能改变“,也就意味着”指针“不能发生改变,也就是说,你仍然可以在const声明的对象上添加属性,因为并未改变其指针。

如果想要完全”冻结“一个引用类型值(不可以在对象上添加属性),你需要使用Object.freeze方法:

const obj = Object.freeze({});
obj.prop = 123; // doesn't work

除了冻结对象本身之外,要想实现”全面封冻“,你还需要连带对象的属性一并冻结:

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

谈谈let与const的更多相关文章

  1. JavaScript系列文章:谈谈let和const

    JavaScript系列文章:谈谈let和const   最近接触到ES6的一些相关新特性,想借let和const两个命令谈谈JavaScript在变量方面的改进. 由于let和const有很多相似之 ...

  2. JavaScript:谈谈let和const

    最近接触到ES6的一些相关新特性,想借let和const两个命令谈谈JavaScript在变量方面的改进. 由于let和const有很多相似之处,我们就先说一说let吧. 1. let添加了块级作用域 ...

  3. 浅析const、let与var

    以前无论声明变量还是常量,总是使用var一勺端,知道接触了es6之后,发现原来变量.常量的声明其实是很讲究的. 这里简单来谈谈var.const与let. 1.var.var声明的变量没有块级作用域, ...

  4. dart之旅(一)

    前言 最近在看 dart 了,本着 "纸上得来终觉浅,绝知此事 markdown" 的原则,准备边学边写,写一个系列,这是第一篇.学习过程中主要是参考 A Tour of the ...

  5. 【我的面试-01】Web前端开发实习岗-面试题总结

    简单开头 首先技术面试官会根据简历里所写的项目和个人掌握技术栈提问(我不知道已经改过多少次简历了,因为前期投简历是真的是沉在茫茫大海,捞漂流瓶都捞不到的那种) 我的技术栈:(Vue还在苦苦的自学当中, ...

  6. openssl 1.1.1 reference

    openssl 1.1.1 include/openssl aes.h: # define HEADER_AES_H aes.h: # define AES_ENCRYPT 1 aes.h: # de ...

  7. 谈谈以下关键字的作用auto static register const volatile extern

    (1)auto 这个这个关键字用于声明变量的生存期为自动,即将不在任何类.结构.枚举.联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量.这个关键字不怎么多写,因为所有的变量默认就是 ...

  8. JavaScript系列文章:从let和const谈起

    注册博客园账号也有好些年了,有事没事经常来逛逛,感觉博客园的同学们一直都很活跃,相比国内其他社区来讲,这里的技术氛围很浓,非常适合学习和交流,所以博主我也决定在这里驻扎了,在这里,博主希望能坚持写一些 ...

  9. 谈谈基于OAuth 2.0的第三方认证 [下篇]

    从安全的角度来讲,<中篇>介绍的Implicit类型的Authorization Grant存在这样的两个问题:其一,授权服务器没有对客户端应用进行认证,因为获取Access Token的 ...

随机推荐

  1. [Android Tips] 26. Multiple Maven repositories in Gradle

    来自 https://gradleproject.wordpress.com/2013/02/14/multiple-maven-repositories-in-gradle/ This DOESN' ...

  2. 技术宅之flappy bird 二逼鸟

    师雪坤和刘阳 风靡一时的虐心小游戏<Flappy Bird>,以玩法简单.难度超高著称,不过,最近这款让全世界玩家几欲怒摔手机的游戏,被两位中国技术宅设计的"玩鸟机器人" ...

  3. JQueryiframe页面操作父页面中的元素与方法(实例讲解)

    1)在iframe中查找父页面元素的方法:$('#id', window.parent.document) 2)在iframe中调用父页面中定义的方法和变量:parent.methodparent.v ...

  4. How to Design a Good API and Why it Matters

    前谷歌首席 Java 架构师谈如何设优秀的 API – 码农网 http://www.codeceo.com/article/google-java-good-api.html 2015-11-24 ...

  5. springData 整合 Rrdis

    导包(使用maven的话,采用坐标的方式) <!-- redis nosql 内存数据库 --> <dependency> <groupId>redis.clien ...

  6. Apache 2.4 配置多个虚拟主机的问题

    以前一直用Apache2.2的版本,最近升级到了2.4的版本,尝尝新版本嘛. 不过遇到了几个问题,一个就是配置了多个virtualhost,虽然没有报错,不过除了第一可以正常访问外,其他的都存在403 ...

  7. 转载(web app变革之rem)

    rem这是个低调的css单位,近一两年开始崭露头角,有许多同学对rem的评价不一,有的在尝试使用,有的在使用过程中遇到坑就弃用了.但是我对rem综合评价是用来做web app它绝对是最合适的人选之一. ...

  8. BBS项目部署

    1.准备 项目架构为:LNM+Python+Django+uwsgi+Redis   (L:linux,N:nginx,M:mysql) 将bbs项目压缩上传到:  /opt 在shell中直接拖拽 ...

  9. 7.Git工作区和暂存区

    Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念. 先来看名词解释. 1.工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的test文件夹就是一个工作区 ...

  10. Mysql大数据量分页优化

    假设有一个千万量级的表,取1到10条数据: select * from table limit 0,10; select * from table limit 1000,10; 这两条语句查询时间应该 ...