学习总结常用的ES6语法(一)
七、 module
ES6 中模块化语法有两种形式
- 如果是输出一个唯一的对象,使用export default
// 创建 util1.js 文件,内容如下
export default {// 导出
a:100
}
// 创建 index.js 文件,内容如下
import obj from './util1.js'// 引入
console.log(obj)
- 如果想要输出许多个对象,就不能使用 default,且import时候加 { … },
// 创建util2.js 文件,内容如下
export function fn1() {// 导出
alert('fn1')
}
export function fn2() {
alert('fn2')
}
// 创建 index.js 文件,内容如下
import {fn1,fn2} from './util2.js'// 引入
fn1()
fn2()
模块化 :主要是用来抽离公共代码,隔离作用域,避免变量冲突等
1.自执行函数;在一个单独的函数作用域中执行代码,避免变量冲突
2.闭包的写法;函数a里面的函数b ,可以访问函数a声明的变量
3.各种过度的规范:AMD CMD UMD
*AMD 使用 require js 来编写模块化,特点; 依赖必须提前声明好
*CMD 使用 seaJs commonJs:nodejs 中自带的模块化来编写模块化,特点; 支持动态引入依赖文件
*UMD 兼容了AMD commonJs 模块化语法
4. webpack(require.ensure):wabpack 2.x 版本中的代码分割。
5. import from: ES6 引入的模块化,支持import 来引入另一个js
八、Proxy
如果你平时有关注 Vue 的进展的话,可能已经知道了在 Vue3.0 中将会通过
Proxy
来替换原本的Object.defineProperty
来实现数据响应式。
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
let p = new Proxy(target, handler)
target
代表需要添加代理的对象,handler
用来自定义对象中的操作,比如可以用来自定义set
或者get
函数。
//接下来我们通过 `Proxy` 来实现一个数据响应式
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
setBind(value, property)
return Reflect.set(target, property, value)
}
}
return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
obj,
(v, property) => {
console.log(`监听到属性${property}改变为${v}`)
},
(target, property) => {
console.log(`'${property}' = ${target[property]}`)
}
)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2
在上述代码中,我们通过自定义
set
和get
函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在get
中收集依赖,在set
派发更新,之所以 Vue3.0 要使用Proxy
替换原本的 API 原因在于Proxy
无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是Proxy
可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。
九、Class 类定义
class 其实一直是 JS 的关键字(保留字),但是一直没有正式使用,直到 ES6 。 ES6 的 class 就是取代之前构造函数初始化对象的形式,从语法上更加符合面向对象的写法。
//JS 构造函数的写法
function MathHandle(x, y) {
this.x = x;
this.y = y;
}
MathHandle.prototype.add = function () {
return this.x + this.y;
};
var m = new MathHandle(1, 2);
console.log(m.add())
//用 ES6 class 的写法
class MathHandle {
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
return this.x + this.y;
}
}
const m = new MathHandle(1, 2);
console.log(m.add())
注意以下几点,全都是关于 class 语法的:
- class 是一种新的语法形式,是
class Name {...}
这种形式,和函数的写法完全不一样- 两者对比,构造函数函数体的内容要放在 class 中的
constructor
函数中,constructor
即构造器,初始化实例时默认执行- class 中函数的写法是
add() {...}
这种形式,并没有function
关键字
使用 class 来实现继承就更加简单了,至少比构造函数实现继承简单很多。
//JS 构造函数实现继承
// 动物
function Animal() {
this.eat = function () {
console.log('animal eat')
}
}
// 狗
function Dog() {
this.bark = function () {
console.log('dog bark')
}
}
Dog.prototype = new Animal()
// 哈士奇
var hashiqi = new Dog()
//ES6 class 实现继承
class Animal {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat`)
}
}
class Dog extends Animal {
constructor(name) {
super(name)
this.name = name
}
say() {
console.log(`${this.name} say`)
}
}
const dog = new Dog('哈士奇')
dog.say()
dog.eat()
注意以下两点:
- 使用
extends
即可实现继承,更加符合经典面向对象语言的写法,如 Java- 子类的
constructor
一定要执行super()
,以调用父类的constructor
十、Set与Map
Set 和 Map 都是 ES6 中新增的数据结构,是对当前 JS 数组和对象这两种重要数据结构的扩展。
由于是新增的数据结构,目前尚未被大规模使用,但是作为前端程序员,提前了解是必须做到的。
先总结一下两者最关键的地方:
- Set 类似于数组,但数组可以允许元素重复,Set 不允许元素重复;
- Map 类似于对象,但普通对象的 key 必须是字符串或者数字,而 Map 的 key 可以是任何数据类型.
Set
Set 实例不允许元素有重复,可以通过以下示例证明。可以通过一个数组初始化一个 Set 实例,或者通过
add
添加元素,元素不能重复,重复的会被忽略。
// 例1
const set = new Set([1, 2, 3, 4, 4]);
console.log(set) // Set(4) {1, 2, 3, 4}
// 例2
const set = new Set();
[2, 3, 5, 4, 5, 8, 8].forEach(item => set.add(item));
for (let item of set) {
console.log(item);
}
// 2 3 5 4 8
Set 实例的属性和方法有:
size
:获取元素数量。add(value)
:添加元素,返回 Set 实例本身。delete(value)
:删除元素,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否是 Set 实例的元素。clear()
:清除所有元素,没有返回值。
const s = new Set();
s.add(1).add(2).add(2); // 添加元素
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
s.clear();
console.log(s); // Set(0) {}
Set 实例的遍历,可使用如下方法:
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。不过由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys()
和values()
返回结果一致。entries()
:返回键值对的遍历器。forEach()
:使用回调函数遍历每个成员。
let set = new Set(['aaa', 'bbb', 'ccc']);
for (let item of set.keys()) {
console.log(item);
}
// aaa
// bbb
// ccc
for (let item of set.values()) {
console.log(item);
}
// aaa
// bbb
// ccc
for (let item of set.entries()) {
console.log(item);
}
// ["aaa", "aaa"]
// ["bbb", "bbb"]
// ["ccc", "ccc"]
set.forEach((value, key) => console.log(key + ' : ' + value))
// aaa : aaa
// bbb : bbb
// ccc : ccc
Map
Map 的用法和普通对象基本一致,先看一下它能用非字符串或者数字作为 key 的特性。
const map = new Map();
const obj = {p: 'Hello World'};
map.set(obj, 'OK')
map.get(obj) // "OK"
map.has(obj) // true
map.delete(obj) // true
map.has(obj) // false
需要使用
new Map()
初始化一个实例,上面代码中set
get
has
delete
顾名即可思义。
其中,map.set(obj, 'OK')
就是用对象作为的 key (不光可以是对象,任何数据类型都可以),并且后面通过map.get(obj)
正确获取了。
Map 实例的属性和方法如下:
size
:获取成员的数量set
:设置成员 key 和 valueget
:获取成员属性值has
:判断成员是否存在delete
:删除成员clear
:清空所有
const map = new Map();
map.set('aaa', 100);
map.set('bbb', 200);
map.size // 2
map.get('aaa') // 100
map.has('aaa') // true
map.delete('aaa')
map.has('aaa') // false
map.clear()
Map 实例的遍历方法有:
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回所有成员的遍历器。forEach()
:遍历 Map 的所有成员。
set('aaa', 100);
map.set('bbb', 200);
for (let key of map.keys()) {
console.log(key);
}
// "aaa"
// "bbb"
for (let value of map.values()) {
console.log(value);
}
// 100
// 200
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// aaa 100
// bbb 200
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// aaa 100
// bbb 200const map = new Map();
map.set('aaa', 100);
map.set('bbb', 200);
for (let key of map.keys()) {
console.log(key);
}
// "aaa"
// "bbb"
for (let value of map.values()) {
console.log(value);
}
// 100
// 200
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// aaa 100
// bbb 200
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// aaa 100
// bbb 200
十一、Promise对象
Promise
是 CommonJS 提出来的这一种规范,有多个版本,在 ES6 当中已经纳入规范,原生支持 Promise 对象,非 ES6 环境可以用类似 Bluebird、Q 这类库来支持。
Promise
可以将回调变成链式调用写法,流程更加清晰,代码更加优雅。
简单归纳下 Promise:三个状态、两个过程、一个方法,快速记忆方法:3-2-1
三个状态:pending
、fulfilled
、rejected
两个过程:1. pending→fulfilled(resolve); 2. pending→rejected(reject)
一个方法:then
// Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
异步编程中的结果怎么拿 —>promise
// 使用promise的步骤:
// a.构造函数
// b.return 一个对象
// c.对象有个 .then()
// d. .then(()=>{})有传一个回调函数
// e.promise((resolve))
// f.resolve() 一调用就会触发外部的 .then的回调
// 技巧: 一个异步就是一个Promise对象,调用resolve传递结果
var p = new Promise((resolve,reject)=>{
// e.promise((resolve))
setTimeout(() => {
var sum = 100
resolve(sum)
}, 0);
})
// c.对象有个 .then()
// d. .then(()=>{})有传一个回调函数
p.then((res)=>{
// f.resolve() 一调用就会触发外部的 .then的回调
console.log('.then 执行了--- ',res);
})
//优点:解决了回调地狱的问题
ajax('XXX1')
.then(res => {
// 操作逻辑
return ajax('XXX2')
}).then(res => {
// 操作逻辑
return ajax('XXX3')
}).then(res => {
// 操作逻辑
})
// promise问题:如果异步很多就会有很多个promise对象,无法取消Promise
// 使用.then 获取结果就会很麻烦 ,错误需要通过回调函数来捕获
十二、async函数
async、await 是异步的终极解决方案
async 表示函数里有异步操作, await必须在async函数的上下文中,紧跟在后面的表达式需要等待结果(主要的意图是用来等待 Promise 对象的状态被 resolved,也就是为了异步操作同步化);
async 函数的返回值是 Promise 对象,可以使用then方法指定下一步操作(只有 async 函数内部的异步操作全部执行完,除非遇到 return 语句或者抛出错误,才会执行 then 方法指定的回调函数)
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
//解决办法:把 await 命令放在 try…catch 代码块中
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
//多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
// 写法一(推荐)
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
// 优秀写法
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc)); // 发送post请求
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
//下面来看一个使用 await 的例子:
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
//首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
//因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
//同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10
//上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。