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;
区别:
-
不存在变量提升
- var 命令会发生变量提升的现象(变量可以在声明之前使用,值为 undefined)
- let 和 const 没有变量提升的功能,所声明的变量一定要在声明后使用
console.log(num); // undefined var num = 10; console.log(n); // 报错:不能在声明前使用 let n = 10;
-
不允许重复声明
- var 命令能重复声明,后者覆盖前者
- let 和 const 不允许在相同作用域内,重复声明同一个变量
let a = 1; let a = 3; // 报错:不允许在相同作用域内,重复声明同一个变量 a = 3; function func(arg) { let arg = 10; // 报错:不能在函数内部重新声明参数 } func()
-
作用域
- 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}
-
变量作为全局
-
属性
- var 定义的变量,会作为 window 对象的属性,let 不会
-
暂时性死区
- 只要块级作用域存在 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
-
参数变量是默认声明的,函数体内,不能用
let
或const
再次声明 -
默认值的参数一般写到函数参数的末尾
-
默认的表达式也可以是一个函数
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;
};
- 当函数没有参数或有多个参数时,() 不能省略
- 当函数只有一个参数、函数体是一句代码、且是返回语句时,参数的 () 可省略、函数体 {} 可省略、return 可省略,中间用 => 连接
- 若函数体只有一句,且不是 return 语句或多条语句时,{} 不能省略
- 若函数的返回值为对象,此时不能省略 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();
- 不可以对箭头函数使用 new 命令
- 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
-
这两个方法都可以接受第二个参数,用来绑定回调函数的 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
-
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 定义对象有两种方法:
-
直接用标识符作为属性名
-
用表达式作为属性名
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 解构赋值
解构:允许按照一定模式,从数组和对象中取值,对变量进行赋值
本质:模式匹配
- 完全解构:模式完全匹配
- 不完全解构:模式不完全匹配
为解构变量设置默认值,不会出现 undefined 的现象
- 解构成功时,解构变量的默认值会被覆盖
- 解构不成功时,解构变量的值为默认值
对象的解构:
- 对象在解构时,变量名要与属性名一致
- 对象解构的解构变量不考虑顺序
- 对象在解构时,为对象属性重命名,可以方便程序的编写(重命名不会更改对象的属性)
- 和解构数组一样,解析对象时可以设置默认值
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 用途
-
交换变量值
let x = 1; let y = 2; [x, y] = [y, x]; console.log(x, y);
-
从函数返回多个值—方便取值
// 返回一个数组 function fun() { return [1, 2, 3]; } let [a, b, c] = fun(); // 返回一个对象 function fun() { return { name: 'zhangsan', age: 20 }; } let {name, age} = fun();
-
提取 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 }
- 因为 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以 keys 方法和 values 方法的行为完全一致
- 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 有多个区别:
-
WeakSet 的成员只能是对象和 Symbol 值,而不能是其它类型的值
const ws = new WeakSet(); ws.add(Symbol('name')); ws.add({id: 12}); ws.add(1); // 报错
-
WeakSet 中的对象都是弱引用(如果其他对向不再引用该对象,垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象是否还存在于 WeakSet 之中),WeakSet 的成员是不适合引用的,可能会随时消失
-
不可迭代
-
没有 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
- 只接受对象(null 除外)和 Symbol 值作为键名,不接受其它类型的值作为键名
- 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 的遍历过程:
- 创建一个指针对象,指向当前数据结构的起始位置,也就是说,遍历器对象本质上就是一个指针对象
- 第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员
- 第二次调用指针对象的 next 方法,可以将指针指向数据结构的第二个成员
- 不断调用指针对象的 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 接口的数据结构有以下几种:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- 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 关键字,将函数挂起,为了改变执行流提供了可能,同时为了做异步编程提供了方案
两个特征:
- function 关键字与函数名之间有一个小星号(*)
- 函数体内部使用 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'
参考文献
- ES6 阮一峰教程:ES6 入门教程 - ECMAScript 6入门 (ruanyifeng.com)
- MDN 教程:[JavaScript (英语) |MDN 系列 (mozilla.org)](