前端学习9—ES6

ES6 简介

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了,他的目标是:使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言

ES6 既是一个历史名词,也是一个泛指,是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等,而 ES2015 是正式名称,特指该年发布的正式版本的语言标准

Babel 转码器

  • 可以将 es6 的代码转换成 es5 的代码,从而在老版本的浏览器中执行(也就是说可以用 ES6 的方式编写程序,不用担心现有环境是否支持)

1 let 和 const 命令

ES6 新增了 let、const 命令,用来声明变量,用法类似于 var,但是 let 声明的变量,只在 let 命令所在的代码块内有效const 声明的变量是一个只读变量,一旦声明,常量的值就不能改变

// ES5
// 通过 var 声明
var num = 10;

// ES6
// 通过 let 声明
let a = 1;
// 通过 const 声明
const PI = 3.14;

区别:

  1. 不存在变量提升

    • var 命令会发生变量提升的现象(变量可以在声明之前使用,值为 undefined)
    • let 和 const 没有变量提升的功能,所声明的变量一定要在声明后使用
    console.log(num);	// undefined
    var num = 10;
    
    console.log(n);		// 报错:不能在声明前使用
    let n = 10;
    
  2. 不允许重复声明

    • var 命令能重复声明,后者覆盖前者
    • let 和 const 不允许在相同作用域内,重复声明同一个变量
    let a = 1;
    let a = 3;		// 报错:不允许在相同作用域内,重复声明同一个变量
    a = 3;
    
    function func(arg) {
        let arg = 10;	// 报错:不能在函数内部重新声明参数
    }
    func()
    
  3. 作用域

    • var 的作用域是以函数为界限
    • let 和 const 的作用域是块作用域(大括号 {} 内的范围)
    • var 可以定义全局变量和局部变量,let 和 const 只能定义局部变量
    • const 的声明的常量不能被修改,但对于引用类型来说,堆内存中的值是可以被改变的。
      • const 一旦声明变量,就必须立即初始化,不能留到以后赋值
      • const 保证的是,变量指向的那个内存地址所保存的数据不能改动,对于简单类型的数据(数值、字符串、布尔值等):值就保存在变量指向的那个内存地址;对于复合类型的数据(对象和数组):变量指向的内存地址,只是一个指向实际数据的指针,const 只能保证这个指针是固定的,但是这个指针指向的数据变化,const 无法控制
    {
        var num = 10;
        let num2 = 10;
    }
    console.log(num);	// 10
    console.log(num2);	// 报错:不在作用域内
    
    const PI;		// 报错:const 声明的变量需要声明的同时就初始化
    
    const PI = 3.14159;
    PI = 3;				// 报错:const 的声明的常量不能被修改
    console.log(PI);
    
    const person = {
        name: 'zhangsan',
        age: 18
    }
    person.name = 'lisi'	// 不会报错:引用类型,堆内存中的值是可以被改变的
    console.log(person);	// {name: 'lisi', age: 18}
    
  4. 变量作为全局

  5. 属性

    • var 定义的变量,会作为 window 对象的属性,let 不会
  6. 暂时性死区

    • 只要块级作用域存在 let 和 const 命令,它所声明的变量就绑定这个区域,不再受外部影响,在代码块内,使用 let 和 const 命令声明变量之前,这个变量都是不可用的,无论这个代码块外是否有 let 命令
    let n = 10;
    if (true) {
        n = 100;	// 报错:存在暂时性死区,只能在声明的位置后面使用
        // n 绑定了这个
        let n;
    }
    

2 ES6 的模板字符串

