let

众所周知,在ES6之前,声明变量的关键字就只有var。var 声明变量要么是全局的,要么是函数级的,而无法是块级的。

var a=1;
console.log(a); //
console.log(window.a); //

function test(){
  var b=2;
  function print(){
    console.log(a,b);
  }
print();
}
test(); //1 2
console.log(b); //Uncaught ReferenceError: b is not defined

for(var i=0;i<=10;i++){
var sum=0;
sum+=i;
}
console.log(i); //
console.log(sum); //10 声明在for循环内部的i和sum,跳出for循环一样可以使用。

再来看看下面这个栗子:

HTML:
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
JS:
window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(i);
};
}

这是一道很经典的笔试题,也是很多初学者经常犯错而且找不到原因的一段代码。想要实现的效果是点击不同的<li>标签,alert出其对应的索引值,但是实际上代码运行之后,我们会发现不管点击哪一个<li>标签,alert出的i都为4。因为在执行for循环之后,i的值已经变成了4,等到点击<li>标签时,alert的i值是4。在ES6之前,大部分人会选择使用闭包来解决这个问题,今天我们使用ES6提供的let来解决这个问题。接下来就看看let的神奇吧。

window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (let i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(i);
}
};
}

有看出什么区别吗?奥秘就在for循环中var i=0变成了let i=0,我们仅仅只改了一个关键字就解决了这个问题,还避免了使用闭包可能造成的内存泄漏等问题。

上述代码中的for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中, 事实上它将其重新绑定到了循环的每一个迭代中, 确保使用上一个循环迭代结束时的值重新进行赋值。

后面就让我们好好来了解一下let这个神奇的关键字吧。

let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。换句话说,let为其声明的变量隐式地了所在的块作用域。  ----《你所不知道的JavaScript(上)》P32

上述代码,可以通过另一种方式来说明每次迭代时进行重新绑定的行为:

window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (let i=0;i<aLi.length;i++){
let j = i;
aLi[j].onclick = function(){
alert(j);
}
};
}

在这里还有个点要说明的,就是 for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。

这就很好理解上面这段代码的意思了。每次循环体执行的时候,let声明的变量 j 会从父作用域(循环语句块)取值保存到自己的块级作用域内,由于块级作用域内的变量不受外部干扰,所以每次循环体生成的块级作用域相互独立,各自保存着各自的 j 值。


来看一下 let 和 var 的一些异同吧。

1、函数作用域 vs 块级作用域

function varTest() {
var x = 31;
if (true) {
var x = 71; // same variable!
console.log(x); //
}
console.log(x); //
} function letTest() {
let x = 31;
if (true) {
let x = 71; // different variable
console.log(x); //
}
console.log(x); //
}

可以看出在letTest函数的 if 判断中重新声明的x并不会影响到 if 代码块之外的代码,而varTest函数中用var声明的却会。这是因为let声明的变量只在代码块(通常是{ }所形成的代码块)中有效。

2、变量提升 vs 暂时性死区

我们都知道,var声明的变量会有变量提升的作用,如下

console.log(a);  //
var a=1; console.log(b); //undefined
var b;

可以看出,虽然代码中console调用a在前,声明a在后,但是由于在js中,函数及变量的声明都将被提升到函数的最顶部,也就是说(var声明的)变量可以先使用再声明。

然后,使用let,const(后面会提及)声明的变量却不存在变量提升。

console.log(foo); // Uncaught ReferenceError: foo is not defined
let foo = 2; console.log(foo1); // Uncaught ReferenceError: foo1 is not defined
let foo1;

ES6明确规定,如果区块中存在let命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。所以在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

注:“暂时性死区”也意味着typeof不再是一个百分之百安全的操作,因为会使typeof报错。

3、let不允许重复声明

if (true) {
let aa;
let aa; // Uncaught SyntaxError: Identifier 'aa' has already been declared
} if (true) {
var _aa;
let _aa; // Uncaught SyntaxError: Identifier '_aa' has already been declared
} if (true) {
let aa_;
var aa_; // Uncaught SyntaxError: Identifier 'aa_' has already been declared
}

