• let声明
  • const声明
  • 块级作用域
  • spread/rest

一、let声明与块作用域

在ES6之前,JavaScript中的作用域基本单元就是function。现在有了let就可以创建任意块的声明,也被称为作用域块。这意味者只需要“{}”就可以创建一个作用域。不再像var声明变量那样总归属于包含函数或全局。例如:

 var a = 2;
{
let a = 3
console.log(a); //
}
console.log(a); //

在相关的资料中看到有介绍,let声明在被正式列入标准之前长这样:

 //这种写法不合法,可以理解它为let语法的前身
let (a = 2, b, c){
//...
}

这种写法从语法设计角度来说被称为显示声明,也就是说声明一个作用域块,而现在{let a = 1;}这种写法被称为隐式声明,这种声明被称为作用域劫持。我想选择隐式的声明方式不是没有原因的,比如let在for中的应用:

 var func = [];
for(let a = 0; a < 5; a++){
func.push(function(){
console.log(a);
});
}
func[3](); //3 (如果for内使用var声明a,打印结果会是5)

通过上面for的示例可以看到let在每次循环时都会劫持一个作用域,每次都会有一个独立的a变量。而不是像以前var会把变量提升到for所在的作用域,循环内使用公共的变量。这种块级作用域如果采用“let(){}”显示声明好像就无法处理了,当然形式不是重点,而是需要说明let会将它所在的块作为独立的一层作用域。但是在if条件语句中let的作用域劫持依然成立,但是if条件语句内产生的块级作用域内部出现的方法,会被if条件所在的作用域引用:

 let something  ;//  true | false
if(something){
let a = 10;
function foo(){
console.log(a);
}
}else{
let a = 20;
function foo(){
console.log(a);
}
}
foo(); //这里会打印 10 或者 20
console.log(a); //这里报错,说明let会劫持条件语句的块,但foo执行正确只能说明foo的被if条件所在的作用域引用

剩下的while和do...while这些块都可以被let劫持,于for循环没有差异,就不逐个展示了。但是,还有一个重要的内容需要注意,有了作用域就必然会有作用域提升编译相关的底层逻辑值得关注。

let块及作用域的编译执行逻辑:

{
console.log(b); //报错:无法在初始化之前访问'b'
let b = 20;
}

这个异常提示说明let声明依然会提升,但是在没有赋值的情况下不会像var那样返回默认值undefined,而是不能访问。也就是说,let声明的变量必须在赋值后使用,没有默认值undefined。

通常也把这种异常称为“临时死区”。然后,到这里让我想到了上一篇的严格模式,如果在let劫持的作用域中出现了不严格的代码书写,会产生什么样的编译逻辑呢?

 console.log(a); //a is not defined
{
console.log(a); //a is not defined
a = 10;
let b = 20;
console.log(a); //
}
console.log(a); //

显然,在let劫持的作用域下出现了未声明的变量赋值,会被默认处理成var声明,但这要发生在赋值时,这个原因是let虽然劫持作用域但是依然保持作用域链的特性,对当前作用域找不到的变量会向上层作用域遍历,直到全局作用域,如果没有找到还是会在全局作用域声明赋值。

最后还有一个Function在块级作用域中的编译:

 {
fun(); //
let a = 10;
function fun(){
console.log(20); //在这段代码中这里不能是console.log(a),不然第二行代码会在执行时报错
}
}

在这个块级代码中的方法fun执行说明了方法在块级作用域中依然会被提升。但是需要注意,如果在fun中如果时打印a的值,以上的代码就会报错,因为出现了临时死区。除了以上的语法和编译值得我们探究,想更深入的了解let的实现原理,可以对比ES5的编译代码:

let声明的底层实现

let本质上起始就是块级作用域和var声明的严格模式,这里用上面if条件的示例来说明:

 //ES6语法
{
let something = true;
if(something){
let a = 10;
function foo(){
console.log(a);
}
}else{
let a = 20;
function foo(){
console.log(a);
}
}
foo();
}
//ES5编译结果
"use strict";
{
var something = true; if (something) {
var _foo = function _foo() {
console.log(a);
}; var a = 10;
} else {
var _foo2 = function _foo2() {
console.log(_a);
}; var _a = 20;
} foo();
}

这里可以可以思考以下for循环的let作用域劫持怎么编译成ES5的代码(还是使用前面for的示例):

 //ES6的语法
var func = [];
for(let a = 0; a < 5; a++){
func.push(function(){
console.log(a);
});
}
func[3]();
//ES5的编译结果
"use strict"; var func = []; var _loop = function _loop(a) {
func.push(function () {
console.log(a);
});
}; for (var a = 0; a < 5; a++) {
_loop(a);
} func[3]();

二、const声明与块作用域