模板字符串是增强版的字符串,用反引号(`)标识,插入变量时使用 ${变量名}

<div></div>
<script>
    const div = document.querySelector('div');
    let id = 1, text = 'andy';
    // 传统写法
    // div.innerHTML = '<ul><li id=' + id + '>' + text + '</li></ul>';
    // 模板字符串写法
    let htmlStr = `<ul>
        <li id=${id}>${text}</li>
    </ul>`
    div.innerHTML = htmlStr;
</script>
  • 使用模板字符串表示多行字符串,所有的空格和换行都会被保留
  • 模板字符串中嵌入变量,需要将变量名写在${}

3 增强的函数

3.1 参数的默认值

允许为函数的参数设置默认值(直接写在参数定义的后面)

// 传统写法
/* function add(a, b) {
    a = a || 10;
    b = b || 20;
    return a+b;
}
console.log(add()); */

// ES6 写法
function add(a = 10, b = 20) {
    return a+b;
}
console.log(add());			// 30
console.log(add(1));		// 21
console.log(add(1, 2));		// 3
  • 参数变量是默认声明的,函数体内,不能用 letconst 再次声明

  • 默认值的参数一般写到函数参数的末尾

  • 默认的表达式也可以是一个函数

    function sum(a, b = getValue(5)) {
        return a + b;
    }
    function getValue(val) {
        return val*val;
    }
    console.log(sum(1, 4));		// 5
    console.log(sum(1));		// 26
    

3.2 rest 参数

rest 参数**(…变量名)**:用于获取函数的多余参数,这样就不需要使用 arguments 对象了

function fun(a, ...args) {
    console.log(args);			// [2, 3, 4, 5]
    console.log(arguments);		// [1, 2, 3, 4, 5, callee: (...), Symbol(Symbol.iterator): ƒ]
}
fun(1, 2, 3, 4, 5);
  • rest 参数只能作为最后一个参数

3.3 箭头函数

允许使用 => 定义函数:function(){} 等同于 ()=>{}

let f = v => v;
// 等同于
let f = function(v) {
    return v;
};
  1. 当函数没有参数或有多个参数时,() 不能省略
  2. 当函数只有一个参数、函数体是一句代码、且是返回语句时,参数的 () 可省略、函数体 {} 可省略、return 可省略,中间用 => 连接
  3. 若函数体只有一句,且不是 return 语句或多条语句时,{} 不能省略
  4. 若函数的返回值为对象,此时不能省略 return

使用箭头函数的注意点:

  • 不是适用于声明函数、DOM 事件
  • 不能作为构造函数(迭代器)
  • 箭头函数体内不能使用 arguments
  • 不能使用 yield 命令

箭头函数的 this 指向

箭头函数没有自己的 this 对象,内部的 this 就是定义时上层作用域中的 this

let PageHandle = {
    id: 1,
    /* init: function() {
        document.addEventListener('click', function(e) {
            // TypeError: this.doSomeThings is not a function
            // 没有使用箭头函数,this 指向 document 对象
            this.doSomeThings(e.type);
        })
    }, */
    init: function() {
        document.addEventListener('click', (e) => {
        	// 使用箭头函数,这里的 this 总是指向 PageHandle 对象
            this.doSomeThings(e.type);
        })
    },
    doSomeThings: function(type) {
        // 事件类型:click, 当前id:1
        console.log(`事件类型:${type}, 当前id:${this.id}`);
    }
}
PageHandle.init();
  1. 不可以对箭头函数使用 new 命令
  2. arguments 对象在箭头函数体内不存在,而是指向外层函数的arguments

Babel 转箭头函数产生的 ES5 代码:

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;
  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

4 拓展的字符串、对象、数组功能

4.1 字符串

字符串遍历

let str = "hello";
//1.for遍历
for (let i = 0; i < str.length; i++) {
    console.log(i, str[i]);     //i 索引    数值类型
}

//2.数组->for->for in
let arr = [1, 2, 3];
for (let i in arr) {
    console.log(i, arr[i]);     //i 索引    字符串类型
}

//3.for... of
for(let i of str){
    console.log(i);     //数据
}

//4.解构
let [a, b, c, d ,e] = str;
console.log(a, b, c, d ,e);

ES6 新增字符串方法

//字符串新增方法:
方法                返回值          作用
includes('str')     boolean         判断字符串中包含子串
endWith('str')      boolean         判断字符串以"str"结尾
startWith('str')    boolean         判断字符串以"str"开头
repeat(n)           重复拼接自身     重复n次输出字符串 repeat + repeat

//不全方法: 补全字符串长度
padStart(length, s);        字符串开头补全
endStart(length, s);        字符串末尾补全

4.2 数组

1、扩展运算符(…)

将一个数组转为用逗号分隔的参数序列

console.log([1, 2, 3]);			// [1, 2, 3]
console.log(...[1, 2, 3]);		// 1 2 3
  • 代替函数的 apply() 方法:不再需要 apply() 方法将数组转为函数的参数

    function fun(a, b, c) {
        return a+b+c;
    }
    let args = [1, 2, 3];
    
    // es5 写法
    fun.apply(null, args);
    
    // es6 写法
    fun(...args);
    

2、Array.from()

将两类对象(类似数组的对象和可遍历的对象)转为真正的数组

// querySelectorAll 返回的是一个类似数组的对象
let lis = document.querySelectorAll('li');
// es5 写法
console.log([].slice.call(lis));
// es6 写法
console.log(Array.from(lis));	
  • 扩展运算符(…)也可以将伪数组转为真正的数组

    [...lis]
    
  • 任何有 length 属性的对象,都可以通过 Array.from() 方法转为数组

  • Array.from() 还可以接收第二个参数,用来对每个元素进行处理

    let lis = document.querySelectorAll('li');
    let liContent = Array.from(lis, li => li.innerHTML);
    console.log(liContent);		// ['1', '2', '3', '4', '5']
    

3、Array.of()

将一组包含任意数据类型的值,转换成数组

Array.of(1, 2, 'hello', ['a', 'b'], {id: 10, name: 'andy'});

4、copyWithin()

数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组

copyWithin(target, start = 0, end = this.length)

[1, 2, 3, 4, 5].copyWithin(0, 2)  // [3, 4, 5, 4, 5]
  • 会修改当前数组

5、find()、findIndex()

find() 用来找到第一个符合条件的数组成员,它的参数是一个回调函数,所有数组成员依次执行这个回调函数,直到找出第一个返回值为 true 的成员并返回,如果没有找到,就返回 undefined

[1, 2, 3, 4, 5].find(n => n % 2 == 0)	// 2
  • find() 方法的回调函数可以接收三个参数:当前的值、当前的位置和原数组

findIndex() 用来返回第一个符合条件的数组成员的位置,如果没有找到,就返回 -1

[1, 2, 3, 4, 5].findIndex(n => n % 2 == 0)	// 1
  1. 这两个方法都可以接受第二个参数,用来绑定回调函数的 this 对象

    function f(n) {
        // 回调函数中的 this 指向的是 Person 对象
        return n > this.id;
    }
    let Person = {
        id: 3,
        name: 'lisi'
    }
    [1, 2, 3, 4, 5].find(f, Person);	// 4
    
  2. ES2022 新增了两个方法 findLast()findLastIndex() ,从数组最后一个成员开始,依次向前检查,其他都保持不变

6、entries()、keys()、values()

entries()keys()values() 用来遍历数组,它们都返回一个遍历器对象,可以用 for … of 循环进行遍历

  • keys() 是对键名进行遍历
  • values() 是对键值进行遍历
  • entries() 是对键值对的遍历
let arr = ['a', 'b', 'c', 'd'];
for(let index of arr.keys()) {
    console.log(index);		// 0 1 2 3
}
for(let elem of arr.values()) {
    console.log(elem);		// a b c d
}
for(let [index, elem] of arr.entries()) {
    console.log(index + elem);		// 0a 1b 2c 3d
}
  • 如果不使用 for … of 循环,可以手动调用遍历器对象的 next() 方法,进行遍历

    let arr = ['a', 'b', 'c', 'd'];
    let it = arr.entries();
    console.log(it.next().value);		// [0, 'a']
    console.log(it.next().value);		// [1, 'b']
    console.log(it.next().value);		// [2, 'c']
    console.log(it.next().value);		// [3, 'd']
    console.log(it.next().value);		// undefined
    

7、includes()

返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似

let arr = [1, 2, 3, 4];
console.log(arr.includes(2));		// true
console.log(arr.includes('3'));		// false
console.log(arr.includes(5));		// false
  • 没有这个方法前,通常使用数组的 indexOf() 方法,检查是否包含某个值(内部使用严格相等运算符 === ,会导致对 NaN 的误判)

    let arr = [1, 2, 3, 4];
    if(arr.indexOf(e) !== -1) {
        // 数组中包含 e 这个值
    }
    
    [NaN].indexOf(NaN)		// -1
    [NaN].includes(NaN)		// true
    

4.3 对象

1、属性的简洁表示法

允许在大括号里面,直接写入变量和函数,作为对象的属性和方法

let id = 10,
    name = 'andy';
let Person = {
    id,		// 等价于	id: id
    name,	
    printMe() {		// 等价于 printMe: function(){}
        console.log(this.name);
    }
}
Person.printMe();
  • 可用于函数的返回值

    function f(a, b) {
        return {a, b};
        // 等价于 return {a: a, b: b}; (返回一个对象)
    }
    console.log(f(1, 3));		// {a: 1, b: 3}
    
  • 简写的对象方法不能用作构造函数,会报错

    const obj = {
        constructor() {
            this.name = 'andy';
        }
        // 因为 constructor 是一个简写的对象方法,所以 o.constructor 不能当作构造函数使用
        constructor: function() {
            this.name = 'andy';
        }
    };
    const o = new obj();
    console.log(o.constructor());	// 报错
    

2、属性名表达式

JavaScript 定义对象有两种方法:

  1. 直接用标识符作为属性名

  2. 用表达式作为属性名

const obj = {};
obj.id = 10;			// 方法一
const name = 'andy';
obj[name + 'bc'] = 123;	// 方法二
obj['a' + 'bc'] = 123;
console.log(obj);		// {id: 10, andybc: 123, abc: 123}
  • 表达式还可以用于定义方法名
  • 属性名表达式与简洁表达式不能同时使用
  • 属性名表达式是一个对象,默认情况下会自动将对象转为字符串 [object object]

3、Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符()和严格相等运算符(=),但他们都有缺点:== 会自动转换数据类型、=== 的 NaN 不等于自身,以及 -0 等于 +0

Object.is() 用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致

(-0)===(+0)				// true
(NaN) === (NaN)			// false
Object.is(-0, +0)		// false
Object.is(NaN, NaN)		// true

4、Object.assign()

用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target):Object.assign(target, obj1, obj2...)

let newObj = Object.assign({a: 1}, {b: 2}, {id: 2, name: 'andy'});
// {a: 1, b: 2, id: 2, name: 'andy'}
console.log(newObj);
  • 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
  • 只拷贝源对象的自身属性(浅拷贝),不拷贝继承属性,也不拷贝不可枚举的属性
  • 属性名为 Symbol 值的属性,也会被 Object.assign() 拷贝

5 解构赋值

解构:允许按照一定模式,从数组和对象中取值,对变量进行赋值

本质:模式匹配

  1. 完全解构:模式完全匹配
  2. 不完全解构:模式不完全匹配

为解构变量设置默认值,不会出现 undefined 的现象

  • 解构成功时,解构变量的默认值会被覆盖
  • 解构不成功时,解构变量的值为默认值

对象的解构:

  1. 对象在解构时,变量名要与属性名一致
  2. 对象解构的解构变量不考虑顺序
  3. 对象在解构时,为对象属性重命名,可以方便程序的编写(重命名不会更改对象的属性)
  4. 和解构数组一样,解析对象时可以设置默认值

5.1 数组的解构

本质上属于“模式匹配”:只要等号两边的模式相同,左边的变量就会被赋予对应的值(数组的元素是按次序排序的,变量的取值由它的位置决定

let [x, , y] = [1, 2, 3]	
console.log(x, y);				// 1 3

let [foo, [[bar], baz]] = [1, [[2], 3]]
console.log(foo, bar, baz);		// 1 2 3

let [head, ...tail] = [1, 2, 3, 4, 5]
console.log(head, tail);		// 1 [2, 3, 4, 5]

let [a, b , ...c] = ['a']
console.log(a, b ,c);			// a undefined []

**不完全解构:**等号左边的模式,只匹配一部分等号右边的数组

let [x, y] = [1, 2, 3]	
console.log(x, y);				// 1 2

let [a, [[b], d]] = [1, [[2, 3], 4]]
console.log(a, b, d);			// 1 2 4

5.2 对象的解析

1、对象的属性没有次序,变量必须与属性同名,才能取到正确的值

let node = {
    id: 12,
    name: 'zhangsan',
    age: 18
}
let {type, name} = node;
console.log(type, name);		// undefined 'zhangsan'
let {id, name} = node;
console.log(id, name);			// 12 'zhangsan'
let {name, id} = node;
console.log(name, id);			// zhangsan 12

2、对象的解构赋值,可以很方便的将现有对象的方法,赋值到某个变量

// 将 Math 对象的对数、正弦、余弦三个方法,复制到对应的变量上
let {log, sin, cos} = Math;
// 将 console.log 赋值给 log 变量
const {log} = console;
log('hello');

3、如果变量名和属性名不一致,必须写成 {属性名: 变量名} 的形式:

let {name: nickel, gender} = {
    name: 'zhangsan',
    gender: 'man'
};
console.log(nickel, gender);		// zhangsan man
  • 对象解构赋值的内部机制,是先找到同名属性,再赋给对应的变量,真正被赋值的是后者,不是前者

    let {name, gender} = {name: ‘zhangsan’, gender: ‘男’}

    是下面形式的简写

    let {name: name, gender: gender} = {name: ‘zhangsan’, gender: ‘男’}

4、如果将一个已经声明的变量用于解构赋值,需要将整个解构赋值语句放到一个大括号里

let x;
{x} = {x:1};		// SyntaxError
({x} = {x:1});		// 正确写法
console.log(x);		// 1
  • 如果不将解构赋值放在圆括号里,JavaScript 引擎会将 {x} 理解为一个代码块,从而发生语法错误

5.3 用途

  1. 交换变量值

    let x = 1;
    let y = 2;
    [x, y] = [y, x];
    console.log(x, y);
    
  2. 从函数返回多个值—方便取值

    // 返回一个数组
    function fun() {
        return [1, 2, 3];
    }
    let [a, b, c] = fun();
    
    // 返回一个对象
    function fun() {
        return {
            name: 'zhangsan',
            age: 20
        };
    }
    let {name, age} = fun();
    
  3. 提取 JSON 数据

    let jsonData = {
        id: 43,
        status: 'ok',
        data: [867, 5300]
    }
    let {id, status, data: number} = jsonData;
    console.log(id, status, number);		// 43 'ok' [867, 5300]
    

6 Symbol

原始数据类型 Symbol :表示独一无二的值,属于 JavaScript 语言的原生数据类型之一,其他数据类型是:undefined、null、布尔值、字符串、数值、大整数、对象

Symbol 值通过 Symbol() 函数生成

let name1 = Symbol('andy');
let name2 = Symbol('andy');
name1 === name2					// false
console.log(typeof(name1));		// symbol
  • 用途:用来定义对象的私有变量

  • Symbol() 函数前不能使用 new 命令,因为生成的 Symbol 是一个原始类型的值,不是对象,所以不能使用 new 命令来调用

  • 如果用 Symbol 定义的对象中的变量,取值时一定要用 [变量名]

    let name = Symbol('name');
    let obj = {};
    obj[name] = 'andy';
    console.log(obj[name]);		// andy
    console.log(obj.name);		// undefined
    

1、作为属性名的 Symbol

let mySymbol = Symbol();
// 第一种方法
let a = {};
a[mySymbol] = 'hello';
// 第二种方法
let a = {
    [mySymbol] : 'hello'
}
// 第三种方法
let a = {};
Object.defineProperty(a, mySymbol, {value: 'hello'});
  • 在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中

2、属性名的遍历

Object.getOwnPropertySymbols() 方法,可以获取指定对象的所有 Symbol 属性名。这个方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

let name = Symbol('name');
let age = Symbol('age');
let obj = {};
obj[name] = 'andy';
obj[age] = 20;
let s = Object.getOwnPropertySymbols(obj);
console.log(s);		// [Symbol(name), Symbol(age)]

Reflect.ownKeys() 方法可以返回所有类型的键名,包括常规键名和 Symbol 键名

let obj = {
    [Symbol('id')]: 18,
    name: 'andy',
    enum: 2,
    arr: [1, 2, 3]
}
// ['name', 'enum', 'arr', Symbol(id)]
console.log(Reflect.ownKeys(obj));

因为 Symbol 作为键名,不会被常规方法遍历得到,所以可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法

7 Map 和 Set

7.1 Set

成员的值都是唯一的,没有重复的值,Set 本身是一个构造函数,用来生成 Set 数据结构

  • 添加元素:add
  • 删除元素:delete
  • 检查某个值是否在 set 中:has
const s = new Set();
// 添加元素(可以添加数组)
s.add(2);
s.add('3');
s.add('3');
s.add(['a', 'b', 'c']);
// 删除元素
s.delete(2);
// 返回该值是否是 Set 的成员
console.log(s.has('3'));
// 返回 Set 实例的成员总数
console.log(s.size);

将 set 转换为数组

let s = new Set([1, 2, 3, 2, 4, 5, 1]);
// ① 拓展运算符
let arr = [...s];
console.log(arr);	// [1, 2, 3, 4, 5]
// ② Array.from()
let arr = Array.from(s);

遍历操作

  • keys():返回键名的遍历器

  • values():返回键值的遍历器

  • entries():返回键值对的遍历器

    let s = new Set(['a', 'b', 'c', 'red']);
    
    for(let key of s.keys()) {
        console.log(key);		// a b c red
    }
    
    for(let value of s.values()) {
        console.log(value);		// a b c red
    }
    
    for(let item of s.entries()) {
        console.log(item);		// ['a', 'a'] ['b', 'b'] ['c', 'c'] ['red', 'red']
    }
    
    for (let item of s) {
        console.log(item);		// a b c red
    }
    
    1. 因为 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以 keys 方法和 values 方法的行为完全一致
    2. Set 结构的实例默认可遍历,它的默认遍历生成器就是他的 values 方法(可以直接省略 values 方法,直接用 for … of 循环遍历 Set)
  • forEach():使用回调函数遍历每个成员,forEach 方法的参数就是一个处理函数

let s = new Set(['a', 'b', 'c', 'red']);
s.forEach(item => console.log(item));
s.forEach((k, v) => console.log(k + ' ' + v));

WeakSet

WeakSet 结构与 Set 类似,也是不重复的值的集合,但是,他与 Set 有多个区别:

  1. WeakSet 的成员只能是对象和 Symbol 值,而不能是其它类型的值

    const ws = new WeakSet();
    ws.add(Symbol('name'));
    ws.add({id: 12});
    ws.add(1);		// 报错
    
  2. WeakSet 中的对象都是弱引用(如果其他对向不再引用该对象,垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象是否还存在于 WeakSet 之中),WeakSet 的成员是不适合引用的,可能会随时消失

  3. 不可迭代

  4. 没有 size 属性

7.2 Map

类似于对象,也是键值对的集合,但是键的范围不限于字符串,各种类型的值(包括对象)都可以当作键(键和值可以是任意类型)

  • set(key, value):设置键名 key 对应的键值为 value,如果 key 已经存在,键值会被更新,否则新生成该键
  • get(key):读取 key 对应的键值,如果找不到就返回 undefined
  • has(key):返回键名 key 是否在当前 Map 对象之中
  • delete(key):删除某个键
  • size 属性:返回 Map 结构成员的总数
  • clear():清除所有成员,没有返回值
let m = new Map();
m.set('name', 'zhangsan');
m.set('age', 23);

console.log(m.get('name'));

console.log(m.has('age'));

m.delete('age');

console.log(m.size);

m.clear();

m.set(['a', [1, 2, 3]], 'hello');	// 键名为 ['a', [1, 2, 3]],键值为 'hello'
console.log(m);

Map 也可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组

let m = new Map([
    ['a', 1],
    ['b', 2]
]);
console.log(m);		

遍历方法(同 Set)

  • keys()
  • values()
  • entries()
  • forEach()

不同于 Set 结构的实例默认可遍历,Map 的默认遍历生成器是他的 entries 方法(可以直接省略 entries 方法,直接用 for … of 循环遍历 Set)

Map 结构转为数组结构

let m = new Map([
    ['a', 1],
    ['b', 2],
    ['c', 3]
]);
[...m.keys()]		// ['a', 'b', 'c']
[...m.values()]		// [1, 2, 3]
[...m.entries()]	// [['a', 1], ['b', 2], ['c', 3]]
[...m]				// [['a', 1], ['b', 2], ['c', 3]]

WeakMap

  1. 只接受对象(null 除外)和 Symbol 值作为键名,不接受其它类型的值作为键名
  2. WeakMap 的键名所引用的对象都是弱引用

用途:

  • DOM 节点作为键名
  • 部署私有属性

7.3 ES6的set结构、map结构类型间转换

Set 和 数组
    1.set -> 数组
        1.Array.from();
        2.遍历set然后push
        3.扩展运算符

    2.数组 -> set
        1.new Set(arr);
    
Map 和 对象 和 string
    1.map -> 对象 -> String
        1.forEach遍历Map -> 对象 -> JSON.stringify
    2.String -> 对象 -> map
        1.JSON.parse -> 对象 -> for...in 遍历对象 -> Map.add()

8 迭代器和生成器

8.1 迭代器 Iterator

是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作(依次处理该数据结构的所有成员)

Iterator 的遍历过程:

  1. 创建一个指针对象,指向当前数据结构的起始位置,也就是说,遍历器对象本质上就是一个指针对象
  2. 第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员
  3. 第二次调用指针对象的 next 方法,可以将指针指向数据结构的第二个成员
  4. 不断调用指针对象的 next 方法,直到他指向数据结构的结束位置

每一次调用 next 方法,都会返回数据结构的当前成员的信息,具体来说,就是返回一个包含 value(当前成员的值)和 done(布尔值,表示遍历是否结束)两个属性的对象

默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性(只要一个数据结构具有 Symbol.iterator 属性,就可以认为是可遍历的),此时可以用 for … of 结构遍历

const items = ['one', 'two', 'three'];
// 创建新的迭代器(因为数组提供了 Iterator 接口,所以这里可以直接使用)
const its = items[Symbol.iterator]();
its.next();		// {value: 'one', done: false} done 如果为 true,表示遍历结束

原生具备 Iterator 接口的数据结构有以下几种:

  1. Array
  2. Map
  3. Set
  4. String
  5. TypedArray
  6. 函数的 arguments 对象
  7. NodeList 对象

对于普通对象,for … of 结构不能直接使用,会报错,必须部署了 Iterator 接口后才可以使用,但 for … in 循环依然可以用来遍历键名

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    }
    return {done: true, value: undefined};
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
  console.log(value); // 0, 1, 2
}

8.2 生成器 Generator

可以通过 yield 关键字,将函数挂起,为了改变执行流提供了可能,同时为了做异步编程提供了方案

两个特征:

  1. function 关键字与函数名之间有一个小星号(*)
  2. 函数体内部使用 yield 表达式,让函数挂起
function* fun() {
    yield 'hello';
    yield 'world';
    return 'ending';
}
var f = fun();
f.next();		// {value: 'hello', done: false}
f.next();		// {value: 'world', done: false}
f.next();		// {value: 'ending', done: false}
f.next();		// {value: undefined, done: true}
  • Generator 函数分段执行,yield 表达式暂停执行, next 方法恢复执行
  • next 方法可以带一个参数,该参数会被当作上一个 yield 表达式的返回值

应用:

function* load() {
    loadUI();
    yield showData();
    hideUI();
}
let itLoad = load();
itLoad.next();
function loadUI() {
    console.log('加载 loading 页面');
}
function showData() {
    setTimeout(() => {
        console.log('数据加载完成');
        itLoad.next();
    }, 2000)
}
function hideUI() {
    console.log('加载页面完成');
}

9 Promise 对象

相当于是一个容器,里面保存着某个未来才会结束的事情(通常是一个异步操作)的结果,Promise 是一个对象,各种异步操作都可以用同样的方法进行处理

  • Promise 对象存在的目的:将异步操作以同步的流程表达出来,避免层层嵌套的回调函数(俗称回调地狱)

  • 对象的状态不受外界影响,处理异步操作有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)

  • 一旦状态发生改变,就不会再变,任何时候都可以得到这个结果,对象的状态改变只有两种情况:pending ➡ fulfilled、pending ➡ rejected

const promise = new Promise(function(reslove, reject) {
    let res = {
        code: 200,
        data: {
            id: 1,
            name: 'zhangsan'
        },
        message: 'success'
    }
    setTimeout(() => {
        if(res.code === 200) {
            reslove(res.data)
        } else {
            reject(res.message)
        }
    })
})
promise.then((val) => {
    console.log(val);
}, (err) => {
    console.log(err);
})
  • reslove 函数:在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
  • reject 函数:在异步操作失败时调用,并将异步操作报出的错误。作为参数传递出去
  • Promise 实例生成以后,可以用 then 方法分别指定 resloved 状态和 rejected 状态的回调函数
    • 第一个参数:reslove 回调函数
    • 第二个参数:reject 回调函数
// 加载图片函数
function loadPic(id, src, sec) {
    return new Promise((resolve, reject) => {
        let oImg = new Image();
        oImg.onload = function () {
            resolve(oImg);
        }
        oImg.onerror = function () {
            reject(`编号为${id}的任务失败`);
        }
        // oImg.src = src;
        setTimeout(() => {
            oImg.src = src;
        }, sec);   //延迟加载函数
    })
}

let s1 = "远程图片";
let s2 = "远程图片";
let p1 = loadPic('001', s1, 1000);
let p2 = loadPic('002', s2, 100);

// Promise.all 方法
// 当所有图片都加载完在执行后续动作,有一张失败都不执行then
let p = Promise.all([p1, p2]);  //all返回新的promise对象
p.then(data=>{
    console.log(data,'加载成功');
    document.body.append(data[0],data[1]);
}).catch(err=>{
    console.log(err);
}).finally(()=>{
    console.log('不论成功与否,我都执行');
}); 


// Promise.race 方法
// 注:
//  1.只要有一张图片加载完成,就执行then的resolve实现
//  2.如果先加载的图片有失败的情况,后续图片就不加载,直接执行catch 或 reject

let p = Promise.race([p1, p2]);
p.then(data => {
    console.log(data);      //只返回最先加载成功的那个
    document.body.append(data);    //由于设置了延迟,所以第二个先加载完成
}).catch(err => {
    console.log(err);
})
  • promise 的异常处理,建议使用 catch 捕获

10 Proxy 对象

const obj = {
    name: 'zhangsan',
    age: 21
}
let div = document.querySelector('div')
div.textContent = obj.name
obj.name = 'lisi'

以上代码 name 的更新不能实现网页上的更新

使用 Proxy 代理可以实现:

let div = document.querySelector('div')
const obj = {
    name: 'zhangsan',
    age: 21
}
const p = new Proxy(obj, {
    get(target, property) {
        return obj[property]
    },
    set(target, property, value) {
        obj[property] = value
        div.textContent = obj.name
    }
})
console.log(p.name);
p.name = 'lisi'
p.age = 18
console.log(obj);

11 async 的用法

可以使得 异步操作更加方便

  • 会返回一个 Promise 对象
    • 可以使用 then 方法添加回调函数,当函数执行的时候,一旦遇到 await 就会先返回,等异步操作完成,再接着执行函数体内后面的语句
  • 是 Generator 的一个语法糖
    • async 函数就是将 Generator 函数的星号替换成了 async,将 yield 替换成了 await
async function f(){
    let s = await 'hello async';
    let data = await s.split('');
    return data;
}
f().then(v=>{console.log(v)});
//所有 await 执行结束后才返回

12 模块的使用

es6 模块功能主要有两个命令构成:export 和 import

  • export 用于规定模块的对外接口
  • import 用于输入其他模块提供的功能(导入)
  • 一个模块就是一个独立的文件
// 暴露
// 第一种写法
export const nama = 'zhangsan'
export const age = 20
export function sayName(){
	return 'my name is zhangsan'
}
export default obj = {
    num: 10
}

// 第二种写法
const nama = 'zhangsan'
const age = 20
function sayName(){
	return 'my name is zhangsan'
}
export{name, age, sayName}
// 引入
import obj, {name, age, sayName} from './modules/index.js'

参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值