let不允许在相同作用域内,重复声明同一个变量。

4、全局变量 vs 全局对象的属性

ES5中全局对象的属性与全局变量基本是等价的,但是也有区别,比如通过var声明的全局变量不能使用delete从 window/global ( global是针对与node环境)上删除,不过在变量的访问上基本等价。

ES6 中做了严格的区分,使用 var 和 function 声明的全局变量依旧作为全局对象的属性,使用 letconst 命令声明的全局变量不属于全局对象的属性。

let let_test = 'test';
console.log(window.let_test); // undefined
console.log(this.let_test); // undefined var var_test = 'test';
console.log(window.var_test); // test
console.log(this.var_test); // test

const

除了let以外,ES6还引入了const,同样可以用来创建块作用域变量,但其值是固定的(常量)。使用const声明变量的时候,必须同时赋值,否则会报错。并且之后任何试图修改值的操作都会引起错误.

const data;  //Uncaught SyntaxError: Missing initializer in const declaration
if (true) {
var a = 2;
const b = 3; // 包含在 if 中的块作用域常量
a = 3; // 正常 !
b = 4; // Uncaught TypeError: Assignment to constant variable.
}
console.log( a ); //
console.log( b ); // Uncaught ReferenceError: b is not defined

注:复合类型const变量保存的是引用。因为复合类型的常量不指向数据,而是指向数据(heap)所在的地址(stack),所以通过 const 声明的复合类型只能保证其地址引用不变,但不能保证其数据不变。

const arr= [1, 2];

// 修改数据而不修改引用地址,正确执行
arr.push(3); // [1, 2, 3] // 修改 arr 常量所保存的地址的值,报错
arr = []; // Uncaught TypeError: Assignment to constant variable.

简单的使用const无法完成对象的冻结。可以通过Object.freeze()方法实现对对象的冻结。使用Object.freeze()方法返回的对象将不能对其属性进行配置(definedProperty()不可用)同时不能添加新的属性和移除(remove)已有属性。彻底冻结对象时需要递归的对它的对象属性进行冻结。

let obj = {
a: 1,
b: {
b1: 2
}
}; obj.b.b1 = 3;
console.log(obj.b.b1 ); // function freeze(obj){
Object.freeze(obj);
Object.values(obj).forEach(function (value,index) {
if(typeof value === 'object'){
freeze(value);
}
})
} freeze(obj); obj.b.b1 = 4;
console.log(obj.b.b1); //

总结:

块级作用域的出现,让广泛使用的 IIFE (立即执行匿名函数)不再必要。

// 匿名函数写法
(function () {
var jQuery = function() {};
// ...
window.$ = jQuery
})(); // 块级作用域写法
{
let jQuery = function() {};
// ...
window.$ = jQuery;
}

附:在ES6之前,关键字with和关键字try/catch都会创建相关的块级作用域。关键字with已经不推荐使用了,我们在这里就不多描述。在ES3规范中规定try/catch的catch分句会创建一个块作用域,其中声明的变量仅在catch内部有效。

try {
undefined(); // 执行一个非法操作来强制制造一个异常
}
catch (err) {
console.log( err ); // 能够正常执行!
}
console.log( err ); // Uncaught ReferenceError: err is not defined