ES6中除了let可以实现块级作用域声明,还有const声明也可以实现块级作用域声明。const是用来声明常量的,也就是说声明必须有显式的初始化。如果需要undefined的常量,也是必须赋值undefined。

 //ES6声明常量
{
const a = 10;
a = 20; //这里报错(看ES5中如何实现这种报错提示)
}
//ES5编译
"use strict";
function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }
{
var a = 10;
a = (_readOnlyError("a"), 20);
}

const声明常量的值不可以改变,是指不可以改变参数指向的栈内存的地址,如果时声明的常量时引用值类型的数据,可以改变对象的内部属性和方法。

 //ES6语法声明引用值类型常量
{
const obj = {
a:10,
b:function(){}
}
obj.a = [1,2,3];
obj.b = 40;
obj = {}
}
//ES5编译结果
"use strict";
function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); } {
var obj = {
a: 10,
b: function b() {}
};
obj.a = [1, 2, 3];
obj.b = 40;
obj = (_readOnlyError("obj"), {});
}

最后,在这里添加一个与let和const无关的块级作用域问题,函数在独立的{}中也存在块级作用域概念(也可以说时严格模式下的函数表达式赋值),前面的if块级中起始已经有相关内容,函数的块级作用域只是作用在函数初始化在被执行执行时,在块级作用域内,函数的名称会以变量名的方式被提升到块级作用域外部,当块级作用域被执行过后才会真正挂载到外部的作用域对应的变量名称上,看下面这个编译结果就明白了:

 //ES6块级作用域下的函数
{
let a = 10;
function fun(){console.log(a)}
}
fun();
//ES5编译结果
"use strict";
{
var _fun = function _fun() {//_fun变量提升,执行时挂载函数
console.log(a);
};
var a = 10;
}
fun();

三、spread展开与rest收集

...展开&收集运算符

  • 写:function test(...arg){}; test(1,2,3)收集作用
  • 读:var arg = [1,2,3];console.log(...arg);展开作用

作用:简化书写长度,提升开发效率

ES6/ES7:ES6可以处理数组,ES7可以处理对象。

3.1应用展开替代apply:

 function foo(x,y,z){
console.log(x,y,z);
}
foo(...[1,2,3]);
//...的展开语法实际编译成ES5就是apply的应用,展开语法执行效率优于apply
"use strict";
function foo(x, y, z) {
console.log(x, y, z);
}
foo.apply(void 0, [1, 2, 3]);

3.2应用展开向数组指定位置合并元素(替代concat):

 var a = [1,2,3];
var b = [0,...a,4,5];
//ES5的编译结果
"use strict";
var a = [1, 2, 3];
var b = [0].concat(a, [4, 5]);

3.3应用收集解决实参与形参不对称问题:

 function foo(a, b, ...c){
console.log(a,b,c);
}
foo(1,2,3,4,5); //1 2 [3,4,5]
//ES5的编译结果
"use strict";
function foo(a, b) {
for (var _len = arguments.length, c = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
c[_key - 2] = arguments[_key];
}
console.log(a, b, c);
}
foo(1, 2, 3, 4, 5); //1 2 [3,4,5]

3.4ES7中的...展开收集运算符实现对象属性浅层克隆:

 var obj1 = {
a:"a",
b:"b",
c:[1,2,3]
}
var obj2 = {
e:"e",
f:{
name:"F_NAME"
}
}
var obj = {
...obj1,
...obj2
}
console.log(obj); //{a: "a", b: "b", c: Array(3), e: "e", f: {…}}
obj.f.name = "f";
console.log(obj2.f.name); //f

如果需要实现深克隆的话可以将子级拆分出来,然后分别展开收集:

 var c = [1,2,3]
var obj1 = {
a:"a",
b:"b",
c:[...c]
}
var f = {name : "F_NAME"}
var obj2 = {
e:"e",
f:{
...f
}
}
var obj = {
...obj1,
...obj2,
c:[...c],
f:{...f}
}
// console.log(obj);
obj.f.name = "f";
console.log(obj2.f.name); //F_NAME

其本质上就是采用了重新赋值的方式解决深克隆的需求。但是这种解决方案显然不适合层级较多的对象来实现,还可以使用JSON对象的方法来解决这类深克隆的问题:

 var obj1 = {
a:"a",
b:"b",
c:[1,2,3]
}
var obj2 = {
e:"e",
f:{
name:"F_NAME"
}
}
var obj3 = {
...obj1,
...obj2
}
var obj = JSON.parse(JSON.stringify(obj3))
console.log(obj);
obj.f.name = "f";
console.log(obj2.f.name); //F_NAME

采用JSON对象方法的这种深度克隆解决方案需要注意的是在方法中不能出现Function类型的属性,如果有这种情况,就只能采用递归的方式实现深度克隆了。

