ES6简介
ES6,全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版
虽然15年就有正式版本了,但是国内普遍商用是在2018年之后去了,甚至到现在有很多前端仍然搞不懂ES6(都2022年了,兄dei~)
ES6 的出现主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念
目前存在少数低版本浏览器的 JavaScript 是 ES5 版本,大多数的浏览器已经支持 ES6
ES6提供了大量的语法糖,让你写代码的时候简直不要太爽!
你必须要知道的是:现在各企业都普遍使用,不会ES6意味着你很难找到工作,上班了你连别人的代码都看不懂
接下来 就然我们来了解了解ES6及以后
1. 变量
var声明的变量是function scope也就是在函数中可以访问到,并不是在大括号里面声明的,外层就访问不到。let const声明的变量都是block scope块级作用域,也就是在大括号里面可以访问到。
let 和 const 都不允许重复声明。他们两个都不可以在同一个作用域中进行重复声明,比如,在函数中声明的变量跟在函数外声明的同名的变量是不同的。let 定义的变量可以重新赋值,const是不可以的。
const 声明的对象,对象是一个引用类型的值,可以进行修改对象的属性。如果想要定义的对象的属性是不可以改变可以使用es5中的Object.freeze()这个方法:const jelly = Object.freeze(person)
let 跟const都是存在暂时性死区的,也就是说;我们在声明前使用调用变量,会报RefrenceError的错误;而var声明的变量是没有的;这样,我们也可以养成习惯,在使用变量之前,进行声明。
let 与 const 避免了全局变量与顶层对象的属性挂钩。不再像 var 声明的变量一样挂载到 window 对象上;
const 只是不能修改变量所指的内存地址;object.freeze 方法只是浅冻结如果属性还是 object,不会进行冻结
2. 箭头函数
优点:
简明语法
可以隐式返回:也就是可以不使用return关键字进行返回;删除{}大括号。
不绑定this:箭头函数没有自己的this值,他的this值是继承他的父级作用域的,this值是定义的时候就绑定了。
箭头函数也是匿名函数,我们可以通过定义变量,等于箭头函数,当进行调用:
const greet = name => { alert (`hello ${name}`)}
一个函数在他独立运行的时候,也就是说他没有作为对象的方法去调用,也没用call、apply等方法进行绑定;这里的数组里面map的回调函数就是一个独立运行的,所以他的this值是在运行的时候动态绑定的,绑定到了window对象上,所以不会获取到name值
const jelly = {
name: 'xiaoxiao',
bobbies: ['sleeping', 'coding', 'reading'],
print: function () {
this.hobbies.map(function (hobby) {
console.log(`${this.name} loves ${hobby}`)
})
}
}
2.2 箭头函数的局限性:
作为构造函数,一个方法需要绑定到对象:下面的代码。Person对象中使用箭头函数,并没有将this绑定到它本身。所以,这里需要使用原始的函数。下面的给原型绑定方法,同样也是,this指向的是父级的this所以指向window对象;
const Person = (name, point) => {
this.name = name;
this.point = point;
}
const jelly = new Person ('hui', 5);
Person.prototype.updatePoints = () => {
this.point ++;
console.count(this.point)
}
当你真的需要this的时候:下面代码的this指向的是调用addEventListener方法的对象;箭头函数的this是不会绑定的,所以这里的也还是window对象。setTimeout里面应该是使用箭头函数,来邦定this值在该作用域
const button = document.querySelector('.zoom');
button.addEventListener('click', () => {
this.classList.add('in');
setTimeout(() => {
this.classList.remove('in')
}, 2000);
})
需要使用arguments对象:箭头函数中是没有arguments对象的。
const sum = () => {
return Array.from(arguments).reduce((prevSum, value) => prevSum + value,0)
}
箭头函数不能作为构造函数来使用;
this 指向定义时所在的对象,而不是调用时所在的对象
ES6 参数默认值
我们在函数中可以通过下面的方法进行设置默认参数值:
function add (a = 1, b = 5) {
return a + b;
}
当我们需要第一个参数使用默认值的时候,这样add(undeffined, 2)这样,如果add(1)这样调用的话,第二个参数使用默认值
模板字符串
es6中的模板字符串是可以嵌套的,以及在${}中可以直接调用方法。 还有一个标签模板字符串:
function highLight(string, user, topic) {
return 'templa'
}
const user = 'jie';
const topic = 'learn es6';
const sentence = highLight`${user} has commented on your topic ${topic}`
然后我们查看sentence,他就会返回标签模板字符串中返回的东西;
我们可以使用标签模板字符串,去处理我们输入的参数,比如在留言板等功能中,为了防止xss攻击,我们可以通过模板字符串进行过滤用户输入的内容。可以使用第三方的包:DOMPurify进行过滤。
对象解构
我们有一个对象如下:
const Tom = {
name: 'tom',
age: 18,
family: {
mother: 'tom mother',
father: 'tom father'
}
}
我们每次访问的时候,需要进行Tom.name这样进行访问,如果属性有很多的话,就比较麻烦,我们可以这样写:这样去访问他的属性
const Tom = {
name: 'tom',
age: 18,
family: {
mother: 'tom mother',
father: 'tom father'
}
}
const { name, age } =Tom
console.log(name)
console.log(age)
如果我们想要先声明变量,然后进行结构对象,可以如下面这样写:
let name = '';
({ name, age } = Tom);
对象解构也可以进行嵌套,如上面的代码,我们想要访问family属性,我们可以这样写:
const Tom = {
name: 'tom',
age: 18,
family: {
mother: 'tom mother',
father: 'tom father'
}
}
const { father, mother } =Tom.family
console.log(mother)
console.log(father)
我们在解构的时候也可以进行重新命名,类似sql中的as;可以这样写:father: f这样,将father属性赋值给f变量。
const Tom = {
name: 'tom',
age: 18,
family: {
mother: 'tom mother',
father: 'tom father'
}
}
const { father: f, mother } =Tom.family
console.log(mother)
console.log(father)
我们在对象解构的时候,也可以给解构后的变量赋予默认值,如果被解构的对象没有该属性为undefined,就使用我们的默认值,如下代码:sister = 'haove no sister'这样。
const Tom = {
name: 'tom',
age: 18,
family: {
mother: 'tom mother',
father: 'tom father'
}
}
const { father: f, mother, sister = 'haove no sister' } =Tom.family
console.log(mother)
console.log(father)
数组解构
数组的解构基本跟对象的解构类似:下面代码是解构了数组的第一项,跟第二项
const number = ['one', 'two', 'three', 'four'];
const [one, two] = number;
console.log(one, two)
如果我们想回去第一个跟第三个:需要添加一个逗号进行分隔,流出第二个位置。
const number = ['one', 'two', 'three', 'four'];
const [one, , two] = number;
console.log(one, two)
如果想要获取到第一个值,跟后面所有的值,我们可以使用扩展运算符,将剩余项组成一个数组:
const number = ['one', 'two', 'three', 'four'];
const [one, ...other] = number;
console.log(one, other)
rest参数只能使最后一个,也就是说解析后面所有的,不能解析某一部分:const [one, …other, four] = number;这样就会报错。
数组的结构也可以进行赋予默认值:同样相应的值为undefined的时候,才会使用默认的值。
const number = ['one', 'two', 'three'];
const [one, two, three = 'three'] = number;
数组的解构的使用技巧可以用在交换两个变量的值:
let a = 10;
let b = 20;
[a,b] = [b,a];
for of 用法
forEach循环不能终止循环,for in循环,循环的是index值,遍历的是变量的可枚举属性。即使是原型的属性也会进行循环出来。使用for of是循环的属性值:在循环中,也可以使用break进行中断循环。
const number = ['one', 'two', 'three', 'four'];
for (item of number) {
console.log(item)
}
for of循环还可以用于遍历数组的遍历器属性:
const number = ['one', 'two', 'three', 'four'];
for ( let [index, fruit] of number.entries()){
console.log(index, number)
}
for of不支持对象的循环。可以应用于字符串:
const number = 'sddsdsd;
for ( let code of number){
console.log(code)
}
for of循环还可以用于nodeList进行循环:
const lis = document.querySelectorAll('li')
for ( let li of lis) {
li.addEventListener('click', function () {
//执行方法
})
}
Array.from() Array.of()
Array.from()
这两个方法并不是数组原型的方法,我们需要通过Array.from() Array.of()这种去调用,而不是声明一个数组点上这个方法。Array.from()方法是将一个类素组对象转换为数组对象。类素组对象也就是拥有length属性的对象,modeList就是一个类数组对象。
const lis = document.querySelectorAll('li')
const lisArr = Array.form(lis)
const names = lisArray.map(li => li.textContent)
Array.from()方法有两个参数,第一个参数是需要准换的类数组,第二个参数类似数组的map方法,会给转化后的数组中的每一项执行该方法,于是,上面的代码可以简写为下面:
const lis = document.querySelectorAll('li')
const names = Array.form(lis, li => li.textContent)
也可以利用Array.from()方法进行转换函数中的arguments属性进行转化。 我们也可以利用Array.from()方法将字符串也能转换为一个数组。
const number = 'sddsdsd;
console.log(Array.from(number))
Array.of()
Array.of()方法就是根据传入的参数,返回一个由传入的参数组成的数组;
数组的其他方法
find()
查找是否有bananas这个选项;返回的是找到的该选项,以对象返回;
const inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5},
]
const bananas = inventory.find(fruit => {
if (fruit.name === 'bananas') {
return true
}
return false
})
findIndex()
findIndex()返回的是要查找的选项的索引值:
const inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5},
]
const bananas = inventory.findIndex(fruit => fruit.name === 'bananas');//返回的是索引值1
some()
返回的布尔值;如果有一部分满足测试函数,就返回true
const isEnough= inventory.some(fruit => fruit.quantity > 0);//返回true
every()
返回的布尔值;如果所有的都满足测试函数,就返回true
const isAllenough= inventory.every(fruit => fruit.quantity > 0);//返回false
剩余参数
function sum (...numbers) {
console.log(numbers)//一个数组[1, 2, 3, 4]
}
sum (1, 2, 3, 4)
数组相关:
let arr = [1, 2, 3, 3, 4, 5, 5]
for(let i = 0; i < arr.length; i++) {
if (arr[i] === 2) {
// break
// 跳出这次循环-在 for 中都可以使用
continue
}
console.log(arr[i])
}
// forEach
arr.forEach((elem, index, array) => {
// 不能使用 break、continue
console.log(elem, index)
})
// map - 同样不能使用 break、continue
arr.map(val => console.log(val))
// filter
let res = arr.filter(val => val > 2)
console.log(res, 'filter')
// some
let resSome = arr.some(val => val > 2)
console.log(resSome, 'some') // boolean
// every
let every = arr.every(val => val > 2)
console.log(every, 'every')
// reduce
let resR = arr.reduce((prev, cur) => prev + cur)
console.log(resR, 'reduce')
// 求出数组最大值
let max = arr.reduce((prev, cur) => Math.max(prev, cur))
console.log(max, 'max')
// 去重
let resC = arr.reduce((prev, cur) => {
prev.indexOf(cur) === -1 && prev.push(cur)
return prev
}, [])
console.log(resC)
// find 返回第一个测试通过的元素
let find = arr.find(val => val === 2)
console.log(find, 'find')
// findIndex
let findIndex = arr.findIndex(val => val === 2)
console.log(findIndex, 'findIndex')
// for of
for(let [index, item] of arr.entries()) {
console.log(index, item)
}
includes
es6 新增的 includes 跟 es5 的 indexOf 是一样的作用,返回数组中是否存在某个元素,但是对于 NaN 来说,indexOf 返回 -1,而 includes 是返回 true;
扩展运算符
我们如果想要两个数组,合并为一个数组,并且,在中间插入一个值,如下我们以前写的代码:
const youngers = ['george' , 'john', 'Thomas'];
const olders = ['James', 'Adrew', 'Martin'];
let members = [];
members = members.concat(youngers);
members.push('Mary');
members = members.concat(olders);
使用es6可以这样写:
const youngers = ['george' , 'john', 'Thomas'];
const olders = ['James', 'Adrew', 'Martin'];
let members = [...youngers, 'Mary', ...olders];
可以通过扩展运算符将一个字符串变为以每一个字符变为数组中的一项:
[...'xiaoxiao']
//["x", "i", "a", "o", "x", "i", "a", "o"]
可以借助扩展运算符,放置我们将数组赋值给另一个数组,修改另一个数组中的数据,会将源数组的值进行改变:
const currentMembers = [...numbers];
我们可以使用扩展运算符将可遍历对象扩展到一个新的数组中:
const todos = [...document.querySelectorAll('li')]
比如我们需要删除一个数组中,对象属性id为2的一项;代码如下:
const todos = [
{id: 1, name: 'Go to store', completed: false},
{id: 2, name: 'Wacth TV', completed: true},
{id: 3, name: 'Go Shopping', completed: false},
]
// 要删除的项的id为2
const id = 2;
const todoIndex = todos.findIndex(todo => todo.id === id)
const newTodos = [...todos.slice(0, todoIndex), ...todos.slice(todoIndex + 1)];
我们还可以将一个数组通过扩展运算符,追加到另一个数组后:
const fruit = ['apple', 'bananas', 'pear'];
const newFruit = ['orange', 'mongo'];
fruit.push(...newFruit)
还可以利用扩展运算符,将数组扩展为函数的参数:
const dateField = [2019, 6, 2];
const data = new Date(...dateField)
console.log(data);
对象的计算属性
假如我们需要定义一个对象:我们希望我们的’user -1’: 1这个数组是每次加一,然后进行定义变量。
let map = {
'user -1': 1
'user-2': 2
}
我们可以这样写:
let map = new Map()
let id = 0
map = {
[`user-${++id}`]: id,
[`user-${++id}`]: id,
[`user-${++id}`]: id,
}
如果我们想要定义一个对象,对象的键是一个数组,对应的每一个值是一个数组,我们可以这样写:
const keys = ['name', 'age', 'birthday'];
const values = ['xiaoxiao', 18, '0501'];
let map = new Map()
map = {
[keys.shift()]: values.shift(),
[keys.shift()]: values.shift(),
[keys.shift()]: values.shift(),
}
ES6 的 Promise 对象
如下代码,then里面是执行ajax执行成功之后的回调函数,catch是如果请求出现异常,执行的方法。这里是返回一个promise对象,所以可以继续在外层使用.then()方法。return axios.get(https://api.github.com/users/${username}/repos);
let username;
const usersPromise = axios.get('https://api.github.com/users');
usersPromise
.then(response => {
username = response.data[0].login;
return axios.get(`https://api.github.com/users/${username}/repos`);
})
.then(response => {
console.log(response.data)
})
.catch(err => {
console.log(err)
})
一个promise对象的实例:
const p = new Promise((resolve, reject) => {
//请求成功执行
resolve('success');
// 请求失败的执行
reject(Error('error'))
})
//请求成功执行的回调
p.then(data => {
console.log(data)
})
// 请求失败的执行的回调
.catch(err => {console.log(err)})
如果我们的页面中有多个Promise对象,这些对象之间执行的顺序时不相关的,互不影响的,我们可以使用下面的方法,处理这些Promise对象返回的结果:这里需要注意的是,.all()方法返回的结果是对应的执行Promise对象的结果,返回的是一个数组,我们可以使用对象解构,去得到不同的请求返回的结果。只有当.all()方法里面的Promise对象全部返回的是resolve的时候,才会执行.then()方法。否则执行.catch()
Promise
.all([userPromise, movePromise])
.then(response => {
const [users, movice] = response;
console.log(users);
console.log(movice);
})
.catch(err => {
console.log(err)
})
与.all()方法相对的是一个·Promise.race()方法,他同样也是处理多个Promise实例,但是,他是处理的Promise实例中,只要第一个Promise实例是执行resolve也就是回调成功,就会去执行.then()方法。否则执行.catch()
这里需要注意的是,下面代码并不是说只要执行 reject,后面的代码不会执行;链式操作 .then 是看前一个 promise 返回的结果而已;
new Promise((resolve, reject) => {
resolve('1')
}).then(res => {
console.log(res)
return new Promise((resolve, reject) => {reject('2')})
}).then(res => {
console.log(res)
}, err => {
console.log(err)
}).then(res => {
console.log(res)
})
// 1
// 2
// undefined
new Promise((resolve, reject) => {
resolve('1')
}).then(res => {
console.log(res, '1')
}).then(res => {
console.log(res, '2')
}, err => {
console.log(err, '3')
}).then(res => {
console.log(res, '4')
})
// 1 1
// undefined "2"
// undefined "4"
如果说我们使用 catch 去处理错误请求,那么 .then 的链式操作如果出现 reject;不会往下执行:
new Promise((resolve, reject) => {
resolve('1')
}).then(res => {
console.log(res)
return new Promise((resolve, reject) => { reject('2') })
}).then(res => {
console.log(res)
}).then(res => {
console.log(res) // 不会执行
}).catch(err => {
console.log(err, 'err')
})
// 1
// 2 error
Symbol
symbol是生成一个唯一的标识符,如下代码:
const one = Symbol('one')
const two = Symbol('two')
console.log(one === two) // false
我们可以使用symbol来给 对象定义一个相同属性名,但是值不同:
const classRoom = {
[Symbol('lily')]: {grade: 60, gender: 'female'},
[Symbol('nina')]: {grade: 80, gender: 'female'},
[Symbol('nina')]: {grade: 90, gender: 'female'},
}
symbol类型的值是不能遍历的,如上面的属性,是不能通过for in等进行遍历的。可以使用Object.getOwnPropertySymbols(classRoom)进行遍历,获取到属性名。获取属性值:classrom[Symbol(lily)]这样进行获取,不能通过.的方法进行获取。前面定义symbol属性的时候,是需要使用计算属性的方式进行定义。
const classRoom = {
[Symbol('lily')]: {grade: 60, gender: 'female'},
[Symbol('nina')]: {grade: 80, gender: 'female'},
[Symbol('nina')]: {grade: 90, gender: 'female'},
}
Object.getOwnPropertySymbols(classRoom)
初始化的时候如果传递一个对象,他会把对象的 toString 方法的返回值设置为标识:
let a = {
name: 'a',
toString() {
return this.name
}
}
let s = Symbol(a)
console.log(s) // Symbol(a)
通过 description 获取标识
let s = Symbol('foo')
console.log(s.description)
通过 Symbol.for 声明全局描述符 (全局环境下):会进行查找,之前有没有声明过;
let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
console.log(s1 === s2) // true
可以通过 Symbol.keyFor 查找标识是否全局定义:
let s1 = Symbol('foo')
console.log(Symbol.keyFor(s1)) // undefined
let s2 = Symbol.for('foo')
console.log(Symbol.keyFor(s2)) // foo
不能使用 for in、Object.keys() 来遍历通过 symbol 在对象上定义的属性,可以通过 Reflect.ownKeys 来遍历获取到普通属性以及 symbol 属性:
const sym = Symbol('im')
class User {
constructor(name) {
this.name = name
this[sym] = 'imc'
}
getName() {
return this.name + this[sym]
}
}
const user = new User('jie')
console.log(user.getName()) // jieimc
for (let key in user) {
console.log(key) // name
}
// 只能获取到 symbol 类型的属性
for (let key of Object.getOwnPropertySymbols(user)) {
console.log(key) // Symbol(im)
}
// 获取所有属性
for (let key of Reflect.ownKeys(user)) {
console.log(key)
}
// name
// Symbol(im)
Symbol 另一种用法:消除魔术字符串:
const shapeType = {
triangle: Symbol(),
circle: Symbol()
}
function getArea(shape) {
let area = 0
switch(shape) {
case shapeType.triangle:
area = 1
break
case shapeType.circle:
area = 2
break
}
return area
}
console.log(getArea(shapeType.triangle))
对象新增
属性简洁表示法:如果对象键跟属性相等可以省略属性值
let name = 'jie'
let person = {
name,
age: 12
}
属性名表达式:属性的键可以动态赋值
let name = 'jie'
let dk = 'school'
let person = {
name,
age: 12,
[dk]: 'gasu'
}
Object.is 判断两个值是否相等;与 === 区别是,两个 NaN 返回的结果是
true:Object.is(NaN, NaN)
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(+0, -0)) // false
扩展运算符与 Object.assign()
let x = {
a: 3,
b: 3
}
let y = {...x}
console.log(y)
let z = {}
Object.assign(z, x)
console.log(z)
in:是否包含该属性
let x = {
a: 3,
b: 3
}
console.log('a' in x)
对象的遍历方式
for (const key in x) {
console.log(key, x[key])
}
Object.keys(x).forEach(key => console.log(key, x[key]))
Object.getOwnPropertyNames(x).forEach(key => console.log(key, x[key]))
Reflect.ownKeys(x).forEach(key => console.log(key, x[key]))
深拷贝与浅拷贝
深拷贝
// 检查类型
const checkType = taregt => Object.prototype.toString.call(taregt).slice(8, -1)
const deepClone = target => {
let targetType = checkType(target)
let result
if (targetType === 'Object') {
result = {}
} else if (targetType === 'Array') {
result = []
} else {
return target
}
for (const key in target) {
let value = target[key]
let valueType = checkType(value)
if (valueType === 'Object' || valueType === 'Array') {
result[key] = deepClone(value)
} else {
result[key] = value
}
}
return result
}
浅拷贝
Object.assign()
import export
默认导出:一个模块只能有一个默认导出;
const apikey = 'abc123';
export default apikey;
默认导出的引入方式:
import api from './config.js'
console.log(api)
命名导出:这里的apikey就是导出的名字,
export const apikey = 'abc123';
引入的时候,必须使用同样的名字,而且,需要使用{}来包裹起来:
import { apikey } from './config'
可以引入多个:
import { apikey,a } from './config'
可以用下面的方式导出多个:
export { apikey, age, greet }
也可以在导出的时候,进行重命名:然后引入的时候,必须使用as命名后的名字;
export { apikey as apk, age, greet }
当然,也可以在引入的时候重新命名:在模块使用的时候,就必须使用重命名的名字;
import { apikey as apk, a } from './config'
默认导出的,吗唉导入的时候,可以使用任意的命名,而命名导出,需要使用我们导出的名字,在导入的时候,进行导入。 一个第三方包:slug,过滤用户名。还有md5包。
引入命名导出以及默认导出的变量:User默认导出的内容。
import User, { apikey as apk, a } from './config'
Class
ES5 中的组合式继承,是构造函数继承 + 原型链继承,构造函数继承是:
function Animal(name) {
this.name = name
}
function Dog(name, color) {
// 继承属性
Animal.call(this, name)
this.color = color
}
原型链继承
function Animal(name) {
this.name = name
}
function Dog(name, color) {
this.color = color
}
// 继承方法
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog
组合式继承:
function Animal(name) {
this.name = name
}
function Dog(name, color) {
// 继承属性
Animal.call(this, name)
this.color = color
}
// 继承方法
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog
Class基本用法
是特殊的函数,定义方式:
//第一种
class User {
}
//第二种
const User = class {
}
typeof User打印出的结果是function,函数是有函数提升的,而类是没有的。在声明之前使用,会报错
这里需要注意的是,类里面定义函数,之间是不需要使用逗号隔开的,加了逗号会报错;>
静态方法的定义:静态方法–不能实例化调用,只能在原型对象调用,一般将原型对象里面的方法定义为静态方法
class User {
constructor (name, email) {
this.name = name;
this.email = email;
}
info () {
console.log(`I'm ${this.name}`)
}
}
const xiaoxiao= new User('xiaoxiao', 'xiaoxiao@163.com')
console.log(xiaoxiao)
定义get set方法:
class User {
constructor (name, email) {
this.name = name;
this.email = email;
}
info () {
console.log(`I'm ${this.name}`);
}
//静态方法--不能实例化调用,只能在原型对象调用,一般将原型对象里面的方法定义为静态方法
static descript () {
console.log(`hi jj`);
}
set github (value) {
this.githubName = value;
}
get github () {
return `http://github.com/${this.githubName}`;
}
}
const xiaoxiao= new User('xiaoxiao', 'xiaoxiao@163.com')
console.log(xiaoxiao)
Class的扩展
在类的定义中,定义方法的时候,也可以使用计算属性的方式进行定义:
let methodName = 'info';
class User {
constructor (name, email) {
this.name = name;
this.email = email;
}
[methodName] () {
console.log(`I'm ${this.name}`);
}
//静态方法--不能实例化调用,只能在原型对象调用,一般将原型对象里面的方法定义为静态方法
static descript () {
console.log(`hi jj`);
}
set github (value) {
this.githubName = value;
}
get github () {
return `http://github.com/${this.githubName}`;
}
}
类必须要使用new关键字进行调用。下面是类的继承:注意super(name);进行调用父类构造函数。
class Animal {
constructor (name) {
this.name = name;
this.belly = [];
}
eat (food) {
this.belly.push(food)
}
}
class Dog extends Animal {
constructor (name, age) {
//在子类中调用父类构造函数
super(name);
this.name = name;
this.age = age;
}
bark () {
console.log(`Barl bark!`);
}
}
const lucky = new Dog('lucky', 2);
Class 进行扩展内建对象
我们可以通过class扩展javascript中的内建对象,比如扩展Array对象:
class MyArray extends Array {
constructor () {
super();
}
}
const colors = new MyArray();
colors[0] = 'red';
console.log(colors.length);
colors.length = 0;
console.log(colors[0])
在子类中其实可以通过this来访问父级Array的一些属性跟方法的。这里就是调用了Array的push方法。
class movieCollaction {
constructor (name, ...items) {
super(...items)
this.name = name;
}
add (item) {
this.push(item)
}
toRated (limit = 10) {
return this.sort((a, b) => (a.scores > b.scores) ? -1 : 1).slice(0, limit);
}
}
const movies = new movieCollaction('favorite movies',
{ name: 'the croods', scored: 8.7},
{ name: 'the days', scored: 9.6},
{ name: 'the shawshank', scored: 9.4},
{ name: 'the summer', scored: 8.0},
);
Proxy
在 es5 中,我们是使用 Object.defineProperty():
let obj = {}
let newVal = 'xiaoxiao'
Object.defineProperty(obj, 'name', {
get() {
console.log('get')
return newVal
},
set(val) {
console.log('set')
newVal = val
}
})
console.log(obj.name)
es6 中是 proxy;比如我们做一个对数组获取值进行代理,如果取得下标没有对应的下标就返回 error
let arr = [7, 8, 9]
arr = new Proxy(arr, {
get(target, prop) {
console.log(target, prop) // [7, 8, 9], "1"
return prop in target ? target[prop] : 'error'
}
})
console.log(arr[1])
set 拦截操作
帮助我们重写对象上的默认方法。比如下面的的方法:
const personn = { name: 'xiaoxiao', age: 200};
// 第一个参数为要代理的对象,第二个参数就是一个对象,包含重写的方法。也就类似vue计算属性
const personProxy = new Proxy(personn, {
get(target, key) {
return target[key].toUpperCase()
},
set (target, key, value) {
if(typeof value === 'string'){
target[key] = value.trim()
}
}
})
personProxy.name = 'xiao'
我们用 proxy 去代理一个对象的时候,并不会破坏对象的原有属性;
let arr = []
arr = new Proxy(arr, {
set(target, prop, val) {
if (typeof val === 'number') {
target[prop] = val
return true
}
return false
}
})
arr.push(5)
arr.push(6)
console.log(arr[0], arr[1], arr.length) // 5, 6, 2
Proxy的一个例子:对电话号码进行格式化输出
const phonerHandle = {
set(target, key, value) {
target[key] = value.match(/[0-9]/g).join('');
},
get(target, key) {
return target[key].replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
}
}
const pgoneNumber = new Proxy({}, phonerHandle)
has (拦截 in 操作符) 拦截操作
拦截 propKey in proxy 的操作,返回一个布尔值。
let range = {
start: 1,
end: 5
}
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end
}
})
console.log(2 in range)
console.log(9 in range)
ownKeys 拦截操作
拦截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。
let obj = {
name: 'imooc',
[Symbol('es')]: 'es6'
}
console.log(Object.getOwnPropertyNames(obj)) // ["name"]
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(es)]
console.log(Object.keys(obj)) // ["name"]
for (let key in obj) {
console.log(key)
}
// ["name"]
// 使用 Reflect.ownKeys 遍历所有属性
for (let key of Reflect.ownKeys(obj)) {
console.log(key)
}
使用代理:比如带下划线的属性为私有属性,下面代码是当遍历对象属性的时候,不对私有属性进行遍历;
let userinfo = {
username: 'xiecheng',
age: 34,
_password: '***'
}
userinfo = new Proxy(userinfo, {
ownKeys(target) {
return Object.keys(target).filter(key => !key.startsWith('_'))
}
})
for (let key in userinfo) {
console.log(key)
}
// username
// age
console.log(Object.keys(userinfo))
deleteProperty 拦截操作
拦截 delete proxy[propKey] 的操作,返回一个布尔值。
比如有下面一个场景,我们对一个对象的带有下划线属性进行代理,设置他不能获取、设置、删除、不能循环遍历,如下代码:
let user = {
name: 'xiaoxiao',
age: 18,
_password: '******'
}
user = new Proxy(user, {
get(target, prop) {
if (prop.startsWith('_')) {
throw new Error('不可访问')
} else {
return target[prop]
}
},
set(target, prop, val) {
if (prop.startsWith('_')) {
throw new Error('不可访问')
} else {
target[prop] = val
return true
}
},
// 删除拦截
deleteProperty(target, prop) {
if (prop.startsWith('_')) {
throw new Error('不可删除')
} else {
delete target[prop]
return true
}
},
ownKeys(target) {
return Object.keys(target).filter(key => !key.startsWith('_'))
}
})
console.log(user.age)
console.log(user._password)
user.age = 18
console.log(user.age)
try {
user._password = 'xxx'
} catch (e) {
console.log(e.message)
}
try {
// delete user.age
delete user._password
} catch (e) {
console.log(e.message)
}
console.log(user.age)
for (let key in user) {
console.log(key)
}
apply 拦截操作
拦截 Proxy 实例作为函数调用的操作,比如 proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
拦截 Proxy 实例作为函数调用的操作,比如 proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
let sum = (...args) => {
let num = 0
args.forEach(item => {
num += item
})
return num
}
sum = new Proxy(sum, {
apply(target, ctx, args) {
return target(...args) * 2
}
})
console.log(sum(1, 2)) // 6
console.log(sum.call(null, 1, 2, 3)) // 12
console.log(sum.apply(null, [1, 2, 3])) // 12
construct 拦截操作
拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(…args)。
let User = class {
constructor(name) {
this.name = name
}
}
User = new Proxy(User, {
constructor(target, args, newTarget) {
console.log('constructor', target, args, newTarget)
return new target(...args)
}
})
console.log(new User('xiaoxiao'))
Set
基本用法
一种唯一的数组,也就是数组中的数据是唯一的,不会重复,不能通过索引进行获取。使用add添加值,delete删除某个值。检验某个值是否存在has,移除所有的元素.clear()。.values是一个set的一个遍历器。可以使用for of进行循环遍历,.size 属性是获取 set 元素的个数;
const colors = new Set();
colors.add('red')
colors.add('green')
colors.add('blue')
可以使用下面的的方法,进行遍历:
const colors = new Set();
colors.add('red')
colors.add('green')
colors.add('blue')
const iterator = colors.values();
iterator.next();
可以链式调用:
const colors = new Set();
colors.add('red').add('green')
对于 set 元素的遍历,可以使用数组的遍历方法,进行遍历元素:
使用for of
for (let color of colors) {
console.log(color)
}
使用forEach进行遍历;
colors.forEach((item, key, ownSet) => {
console.log(item,key,ownSet)
})
set接收的是一个可遍历对象,我们也可以传入一个数组,进行初始化:
const fruits = new Set(['apple', 'banana', 'mongo'])
应用
求交集
let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let result = new Set(arr1.filter(item => s2.has(item)))
console.log([...result])
求差集
let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let arr3 = new Set(arr1.filter(item => !s2.has(item)))
let arr4 = new Set(arr2.filter(item => !s1.has(item)))
console.log([...arr3, ...arr4])
weakSet跟set非常相似,只不过,他存储的项只能是对象,不能存储字符串,而且不能通过for of进行循环,他没有迭代器,也不能使用forEach进行循环。他没有clear()清除全部,他可以自己进行检测进行注销对应的数据。
weakSet 是弱引用;不会进入垃圾回收机制进行计数;
Map
map跟set非常相似,不同的是,map通过.set(‘key’, value)这种方法进行添加元素,他也相当于一个数组,数组中是一个个对象。对象的键可以是任意数据类型;
const people = new Map();
people.set('jelly', 23);
people.set('{}',3)
获取对应的值:
people.get('jelly')//传入键
获取里面j键值对的数量:
people.size()
删除某个项:
people.delete('jelly')
删除所有元素:
people.clear()
循环遍历元素:
forEach进行循环;
people.forEach((value, key, map) => {
console.log(value, key, map)
})
使用for of进行循环:
for (let [key,value] of people) {
console.log(key, value)
}
使用 .entries() 方法:
for (let [key, value] of people.entries()) {
console.log(key, value)
}
初始化参数:
const fruits= new Map([
['apple', 6],
['banans', 5]
])
wekMap没有size属性,他的 key 只能是引用类型的数据;不能循环遍历;没有 clear、size 等属性以及方法;他也是弱引用,垃圾回收机制,会自动回收。
函数的参数
参数可以设置默认值
function foo(x, y = 'foo') {
// 不能再次声明
// let x = 1
console.log(x, y)
}
函数的 length 是返回没有指定默认值的参数的个数
函数参数会形成作用域
let x = 1
function foo(x, y = x) {
console.log(y) // 2
}
foo(2)
函数 name 属性
console.log(new Function().name) // anonymous
// bind 改变
function foo(x, y) {
console.log(this, x, y)
}
console.log(foo.bind({}).name) // bound foo
// 匿名函数使用 bind
console.log((function() {}).bind({}).name) // bound
字符串的扩展
字符串的遍历器接口
可以通过 for of 去遍历字符串:
for(let item of 'xiaoxiao') {
console.log(item)
}
带标签的模板字符串
const foo = (a, b, c, d) => {
console.log(a)
console.log(b)
console.log(c)
console.log(d)
}
const name = 'xiaoxiao'
const age = 18
foo`这是${name},他的年龄是${age}岁, ${22}`
// ["这是", ",他的年龄是", "岁, ", ""]
// xiao
// 18
// 22
String.prototype.includes()
ES5中可以使用indexOf方法来判断一个字符串是否包含在另一个字符串中,indexOf返回出现的下标位置,如果不存在则返回-1。
ES6提供了includes方法来判断一个字符串是否包含在另一个字符串中,返回
String.prototype.startsWith() 判断参数字符串是否在原字符串的头部, 返回boolean类型的值。
String.prototype.endsWith()
判断参数字符串是否在原字符串的尾部, 返回boolean类型的值。
String.prototype.repeat()
repeat 方法返回一个新字符串,表示将原字符串重复n次。
const str = 'imooc'
console.log(str.indexOf('mo'))
const str = 'imooc'
console.log(str.includes('mo'))
const str = 'imooc'
console.log(str.startsWith('im'))
const str = 'imooc'
console.log(str.endsWith('mooc'))
const str = 'imooc'
const newStr = str.repeat(10)
console.log(newStr)
数值的扩展
新增方法
Number.isFinite() 用来检查一个数值是否为有限的(finite),即不是 Infinity。 如果不是数值,都是 false;
Number.isFinite(15) // true
Number.isFinite(0.8) // true
Number.isFinite(NaN) // false
Number.isFinite(Infinity) // false
Number.isFinite(-Infinity) // false
Number.isFinite('foo') // false
Number.isFinite('15') // false
Number.isFinite(true) // false
Number.isNaN()
用来检查一个值是否为NaN。
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9 / NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
Number.parseInt()
ES6 将全局方法 parseInt() 移植到 Number 对象上面,行为完全保持不变。 这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。
// ES5的写法
parseInt('12.34') // 12
// ES6的写法
Number.parseInt('12.34') // 12
Number.parseFloat() ES6 将全局方法 parseFloat() 移植到 Number 对象上面,行为完全保持不变。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化
// ES5的写法
parseFloat('123.45#') // 123.45
// ES6的写法
Number.parseFloat('123.45#') // 123.45
Number.isInteger()
用来判断一个数值是否为整数。
Number.isInteger(25) // true
Number.isInteger(25.1) // false
Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false
js 数值精度
在 js 中,我们存储数字精度的标准是 IEEE 754,在存储 0.1 的时候(因为计算机存储数值为二进制)会存在精度缺失,该标准会因为存储的数值有一定空间,会舍弃一部分;所以在 js 中会存在 0.1 + 0.2 不等于 0.3 这样的结果:
35 -> 00100011
0.35 -> 0.011
0.1 -> 0.0000440044.....
// 超出 14 位会舍弃
console.log(0.1000000000000001) // 0.1000000000000001
console.log(0.10000000000000001) // 0.1
console.log(0.10000000000000001 === 0.1) // true
Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER
最大的安全整数;
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 // true
Number.MAX_SAFE_INTEGER === 9007199254740991 // true
Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER // true
Number.MIN_SAFE_INTEGER === -9007199254740991 // true
Number.isSafeInteger()
JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。
Math.pow(2, 53) // 9007199254740992
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
console.log(Number.MAX_SAFE_INTEGER) // true
console.log(Number.MAX_SAFE_INTEGER + 1) // false
Math 扩展
Math.trunc() 方法用于去除一个数的小数部分,返回整数部分。
console.log(Math.trunc(5.5))
console.log(Math.trunc(-5.5))
console.log(Math.trunc(true)) // 1
console.log(Number.parseInt(true)) // NaN
console.log(Math.trunc(false)) // 0
console.log(Math.trunc(NaN)) // NaN
console.log(Math.trunc(undefined)) // NaN
console.log(Math.trunc()) // NaN
Math.sign() 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值。
参数为正数,返回+1
参数为负数,返回-1
参数为 0,返回0
参数为-0,返回-0
其他值,返回NaN
console.log(Math.sign(5)) // 1
console.log(Math.sign(-5)) // -1
console.log(Math.sign(0)) // 0
console.log(Math.sign(NaN)) // NaN
console.log(Math.sign(true)) // 1
console.log(Math.sign(false)) // 0
Math.cbrt()
方法用于计算一个数的立方根。
console.log(Math.cbrt(8)) // 2
console.log(Math.cbrt('imooc')) // NaN
Reflect
Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。
设计目的
将 Object 属于语言内部的方法放到 Reflect 上
let obj = {}
let newVal = ''
Reflect.defineProperty(obj, 'name', {
get() {
return newVal
},
set(val) {
console.log('set')
// this.name = val
newVal = val
}
})
obj.name = 'es'
console.log(obj.name)
修改某些 Object 方法的返回结果,让其变得更合理
es5 我们通过 Object.defineProperty 定义对象属性时,有的属性是不能被定义的,如果定义操作会抛出异常,我们通过 try catch 来捕获;es6 可以通过 Reflect.defineProperty,他会返回 boolean 类型的值,表示是否可以定义;
// 老写法
try {
Object.defineProperty(target, property, attributes)
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
让 Object 操作变成函数行为
比如下面代码,判断当前对象是否具有某个方法;
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
Reflect 对象的方法与 Proxy 对象的方法一一对应
只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。
let user = {
name: 'xiaoxiao',
age: 18,
_password: '******'
}
user = new Proxy(user, {
get(target, prop) {
if (prop.startsWith('_')) {
throw new Error('不可访问')
} else {
// return target[prop]
return Reflect.get(target, prop)
}
},
set(target, prop, val) {
if (prop.startsWith('_')) {
throw new Error('不可访问')
} else {
// target[prop] = val
Reflect.set(target, prop, val)
return true
}
},
// 删除拦截
deleteProperty(target, prop) {
if (prop.startsWith('_')) {
throw new Error('不可删除')
} else {
// delete target[prop]
Reflect.deleteProperty(target, prop)
return true
}
},
ownKeys(target) {
// return Object.keys(target).filter(key => !key.startsWith('_'))
return Reflect.ownKeys(target).filter(key => !key.startsWith('_'))
}
})
console.log(user.age)
try {
console.log(user._password)
} catch(e) {
console.log(e.message)
}
user.age = 19
console.log(user.age)
delete user.age
console.log(user.age)
for (let key in user) {
console.log(key)
}
常用方法
Reflect.apply()
Reflect.apply(target, thisArgument, argumentsList) target: 目标函数;thisArgument:target 函数调用时绑定的 this 对象;argumentsList:target 函数调用时传入的实参列表,该参数应该是一个类数组的对象
Reflect.apply(Math.floor, undefined, [1.75])
// 1
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111])
// "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index
// 4
Reflect.apply(''.charAt, 'ponies', [3])
// "i"
该方法与 ES5 中 Function.prototype.apply() 方法类似:调用一个方法并且显式地指定 this 变量和参数列表 (arguments) ,参数列表可以是数组,或类似数组的对象。
Function.prototype.apply.call(Math.floor, undefined, [1.75])
Reflect.construct()
Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(...args).
Reflect.construct(target, argumentsList[, newTarget]) target:被运行的目标函数;argumentsList:调用构造函数的数组或者伪数组;newTarget:该参数为构造函数, 参考 new.target 操作符,如果没有 newTarget 参数, 默认和 target 一样
var obj = new Foo(...args)
var obj = Reflect.construct(Foo, args)
var d = Reflect.construct(Date, [1776, 6, 4])
d instanceof Date // true
d.getFullYear() // 1776
// 如果使用 newTarget 参数,则表示继承了 newTarget 这个超类:
function someConstructor() {}
var result = Reflect.construct(Array, [], someConstructor)
Reflect.getPrototypeOf(result) // 输出:someConstructor.prototype
Array.isArray(result) // true
Reflect.deleteProperty()
Reflect.deleteProperty 允许你删除一个对象上的属性。返回一个 Boolean 值表示该属性是否被成功删除。它几乎与非严格的 delete operator 相同
var obj = {
x: 1,
y: 2
}
Reflect.deleteProperty(obj, "x") // true
obj // { y: 2 }
var arr = [1, 2, 3, 4, 5]
Reflect.deleteProperty(arr, "3") // true
arr // [1, 2, 3, , 5]
// 如果属性不存在,返回 true
Reflect.deleteProperty({}, "foo") // true
// 如果属性不可配置,返回 false
Reflect.deleteProperty(Object.freeze({
foo: 1
}), "foo") // false
Reflect.getOwnPropertyDescriptor()
静态方法 Reflect.getOwnPropertyDescriptor() 与 Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined。
Reflect.getOwnPropertyDescriptor({
x: 'hello'
}, 'x')
// {value: "hello", writable: true, enumerable: true, configurable: true}
Reflect.getOwnPropertyDescriptor({
x: 'hello'
}, 'y')
// undefined
Reflect.getOwnPropertyDescriptor([], 'length')
// {value: 0, writable: true, enumerable: false, configurable: false}
如果该方法的第一个参数不是一个对象(一个原始值),那么将造成 TypeError 错误。而对于 Object.getOwnPropertyDescriptor,非对象的第一个参数将被强制转换为一个对象处理。
Reflect.getOwnPropertyDescriptor("foo", 0)
// TypeError: "foo" is not non-null object
Object.getOwnPropertyDescriptor("foo", 0)
// { value: "f", writable: false, enumerable: true, configurable: false }
Reflect.getPrototypeOf()
静态方法 Reflect.getPrototypeOf() 与 Object.getPrototypeOf() 方法是一样的。都是返回指定对象的原型(即,内部的 [[Prototype]] 属性的值)。
Reflect.getPrototypeOf({a: 'a'})
// constructor: ƒ Object()
// hasOwnProperty: ƒ hasOwnProperty()
// isPrototypeOf: ƒ isPrototypeOf()
// propertyIsEnumerable: ƒ propertyIsEnumerable()
// toLocaleString: ƒ toLocaleString()
// toString: ƒ toString()
// valueOf: ƒ valueOf()
// __defineGetter__: ƒ __defineGetter__()
// __defineSetter__: ƒ __defineSetter__()
// __lookupGetter__: ƒ __lookupGetter__()
// __lookupSetter__: ƒ __lookupSetter__()
// get __proto__: ƒ __proto__()
// set __proto__: ƒ __proto__()
Reflect.isExtensible()
Reflect.isExtensible 判断一个对象是否可扩展 (即是否能够添加新的属性),它与 Object.isExtensible() 方法一样。
Reflect.isExtensible(target) target: 获取原型的目标对象
Reflect.isExtensible({})
// true
Reflect.isExtensible(Object.create(null))
// true
Reflect.preventExtensions()
Reflect.preventExtensions 方法阻止新属性添加到对象 例如:防止将来对对象的扩展被添加到对象中)。该方法与 Object.preventExtensions() 方法一致;
// Objects are extensible by default.
var empty = {}
Reflect.isExtensible(empty) // === true
// ...but that can be changed.
Reflect.preventExtensions(empty)
Reflect.isExtensible(empty) // === false
Reflect.preventExtensions(1)
// TypeError: 1 is not an object
Object.preventExtensions(1)
// 1
Reflect.setPrototypeOf()
Reflect.setPrototypeOf 方法改变指定对象的原型 (即,内部的 [[Prototype]] 属性值
Reflect.setPrototypeOf({}, Object.prototype) // true
// It can change an object's [[Prototype]] to null.
Reflect.setPrototypeOf({}, null) // true
// Returns false if target is not extensible.
Reflect.setPrototypeOf(Object.freeze({}), null) // false
// Returns false if it cause a prototype chain cycle.
var target = {}
var proto = Object.create(target)
Reflect.setPrototypeOf(target, proto) // false
Generator
比普通函数多一个 *
函数内部用 yield 来控制程序的执行的“暂停”
函数的返回值通过调用 next 来“恢复”程序执行
function* foo() {
for (let i = 0; i < 3; i ++) {
console.log(i)
yield i
}
}
let f = foo()
console.log(f.next()) // {value: 0, done: false}
Generator 函数的定义不能使用箭头函数,否则会触发 SyntaxError 错误:
let generator = * () => {} // SyntaxError
let generator = () * => {} // SyntaxError
let generator = ( * ) => {} // SyntaxError
yield 表达式
yield 关键字用来暂停和恢复一个生成器函数,yield 只能出现在 generator 函数内部,否则会报错:
// 注意在写循环的时候,类似 forEach 这种需要回调函数,往往会忽略
function* gen(args) {
args.forEach(item => {
yield item + 1
})
}
yield 表达式的返回值是 undefined,但是遍历器对象的 next 方法可以修改这个默认值。
理解 yield 的含义:首先第一个输出 6,因为走到第一个 yield,x + 1 就是 6;所以,g.next() 返回是 yield 的结果;然后走到第二个 yield,因为第二个打印的 g.next() 并没有传递参数,导致第一个 yield 为 undefined;所以 2 * undefined 为 NaN,所以 y 的结果就是 NaN;得到 z 的结果也是 NaN;所以打印结果 NaN;最后 return 也是 NaN,所以最后打印也是 NaN;
这里要说一下,g.next 是可以传参的,表示上一次 yield 的执行结果;
function* gen(x) {
let y = 2 * (yield (x + 1))
let z = yield(y / 3)
return x + y + z
}
let g = gen(5)
console.log(g.next()) // 6
console.log(g.next()) // NaN
console.log(g.next()) // NaN
如果我们传参,如下代码:
function* gen(x) {
let y = 2 * (yield (x + 1))
let z = yield(y / 3)
return x + y + z
}
let g = gen(5)
console.log(g.next()) // 6
console.log(g.next(12)) // y = 24 8
console.log(g.next(13)) // z = 13 x = 5 42
应用场景
按顺序读取 a.json、b.json、c.json,如果使用 Generator 该如何实现呢?
function request(url) {
ajax(url, res => {
getData.next(res)
})
}
function* gen() {
let res1 = yield request('static/a.json')
console.log(res1)
let res2 = yield request('static/b.json')
console.log(res2)
let res3 = yield request('static/c.json')
console.log(res3)
}
let getData = gen()
getData.next()
我们经常玩一些小游戏,比如数数字,敲7,到7和7的倍数,无限循环转圈去数数
function* count(x = 1) {
while (true) {
if (x % 7 === 0) {
yield x
}
x++
}
}
// es5 中就是个死循环 因为 es5 的循环需要有个终止值,但我们这个需求没有终止,一直在数数
let n = count()
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
通过 Generator 我们就能轻松实现,只要调用 n.next 我们就知道下一个数是什么了,而使用普通函数却没法做到。
Iterator
Iterator 就是 ES6 中用来实现自定义遍历的接口,按照上述的示例,我们来实现下这个接口:
let authors = {
allAuthors: {
fiction: [
'Agatha Christie',
'J. K. Rowling',
'Dr. Seuss'
],
scienceFiction: [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
],
fantasy: [
'J. R. R. Tolkien',
'J. K. Rowling',
'Terry Pratchett'
]
}
}
// 这个代码在数据结构上部署了 Iterator 接口,我们就可以用 for...of 来遍历代码了
authors[Symbol.iterator] = function() {
let allAuthors = this.allAuthors
let keys = Reflect.ownKeys(allAuthors)
let values = []
return {
next() {
if (!values.length) {
if (keys.length) {
values = allAuthors[keys[0]]
keys.shift()
}
}
return {
done: !values.length,
value: values.shift()
}
}
}
}
for (let value of authors) {
console.log( `${value}` )
}
ES7-11
Array.prototype.includes()
在 ES7 之前想判断数组中是否包含一个元素,基本可以这样写:
console.log(array1.find((item) => {
return item === 2
}))
// 或者
console.log(array1.filter((item) => {
return item === 2
}).length > 0)
ES7 引入的 Array.prototype.includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
基本用法
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es6')) // true
console.log(arr.includes('es9')) // false
接收俩个参数:要搜索的值和搜索的开始索引。第二个参数可选。从该索引处开始查找 searchElement。如果为负值,
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es7', 1)) // true
console.log(arr.includes('es7', 2)) // false
console.log(arr.includes('es7', -1)) // false
console.log(arr.includes('es7', -2)) // true
与 indexOf() 比较
['a', 'b', 'c'].includes('a') // true
['a', 'b', 'c'].indexOf('a') > -1 // true
console.log(arr.indexOf('es7')) // 1
console.log(arr.indexOf('es7') > -1) // true
注意:只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些是无法判断的.
const arr = [1, [2, 3], 4]
arr.includes([2, 3]) //false
arr.indexOf([2, 3]) //-1
优缺点比较
两者都是采用 === 的操作符来作比较的,不同之处在于:对于 NaN 的处理结果不同。我们知道 js 中 NaN === NaN 的结果是 false, indexOf() 也是这样处理的,但是 includes() 不是这样的。
const demo = [1, NaN, 2, 3]
demo.indexOf(NaN) // -1
demo.includes(NaN) // true
如果只想知道某个值是否在数组中存在,而并不关心它的索引位置,建议使用 includes()。如果想获取一个值在数组中的位置,那么只能使用 indexOf 方法。
幂运算符 **
如果不使用任何函数,如何实现一个数的求幂运算?
function pow(x, y) {
let res = 1
for (let i = 0; i < y; i++) {
res *= x
}
return res
}
pow(2, 10)
// 1024
除了自己封装函数来实现,也可是使用 Math.pow() 来完成。
Math.pow() 函数返回基数(base)的指数(exponent)次幂。
console.log(Math.pow(2, 10)) // 1024
在 ES7 可以这样写了:
console.log(2 ** 10) // 1024
async / await
async 和 await 是一种更加优雅的异步编程解决方案,是Promise 的拓展,在我们处理异步的时候,比起回调函数,Promise的then方法会显得较为简洁和清晰,但是在处理多个彼此之间相互依赖的请求的时候,就会显的有些繁琐。这时候,用async/await更加优雅。
我们知道 JavaScript 是单线程的,使用 Promise 之后可以让我们书写异步操作更加简单,而 async 是让我们写起 Promise 像同步操作。
前面添加了 async 的函数在执行后都会自动返回一个 Promise 对象:
基本语法:
async function foo() {
return 'imooc' // Promise.resolve('imooc')
}
console.log(foo()) // Promise
await 后面需要跟异步操作,不然就没有意义,而且 await 后面的 Promise 对象不必写 then,因为 await 的作用之一就是获取后面Promise 对象成功状态传递出来的参数。
function timeout() {
return new Promise(resolve => {
setTimeout(() => {
console.log(1)
resolve() // resolve('success')
}, 1000)
})
}
// 不加 async 和 await 是2、1 ;加了是 1、2
async function foo() {
await timeout() // let res = await timeout() res 是 success
console.log(2)
}
foo()
对于失败的处理:
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('success')
reject('error')
}, 1000)
})
}
async function foo() {
return await timeout()
}
foo().then(res => {
console.log(res) // resolve success
}).catch(err => {
console.log(err) // reject error
})
Object 扩展
之前的语法如何获取对象的每一个属性值:
const obj = {
name: 'xiao',
web: 'xiaoxiao.github.io',
course: 'webgis'
}
console.log(Object.keys(obj))
const res = Object.keys(obj).map(key => obj[key])
console.log(res)
// ["xiao", "xiaoxiao.github.io", "webgis"]
ES8 中对象扩展补充了两个静态方法,用于遍历对象:Object.values(),Object.entries()
Object.values()
Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for…in,但是 for…in 还会遍历原型上的属性值)。
const obj = {
name: 'jie',
web: 'xiaoxiao.github.io',
course: 'webgis'
}
console.log(Object.values(obj))
// ["xiao", "xiaoxiao.github.io", "webgis"]
Object.entries() Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致。(区别在于 for-in 循环也枚举原型链中的属性)
const obj = {
name: 'jie',
web: 'xiaoxiao.github.io',
course: 'webgis'
}
console.log(Object.entries(obj))
// [["name", "jie"]
// ["web", "xiaoxiao.github.io"]
// ["course", "webgis"]]
for (let [key, value] of Object.entries(obj)) {
console.log(key, value)
}
// name xiao
// web xiaoxiao.github.io
// course webgis
Object.getOwnPropertyDescriptors()
对象有以下描述符:
value [属性的值]
writable [属性的值是否可被改变]
enumerable [属性的值是否可被枚举]
configurable [描述符本身是否可被修改,属性是否可被删除]
获取对象指定属性的描述符:
const obj = {
name: 'jie',
web: 'xiaoxiao.github.io',
course: 'webgis'
}
console.log(Object.getOwnPropertyDescriptor(obj, 'name'))
// {value: "jie", writable: true, enumerable: true, configurable: true}
如果想获取对象的所有属性的描述符:
console.log(Object.getOwnPropertyDescriptors(obj))
String 扩展
在 ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾;
String.prototype.padStart()
把指定字符串填充到字符串头部,返回新字符串;该方法有两个参数,第一个参数是目标字符要保持的长度值;第二个参数是如果目标字符的长度不够需要的补白字符,默认为空;如下例子:
const str = 'gis'
console.log(str.padStart(8, 'x')) // xxxxxgis
console.log(str.padEnd(8, 'y')) // gisyyyyy
console.log(str.padStart(8)) // gis
场景1:日期格式化
希望把当前日期格式化城:yyyy-mm-dd的格式:
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = (now.getDate()).toString().padStart(2, '0')
console.log(`${year}-${month}-${day}`)
场景2:数字替换
// 数字替换,比如手机号,身份证号
const tel = '13012345678'
const newTel = tel.slice(-4).padStart(tel.length, '*')
console.log(newTel) // *******5678
String.prototype.padEnd()
方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。
const str = 'gis'
console.log(str.padEnd(8, 'y')) // gisyyyyy
场景:时间戳统一长度
在 JS 前端我们处理时间戳的时候单位都是 ms 毫秒,但是,后端同学返回的时间戳则不一样是毫秒,可能只有 10 位,以 s 秒为单位。所以,我们在前端处理这个时间戳的时候,保险起见,要先做一个 13 位的补全,保证单位是毫秒。
// 伪代码
console.log(new Date().getTime()) // 时间戳 13 位的
timestamp = +String(timestamp).padEnd(13, '0')
尾逗号 Trailing commas
ES8 允许函数的最后一个参数有尾逗号(Trailing comma)。
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
function clownsEverywhere(
param1,
param2
) {}
clownsEverywhere(
'foo',
'bar'
)
上面代码中,如果在param2或bar后面加一个逗号,就会报错。
如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数 clownsEverywhere 添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。
function clownsEverywhere(
param1,
param2,
) {}
clownsEverywhere(
'foo',
'bar',
)
这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
for await of
异步迭代器(for-await-of):循环等待每个 Promise 对象变为 resolved 状态才进入下一步。
下面是实现迭代的方法:
const arr = ['es5', 'es6', 'es7']
arr[Symbol.iterator] = function () {
let nextIndex = 0
return {
next() {
return nextIndex < arr.length ? {
value: arr[nextIndex++],
done: false
} : {
value: undefined,
done: true
}
}
}
}
我们知道 for…of 是同步运行的,有时候一些任务集合是异步的,那这种遍历怎么办呢?
function Gen(time) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(time)
}, time)
})
}
async function test() {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), item.then(console.log))
}
}
test()
// 1560090138232 Promise {<pending>}
// 1560090138234 Promise {<pending>}
// 1560090138235 Promise {<pending>}
// 100
// 2000
// 3000
这里写了几个小任务,分别是 2000ms 、100ms、3000ms 后任务结束。在上述遍历的过程中可以看到三个任务是同步启动的,然后输出上也不是按任务的执行顺序输出的,这显然不太符合我们的要求。
我们可以使用 async 来实现异步迭代:
function Gen(time) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(time)
}, time)
})
}
async function test() {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), await item.then(console.log))
}
}
test()
// 2000
// 1560091834772 undefined
// 100
// 1560091836774 undefined
// 3000
// 1560091836775 undefined
在 ES9 中也可以用 for…await…of 的语法来操作:
function Gen(time) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(time)
}, time)
})
}
async function test() {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for await (let item of arr) {
console.log(Date.now(), item)
}
}
test()
// 1560092345730 2000
// 1560092345730 100
// 1560092346336 3000
上面的 for…await…of 的语法是异步可迭代协议(Symbol.asyncIterator),类似 for of 这种循环需要遵循 Symbol.interator 可迭代协议一样;Symbol.asyncIterator 异步可迭代协议如下:也是 next 函数返回 done、value 两个属性,不过遍历的值是异步函数;
let obj = {
count: 0,
Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve({ done: false, value: time })
}, time)
})
},
[Symbol.asyncIterator] () {
let self = this
return {
next () {
self.count++
if (self.count < 4) {
return self.Gen(Math.random() * 1000)
} else {
return Promise.resolve({
done: true,
value: ''
})
}
}
}
}
}
async function test () {
for await (let item of obj) {
console.log(Date.now(), item)
}
}
// 1560093560200 649.3946561938179
// 1560093560828 624.6310222512955
// 1560093561733 901.9497480464518
Object Rest & Spread 对象扩展
在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:
const input = {
a: 1,
b: 2
}
const output = {
...input,
c: 3
}
console.log(output) // {a: 1, b: 2, c: 3}
我们再来看下 Object rest 的示例:
const input = {
a: 1,
b: 2,
c: 3
}
let { a, ...rest } = input
console.log(a, rest) // 1 {b: 2, c: 3}
注意这是浅拷贝
Promise.prototype.finally()
指定不管最后状态如何都会执行的回调函数。 Promise.prototype.finally() 方法返回一个 Promise,在promise执行结束时,无论结果是 fulfilled 或者是 rejected,在执行 then() 和 catch() 后,都会执行 finally 指定的回调函数。这为指定执行完promise后,无论结果是 fulfilled 还是 rejected 都需要执行的代码提供了一种方式,避免同样的语句需要在 then() 和 catch() 中各写一次的情况。例如,我们一般写得 loading 状态;可以直接写在 finally 里面去停止 loading;
其他,比如数据库断开链接:
let connection
db.open()
.then(conn => {
connection = conn
return connection.select({
name: 'Jane'
})
})
.then(result => {
// Process result
// Use `connection` to make more queries
})···
.catch(error => {
// handle errors
})
.finally(() => {
connection.close()
})
Object.fromEntries()
方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。
const obj = {
name: 'imooc',
course: 'es'
}
const entries = Object.entries(obj)
console.log(entries)
// [Array(2), Array(2)]
// ES10
const fromEntries = Object.fromEntries(entries)
console.log(fromEntries)
// {name: "imooc", course: "es"}
我们可以将 Map 转 Object:
const map = new Map()
map.set('name', 'imooc')
map.set('course', 'es')
console.log(map)
// Map(2) {"name" => "imooc", "course" => "es"}
const obj = Object.fromEntries(map)
console.log(obj)
// {name: "imooc", course: "es"}
也可以过滤对象的属性:
const course = {
math: 80,
english: 85,
chinese: 90
}
const res = Object.entries(course).filter(([key, val]) => val > 80)
console.log(res) // 0: (2) ["english", 85] 1: (2) ["chinese", 90]
console.log(Object.fromEntries(res))
// {english: 85, chinese: 90}
String 扩展
String.prototype.trimStart()
trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。
String.prototype.trimEnd()
trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。
const str = ' xiao '
// 去掉前面的空格
console.log(str.replace(/^\s+/g, ''))
// 去掉后面的空格
console.log(str.replace(/\s+$/g, ''))
// 去掉前面的空格
console.log(str.trimStart())
console.log(str.trimLeft())
// 去掉后面的空格
console.log(str.trimEnd())
console.log(str.trimRight())
// 去掉所有空格
console.log(str.trim())
Array 扩展
Array.prototype.flat() flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
语法:const newArray = arr.flat(depth) depth 指定要提取嵌套数组的结构深度,默认值为 1
const numbers = [1, 2, [3, 4, [5, 6]]]
// 此时 flat 的参数没有设置,取默认值 1,也就是说只扁平化向下一级,遇到 [3, 4, [5, 6]] 这个数组会扁平会处理,不会再继续遍历内部的元素是否还有数组
console.log(numbers.flat())
// [1, 2, 3, 4, [5, 6]]
console.log(numbers.flat(2))
// [1, 2, 3, 4, 5, 6]
// 使用 Infinity 也是一样的效果
console.log(numbers.flat(Infinity))
// [1, 2, 3, 4, 5, 6]
Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
const numbers = [1, 2, 3]
numbers.map(x => [x * 2]) // [[2], [4], [6]]
numbers.flatMap(x => [x * 2]) // [2, 4, 6]
这个示例可以简单对比下 map 和 flatMap 的区别。当然还可以看下下面的示例:
let arr = ['今天天气不错', '', '早上好']
arr.map(s => s.split(''))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
arr.flatMap(s => s.split(''))
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
修订 Function.prototype.toString()
函数是对象,并且每个对象都有一个 .toString() 方法,因为它最初存在于Object.prototype.toString() 上。所有对象(包括函数)都是通过基于原型的类继承从它继承的。这意味着我们以前已经有 funcion.toString() 方法了。
Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串。
这意味着还将返回注释、空格和语法详细信息。
function foo() {
// es10新特性
console.log('imooc')
}
console.log(foo.toString())
// function foo() {
// es10新特性
// console.log('imooc')
// }
// 直接在方法名 toString()
console.log(Number.parseInt.toString())
// function parseInt() { [native code] }
可选的 Catch Binding
在 ES10 之前我们都是这样捕获异常的:
try {
// tryCode
} catch (err) {
// catchCode
}
在这里 err 是必须的参数,在 ES10 可以省略这个参数:
// 省略 catch 绑定的参数和括号
try {
console.log('Foobar')
} catch {
console.error('Bar')
}
JSON 扩展
JSON superset (JSON 超集)
什么是 JSON 超集?,简而言之就是让 ECMAScript 兼容所有 JSON 支持的文本。 ECMAScript 曾在标准 JSON.parse 部分阐明 JSON 确为其一个子集,但由于 JSON 内容可以正常包含 \u2028 行分隔符 与 \u2029段分隔符,而 ECMAScript 却不行。
// 在这之前是不支持分隔符在代码中
eval('var str = "ji"; \u2029 function foo() { return str; };')
console.log(foo())
// ji
JSON.stringify() 增强能力
SON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。
// \uD83D\uDE0E emoji 多字节的一个字符
console.log(JSON.stringify('\uD83D\uDE0E')) // 笑脸
// 如果我们只去其中的一部分 \uD83D 这其实是个无效的字符串
// 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列
console.log(JSON.stringify('\uD83D')) // "\ud83d"
Symbol 扩展
Symbol.prototype.description
我们知道,Symbol 的描述只被存储在内部的 Description ,没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:
const name = Symbol('es')
console.log(name.toString()) // Symbol(es)
console.log(name) // Symbol(es)
console.log(name === 'Symbol(es)') // false
console.log(name.toString() === 'Symbol(es)') // true
现在可以通过 description 方法获取 Symbol 的描述:
const name = Symbol('es')
console.log(name.description) // es
console.log(name.description === 'es') // true
description 是只读的属性;
Dynamic Import
现代前端打包资源越来越大,打包成几M的JS资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。
如下例子:页面上有一个按钮,点击按钮才去加载 ajax 模块。这里的 ajax 是自己封装的一个方法
const oBtn = document.querySelector('#btn')
oBtn.addEventListener('click', () => {
import('./ajax').then(mod => {
mod.default('static/a.json', res => {
console.log(res)
})
})
})
BigInt
BigInt,表示一个任意精度的整数,可以表示超长数据,可以超出 2 的 53 次方。
Js 中 Number 类型只能安全的表示-(2^53-1)至 2^53-1 范的值
console.log(2 ** 53) // es7 幂运算符
console.log(Number.MAX_SAFE_INTEGER) // 最大值-1
使用 BigInt 有两种方式:
方式一:数字后面增加 n
const bigInt = 9007199254740993n
console.log(bigInt) // 9007199254740993n
console.log(typeof bigInt) // bigint
console.log(1n == 1) // true
console.log(1n === 1) // false 值相同,类型不同
方式二:使用 BigInt 函数
const bigIntNum = BigInt(9007199254740993n)
console.log(bigIntNum) // 9007199254740993n
也可以进行相加:
const bigIntNum1 = BigInt(9007199254740993n)
const bigIntNum2 = BigInt(9007199254740993n)
console.log(bigIntNum1 + bigIntNum2) // 18014398509481986n
Promise.allSettled()
Promise.all() 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入 reject 状态。
场景:现在页面上有三个请求,分别请求不同的数据,如果一个接口服务异常,整个都是失败的,都无法渲染出数据:
Promise.all([
Promise.reject({
code: 500,
msg: '服务异常'
}),
Promise.resolve({
code: 200,
data: ['1', '2', '3']
}),
Promise.resolve({
code: 200,
data: ['4', '5', '6']
})
]).then(res => {
console.log(res)
console.log('成功')
}).catch(err => {
console.log(err)
console.log('失败')
})
我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态:
Promise.allSettled([
Promise.reject({
code: 500,
msg: '服务异常'
}),
Promise.resolve({
code: 200,
data: ['1', '2', '3']
}),
Promise.resolve({
code: 200,
data: ['4', '5', '6']
})
]).then(res => {
console.log(res)
console.log('成功')
}).catch(err => {
console.log(err)
console.log('失败')
})
globalThis
提供了一个标准的方式去获取不同环境下的全局对象;
Javascript 在不同的环境获取全局对象有不通的方式:
node 中通过 global
web 中通过 window, self 等.
self:打开任何一个网页,浏览器会首先创建一个窗口,这个窗口就是一个 window 对象,也是js运行所依附的全局环境对象和全局作用域对象。self 指窗口本身,它返回的对象跟window对象是一模一样的。也正因为如此,window 对象的常用方法和函数都可以用 self 代替 window。
self.setTimeout(() => {
console.log(123)
}, 1000)
以前想要获取全局对象,可通过一个全局函数
const getGlobal = () => {
if (typeof self !== 'undefined') {
return self
}
if (typeof window !== 'undefined') {
return window
}
if (typeof global !== 'undefined') {
return global
}
throw new Error('无法找到全局对象')
}
const globals = getGlobal()
console.log(globals)
globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis。
console.log(globalThis)
可选链 Optional chaining
可让我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。
const user = {
address: {
street: 'xx街道',
getNum() {
return '80号'
}
}
}
在之前的语法中,想获取到深层属性或方法,不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property… 这种错误,这极有可能让你整个应用挂掉。
const street = user && user.address && user.address.street
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(street, num)
用了 Optional Chaining ,上面代码会变成:
const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)
空值合并运算符(Nullish coalescing Operator)
空值合并运算符(??)是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。
当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。
// 如果设置为 false '' 0 都会判断为 false;执行 5
const b = 0 // 或者 null undefined false ''
const a = b || 5
console.log(a)
空值合并运算符 ?? 我们仅在第一项为 null 或 undefined 时设置默认值
// false 0 无效,只有值为 null 或 undefined 时才会取后面的数
const a = b ?? 123
console.log(a)