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. HDU 3450 Counting Sequences(线段树)

    Counting Sequences Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/65536 K (Java/Other ...

  2. Code Forces 21 A(模拟)

    A. Jabber ID time limit per test 0.5 second memory limit per test 256 megabytes input standard input ...

  3. SQL中的循环获取根节点

    方法一: DECLARE @employeeid INT; set @employeeid = 8115; DECLARE @id INT; DECLARE @pid int; DECLARE @le ...

  4. linux使用http代理连接服务器设置方法

    连接腾讯的额cvm服务器官方给出的也有个方法,详细可以看这里:http://wiki.open.qq.com/wiki/%E4%BB%8E%E6%9C%AC%E5%9C%B0linux%E6%9C%B ...

  5. VC++SDK编程——模拟时钟

    #include <Windows.h> #include <tchar.h> #include <math.h> typedef struct Time { in ...

  6. javaweb前后台中文参数乱码

    一.描述 从前台传中文参数到后台,发现中文乱码. 二.解决 首先,统一所有文件为utf-8格式. 其次,在传参时,使用js的encodeURI函数,对参数进行编码. 然后一定要对该中文参数进行两次编码 ...

  7. 如何缩减手游app安装包的大小?

    包体过大对手游的影响更是诟病已久,有具体数据证明,游戏包体越大,在游戏运营推广过程中游戏用户的转化率就越低:反之,游戏包体越小,游戏用户的下载转化率就越高(如下图),所有的手机app.游戏在大版本更新 ...

  8. app后端设计(0)--总目录(转)

    原文:http://blog.csdn.net/newjueqi/article/details/19003775 做了接近两年app相关的系统架构,api设计,先后在两个创业公司中工作,经历过手机网 ...

  9. 解决线上Tomcat启动慢

    vim /application/jdk/jre/lib/security/java.security # securerandom.source=file:/dev/randomsecurerand ...

  10. java7(1)——反编译深入理解增强的switch(读字节命令实战)

    [本文介绍] 本文主要讲java_7 的改进switch的底层实现.反编译一个使用带String的switch的demo并一步步解析反编译出来的字节命令,从编译的角度解读switch的底层实现. [正 ...