ES6 | 青训营笔记

137 阅读13分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

ES6

声明与表达式

let和const

  1. let

    • 声明格式

      let 变量名1, 变量名2;
      let 变量名1, 变量名2 = 初始值1, 初始值2;
      
    • 特性

      1. 变量不能重复声明

      2. 块级作用域:变量只在 代码块{} 里面有效

      3. 不存在变量提升:不允许在变量声明之前使用变量

      4. 不影响作用域链:函数作用域中找不到的变量向上一级寻找

  2. const

    • 声明格式

      const 常量名 = 初始值;

    • 特性

      1. 一定要赋初始值

      2. 一般常量名设置为大写英文

      3. 常量值不能修改。但对于数组和对象的元素修改,不算对常量的修改,不会报错(常量所指向的地址没有改变)

      4. 块级作用域

  3. let & var & const 的区别

    • 变量提升

      1. var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined

      2. let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错

    • 块级作用域

      1. var不存在块级作用域

      2. let和const存在块级作用域

    • 重复声明

      1. var允许重复声明变量

      2. let和const在同一作用域不允许重复声明变量

    • 修改声明的变量

      1. var和let可以修改

      2. const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数组这种引用类型,内存地址不能修改,可以修改里面的值。

  4. 使用

    const > let > var

解构赋值

  1. 解构

    ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。

  2. 数组的解构

    数组名 = [值1, 值2, ...]
    
    let [V1, V2, ...] = 数组名;
    
    // ==解构赋值==> V1 = 值1; V2 = 值2; ...
    
  3. 对象的解构

    对象名 = {
        key1: value1;
        key2: value2;
        ...
    }
    let {V1, V2, ...} = 对象名;
    // ==解构赋值==> V1 = value1; V2 = value2; ... 
    

Symbol

  1. ES6引入了一种新的原始数据类型 Symbol ,表示独一无二的值,主要用来定义对象的唯一属性名。

  2. Symbol函数栈不能用new命令,因为Symbol是原始数据类型,不是对象。

  3. Symbol可以接受一个字符串作为参数,为新创建的Symbol提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。

  4. 相同参数Symbol("字符串参数")的返回值不相等。

内置对象

模板字符串

  1. 声明

    let 字符串名 = 字符串内容;

  2. 内容中可以直接出现换行符,但是 '' 和 "" 不可以

  3. 变量拼接 ... ${要拼接的变量名} ...

对象的简化写法

ES6允许直接写入变量和函数(在对象外部已定义),作为对象的属性和方法(与变量和函数同名)

对象名2 = {
    key1,
    key2,
    ...
    函数名(){
        函数体
	}
}

对象方法的扩展

  1. Object.is(v1, v2); 判断两个值是否相等
  2. Object.assign(obj1, obj2); 合并两个对象
  3. Object.setPrototypeOf(obj, Proto_Obj); 设置原型对象
  4. Object.getPrototypeOf(obj); 获取原型对象

Map & Set

  1. 集合Set()

    • 集合中的值都是唯一的,可用于去重。

    • 基本使用

      1. 声明集合
        let 集合名 = new Set();
        
      2. 元素个数
        集合名.size
        
      3. 添加元素
        集合名.add(要添加的元素);
        
      4. 删除元素
        集合名.delete(要删除的元素);
        
      5. 检测/查询元素是否存在
        集合名.has(要检测的元素);
        
      6. 清空集合
        集合名.clear();
        
    • 集合的应用

      1. 数组去重
        去重后的数组 = [...new Set(原数组)];
        
      2. 并集
        并集 = [...new Set(数组1)].filter(item => new Set(数组2).has(item));
        
      3. 交集
        交集 = [...new Set([...数组1,...数组2])];
        
      4. 差集
        差集 = [...new Set(数组1)].filter(item => !(new Set(数组2).has(item)));
        
  2. Map()

    • Map类似于Object,也是键值对的组合。不同的是,Map的键可以是任意数据类型,而Object的键只能是字符串。

    • 基本使用

      1. 声明Map
        let Map名 = new Map();
        
      2. 元素个数
        Map名.size
        
      3. 添加元素
        Map名.set(key, value);
        
      4. 删除元素
        Map名.delete(要删除的元素key);
        
      5. 获取元素
        Map名.get(要获取的元素key);
        
      6. 清空
        Map名.clear();
        
      7. 遍历
        for(let item of Map名) {
        	// item为数组,第一个元素为key,第二个元素为value
        }
        

运算符与语句

