ES6新特性
(一)变量的解构赋值
ES6允许按照一定模式,从不同类型数据中提取值,然后对变量进行赋值,这杯称为「解构赋值」。
数组的解构赋值
let [a, b, c] = [1, 2, 3]
可以从数组中提取值,按照对应位置,对变量赋值。
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let {name,age}={age:10,name:"小花"}
console.log(name,age);
输出:小花 10
let x = 1;
let y = 2;
[x,y]=[y,x]
console.log(x,y);
输出:2 1
对象的解构也可以指定默认值:
let {name,age=10}={name:"小花"}
console.log(name,age);
输出:小花 10
函数参数的解构赋值
function add([x, y]){
return x + y
}
add([1, 2]) // 3
(二)展开运算符
展开运算符,也叫扩展运算符,写法:三个点(...)
(1)展开字符串
let str = 'app'
console.log(...str) // a p p,将字符串展开成一个个的单个字符
console.log([...str]) // ["a", "p", "p"]
console.log([...str].reverse().join('')) // ppa,将字符串倒序
(2)展开数组
let arr=["a","b","c"]
console.log(...arr);
输出:a b c
应用场景:
1.数组浅拷贝
let arr=["a","b","c"]
let arr2=[...arr]
2.数组合并
let arr=["a","b","c"]
let arr1=[1,2,3,4,5]
let arr2=[...arr,...arr1,"小花"]
console.log(arr2);
输出: ['a', 'b', 'c', 1, 2, 3, 4, 5, '小花']
3.伪数组转为真数组
function sum() {
console.log([...arguments]) // [1, 2, 3, 4, 5]
}
sum(1, 2, 3, 4, 5)
(3)展开对象
应用场景:
1.对象浅拷贝
let obj={name:"小花",age:19}
let obj1={...obj}
2.对象的合并
let obj={name:"小花",age:19}
let obj1={sex:1}
let obj2={love:"读书",...obj,...obj1}
console.log(obj2);
输出:{love: '读书', name: '小花', age: 19, sex: 1}
(三)模板字符串
模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量:
let obj={name:"小花",age:19}
let str=`我叫${obj.name},今年${obj.age}`
console.log(str);
输出:我叫小花,今年19
(四)对象的简洁表示
// ES5 写法
let obj1 = {
name: name,
show: function() {
console.log('我是show函数')
}
}
// ES6 写法
let obj2 = {
name, // key: value,如果 key 和 value 相同,省略
show() { // 省略 : function
console.log('我是show函数')
}
}
(五)class
// 类
class Person {
// 构造器
constructor(name, age) {
this.name = name
this.age = age
}
// 原型方法
show() {
console.log('我是原型方法')
}
// 静态方法
static say() {
console.log('我是静态方法')
}
// 创建实例对象
let yuan = new Person('小花', 10)
注意:(1)constructor方法:constructor方法就相当于ES5写法的构造函数,该方法是类的默认方法,通过new命令生成实例对象时会自动调用它。即便没有显示地定义它,一个空的constructor方法也会被默认添加;
(2)静态方法:ES6的class中通过在方法前加static关键字来表明该方法是一个静态方法,从而可以直接在类上调用;
es6继承改写ex5寄生组合式继承
ES5
function Father(name, age) { // 父类
this.name = name
this.age = age
this.skills = ['耍', '睡觉', '吃饭']
}
Father.prototype.getFatherSkill = function() { // 父类方法-获取父类技能
return this.skills
}
function Son(name, age) { // 子类
// 1.调用父类构造函数,从而继承父类的属性
Father.call(this, name, age)
// 可以继续定义子类的属性
this.love = '读书'
}
// 2.继承父类原型上的方法
Son.prototype = Object.create(Father.prototype)
// 3.完善子类的 constructor 指向
Son.prototype.constructor = Son
// 调用
const son = new Son('小花', 5)
son.skills.push('装死')
console.log(son.skills) // ['耍', '睡觉', '吃饭','装死']
const twoson = new Son('小于', 2)
console.log(twoson.skills) // ['耍', '睡觉', '吃饭']
ES6
class Father { // 父类
constructor(name, age) {
this.name = name
this.age = age
}
skills = ['耍', '睡觉', '吃饭']
getFatherSkill() { // 原型方法
console.log(this.skills)
}
}
class Son extends Father { // 子类
constructor(name, age) {
// ES6 要求,子类的构造函数必须执行一次super函数。
super(name, age) // 调用父类构造函数。
}
love = '读书'
}
const son = new Son('小花', 5)
console.log(son.skills) // ['耍', '睡觉', '吃饭','装死']
son.skills.push('装死')
const twoson = new Son('小于', 6)
console.log(twoson.skills) // ['耍', '睡觉', '吃饭']
(六)ES6模块化
ES6之前没有模块化,但需要模块化,需要它主要是要解决2个问题:
(1)命名冲突;
(2)需要各个代码块职责分离,高内聚、低耦合,这样维护性和可读性更高;
主要的优势是「静态加载」
语法
模块功能主要由两个命令构成:import和export:
(1)import命令用于导入其他模块提供的功能;
(2)export命令用于规定模块的对外接口;
单文件导出
导出:
export 要导出的内容1
export 要导出的内容2
export 要导出的内容3
导入:
import { 导入的内容1, 导入的内容2, 导入的内容3 } from '文件模块路径'
整个文件导出
导出:
export default 要导出的内容
导入:
import 变量 from '文件模块路径'
示例:
a.js:这个模块负责导出
const num = 100
const name = '小明'
const fn = () => {}
const arr = ['a', 'b', 'c']
export default {
num,
name,
fn,
arr
}
b.js:这个模块负责导入,并使用
import obj from './a.js'
const { num, name, fn, arr } = obj
console.log(num, name, fn, arr)
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量,加载效率要比运行时高得多.
(七)Set和Map数据结构
Set和Map是ES6新增的数据结构(或者叫类,因为它们也是构造函数)。 Set类似于数组,但Set里的元素是唯一的,不会重复。
const s = new Set([1, 2, 3, 3, 2, 1])
const arr = [...s] // Set 也可以使用展开运算符!
console.log(arr) // [1, 2, 3]
Set实例的常用属性:
(1).size:实例的元素数;
Set实例的常用方法:
(1).add(value):添加元素
(2).delete(value):删除元素
(3).clear():清除所有元素
(4).has(value):判断是否有某个元素
let s = new Set([1, 2, 3, 3, 2, 1])
console.log(s) // {1, 2, 3}
console.log(s.size) // 3
s.add(3) // s 为 {1, 2, 3},已有的值不会被重复添加
s.add(4) // s 为 {1, 2, 3, 4}
console.log(s.has(4)) // true, 判断是否有某个元素
s.clear() // s 为 {}
Map类似于对象,也是键值对的集合,但“键”的范围不仅限于字符串,任意类型值(包括对象)都可以作为键:
// Map 构造函数接收包含表示键值对的嵌套数组
const m = new Map([
['str', 'String'],
[[1, 2, 3], 'Array'],
[{}, 'Object'],
[false, 'Boolean']
])
console.log(m)
// {"str" => "String", Array(3) => "Array", {…} => "Object", false => "Boolean"}
Map实例的常用属性:
(1).size:实例的元素数;
Set实例的常用方法:
(1).set(key, value):设置key所对应的键值,返回整个Map结构。若key已有值,则键值会被更新;
(2).get(key):取key对应的值,若找不到key则返回undefined
(3).delete(key):删除某个键,删除成功返回true,失败返回false
(4).clear():清除所有成员
(5).has(key):判断某个键key是否在Map实例数据结构中
const m = new Map([
[1, 'Number']
])
console.log(m.size) // 1
m.set([], 'Array') // m 为 {1 => "Number", Array(0) => "Array"}
console.log(m.get(1)) // 'Number'
console.log(m.has({})) // false
m.delete(1) // m 为 {Array(0) => "Array"}
m.clear() // m 为 {}
(八)异步编程
同步:代码从上往下依次执行,后面的代码要等到前面的代码执行完之后,才能执行;
异步:代码从上往下依次执行,遇到异步代码,异步代码会先让开,在一边等待,等所有同步代码都执行完,再执行异步代码;
JS中常见的异步场景
(1)定时器
(2)事件处理函数
(3)异步Ajax请求
(4)回调函数
(九)Promise
回调地狱:进行第一次请求,得到用来第二次请求的数据,再进行第二次请求,得到 第三次请求的数据....一旦嵌套超过2层,这“坨”代码就变得非常难以阅读了。这种回调函数中嵌套着回调函数的现象,称之为「回调地狱」。
Promise是用来「管理异步操作」的,用同步的方式来编写异步代码,用来解决回调地狱问题的。
(1)Promise的状态
初始化,状态:pending
当调用resolve(成功),状态:pengding=>fulfilled
当调用reject(失败),状态:pending=>rejected
Promise是一个内置的构造函数,用“new Promise构造函数”的形式来生成Promise实例
// 创建 Promise 的实例对象
new Promise((resolve, reject) => {
// 可以写很多逻辑代码,同步或者异步都可以
if (true) { // 假设异步操作成功
// 改变状态:进行中 pending -> 已成功 fulfilled
resolve('成功的数据')
} else {
// 改变状态:进行中 pending -> 已失败 rejected
reject('失败的数据')
}
})
.then((data) => {
// data 是上一步 resolve 传入的数据
console.log(data)
})
.catch((err) => {
// 上一步 reject 传入的数据,也可以捕获之前所有 then() 方法执行的错误
console.log(err)
})
Promise构造函数接收一个函数作为参数,该函数的两个参数又是函数,分别是resolve和reject: (1)resolve()函数:改变Promise对象状态,进行中 -> 已成功,并将异步操作结果作为参数传进去; (2)reject()函数:改变Promise对象状态,进行中 -> 已失败,将异步操作的报错作为参数传进去;
Promise实例生成后,可以用then()方法指定已成功和已失败时的回调: 注意,虽然then()函数的第2个参数和catch()都能拿到已失败的数据,但一般推荐用catch,因为catch能够捕获它之间所有出现的then中的错误。
用Promise来改写回调地狱的例子
new Promise((resolve) => {
$.ajax({
url: 'url1',
type: 'get',
success: (data) => {
// 请求成功时,调用 resolve() 将状态改为已成功,并传递成功数据
resolve(data)
}
})
})
.then((data) => { // 状态变为已成功时执行回调,拿到成功数据
const { id } = data
return new Promise((resolve) => { // 返回一个新的 Promise,来使结果继续传递
$.ajax({
url: 'url2',
type: 'get',
data: { id },
success: (data) => {
resolve(data)
}
})
})
})
.then((data) => {
const { code } = data
$.ajax({
url: 'url3',
type: 'get',
data: { code },
success: (data) => {
console.log('终于拿到', data)
}
})
})
// 封装请求数据的逻辑
const getData = (url, params = {}) => {
return new Promise((resolve) => {
$.ajax({
url,
type: 'get',
data: params,
success: (data) => {
// 请求成功后把数据传递给下一个 .then()
resolve(data)
}
})
})
}
getData('url1')
.then((data) => getData('url2', { id: data.id }))
.then((data) => getData('url3', { code: data.code }))
.then((data) => {
console.log('终于拿到', data)
})
(十)async与await
async关键字写在函数前面,表示该函数中有异步操作:await只能使用在async修饰的函数中,用于等待异步操作结果(一般等待的是一个Promise的状态改变)。
let timer=()=>new Promise((resolve)=>{
setTimeout(()=>{
resolve("异步操作1")
console.log("异步操作");
},1000)
}
)
async function test(){
let res=await timer()
console.log(res);
console.log(234);
}
test()
console.log(1);
输出:1;异步操作;异步操作1;234
function timer(){
return new Promise((resolve)=>{
setTimeout(()=>{
console.log("异步操作");
resolve("异步操作1")
},1000)
})
}
async function test(){
let res=await timer()
console.log(res);
console.log(234);
}
test()
console.log(1);
输出:1;异步操作;异步操作1;234