let和const----你所不知道的JavaScript系列(2)的更多相关文章

  1. js值----你所不知道的JavaScript系列(6)

    1.数组 在 JavaScript 中,数组可以容纳任何类型的值,可以是字符串.数字.对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的) .----<你所不知道的JavaS ...

  2. js类型----你所不知道的JavaScript系列(5)

    ECMAScirpt 变量有两种不同的数据类型:基本类型,引用类型.也有其他的叫法,比如原始类型和对象类型等. 1.内置类型 JavaScript 有七种内置类型: • 空值(null) • 未定义( ...

  3. 闭包----你所不知道的JavaScript系列(4)

    一.闭包是什么? · 闭包就是可以使得函数外部的对象能够获取函数内部的信息. · 闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. · 闭包就 ...

  4. 提升----你所不知道的JavaScript系列(3)

    很多编程语言在执行的时候都是自上而下执行,但实际上这种想法在JavaScript中并不完全正确, 有一种特殊情况会导致这个假设是错误的.来看看下面的代码, a = 2; var a; console. ...

  5. LHS 和 RHS----你所不知道的JavaScript系列(1)

      变量的赋值操作会执行两个动作, 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后在运行时引擎会在作用域中查找该变量, 如果能够找到就会对它赋值.----<你所不知道的Ja ...

  6. 你所不知道的JavaScript数组

    相信每一个 javascript 学习者,都会去了解 JS 的各种基本数据类型,数组就是数据的组合,这是一个很基本也十分简单的概念,他的内容没多少,学好它也不是件难事情.但是本文着重要介绍的并不是我们 ...

  7. 你所不知道的javascript数组特性

    工作中,我们经常使用js的数组,但是,下面的东西你见过吗? 1,文本下标: var a=[]; a[-1]=1; 你想过数组的下标为负数的情况吗?我们对数组的下标规定从0开始.但是上面那么写也还是可以 ...

  8. JavaScript中你所不知道的Object(二)--Function篇

    上一篇(JavaScript中你所不知道的Object(一))说到,Object对象有大量的内部属性,而其中多数和外部属性的操作有关.最后留了个悬念,就是Boolean.Date.Number.Str ...

  9. 一些你所不知道的VS Code插件

    摘要: 你所不知道的系列. 原文:提高 JavaScript 开发效率的高级 VSCode 扩展之二! 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 作为一名业余爱好者.专业人员,甚 ...

随机推荐

  1. 单元测试,模拟用户Get登陆,并携带登录后的token访问接口

    HttpClient _httpClient; HttpClient _businessHttpClient; private async Task<string> GetAccessTo ...

  2. Python实例---模拟微信网页登录(day1)

    第一步:创建Django项目 创建Django项目,添加App 创建静态文件夹static 修改settings.py文件 1. 取消csrf注释 2. 添加静态文件路径 # 添加静态文件路径 STA ...

  3. JAVA 连接 SQL Server 2008:java.lang.ClassNotFoundException: com.microsoft.jdbc.sqlserver.SQLServerDriver

    新项目需要修改Java开发的MES系统...Java忘的也差不多了...简单尝试以下JAVA连接SQL Server吧,没想到坑还是很多的.以前直接连oracle时没有这么多麻烦啊....可能微软和o ...

  4. mysql数据库管理工具(navicat for mysql) 10.1.7 绿色中文版

    Navicat for MySQL:Navicat for MySQL 是一套专为 MySQL 设计的高性能数据库管理及开发工具.它可以用于任何版本 3.21 或以上的 MySQL 数据库服务器,并支 ...

  5. NoSQL——not onlySQL不仅仅是SQL

    数据有很大一部分是由关系数据库管理系统(RDBMS)来处理. 1970年 E.F.Codd's提出的关系模型的论文 "A relational model of data for large ...

  6. vue 文本比较插件

    npm install codemirror diff-match-patch diff-match-patch template: <div id="view">&l ...

  7. MySQL多实例.md

    MySQL5.7多实例配置 数据库实例1配置文件 # cat /etc/my.cnf [mysqld] datadir=/data/mysql port=3306 socket=/tmp/mysql. ...

  8. 【洛谷】【单调栈】P4333 [COI2007] Patrik

    --接上一篇题解,[洛谷][单调栈]P1823音乐会的等待 关于题目大意在上一篇题解里已经说清楚了,这里不再多阐述 想看题目->戳这里 [算法分析:] 在对元素a进行判断时,如果它与栈顶元素相等 ...

  9. 如何删除VS2015中的OpenCV的配置

    首先,在C盘--用户--AppData--Local--Microsoft--MSBuild--v4.0  路径下,找到  Microsoft.Cpp.Win32.user  文件,用记事本打开,如下 ...

  10. Java java.text.ParseException: Unparseable date

    用java将字符串转换成Date类型是,会出现java.text.ParseException: Unparseable date异常. 例如下面的这段代码就会出现上面的异常: public bool ...