箭头函数 =>

  • 声明

    let 函数名 = (形参) => {
        函数体
    }
    
  • 简写

    1. 当形参只有一个时,可以省略小括号( )
    let 函数名 = 一个形参 => {
        函数体
    }
    
    1. 当函数体只有一条语句时,可以省略花括号{ },return语句必须省略,而且语句的执行结果就是函数的返回值
    let 函数名 = (形参) => 一条语句;
    
  • 调用

    函数名(实参);

  • 特点

    1. this是静态的:始终指向函数声明时所在作用域下的this值
    2. 箭头函数不能作为构造函数去实例化对象
    3. 不能使用arguments变量(arguments保存实参)
  • 适用场景

    1. 箭头函数适合与this无关的回调函数:定时器、数组的回调
    2. 箭头函数不适合与this有关的回调函数:事件回调、对象的方法

函数形参默认值

函数名(形参1, ... , 形参n = 默认值n, ... ) {
	函数体
}

带有默认值的形参一般放在最后,若调用函数时没有传递实参,则使用参数的默认值。

rest参数(剩余参数)

  • 为解决传入的参数数量不一定的问题,es6引入了rest参数,用来代替arguments。
  • 如果函数的最后一个命名参数以...为前缀,则它将成为一个由rest参数组成的真数组,rest参数必须放在参数最后。
  • 使用方法
函数名(形参1, 形参2, ... , ...theArgs) { }
函数名(实参1, 实参2, ... , 实参x, ... , 实参y);
// 则函数中的 theArgs = [实参x, ... , 实参y]
  • rest与arguments的区别
    1. rest不会为每个变量给一个单独的名称,arguments包含所有参数传递给函数
    2. arguments不是真数组,而是对象;rest参数是真实的数组实例,可以直接使用sort、map、forEach、pop等数组方法
    3. arguments有它自己额外的特性(例如callee属性)

扩展运算符...

  • 可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开

    对象中的扩展运算符用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

  • 用法

    1. 数组的合并
    数组1 = [v1, v2, ...];
    数组2 = [w1, w2, ...];
    数组3 = [...数组1, ...数组2];
    // 则 数组3 = [v1, v2, ... , w1, w2, ... ]
    
    1. 数组的克隆
    数组1 = [v1, v2, ...];
    数组2 = [...数组1 ];
    // 则 数组2 = 数组1 = [v1, v2, ...]
    
    1. 将伪数组转换成真数组
      • 伪数组(类数组)

        无法直接调用数组方法或期望length属性有什么特殊的行为,但仍可以对真正数组的遍历方法来遍历它们

        1. 典型的是函数的arguments参数

        2. 调用getElementsByTagName,document.childNodes等,它们都返回的NodeList对象都属于伪数组

      • 转换方法

        真数组 = [...伪数组 ];

迭代器Iterator

遍历器(Iterator)是一种机制,也可以说是一种接口,它为各种不同的数据结构提供了统一的访问机制。任何数据结构只要配置了Iterator接口(数据结构的Symbol.iterator属性),就可以完成遍历操作(即依次处理该数据结构的所有成员)。

  • 在js里原生具备Iterator接口(支持for...of遍历,可以调用自身的symbol.iteration方法)的数据结构如下: Array Map Set String TypedArray 函数的arguments对象 NodeList对象

  • 迭代器的工作原理

    1. 创建一个指针对象,指向当前数据结构的起始位置

    2. 第一次调用对象的next方法,指针自动指向数据结构的第一个成员

    3. 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员

    4. 每调用next方法返回一个包含value和done属性的对象

      若迭代器可以产生序列中的下一个值,则done为false;若迭代器已将序列迭代完毕,则done为 true。

      value为迭代器返回的值,done为 true时可省略。

  • 自定义可迭代对象

    需要自定义遍历数据的时候,要想到迭代器

    let obj = {
            'name': '前端小鹿',
            'age': '18',
            'sex': '男'
        }
        for (let i of obj){
            console.log(i); //obj is not iterable,obj不是可迭代对象
        }
    //对obj改造
    let obj = {
        data: ['name:前端小鹿','age:18', 'sex:男'],
        [Symbol.iterator]: function(){
            const self = this;
            let index = 0;
            return {
                next: function(){
                    if (index < self.data.length) {
                        return {
                            value: self.data[index++],
                            done: false
                        };
                    }
                    return { value: undefined, done: true };
                }
            }
        }
    }
    for (let i of obj){
        console.log(i); //"name:前端小鹿" "age:18" "sex:男"
    }
    

class类

