ECMAScript总结
- 在node环境下,可以使用一个小工具 nodemon来实现自动执行js文件
let、var、const
- let是没有变量提升的,而var定义的,会被声明到代码最初
- let是会创造一个块级作用域,每一个作用域中的同名变量都是不同的
- const 是在let的基础上加了一个只读的特性,声明过后不允许再修改
结构赋值
数组
const arr = [1, 2, 3, 4]
const [a, b, c, d, e = 5] = arr
// 此时a, b, c, d四个变量按位置依次对应,没有对应位置的为undefiend,也可以赋默认值
对象
const obj = { name: '张三', age: 19 }
const { name } = obj
// 此时name变量为张三,也可以重命名,防止冲突
const { name: actName } = obj
// 此时actName变量值为张三
模板字符串
const name = 'tom'
const gender = true
const myTag = (strings, name, gender) => {
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTag`hey, ${name} is a ${gender}`
console.log(result)
- 可以在模板字符串``前边定义一个函数作为模板来使用,可以处理比如一些true, false变汉字了等情况
新增的字符串方法
const message = 'Error: xxxxx.'
console.log(message.startsWith('Error'))
console.log(message.endsWith('.'));
console.log(message.includes('xx'))
startsWith
- 检查字符串的开头是不是有参数中的内容
endsWith
- 检查字符串的结尾是不是有参数中的内容
includes
- 检查字符串中间是不是有参数中的内容
参数默认值
- 有时候一个函数如果没有传参数进来的话,我们也需要一个默认值,此时可以直接在形参后面跟一个=赋值
const fn = (a, b = 'xxx') => {
console.log(a, b)
}
fn(11)
另外注意: 赋默认值的形参一定要在参数的后边
剩余参数
- 可以通过...展开的形式来获取到函数的所剩参数
function fn(a, ...args) {
// 此时args是一个接收剩余参数的数组,如果括号中只有一个...args,那么args代表所有参数
console.log(args)
}
fn(1, 2, 3, 4)
展开符
- 可以把数组按顺序展开
const arr = [1, 2, 3, 4]
console.log(...arr)
对象字面量的增强
const bar = '123'
const obj = {
name: 'xxx',
// 如果是同名变量或者是函数,可以省略前面的
bar,
// 也可以使用方括号,在对象中放一些表达式
[1 + 1]: 123
}
Object.assign
- 合并对象,将后面的对象合并到第一个参数中,也就是目标对象,相同的会覆盖
- 返回值是一个与目标对象相同的对象
const tar = { a: 1, b: 2 }
const obj = { a: 2, c: 3 }
const result = Object.assign(tar, obj)
console.log(tar) // 输出{ a: 2, b: 2, c: 3 }
console.log(result) // 输出{ a: 2, b: 2, c: 3 }
Object.is(不常用)
- 判断两个是不是相等
- 比如NAN和NAN,在===的情况下是不相等的
- 在Object.is(NAN, NAN)的情况就是相等的
Proxy
- 可以利用proxy(代理)的方式来给对象增加一个安保角色
- Proxy接收两个参数,一个是目标处理对象,一个是有着get和set等监视函数的对象
const person = { name: '张三', age: 11 }
const personProxy = new Proxy(person, {
// get接收两个参数,一个是目标对象,一个是get的属性名
get(target, property) {
// 如果get的属性在目标对象中,取出,不在返回默认值
return property in target ? target[property] : 'default'
},
// set接收三个参数,一个是目标对象,一个是set的属性名,一个是set的属性值
set(target, property, value) {
// 也可以设定要set的属性值的一个规范要求
if (property === 'age') {
// Number.isInteger用来判断参数是不是整数,如果不是,throw(抛出)错误
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not a int`)
}
}
target[property] = value
}
})
console.log(personProxy.name) // 输出张三
console.log(personProxy.xxxx) // 输出default
personProxy.newProperty = '新的属性'
console.log(personProxy) // 输出{ name: '张三', age: 11, newProperty: '新的属性' }
Proxy对比defineProperty所存在的优势
-
- defineProperty只能监视属性的读写,而Proxy有着很多完善的对象操作
- 例如deleteProperty
const person = { name: '张三', age: 11 }
const personProxy = new Proxy(target, {
deleteProperty(target, property) {
// 利用delete操作符
delete target[property]
}
})
delete personProxy.age
console.log(personProxy) // 输出 { name: '张三' }
-
- Proxy能够更好的支持数组对象的监视
const list = []
const listProxy = new Proxy(list, {
set(target, property, value) {
// property相等于下标
console.log('set->', property, value)
target[property] = value
return true
}
})
listProxy.push(1)
console.log(listProxy)
-
- Proxy以非侵入的方式监管了对象的读写
Reflect
- Reflect是一个内置对象,提供一些拦截js对象的一些方法
- 以往操作对象有的是用像in ,delete的操作符,有的是用Object.keys等的一些api方法,Reflect正是解决了这个问题,把两者进行了统一
class 继承
class Person{
constructor(name) {
this.name = name
}
say() {
console.log(`${this.name} say hello`)
}
}
const person = new Person('张三')
person.say()
class Students extends Person{
// Students类继承Person类的name属性以及say方法
constructor(name, code) {
super(name)
this.code = code
}
logCode() {
console.log(this.code)
}
}
const students = new Students('李四', 001)
students.say()
students.logCode()
Set集合
- Set当中里面是没有重复的,最常用的是给数组去重
const arr = [1, 2, 3, 1, 3]
const result = Array.from(new Set(arr))
console.log(result) // 输出[1, 2, 3]
add(添加集合元素)
forEach(遍历集合)
for...of(同上)
size(集合长度)
has(判断集合中是否拥有这个元素)
delete(删除集合中的某个元素)
clear(清除集合内所以元素)
Map键值对
- 在日常工作中,可能会遇到拿一个对象来做唯一键,此时如果放在对象里,我们取出来的键是[Object Object]
- Map键值对就是来解决这个问题的,它可以将任何数据类型来作为键
const m = new Map()
const tom = { name: '张三' }
m.set(tom, 10) // 添加一条键值对
console.log(m) // 输出{ { name: '张三' } => 10 }
m.has() // 判断是否拥有参数键
m.delete() // 删除参数键
m.clear() // 清除这个键值对Map
m.forEach((val, key) => console.log(val, key))
Symbol
- 唯一符号,多用来为对象添加一个独一无二的属性名
console.log(Symbol('foo') === Symbol('foo')) // 因为Symbol返回的独一无二的,所以为false
console.log(Symbol.for('foo') === Symbol.for('foo')) // Symbol的for方法就是传入相同的字符串,返回相同的Symbol,任何参数都会转成字符串
- 对象的toString标签
const obj = {}
console.log(obj.toString()) // 输出[object Object]
为了不与对象的内置属性重名,所以js规定用Symbol来自定义toString标签
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString()) // 输出[object XObject]
- 遍历和取键
- 像Object.keys了,for...in...了,都是只取普通属性
- 可以通过Object.getOwnPropertySymbols(obj)来取Symbol属性
- 所以Symbol非常适合做对象的私有属性
for...of...
-
可以遍历所有的数据类型
-
举例1
const m = new Map()
m.set('aaa', 111)
m.set('bbb', 222)
for(let i of m) {
console.log(i) // 输出的是['aaa', 1], ['bbb', 222]
}
// 所以我们可以通过解构的方式
for (let [key, val] of m) {
// 拿到key 和 val
}
迭代器模式
const obj = {
work: ['撸代码', '画画', '搬砖'],
life: ['吃饭', '睡觉', '打豆豆']
}
// 如果我们要拿到这里面数组所有数据,只能通过分别取出work,life的方式
for (let i of obj.work){}
for(let i of obj.life){}
- 这样的问题,代码耦合太高,上面改了,下面也得改,所以可以添加一个each方法
const obj = {
work: ['撸代码', '画画', '搬砖'],
life: ['吃饭', '睡觉', '打豆豆'],
each: function(callback) {
// 统一在这边处理
const all = [...this.work, ...this.life]
for (let i of all) { callback(i) }
}
}
obj.each((i) => console.log(i)) // 这样就拿到所有数据了
不过这样,还是存在着要根据情况来实现的这么一个弊端
- 所以,迭代器模式的出现,就解决了这么一个问题
Iterable(可迭代接口)
for...of...是可以遍历所有数据类型的,我们通过观察数组了,Set了,Map了,他们都有一个公共的Symbol.iterator方法
同时这个方法的执行结果是一个有着next方法的对象,可以返回一个对象{ value: xx, done: false }
当数据全部输出一遍以后,返回{ value: undefined, done: true }
- 这也是对象无法直接使用for...of...的原因,所以我们可以手动写入一个Symbol.iterator方法
const obj = {
work: ['撸代码', '画画', '搬砖'],
life: ['吃饭', '睡觉', '打豆豆'],
[Symbol.iterator]: function() {
const all = [...this.work, ...this.life]
let index = 0
return {
next: function() {
return { value: all[index], done: index++ >= all.length }
}
}
}
}
for (let i of obj) {
console.log(i)
}
生成器Generator
function * createGenerator() {
yield 100
}
console.log(createGenerator().next()) // 输出{ value: 100, done: false }
-
可以通过yield关键字来实现函数的惰性执行,它会将函数暂停下来,调用一次next()方法,便继续执行,并且把yiled的值返回过去
-
箭头函数暂无生成器写法
-
案例1 --发号器
function *createIdMaker(){
let id = 1
while(true) {
yield id++
}
}
// 在这里将createIdMaker()存到执行栈,不会被执行立即被弹出
const idMaker = createIdMaker()
for(let i = 0; i < 10; i++){
console.log(idMaker.next())
}
- 案例二 -- 可以简化迭代器函数
const obj = {
work: ['撸代码', '画画', '搬砖'],
life: ['吃饭', '睡觉', '打豆豆'],
[Symbol.iterator]: function *() {
const all = [...this.work, ...this.life]
for (let i of all) yield i
}
}
includes
- 在之前的版本,如果要查找一个元素在不在数组中,用的是arr.indexOf()来判断返回的是不是-1,但是这样,如果要判断的元素是NAN的话,就算是有,也返回-1
- 所以就可以直接用arr.includes(),直接返回true或者false
指数运算符
- 之前是通过Math.pow(2, 10) 来算2的10次方
- 现在可以通过两个乘法运算符来直接写指数运算了
console.log(2 ** 10) // 输出1024
ES2017
Object.values
const obj = { name: '张三', age: 16 }
console.log(Object.values(obj)) // 输出[ '张三', 16 ]
Object.entries和Object.fromEntries
// 大家熟悉的Object.entries()是把一个对象变成一个自身的键值对数组 例如:
const a = { name: '张三', age: 16 }
console.log(Object.entries(a)) // 得到[['name', '张三'], ['age', 16]]
// Object.fromEntries()则与Object.entries()相反,是把键值对数组转成自身对象 例如:
const a = [['name', '张三'], ['age', 16]]
console.log(Object.fromEntries(a)) // 得到{ name: '张三', age: 16 }
Object.getOwnPropertyDescriptors和Object.defineProperties
const o1 = {
name: 'xxx',
val: 'bbb',
// es2015新增的get方法和set方法
get label() {
return this.name + ' ' + this.val
}
}
console.log(o1.label)
let o2 = Object.assign({}, o1)
o2.val = 'qqq'
console.log(o2.label) // 此时输出的还是xxx bbb
// 因为Object.assign没有复制到get和set属性,所以要复制的话需要用到下面这俩
const descriptors = Object.getOwnPropertyDescriptors(o1)
const o2 = Object.defineProperties({}, descriptors)
o2.val = 'www'
console.log(o2.label)