ES6
es6 最好的学习方式应该是跟着阮一峰的《ECMAScript 6 入门教程》一路敲过去.
set
set 结构类似数组
set 的值都是唯一的, 内部元素都是强类型, 会进行类型检查
可以使用 set 快速求得数组的合集, 差集, 并集
方法
new Set(Array|具有iterable接口的其他数据结构)
创建Set.has(value)
判断这个 set 合集中是否存在某个值,返回 Boolean 值Set.add()
给 set 添加一个成员Set.delete()
删除某个值,返回布偶值,表示是否删除成功Set.size
set 当前成员数,size 不是函数不用()Set.clear()
清除所有成员,没有返回值
实用案例
javascript
// set去重Array
[...new Set(arr)]
// set去重String
[...new Set(str)].join('')
map
- map 结构类似对象,是一个键值对的集合
- 区别是 map 的
键
不限于字符串, 各种类型的值都可以当键(包括对象). - 方法
new Map([[key, value], [key, value]])
创建xx.set(key, value)
给 map 合集设置键值对xx.get(key)
通过键名去匹配获取对应的值xx.has(key)
判断是否存在这个键的属性xx.size
set 当前成员数,size 不是函数不用()xx.delete()
删除某个值,返回布偶值,表示是否删除成功
set 和 map 都有的 4 个遍历方法
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每个成员
Proxy
proxy
可以在目标对象架设一层 拦截 ,外界对该对象的访问,都会先通过这层拦截,因此可以对外界的访问进行过滤和改写.Proxy
代理的情况下,目标对象内部的this
关键字指向Proxy
代理,目标对象的this
指向如果发生变化可能导致Proxy
无法代理目标对象!Proxy
拦截函数内部的this
指向是handler
对象.示例->new Proxy(target, handler);
Proxy 和 Object.defineProperty 的区别:
Object.defineProperty
只能监听对象,不能监听数组的变化,无法触发 pop,push,unshift,shift,reverse,sort,splice(vue 的 2.x 版本可以触发 splice,reverse 是因为这几个方法被重写了),而proxy
都可以监听Object.defineProperty
必须遍历对象的每个属性,只能劫持当前对象属性,如果要深度劫持,必须要深层次遍历嵌套的对象.
基础使用
javascript
// set/get的函数参数,最后一个receiver都是可选,receiver指向proxy对象
let obj = new Proxy(
{},
{
// 目标对象{},设置时的健名(属性名), 设置时的值, proxy对象
set(target, propKey, value, receiver) {
console.log("set-target:", target);
console.log("set-propKey:", propKey);
console.log("set-value:", value);
console.log("set-receiver:", receiver);
return Reflect.set(target, propKey, value, receiver);
},
// 目标对象{},获取时的健名(属性名), proxy对象
get(target, propKey, receiver) {
console.log("get-target:", target);
console.log("get-propKey:", propKey);
console.log("get-receiver:", receiver);
return Reflect.get(target, propKey, receiver);
},
},
);
obj.name = "hehe";
// set-target: {}
// set-propKey: name
// set-value: hehe
// set-receiver: Proxy {} (这里打印proxy对象)
console.log(obj.name);
// get-target: {name: "hehe"}
// get-propKey: name
// get-receiver: Proxy {name: "hehe"} (这里打印proxy对象)
console.log(obj);
// Proxy {name: "hehe"} (这里打印proxy对象)
proxy 相对于 Object.defineProperty 有哪些优点?
- 可以监听数组变化
- 可以劫持整个对象
- 操作时不是对原对象操作, 是 new Proxy 返回的一个新对象
- 可以劫持的操作有 13 种
Reflet
- 理解为对象内部方法的一种新写法,
- 可以把 Object 的一些命令式操作变为函数式行为.
- 具体需要查看 es6 阮一峰...
Promise 对象
promise 对象有两个特点:
- 对象不受外界影响. 有三种状态:
pending
fulfilled
和rejected
,只有异步操作的结果可以改变这个状态 - 一旦状态改变,不会有再次变更 (promise 状态改变只有两种
pending->fulfilled
或pending->rejected
)
promise 也有缺点:
- 首先是一旦创建它就会立即执行,无法中途取消.
- 其次就是如果不设置回调函数,promise 内部抛出的错误是不会反应到外部的.
- 当处于 pending 状态时,无法得知目前到哪个阶段了(刚开始还是即将完成?)
应用场景-异步操作
加载图片
javascript
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
基本用法
javascript
const promise = new Promise((resolve, reject) => {
// ...进行异步操作
if (true) {
// 根据异步结果更改promise状态
resolve();
} else {
reject();
}
});
promise.then(
resolveResult => {
console.log(resolveResult);
},
rejectErr => {
console.log(rejectErr);
},
);
Promise.then
- Promise 的实例具有
then
方法(then
方法定义在原型对象Promise.prototype
上),then
方法的两个参数分别是resolved
和rejected
状态的回调函数 - 每个
then
方法返回的都是新的 promise 实例,可以用链式写法,每个then
都是前面then
的返回值(前面 then 的返回值,还有可能是 promise 对象,既有异步操作!)
Promise.catch
Promise.prototype.catch()
方法是.then((null/undefined其中一个), rejection)
的别名.rejected
状态会调用catch
方法的回调函数,另外then
方法的回调函数如果抛出了错误也会触发catch
- promise 对象的错误具有
冒泡性质
会一直向后传递,直到被捕获,也就是说总在下一个catch
语句捕获, 实际使用一般不要在then
方法的第二个参数定义 reject 状态的回调函数,而是总是使用catch
方法 - 如果不使用
catch
方法指定错误回调函数,promise 对象抛出的错误不会传递到外层代码->不会有任何反应 catch
后可接then
方法,如果前面没抛出错误会跳过catch
执行后面的then
,后面then
的抛出的错误跟前面的catch
无关,最后还需要一个catch
捕获错误.catch
方法中还能再抛出错误.
Promise.finally
- 不管 promise 的状态,在执行完
then
和catch
方法以后都会执行finally
回调函数 finally
函数不接受任何参数,也就是说这个函数里的操作必须是不依赖promise
执行结果的.
Promise.all
all
方法用于将多个 promise 实例,包装成一个新的 promise 实例.接收一个数组(或 Iterator 接口)作为参数.且数组每项需要是 promise 实例,如果不是,默认会调用 promise.resolve 方法将参数转为 promise 实例,后进一步处理.const p = Promise.all()
方法的返回值状态分两种情况- 数组每项返回值都为
fulfilled
状态,p
的状态才是fulfilled
,返回值组成数组给到p
的回调函数 - 数组每项中有一个
rejected
状态,p
的状态就会变成rejected
,第一个被reject
的值给到回调函数
- 数组每项返回值都为
all
方法的数组参数(promise 对象)如果有自己的catch
方法,不会触发all
函数的catch
方法,因为数组实例参数的catch
函数执行完后返回的是一个新的 promise 实例,也会是resolved
Promise.race
- 跟
all
方法类似,不同的是,数组参数只要有一个改变状态,p
就会跟着改变状态.并返回改变状态实例的那个值给 p 的回调函数
Promise.any
- 只要有一个参数实例变成
fulfilled
状态,包装实例就会变成fulfilled
状态,所有参数实例变成rejected
状态 ,包装实例就会变成rejected
状态 - 跟
race
方法很像,但不会因为某个promise
变成rejected
状态而结束!
Promise.allSettled
- 跟
all
方法类似,但只有数组所有实例都返回结果,才会结束, 此方法主要用于确保所有异步操作都已结束
Promise.resolve
- 将现有对象转为 promise 对象,状态为
rejected
实现一个 sleep 函数,可以从 Promise,Generator,Async/Await 考虑
- Promise
javascript
function sleep(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// ...这里开始你的骚操作
resolve();
}, ms);
});
}
async await
- async 语句往下的其他语句都会变成
微任务
最新提案(尚未进入标准、但很有希望的最新提案)
do 表达式
- 本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。
do
表达式的逻辑非常简单:封装的是什么,就会返回什么。do
块级作用域提供了单独的作用域,内部操作可以与全局作用域隔绝。do
表达式的好处是可以封装多个语句,让程序更加模块化,就像乐高积木那样一块块拼装起来。do
表达式在 jsx 语法上非常好用,可以代替逻辑复杂,可读性差的三元运算符 去拼接组件等.
javascript
// 等同于 <表达式>
do { <表达式>; }
// 等同于 <语句>
do { <语句> }
// 下面代码的本质,就是根据函数foo的执行结果,调用不同的函数,将返回结果赋给变量x。
// 使用do表达式,就将这个操作的意图表达得非常简洁清晰。
let x = do {
if (foo()) { f() }
else if (bar()) { g() }
else { h() }
};
throw 表达式
- JavaScript 语法规定
throw
是一个命令,用来抛出错误,不能用于表达式之中。
javascript
// console.log的参数必须是一个表达式,是throw语句会报错
console.log(throw new Error());
- 新提案是允许 throw 用于表达式
javascript
// 参数的默认值
function save(filename = throw new TypeError("Argument required")) {}
// 逻辑表达式
class Product {
get id() {
return this._id;
}
set id(value) {
this._id = value || throw new Error("Invalid value");
}
}
// 条件表达式
// 箭头函数的返回值
管道符
- 新提案,让 JavaScript 也拥有管道机制。
javascript
// JavaScript管道运算符: |>
// ----------------------------- 基础使用 -----------------------------
x |> f // 等同于f(x)
// ----------------------------- 链式表达式 -----------------------------
function doubleSay (str) {
return str + ", " + str;
}
function capitalize (str) {
return str[0].toUpperCase() + str.substring(1);
}
function exclaim (str) {
return str + '!';
}
'hello'
|> doubleSay
|> capitalize
|> exclaim
// "Hello, hello!"
// ----------------------------- 支持await函数 -----------------------------
const userAge = userId |> await fetchUserById |> getAgeFromUser;
// 等同于
const userAge = getAgeFromUser(await fetchUserById(userId));
双冒号运算符
- 函数绑定运算符是并排的两个冒号(
::
),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this
对象),绑定到右边的函数上面.
javascript
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
- 如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
javascript
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
let log = ::console.log;
// 等同于
var log = console.log.bind(console);
- 如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法。
javascript
import { map, takeWhile, forEach } from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));
?.(链式判断运算符)
- 短路机制,
?.
运算符相当于一种短路机制,只要不满足条件,就不再往下执行。
javascript
delete obj?.name;
// obj为假则不执行,为真则删除obj.name属性.
??运算符
- 理解跟逻辑或||类似,不同的是,逻辑或不能区分出
0
false
''
??
运算符找到第一个不为null
或undefined
的值
javascript
let num = 0;
num ?? 100; // 0
num || 100; // 100
false ?? 100; // false
false || 100; // 100
"" ?? 100; // ''
"" || 100; // 100