实际上,class只是一个语法糖,是构造函数的另一种写法(语法糖:一种为避免编码出错和提高效率编码而生的语法层面的优雅解决方案)。

  • class声明

    // 声明
    class 类名 {
    	// 定义属性
    	constructor(形参1, 形参2, ...) {
    		this.属性名1 = 形参1;
    		this.属性名2 = 形参2;
    		...
    	}
    	// 定义方法
    	方法名(形参) {
    		函数体
    	}
    }
    // 实例化对象
    let 实例化对象名 = new 类名(实参);
    
    1. constructor是一个构造函数方法,创建对象时自动调用该方法。constructor是默认存在的,可以省略,程序亦可以调用。

    2. this指的是实例化对象

    3. 类中声明的方法不能加function关键字,方法之间不要用逗号分隔,否则会报错

  • class静态成员(static关键字)

    // 声明 
    class 类名 {
    	// 静态属性
    	static 属性名 = 属性值;
    	// 静态方法
    	static 方法名() {
    		函数体
    	}
    }
    
    1. static关键字是类的方法

    2. 只能通过类名来调用,不能被实例对象调用

      类名.静态属性名 类名.静态方法()

    3. static方法也可以被继承

  • 类继承

    子类继承了父类的所有方法,通过在 constructor 方法中调用 super() 方法,我们调用了父级的 constructor 方法,获得了父级的属性和方法的访问权限。

    // 声明子类
    class 子类名 extends 父类名 {
    	// 定义属性
    	constructor(形参1, 形参2, ...) {
    		// 继承父类属性
    		super(形参1, 形参2, ...);
    }
    	// 重写父类方法(super调用父类方法)
    	与父类同名的方法名(形参) {
    		函数体
    	}
    }
    
  • Get & Setter

    get用于获取对象属性,set用于修改对象属性值。

    class 子类名 extends 父类名 {
    	constructor(形参1, 形参2, ...) { }
    	// 方法
    	get 方法名() {
              return this.属性名;
    	}
          set 方法名(形参) {
              this.属性名 = 新的属性值;
    	}
    }
    

模块

  • export:规定模块对外接口

    // 1. 分别暴露
    export 变量名 = 值;
    export function 函数名() { }
    // 2. 统一暴露
    export {变量名, 函数名};
    // 3. 默认暴露
    export default{
        变量名: 值,
        函数名: function() { }
    }
    

    在一个文件或模块中,export、import可以有多个,export default仅有一个。

  • import:引入其他模块提供的功能

    // 1. 通用导入方式
    import * as 别名 from "文件路径";
    // 2. 解构赋值形式
    import {导出接口名1, 导出接口名2,} from "文件路径";
    import {导出接口名 as 别名} from "文件路径";
    import {default as 别名} from "文件路径"; //用于默认暴露的接口
    // 3. 针对默认暴露的简便形式
    import 别名 from "文件路径";
    
  • HTML使用引入了模块后的js文件时,script标签的type = "module"

异步编程【重点】

线程

  1. js是单线程的,一个任务完成后才执行另一个任务。
  2. 主线程:浏览器用来处理用户事件、渲染和绘制显示以及运行组成典型网页或应用程序的大部分代码的线程。因为这些事情都发生在一个线程中,一个缓慢的网站或者应用程序脚本会减低整个浏览器的速度;更糟糕的是,如果一个网站或者应用程序脚本进入无限循环,整个浏览器将会挂起,这导致了令人沮丧、迟缓(或更糟)的用户体验。
  3. 现代 JavaScript 提供了创建额外线程的方式,每个线程独立执行,同时可能相互通信。这是使用例如 web workers 等技术实现的,这些技术可以用来衍生出一个在自己的线程中与主线程同时运行的子程序。这允许缓慢、复杂或者长时间运行的任务独立于主线程执行,以保护网站或者应用程序的整体性能,以及浏览器的整体性能。这也允许个人利用现代多核处理器。
  4. 可以创建一种称为 service worker 的特殊类型的 worker,可以在即使用户当前不使用该网站的情况下在网站背后运行(在用户许可的情况下)。这用于创建在用户与网站交互不活跃的情况下当发生事情时能够通知用户的网站。

异步编程 & 同步编程

  • 同步编程 浏览器会等待代码的解析和工作,在上一行完成后才会执行下一行。
  • 异步编程 程序可以在执行一个可能长期运行的任务的同时,继续对其他事件做出反应而不必等待任务完成。与此同时,程序也将在任务完成后显示结果。
  • 同步任务执行完毕后再执行异步任务

宏任务 & 微任务

