let的作用是声明变量,和var差不多。

let是ES6提出的,在了解let之前,最好先熟悉var的原理。

JavaScript有一个机制叫“预解析”,也可以叫“提升(Hoisting)机制”。很多刚接触JavaScript的人都会被这个机制弄混。比如:

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

在预编译阶段,JavaScript引擎会将上面的a函数修改成下面的写法:

var a;  //声明且初始化为undefined
console.log(a);
a=2;

我们把上面的 var 变成 let ;

// let 的情况
console.log(a); // 报错ReferenceError
let a = 2;
//相当于在第一行先声明bar但没有初始化,直到赋值时才初始化

由此我们得出:

变量提升现象:浏览器在运行代码之前会进行预解析,首先解析函数声明,定义变量,解析完之后再对函数、变量进行运行、赋值等。

-不论var声明的变量处于当前作用域的第几行,都会提升到作用域的头部。 
-var 声明的变量会被提升到作用域的顶部并初始化为undefined,而let声明的变量在作用域的顶部未被初始化

在ES6中对块级作用域做了进一步强化,从而使变量在生命周期内能被更好的控制。

块级声明用于声明在指定块的作用域之外无法访问的变量。
“块级作用域”也可以称为“词法作用域”。

  • 块级作用域存在于
  • 函数内部

块中(字符 { 和 } 之间的区域)
比如 if 和 for 在ES6中也被定义成一个块级。

let声明的用法与var相同,用let代替var来声明变量,就可以把变量的作用域限制在当前代码块中。

而且let声明不会被提升(在预解析的过程中,不会把声明变量放在所有代码的之前),因此开发者通常会将let声明语句放在封闭代码块的顶部,以便整个代码块都可以访问。

var a = 123;
if (true) {
a = 456; // ReferenceError
let a;
}
console.log(a); //输出值为123,全局 a 与局部 a 不影响

上面这小段代码,先声明全局变量 a = 123; 按照我们以往的思维,如果if 判断语句中没有 let a;则最后会输出 456;但是if 判断语句作为块作用域,内部在未声明变量的时候直接给 a 赋值为 456;因此会报错。

由此可见,用let来声明变量比var更严紧。

let的另一个特性是禁止在同一个作用域下重复声明。

var a = 10;
let a = 20;
// 抛出语法错误
// Uncaught SyntaxError: Identifier 'a' has already been declared
// 很直接的告诉开发者变量a已经被定义过了。

不管之前用var还是let声明,只要后面再重复声明同一个变量,都会报错。

let a = 10;
var a = 20;
// 也会报错

上面是的报错是因为在同一个作用域下,用let重复声明。

熟悉JavaScript的开发者都知道,var是可以重复声明的,而后面声明的操作会覆盖前面的声明。

如果不在同一个作用域下,是可以用let来重复声明相同名的变量。

let a = 30;
if (true) {
let a = 40;
console.log(a);
// 输出40
}
console.log(a); // 输出30

同时 let 还有一个功能是防止变量泄露,

用var声明

for (var i=0; i<10; i++) {}
console.log(i);
// 输出 10

用let声明

for (let i=0; i<10; i++) {}
console.log(i);
// 抛出错误:Uncaught ReferenceError: i is not defined

最后我们总结出:let

  • 在同一个作用域下,不可以被重复声明
  • 可以重新赋值
  • 可以防止变量泄露

接下来我们看一个 var 和 let 的实战练习:

我们先看一个正常的for循环,普通函数里面有一个for循环,for循环结束后最终返回结果数组

function foo(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = i;
}
return arr;
}
console.log(foo())
//输出结果为 [0,1,2,3,4]

有时我们需要在for循环里面添加一个匿名函数来实现更多功能,看下面代码

//循环里面包含闭包函数
function foo(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = function(){
return i;
}
}
return arr;
}
console.log(foo()); //执行5次匿名函数本身 --> [ [Function], [Function], [Function], [Function], [Function] ]
console.log(foo()[1]);     //执行第2个匿名函数本身 --> [Function]
console.log(foo().length); //最终返回的是一个数组,数组的长度为5 --> 5
console.log(foo()[0]()); //数组中的第一个数返回的是5 --> 5

上面这段代码就形成了一个闭包:

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。

在for循环里面的匿名函数执行 return i 语句的时候,由于匿名函数里面没有i这个变量,所以这个i他要从父级函数中寻找i,而父级函数中的i在for循环中,当找到这个i的时候,是for循环完毕的i,也就是5,所以这个 foo 得到的是一个数组[5,5,5,5,5]。

那我们怎么才能输出 [1,2,3,4,5],在上述中我们说了在闭包内部函数调用 i 时没有找到转而向上层父级去找,那我们在调用内部函数时将值传给内部函数不就可以不需要去父级找了,在 JavaScript 函数中有有匿名函数的自我执行,即 在函数体外面加一对圆括号,形成一个表达式,在圆括号后面再加一个圆括号,里面可传入参数。

(function(){
console.log(123);
})();
//输出 123
var a = 123;
(function(b){
console.log(b);
})(a);
//输出 123

根据上面的执行结果我们可以将目标函数改造如下:

function foo(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = (function(num){
return num;
})(i);
}
return arr;
}
console.log(foo()); // [ 0, 1, 2, 3, 4 ]
console.log(foo()[1]); // 1
console.log(foo().length); // 5
console.log(foo()[0]); // 0

这样在每次调用 foo 的时候内部的匿名函数都会自我执行,并且将 i 传入匿名函数进行返回。

我们现在来看下面的代码:

