ES6入门一:块级作用域(let&const)、spread展开、rest收集

时间:2021-12-12 00:30:39
  • 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&period;24 ES6浅谈--块级作用域,let

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

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

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

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

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

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

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

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

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

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

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

  7. ES6(块级作用域)

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

  8. ES6——块级作用域

    前面的话 过去,javascript缺乏块级作用域,var声明时的声明提升.属性变量等行为让人困惑.ES6的新语法可以帮助我们更好地控制作用域.本文将详细介绍ES6新引入的块级作用域绑定机制.let和 ...

  9. ES6里关于作用域的拓展:块级作用域

    过去,javascript缺乏块级作用域,var声明时的声明提升.属性变量等行为让人困惑.ES6的新语法可以帮助我们更好地控制作用域. 一.var声明 1.变量提升:var声明会发生“变量提升”现象, ...

随机推荐

  1. jquery实现更多内容效果

    体验效果:http://hovertree.com/texiao/jquery/33/ 写个“更多内容的展开/收起”的js 代码如下: <!DOCTYPE html> <html&g ...

  2. iOS修改TextField占位符颜色大小

    UITextField *addCtrolField = [[UITextField alloc]initWithFrame:CGRectMake(CGRectGetMaxX(rightTitleLa ...

  3. sklearn基础知识-准备阶段

    6.标签特征二元化 处理分类变量还有另一种方法,不需要通过OneHotEncoder,我们可以用LabelBinarizer. 这是一个阈值与分类变量组合的方法. In [1]: from sklea ...

  4. slf4j-api-1&period;7&period;5日志打印实验

    下面一段话来自:百度百科 假设你开发的是类库或者嵌入式组件,那么就应该考虑採用SLF4J,由于不可能影响终于用户选择哪种日志系统.在还有一方面,假设是一个简单或者独立的应用,确定仅仅有一种日志系统,那 ...

  5. C&num; sliverlight调用WCF服务出现的一个错误

    错误提示如下: 尝试向 URI“http://localhost:8396/Service1.svc”发出请求时出错.这可能是由于试图以跨域方式访问服务而又没有正确的跨域策略,或策略不适用于 SOAP ...

  6. 笔记3 linux 多线程 条件变量&plus;互斥锁

    //cond lock #include<stdio.h> #include<unistd.h> #include<pthread.h> struct test { ...

  7. px妙转rem

    px:像素,相对长度单位,相对于显示器屏幕的分辨率而言(其实我个人认为可以理解为固定单位): rem:这是个web前端中的新成员,是CSS3中新增的一个相对单位.相对的只是html根元素: 1.设定两 ...

  8. 当padding&sol;margin的取值形式为百分比时。。。。。

    一个基础却又容易混淆的css知识点:当margin/padding取形式为百分比的值时,无论是left/right,还是top/bottom,都是以父元素的width为参照物的!也许你会说,left/ ...

  9. C&sol;JS&lowbar;二分法查找

    1. 二分法查找 前提: 数据是排好序的. 题设:给出一个有序arr,从中找出key,arr的区间是array[ low , higt]. 步骤: (1)mid=(low+high)/2 (2)arr ...

  10. scrapy-redis爬取豆瓣电影短评,使用词云wordcloud展示

    1.数据是使用scrapy-redis爬取的,存放在redis里面,爬取的是最近大热电影<海王> 2.使用了jieba中文分词解析库 3.使用了停用词stopwords,过滤掉一些无意义的 ...