异步任务可分为宏任务与微任务

  • 宏任务

    1. 计时器(setTimeout、setInterval)
    2. ajax
    3. 读取文件
  • 微任务

    promise.then

  • 执行顺序

    1. 同步任务
    2. process.nextTick
    3. 微任务
    4. 宏任务
    5. setImmediate

回调地狱(厄运金字塔)

  1. 回调函数:一个被传递到另一个函数中的,会在适当的时候被调用的函数(常常第一个的函数的输出是第二个函数的输入)
  2. 在回调函数中调用回调函数,会得到深度嵌套的函数,这就很难阅读、调试和维护了,这称为回调地狱
  3. 大多数现代异步API都不使用回调(使用回调的异步API易造成回调地狱)

生成器(Generator)函数

  1. 生成器函数在执行时能暂停,后面又能从暂停处继续执行(暂停标识:yield),有效解决回调地狱的问题。

  2. 调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器(iterator)对象。当这个迭代器的next()方法被首次(后续)调用时,生成器内的语句会执行到第一个(后续)出现yield的位置为止,yield后紧跟迭代器的返回值(next()执行后返回的对象的value属性值)。

  3. 假如生成器函数中没有yield关键字,该函数就变成了一个暂缓执行函数,具体表现为执行生成器函数不会执行函数内的代码,只有调用生成的迭代器的next()后,函数内的代码才会执行。

  4. next()返回一个对象,表示当前成员的信息,这个对象包含两个属性:valuedone

    value:本次yield后面表达式的生成值,若后面没有yield语句则返回undefined

    done:布尔类型,表示生成器后续是否还有yield语句,即生成器函数是否已经执行完毕并返回

  5. 使用方法

    function* 生成器函数名(形参) { //function与函数名之间有一个*,可以放在左边、中间、右边
        第一部分代码
        yield 表达式1;
        第二部分代码
        yield 表达式2;
        第三部分代码
        yield* 生成器函数名2; //当前生成器暂停执行,将执行权移交给生成器函数2
        ...
        return 返回值;
    }
    let 生成器对象名 = 生成器函数名;
    // 执行生成器函数
    // 调用next()方法时,如果传入了参数,那么这个参数赋值给上一个yield语句前的变量
    生成器对象名.next(实参); //执行第一部分代码,yield返回表达式1的值
    生成器对象名.next(实参); //执行第二部分代码,yield返回表达式2的值
    

    Promise函数

  • 概述

    1. Promise是ES6引入的异步编程的新解决方案,但本身不能说promise是异步的。
    2. 语法上,Promise是一个构造函数(对象),用来封装异步操作并可以获取其成功或失败的结果。
    3. 本意上,Promise是承诺,承诺它过一段时间会给你一个结果。
    4. promise可以支持多个并发的请求,获取并发请求中的数据。
  • Promise状态

    1. Promise异步操作有三种状态

      pending(进行中)

      fulfilled(成功)

      rejected(失败)

      除了异步操作的结果,任何其他操作都无法改变这个状态。

    2. Promise对象状态改变情况

      pending ==> fulfilled

      pending ==> rejected

      只要处于fulfilled(成功)或rejected(失败),状态就不会再变了,即resolved(定型)。

    3. 状态的缺点

      无法取消Promise,一旦新建它就会立即执行,无法中途取消。

      如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。

      当处于pending(进行中)状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

  • Promise.prototype.then

    • then方法接收两个函数作为参数

      第一个参数是Promise执行成功时的回调,

      第二个参数是Promise执行失败时的回调,

      两个函数只会有一个被调用,可以只写成功时的回调。

    • 语法

      Promise实例对象名.then(执行成功时的回调, 执行失败时的回调);
      
    • 返回值(Promise对象) —— 根据then中回调函数的返回值类型决定

      1. 返回值是非Promise

        then方法将返回一个resolved/rejected状态的Promise对象用于链式调用,且Promise对象的值就是这个返回值。

      2. 返回值是Promise

      (1)返回值是成功状态的Promise

      then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。

      (2)返回值是失败状态的Promise

      then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。

      (3)返回值是未定状态的Promise

      then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。

  • Promise.prototype.catch

    只用于Promise中的错误处理

    p.catch((形参:失败时传入的数据) => {
    失败后的操作;
    });
    
  • 基本使用

    // 实例化Promise对象
    const Promise实例对象名 = new Promise(function(resolve, reject){
        开始执行异步操作;
        resolve(实参:成功时传入的数据); //调用异步操作执行成功后的回调函数:than方法第一个参数
        reject(实参:失败时传入的数据); //调用异步操作执行失败后的回调函数:than方法第二个参数
    });
    // 调用Promise对象的then方法(Promise回调函数)
    Promise实例对象名.then(function(形参:成功时传入的数据){
    	成功后的操作;
    }, function(形参:失败时传入的数据){
    	失败后的操作;
    });
    
  • 使用promise封装ajax请求

    // 实例化Promise对象
    const p = new Promise((resolve, reject) => {
        // 1.创建异步对象
        const xhr = new XMLHttpRequest();
        // 2.设置请求行
        xhr.open("请求方式", "请求地址");
        // 3.设置请求头(get请求可以省略,post请求不发数据也可以省略)
        xhr.setRequestHeader("键名","值");
        // 4.发送请求(get不传参,post传参)
        xhr.send(参数1=值1&参数2=值2);
        // 5.注册回调函数
        xhr.onreadystatechange = function() {
            if(xhr.readystate === 4){
                if(xhr.status >= 200 && xhr.status < 300){                
                    resolve(实参:成功时传入的数据); // 调用请求成功时的回调函数
                    // xhr.response:响应结果(请求返回的结果),可作为成功时传入的数据
                } 
                else{                       
                    reject(实参:失败时传入的数据); // 调用请求失败时的回调函数
                    // xhr.status:状态码,可作为失败时传入的数据
                }
            }
        }
    });
    // Promise回调函数
    p.then((形参:成功时传入的数据) => {
    	成功后的操作;
    }, (形参:失败时传入的数据) => {
    	失败后的操作;
    });
    
  • 链式调用then方法:避免回调地狱

    then方法返回一个Promise对象,如果then回调函数返回一个Promise,一个等价的Promise将暴露给后续的方法链。

    // 实例化Promise对象
    const p = new Promise((resolve, reject) => {
        开始执行异步操作;
        resolve(实参:成功时传入的数据); //调用异步操作执行成功后的回调函数:than方法第一个参数
    });
    // 链式调用then方法
    p.then((形参:成功时传入的数据) => {
        return new Promise((resolve, reject) => {
        	resolve(实参);
    	});
    }).then((形参) => {
        return new Promise((resolve, reject) => {
            开始执行异步操作;
            resolve(实参);
    	});
    }).then((形参) => {
    	成功后的操作;
    });
    