function foo(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = function(){
return i;
}
}
return arr;
}
console.log(foo()[0]()); // 5
function foo(){
var arr = [];
for(let i=0;i<5;i++){
arr[i] = function(){
return i;
}
}
return arr;
}
console.log(foo()[0]()); // 0

在上面的代码中,我们分别用 var 和 let 来声明 i 的值,获得的结果却是不一样的,当为 var 时我们在上面的时候已经解释过了,但是为什么当 var 换为 let 之后会变呢,只是由于使用 let 声明块级变量,这样每次循环时就会在自己的作用域内找 i 的变量,而不是去全局找 i 的变量。

我们再来看一道面试时会经常问到的题目:

for(var i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
},1000);
} // 5,5,5,5,5 for(let i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
},1000);
} // 0,1,2,3,4

在setTimeout的时候,匿名函数function(){console.log(i);}会被声明创建,当匿名函数执行的时候,会查找当前运行环境的 i 的值。
var声明的 i ,运行环境的 i 的值为5,但是let声明的 i,运行环境中 i 的值是每一个循环创建匿名函数时候的 i。
所以得到了0-4的值。

let替换var,可以很好的解决闭包的问题。

javascript ES6 新特性之 let的更多相关文章

  1. javascript ES6 新特性之 扩展运算符 三个点 ...

    对于 ES6 新特性中的 ... 可以简单的理解为下面一句话就可以了: 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中. 作用类似于 Object.assign() ...

  2. JavaScript ES6 新特性详解

    JavaScript ES6 带来了新的语法和新的强大功能,使您的代码更现代,更易读 const ,  let and var 的区别: const , let 是 ES6 中用于声明变量的新关键字. ...

  3. JavaScript ES6新特性介绍

    介绍 ES6:ECMScript6 首先,一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系? ECMAScript是一个国际通过的标准化脚本语言: JavaScript ...

  4. javascript ES6 新特性之 Promise,ES7 async / await

    es6 一经推出,Promise 就一直被大家所关注.那么,为什么 Promise 会被大家这样关注呢?答案很简单,Promise 优化了回调函数的用法,让原本需要纵向一层一层嵌套的回调函数实现了横向 ...

  5. javascript ES6 新特性之 class

    在之前的文章中我们讲过原型,原型链和原型链继承的文章,在 ES6 中为我们提供了更为方便的 class,我们先来看一下下面的例子: function Person(name) { //构造函数里面的方 ...

  6. javascript ES6 新特性之 解构

    解构的作用是可以快速取得数组或对象当中的元素或属性,而无需使用arr[x]或者obj[key]等传统方式进行赋值 var arr = [1, 2, 3]; //传统方式 var a = arr[0], ...

  7. ES6新特性概览

    本文基于lukehoban/es6features ,同时参考了大量博客资料,具体见文末引用. ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony( ...

  8. Atitit js版本es5 es6新特性

    Atitit js版本es5 es6新特性 Es5( es5 其实就是adobe action script的标准化)1 es6新特性1 Es5( es5 其实就是adobe action scrip ...

  9. ES6新特性:Proxy代理器

    ES6新特性:Proxy: 要使用的话, 直接在浏览器中执行即可, node和babel目前还没有Proxy的polyfill;,要使用的话,直接在浏览器中运行就好了, 浏览器的兼容性为:chrome ...

随机推荐

  1. css居中,margin_and_position

    首先父元素肯定是要相对定位的,其次我们上下左右居中的元素的css如下: width: 50px; height: 50px; margin: auto; position: absolute; lef ...

  2. jdk1.8安装

    jdk1.8:链接: https://pan.baidu.com/s/1Orv7Rjz0jkprcdoRSFWiPw 提取码: rn73 设置jdk和jre文件位置 配置环境变量: 系统变量中jdk ...

  3. php的运行机制

    php的解析过程是 apache -> httpd -> php5_module -> sapi -> php cgi (外部应用程序)只是用来解析php代码的 sapi中的其 ...

  4. Logstash 6.4.3 导入 csv 数据到 ElasticSearch 6.4.3

    本文实践最新版的Logstash从csv文件导入数据到ElasticSearch. 本文目录: 1.初始化ES.Kibana.Logstash 2.安装logstash文件导入.过滤器等插件 3.配置 ...

  5. BZOJ2681 : 玩游戏2

    首先若存在多个连通块,那么答案显然是$+\infty$. 否则以$m$为根,每棵子树的根节点都最多只能放一个金币,且这些子树之间互不干扰. 对于一棵父亲为$m$的子树,最优方案下一定可以将子树剖分成若 ...

  6. sql server 关于日期格式转换查询备注

    select GETDATE()--2016-01-08 16:15:05.787select convert(varchar,getdate(),11)--16/01/08select conver ...

  7. Android Gradle Task-中文

    任务可以从根项目运行 Android 任务 androidDependencies-显示项目的Android依赖项 signingReport-显示基础和测试模块的签名信息 sourceSets-打印 ...

  8. Shell 脚本处理用户输入

    传递参数 跟踪参数 移动变量 处理选项 将选项标准化 获得用户的输入 bash shell提供了一些不同的方法来从用户处获取数据,包括命令行参数(添加在命令后数据),命令行选项(可以修改命令行为的单个 ...

  9. JavaScript经典作用域问题(转载)

    题目 var a = 10; function test(){ a = 100; console.log(a); console.log(this.a); var a; console.log(a); ...

  10. Linux系统下如何运行.sh文件

    在Linux系统下运行.sh文件有两种方法,比如我在root目录下有个datelog.sh文件 第一种(这种办法需要用chmod使得文件具备执行条件(x): chmod u+x datelog.sh) ...