ES6入门一:块级作用域(let&const)、spread展开、rest收集的更多相关文章

  1. 12.24 ES6浅谈--块级作用域,let

    第一部分:ES6新增了块级作用域,let关键字用于声明变量,相较于var而言,let关键字不存在声明提前. 1.ES6真正的出现了块级作用域,使用双花括号括住并在其中用let声明变量,会存在暂时性死区 ...

  2. ES6 - Note1:块级作用域与常量

    在ES6以前,ES不支持块级作用域,只有全局作用域和函数作用域,所有变量的声明都存在变量声明提升. 1.let 关键字 声明一个块级变量,只在一个代码块中有效,如果在块外面访问便会报错,如下所示: { ...

  3. 《深入理解ES6》笔记——块级作用域绑定(1)

    本章涉及3个知识点,var.let.const,现在让我们了解3个关键字的特性和使用方法. var JavaScript中,我们通常说的作用域是函数作用域,使用var声明的变量,无论是在代码的哪个地方 ...

  4. 深入理解ES6之《块级作用域绑定》

    众所周知,js中的var声明存在变量提升机制,因此ESMAScript 6引用了块级作用域来强化对变量生命周期的控制let const 声明不会被提升,有几个需要注意的点1.不能被重复声明 假设作用域 ...

  5. es6中添加块级作用域的目的

    原本只有函数作用域和全局作用域两种,这就导致出现很多不方便的地方: 1)for循环问题:在看js高程的时候,纠结在第七章好久,就是一个这样的实例 function createFunctions(){ ...

  6. ES6的 let const 以及块级作用域

    let声明变量 用法类似于var,但是所声明的变量只在let所在的代码块内有效. 1 . 在ES6环境下,let声明的变量不能在声明之前调用. 例: console.log(i); //会报错,这叫做 ...

  7. ECMAScript6 入门教程 初学记录let命令 块级作用域

    一.基本语法-let命令 (1)ES6新增了let命令,用来声明变量.所声明的变量,只在let命令所在的代码块内有效. 循环的计数器,就很合适使用let命令.计数器i只在for循环体内有效,在循环体外 ...

  8. ES6-let、const和块级作用域

    1.介绍 总的来说,ES6是在ES2015的基础上改变了一些书写方式,开放了更多API,这样做的目的最终还是为了贴合实际开发的需要.如果说一门编程语言的诞生是天才的构思和实现,那它的发展无疑就是不断填 ...

  9. ES6(块级作用域)

    我们都知道在javascript里是没有块级作用域的,而ES6添加了块级作用域,块级作用域能带来什么好处呢?为什么会添加这个功能呢?那就得了解ES5没有块级作用域时出现了哪些问题. ES5在没有块级作 ...

随机推荐

  1. 发布机制-灰度发布-例子:Gmail Labs

    ylbtech-发布机制-灰度发布-例子:Gmail Labs Gmail Labs是一个新特性橱窗,用户可以自己选择一些未正式发布的新特性进行体验,不喜欢可以关闭,在这个过程中,吃了螃蟹,也当了Go ...

  2. OSI 的七层模型

    一.概念 概念:开放系统互联参考模型,是由 ISO(国际标准化组织)定义的.目的:规范不同系统的互联标准,使两个不同的系统能够较容易的通讯. 网络刚面世时,通常只有同一家厂商的计算机才能彼此通讯.OS ...

  3. spring boot系列(七)spring boot 使用mongodb

    1 pom.xml配置 增加包依赖:spring-boot-starter-data-mongodb <dependency> <groupId>org.springframe ...

  4. jack反序列化自定义字段绑定,报错:can only instantiate non-static inner class by using default, no-argument constructor

    package com.xxx; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lo ...

  5. spring mvc 最详细文档,前无古人后无来者 掉渣天~

    一.SpringMVC基础入门,创建一个HelloWorld程序 1.首先,导入SpringMVC需要的jar包. 2.添加Web.xml配置文件中关于SpringMVC的配置 <!--conf ...

  6. JMeter—压力测试&性能测试工具

    安装 下载 官方网站下载最新版本: http://jmeter.apache.org/download_jmeter.cgi,使用JMeter依赖jdk,建议安装jdk 1.6版本以上. 环境变量配置 ...

  7. ES6中Set和Map

    1.Set 实例的创建 Set实例它类似于数组,但是成员的值都是唯一的,没有重复的值. Set本身是一个构造函数用来生成Set数据结构. Set 函数可以接受一个数组(或者具有 iterable 接口 ...

  8. js-array自增长方式

    function a(){ var colors = ["red","blue","green"]; colors[colors.lengt ...

  9. Spring+SpringMvc+Hibernate整合记录

    Spring+SpringMvc+Hibernate+Maven整合各大配置文件记录 1.Idea新建的Maven架构 2.Maven的对象模型的内容 <project xmlns=" ...

  10. hdoj6446(树形DP)

    题目链接:https://vjudge.net/problem/HDU-6446 题意:简化题意后就是求距离和的2*(n-1)!倍. 思路: 简单的树形dp,通过求每条边的贡献计算距离和,边(u,v) ...