从简到难,重新学习ES6(下)
开始
快速入门ES6的最后一篇,也是最难的一篇。
附上思维导图:
Symbol
ES6 引入了一种新的原始数据类型 Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型
,是一种类似于字符串的数据类型。回顾一下,JavaScript有number
,string
,boolean
,null
,undefined
,object
六种数据类型,加上symbol
就是七种基本数据类型。
它有以下特点:
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算
- Symbol 定义 的 对象属 性 不能 使 用 for…in /of循 环遍 历 ,但 是可 以 使 用
Reflect.ownKeys
或Object.getOwnPropertySymbols
来获取对象的所有键名
Symbol的基本使用
Symbol
的创建
let s = Symbol();
console.log(s);
let s2 = Symbol("我是symbol类型");
console.log(s === s2);// false
let s3 = Symbol("我是symbol类型")
console.log(s2===s3)// false
// Symbol.for()
// 也可以创建Symbol类型数据,这样的话,就方便我们通过描述(标识)区分开不同的Symbol了
let s4 = Symbol.for("sym");
let s5 = Symbol.for("sym");
console.log(s4 === s5); // true
不能参与运算
let s = Symbol()
let res = s + 100// 报错
res = s > 200// 报错
res = s + "symbol"// 报错
获得symbol
属性
let sy = Symbol();
const user = {
name: "张三",
age: 18,
};
user[sy] = "symbol";
console.log(user);
for (let key in user) {
console.log(key);// name,age
}
console.log(Object.getOwnPropertySymbols(user));// [Symbol()]
console.log(Reflect.ownKeys(user));// ['name', 'age', Symbol()]
Symbol
的值是唯一的,我们可以往对象添加属性,解决命名冲突的问题
let game = {
name: "魂斗罗",
up: function () {},
down: function () {},
//方式一
[Symbol("手雷")]: function () {
console.log("扔手雷");
},
};
let methods = {
up: Symbol(),
down: Symbol(),
};
// 方式二
game[methods.up] = function () {
console.log("我会跳");
};
game[methods.down] = function () {
console.log("我会蹲");
};
console.log(game);
game[methods.up]();
Symbol内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11
个内置的 Symbol 值,指向语言内部使用的方 法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。特别的, Symbol内置值的使用,都是作为某个对象类型的属性去使用。
内置Symbol的值 | 调用时机 |
---|---|
Symbol.hasInstance | 当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 对象的 Symbol.isConcatSpreadable 属性等于的是一个 布尔值,表示该对象用于 Array.prototype.concat()时, 是否可以展开。 |
Symbol.species | 创建衍生对象时,会使用该属性 |
Symbol.match | 当执行 str.match(myObject) 时,如果该属性存在,会 调用它,返回该方法的返回值。 |
Symbol.replace | 当该对象被 str.replace(myObject)方法调用时,会返回 该方法的返回值。 |
Symbol.search | 当该对象被 str.search (myObject)方法调用时,会返回 该方法的返回值。 |
Symbol.split | 当该对象被 str.split(myObject)方法调用时,会返回该 方法的返回值。 |
Symbol.iterator | 对象进行 for...of 循环时,会调用 Symbol.iterator 方法, 返回该对象的默认遍历器 |
Symbol.toPrimitive | 该对象被转为原始类型的值时,会调用这个方法,返 回该对象对应的原始类型值。 |
Symbol. toStringTag | 在该对象上面调用 toString 方法时,返回该方法的返 回值 |
Symbol. unscopables | 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。 |
// 检测类型
class Person{
static [Symbol.hasInstance](param){
console.log(param);
console.log("我检测类型了");
return false;
}
}
let o = {};
console.log(o instanceof Person);
// 合并数组:false数组不可展开,true可展开
const arr = [1,2,3];
const arr2 = [4,5,6];
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr.concat(arr2));
迭代器(Iterator)
迭代器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数 据结构只要部署 Iterator 接口,就可以完成遍历操作。ES6 创造了一种新的遍历命令 for...of
循环,Iterator 接口主要供 for...of 消费。
原生具备 iterator 接口的数据(可用 for of 遍历):
-
Array
-
Arguments
-
Set
-
Map
-
String
-
TypedArray
-
NodeList
工作原理:
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
- 每调用 next 方法返回一个包含
value
和done
属性的对象
什么时候用:需要自定义遍历数据的时候,要想到迭代器
const arr = ["red", "green", "blue","yellow"]
// 使用 for...of 遍历数组
for(let v of arr){
console.log(v)
}
let iterator = arr[Symbol.iterator]()
// 调用对象的next方法
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
// 重新初始化对象,指针也会重新回到最前面
let iterator1 = arr[Symbol.iterator]();
console.log(iterator1.next());
迭代器自定义遍历对象:
const nb = {
name: "清北班",
stu: ["张三", "李四", "王五"],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.stu.length) {
return { value: this.stu[index++], done: false };
} else {
return { value: undefined, done: true };
}
},
};
},
};
for (let v of nb) {
console.log(v);// 张三 李四 王五
}
生成器(Generator)
生成器Generator
函数是 ES6 提供的一种异步编程
解决方案,语法行为与传统函数完全不同。
注意点:
- 函数的声明带一个
*
号 - 函数不能直接执行,用
yield
分割执行区域,next()
依次执行,每次返回一个对象 next()
方法是可以传入参数的,传入的参数作为上一条yield
的返回结果
观察下面的执行:
function* gen() {
console.log(1);
yield "代码片1";
console.log(2);
yield "代码片2";
console.log(3);
yield "代码片3";
console.log(4);
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
for (let v of gen()) {
console.log(v)
}
生成器函数的参数传递:
function* gen(arg){
console.log(arg);
let one = yield 1;
console.log(one);
let two = yield 2;
console.log(two);
let three = yield 3;
console.log(three);
}
let iterator = gen("A");
console.log(iterator.next()); // 会执行yield 1;
// next()方法是可以传入参数的,传入的参数作为第一条yield的返回结果
console.log(iterator.next("B")); // 会执行yield 2;
console.log(iterator.next("C")); // 会执行yield 3;
console.log(iterator.next("D")); // 继续往后走,未定义;
生成器实例:异步获取用户数据
// 模拟获取: 用户数据 订单数据 商品数据
function getUsers(){
setTimeout(()=>{
let data = "用户数据";
iterator.next(data); // 这里将data传入
},1000);
}
function getOrders(){
setTimeout(()=>{
let data = "订单数据";
iterator.next(data); // 这里将data传入
},1000);
}
function getGoods(){
setTimeout(()=>{
let data = "商品数据";
iterator.next(data); // 这里将data传入
},1000);
}
function* gen(){
let users = yield getUsers();
console.log(users);
let orders = yield getOrders();
console.log(orders);
let goods = yield getGoods();
console.log(goods);
}
let iterator = gen();
iterator.next()
Promise
Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作 并可以获取其成功或失败的结果。Promise在开发中用非常多,在面试中也是常考点,显得尤为重要。
知识点:
-
Promise是一个构造函数,并且一创建就会执行
-
Promise的三种状态:
pending
(等待态),fulfiled
(成功态),rejected
(失败态) -
Promise接收一个参数:
函数
,并且这个函数需要传入两个参数resolve
,reject
,也是函数 -
Promise.prototype上的
.then()
:预先指定成功和失败的回调函数 -
Promise.prototype上的
.catch()
:捕获异步操作中的错误 -
Promise.finally()
、Promise.all()
、Promise.race()
等
回调地狱
多层回调函数的相互嵌套,就形成了回调地狱,回调地狱造成代码耦合性太强,难以维护
,深嵌套,代码可读性差,不美观
。如示例代码:
// 回调地狱
setTimeout(() => {
console.log("开始第一件事")
setTimeout(() => {
console.log("开始第二件事")
setTimeout(() => {
console.log("开始第三件事")
}, 1000)
}, 1000)
}, 1000)
Promise使用
我们再来看看用Promise写的
const p = new Promise((resolve,reject) => {// 初始化,状态置为`pending`(等待态)
setTimeout(() => {
let data = "数据";
resolve(data)// 调用resolve,状态置为`fulfiled`(成功态)
let err = "失败了!";
//reject(err) // 调用reject,状态置为`rejected`(失败态)
},1000);
});
// 调用 Promise 对象的then方法,两个参数都为函数,成功的回调必传,失败的回调可传可不传
p.then((res) => { // 成功回调
console.log(res);// 数据
}, (err) => { // 失败回调
console.log(err);// 失败了
});
p.catch((err) => console.log(err))// 在catch中也能捕获错误
实践:用Promise
封装异步读取多个文件(node.js
环境下)
还有Promise.finally()
和``Promise.all()和
Promise.race()`,由于内容比较多,不好展开讲,可以去看一下阮一峰 - ES6 入门教程
async和await
ES8引入了async
和await
,更近一步地简化了Promise
异步操作,可以让异步代码看起来像同步代码一样,在开发中用的也比较多。
注意点:
- 如果使用了
await
,则函数必须被async
所修饰,async函数的返回值为Promise对象 - await右侧表达式一般为Promise对象
演示下async/await
简单使用:
//
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
let data = "数据"
resolve(data)
}, 2000)
})
async function getData() {
const res1 = await p1
console.log(res1)
}
getData()
p1.then(res2 => console.log(res2)) // 执行后,可以发现res1的值和res2一样
说简单点,用async
修饰后可以得到Promise对象的成功回调的值,不需要再用.then()
链式调用,从而简化了Promise的异步操作。
最后
感谢读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正❤️❤️。
推荐文章:
转载自:https://juejin.cn/post/7064507539450232862