async & await

  • async函数

    async 函数是AsyncFunction构造函数的实例,并且其中允许使用await关键字。

    使用asyncawait关键字可以简化使用Promise的语法,而无需刻意地链式调用promise。

    // 声明
    async function 函数名(形参) {
        函数体(可以包含0个或多个await表达式)
    }
    // 调用
    函数名(实参);
    

    async函数的返回值为 promise 对象,可以使用 then 方法添加回调函数。这个 promise 要么会通过一个由 async 函数执行的返回值被解决,要么会通过一个从 async 函数中抛出的(或其中没有被捕获到的)异常被拒绝。

  • await表达式

    await 只能写在 async 函数体内,但 async 函数体内可以没有 await 表达式 await 右侧的表达式一般为 promise 对象 await返回的是 promise 成功的值 若 await 的 promise 失败了,就会抛出异常,需要通过 try...catch 捕获处理

    async 函数执行时,如果遇到 await 就会先暂停执行 ,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。

    async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。这样的话,一个不含 await 表达式的 async 函数是会同步运行的。然而,如果函数体内有一个 await 表达式,async 函数就一定会异步执行。

    await针对所跟不同表达式的处理方式:

    • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
    • 非 Promise 对象:直接返回对应的值。
  • async 和 await 结合发送 ajax 请求

    // 封装发送ajax get请求的函数
    function sendAjax(url) {
    	return new promise((resolve, reject) => {
            // 1. 创建异步对象
            const xhr = new XMLHttpRequest();
            // 2.设置请求行
            xhr.open("GET", url);
            // 3.发送请求
            xhr.send();
            // 4.注册回调函数
            xhr.onreadystatechange = function() {
                if(xhr.readystate === 4){
                    if(xhr.status >= 200 && xhr.status < 300) {                
                        resolve(实参:成功时传入的数据); // 调用请求成功时的回调函数
                        // xhr.response:响应结果(请求返回的结果),可作为成功时传入的数据
                    } 
                    else {                       
                        reject(实参:失败时传入的数据); // 调用请求失败时的回调函数
                        // xhr.status:状态码,可作为失败时传入的数据
                    }
                }
            }
        });
    }
    // async和await发送ajax请求
    async function async函数名() {
        let res = await sendAjax("get请求地址");
        console.log(res);
    }
    async函数名();