目录
概述
ECMAScript
- ECMAScript简写成ES, 也是一门编程语言,
- 通常看作是Javascript的标准化规范,Javascript可以看作是ES的扩展语言,ES只提供最基础的语法
- 在浏览器环境中的Javascript等于ES+web API(BOM, DOM)
- 在Node环境中的Javascript就等于ES+ node API(fs, net...etc)
ES2015 概述
- ES发布了很多版本,其中变化最多的就是2015年发布的ECMAScript2015,一般称为ES6
- 从2015年开始,ES保持每年一个版本的迭代
ES2015 let 与块级作用域
- 作用域就是某成员能够起作用的范围,ES6之前,只有全局作用域和函数作用域,ES6新增了块级作用域
- 块就是一堆花括号包起来的范围,ES6中和let配合使用
// let 声明的成员只会在所声明的块中生效
if (true) {
var foo = 'zce'
}
console.log(foo)//zce
if (true) {
let x = 'zce'
}
console.log(x)//undefined, let声明的变量只能在作用域内访问
- let能解决for循环变量重名的问题
// let 在 for 循环中的表现
for (var i = 0; i < 3; i++) {
for (var i = 0; i < 3; i++) {
console.log(i)
}
console.log('内层结束 i = ' + i)
}
//0 1 2 内层结束 i = 3
//加上let
for (var i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i)
}
console.log('内层结束 i = ' + i)
}
Const 常量/恒量
- 只读,声明后不允许修改
const name = 'zce'
// 恒量声明过后不允许重新赋值
// name = 'jack'会报错
- 不能修改对象的内存地址,可以修改对象中的属性值(对象所占内存的数据)
// 恒量只是要求内层指向不允许被修改
const obj = {}
// 对于数据成员的修改是没有问题的
obj.name = 'zce'
- 必须先声明再赋值
ES2015 数组的解构
const arr = [100, 200, 300]
const [foo, bar, baz] = arr
console.log(foo, bar, baz)//100, 200, 300,按顺序输出
const [, , baz] = arr
console.log(baz)//只输出300,
const [foo, ...rest] = arr
console.log(rest)//...reset只可用于数组最后一位,此处输出[200, 300]
const [foo, bar, baz, more] = arr
console.log(more)//会输出undefined,因为arr一共只有三个数据
const [foo, bar, baz = 123, more = 'default value'] = arr
console.log(bar, more)//设置默认值
const path = '/foo/bar/baz'
const [, rootdir] = path.split('/')
console.log(rootdir)//字符串分割
ES2015 对象的解构
- 根据属性名去提取而非下标
const obj = { name: 'xm', age: 18 }
const { name } = obj
console.log(name)//xm
const name = 'ami' //变量和属性名重复
const { name: objName } = obj//属性名赋给一个新变量名
console.log(objName)//xm
const name = 'ami'
const { name: objName = 'lily' } = obj
console.log(objName)//xm
- 具备和数组一样的操作(...rest)
ES2015 模板字符串
- 可直接换行
- 可以通过 ${} 去插入** 任何标准的JavaScript语句 **
const name = 'tom'
// 可以通过 ${} 插入表达式,表达式的执行结果将会输出到对应位置
const msg = `hey, ${name}`
console.log(msg)//hey, tom
- 可以设置带标签的模板字符串
// 模板字符串的标签就是一个特殊的函数,
// 使用这个标签就是调用这个函数
// const str = console.log`hello world`
const name = 'tom'
const gender = false
//标签函数可以处理字符串
function myTagFunc (strings, name, gender) {
// console.log(strings, name, gender)
// return '123'
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTagFunc`hey, ${name} is a ${gender}.`
console.log(result)
ES2015 字符串拓展方法
const message = 'Error: foo is not defined.'
console.log(
message.startsWith('Error'),//是否以某些字符串开头
message.endsWith('.'),//是否以某些字符串结尾
message.includes('foo')//是否包含某些字符串
)
ES2015 函数形参新语法
- 参数的默认值 参数=xxx
// 默认参数一定是在形参列表的最后
function foo (x, enable = true) {
console.log('foo invoked - enable: ')
console.log(x, enable)
}
foo("a")
- 剩余参数 (...args)
//只能出现在最后一位,使用一次,表示之后的所有参数
function foo (first, ...args) {
console.log(args)
}
foo(1, 2, 3, 4)
- 展开数组 (...array)
const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr)//foo, bar, baz
console.log(...arr)//foo, bar, baz
ES2015 箭头函数
- 简化代码
// 最简方式
const inc = n => n + 1
// 函数体多条语句,返回值仍需 return
const inc = (n, m) => {
console.log('inc invoked')
return n + 1
}
const arr = [1, 2, 3, 4, 5, 6, 7]
// 常用场景,回调函数
arr.filter(i => i % 2)
- this指向问题
// 箭头函数不会改变 this 指向
const person = {
name: 'tom',
// sayHi: function () {
// console.log(`hi, my name is ${this.name}`)//this指向person对象
// }
sayHi: () => {
console.log(`hi, my name is ${this.name}`)//this指向sayHi这个方法
},
sayHiAsync: function () {
// const _this = this//es6之前改变this指向
// setTimeout(function () {
// console.log(_this.name)
// }, 1000)
console.log(this)
setTimeout(() => {
// console.log(this.name)//this指向sayHiAsync函数
console.log(this)
}, 1000)
}
}
person.sayHiAsync()
ES2015 对象字面量的增强
const bar = '345'
const obj = {
foo:123,
// bar:bar, 等价于下面的
bar,//属性名和属性值相同的,可以只写属性名
// method1:function(){
// console.log('method1')
// },
method1(){//方法可以省略“:function”
console.log('method1');
console.log(this)
},
[1+1]:2,//计算属性名: 表达式执行结果将会变为属性名
}
ES2015 Object.assign
- 将源对象合并到目标对象中,如有相同属性,则以源对象的为主
// Object.assign 方法
const source1 = {
a: 123,
b: 123
}
const source2 = {
b: 789,
d: 789
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1, source2)
console.log(target)//{ a: 123, c: 456, b: 789, d: 789 }
console.log(result === target)//true
- 使用场景
// 应用场景
function func (obj) {
// obj.name = 'func obj'//在内部直接修改obj对象属性值则会影响到外部的obj对象
// console.log(obj)
const funcObj = Object.assign({}, obj)//为了不影响外部的obj对象,就可以用assign生成新的对象
funcObj.name = 'func obj'
console.log(funcObj)
}
const obj = { name: 'global obj' }
func(obj)
console.log(obj)
ES2015 Object.is
- 判断两个值是否相等(很少使用)
// Object.is
console.log(
0 == false, // => true
0 === false, // => false
+0 === -0, // => true
NaN === NaN, // => false
Object.is(+0, -0), // => false
Object.is(NaN, NaN), // => true
)
ES2015 Proxy
- 代理,比Object.defineProperty更高级,用来监视属性的读写,Vue3中已经开始用到
// Proxy 对象
const person = {
name: 'zce',
age: 20
}
//Proxy第一个参数是目标对象,第二个参数是一个对象,具备get,set方法
const personProxy = new Proxy(person, {
// get监视属性读取
get (target, property) {
return property in target ? target[property] : 'default'
// console.log(target, property)
// return 100
},
// set监视属性设置
set (target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
// console.log(target, property, value)
}
})
personProxy.age = 100
personProxy.gender = true
console.log(personProxy.name)
console.log(personProxy.xxx)
打印内容
- 比较Object.DefineProperty和Proxy
1. Proxy相对而言功能更强大,例如可以监视对象的delete操作
// 优势1:Proxy 可以监视读写以外的操作 --------------------------
const person = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person, {
deleteProperty (target, property) {
console.log('delete', property)
delete target[property]
}
})
delete personProxy.age
console.log(person)
还有其他方法
2. Proxy可以监控数组
const list = []
const listProxy = new Proxy(list, {
set (target, property, value) {
console.log('set', property, value)
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
listProxy.push(100)
注意,监听的是下标和对应的值
3. Proxy不需要侵入对象,Object.defineProperty使用的时候需要拆解对象本身,去对每个属性进行读写,Proxy不需要对对象进行多于拆解(此处是自己的理解)
ES2015 Reflect
- Reflect 内部封装了一系列对对象的底层操作
- 无法使用 new 关键字 是一个静态类
- 对 Proxy 处理对象的方法的默认实现
- 统一提供一套用于操作对象的API
// Reflect 对象
const obj = {
foo: '123',
bar: '456'
}
const proxy = new Proxy(obj, {
get (target, property) {
console.log('watch logic~')
//返回reflect的方法
return Reflect.get(target, property)
}
})
console.log(proxy.foo)
const obj = {
name: 'zce',
age: 18
}
//以前使用的方法
console.log('name' in obj)
console.log(delete obj['age'])
console.log(Object.keys(obj))
//现在统一可以用reflect
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))
ES2015 Class
- 创建class
// class 关键词
function Person (name) {
this.name = name
}
//以前是通过原型链去添加方法
Person.prototype.say = function () {
console.log(`hi, my name is ${this.name}`)
}
class Person1 {
constructor (name) {
this.name = name
}
//直接添加方法
say () {
console.log(`hi, my name is ${this.name}`)
}
}
//通过new的方式构造实例对象
const p = new Person1('tom')
p.say()
- 实例方法VS静态方法
实例方法就是通过类构造实例对象去调用,静态方法直接通过这个类去调用,ES6中用static来写静态方法
class Person {
constructor(name){
this.name = name
}
say(){
console.log(`hi,my name is ${this.name}`);
}
//静态方法this返回当前类,并不会指向某个实例对象
static create(name){
return new Person(name)
}
}
const tom = Person.create('tom')
tom.say();
-
extends(继承)
// extends 继承
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
class Student extends Person {
constructor (name, number) {
super(name) // 父类构造函数
this.number = number
}
hello () {
super.say() // 调用父类成员
console.log(`my school number is ${this.number}`)
}
}
const s = new Student('jack', '100')
s.hello()
ES2015 Map
和Object对象一样的键值对结构,对象键是string类型,但是map的键可以是任意类型
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
console.log(Object.keys(obj))
console.log(obj['[object Object]'])
const m = new Map()
const tom = { name: 'tom' }
m.set(tom, 90)
console.log(m)
具备set.get.clear.delete等方法
ES2015 Symbol函数
- 为对象添加一个独一无二的属性标识符
// 场景1:扩展对象,属性名冲突问题
// // shared.js ====================================
const cache = {}
// // a.js =========================================
cache['foo'] = Math.random()
// // b.js =========================================
cache['foo'] = '123'//键名重复
console.log(cache)
// =========================================================
const s = Symbol()
console.log(s)
console.log(typeof s)
// 两个 Symbol 永远不会相等
console.log(
Symbol() === Symbol()
)
// Symbol 描述文本
console.log(Symbol('foo'))
console.log(Symbol('bar'))
console.log(Symbol('baz'))
// 使用 Symbol 为对象添加用不重复的键
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)
// 也可以在计算属性名中使用
const objects = {
[Symbol()]: 123
}
console.log(objects)
// 案例2:Symbol 模拟实现私有成员
// a.js ======================================
const name = Symbol()
const person = {
[name]: 'zce',
say () {
console.log(this[name])
}
}
// 只对外暴露 person
// b.js =======================================
// 由于无法创建出一样的 Symbol 值,
// 所以无法直接访问到 person 中的「私有」成员
// person[Symbol()]
person.say()
正因为Symbol为对象添加了唯一属性名, 所以一般的循环拿不到这个属性名,Object.getOwnPropertySymbols才可以拿到
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
for (var key in obj) {
console.log(key)//foo
}
console.log(Object.keys(obj))//[ 'foo' ]
console.log(JSON.stringify(obj))//{"foo":"normal value"}
//下面方法可以拿到
console.log(Object.getOwnPropertySymbols(obj))//[ Symbol() ]
- Symbol.for方法
console.log(
Symbol() === Symbol(),
Symbol('foo') === Symbol('foo')
)//false false
// 两个Symbol无法相等,但是可以用for方法来让处理后的值相等
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2)
console.log(//注意,这里接收的参数会转换成字符串,所以此处输出true
Symbol.for(true) === Symbol.for('true')
)
- 内置常量
//用Symbol常量标识内部方法
console.log(Symbol.iterator)//Symbol(Symbol.iterator)
console.log(Symbol.hasInstance)//Symbol(Symbol.hasInstance)
const obj1 = {}
console.log(obj1.toString())//[object Object]
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString())//[object XObject]
ES2015 for..of
- 拿到数组或者伪数组所有的值
const arr = [100, 200, 300, 400]
for (const item of arr) {
console.log(item)
}
-
for...of 循环可以替代 数组对象的 forEach 方法
arr.forEach(item => {
console.log(item)
})
for (const item of arr) {
console.log(item)
if (item > 100) {
break
}
}
// forEach 无法跳出循环,必须使用 some 或者 every 方法
-
遍历 Set 与遍历数组相同
-
遍历 Map 可以配合数组结构语法,直接获取键值
const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const [key, value] of m) {
console.log(key, value)
}
-
普通对象不能被直接 for...of 遍历
const obj = { foo: 123, bar: 456 }
for (const item of obj) {
console.log(item)
}
ES2015迭代器Iterator
- 实现可迭代接口Iterable就是for...of能遍历的前提
const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
也可以用while实现
while (true) {
const current = iterator.next()
if (current.done) {
break // 迭代已经结束了,没必要继续了
}
console.log(current.value)
}
- 实现Iterator
const obj = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: function () {
let index = 0
const self = this
return {
next: function () {
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++
return result
}
}
}
}
for (const item of obj) {
console.log('循环体', item)
}
- 迭代器设计模式对外提供统一的接口,开发者不必关心内部的结构
ES2015 生成器
- 异步编程中减少回调嵌套
- 惰性执行,和yield关键字一起用(迭代器模式),遇见yield关键字函数的执行就会暂停,直到下一次调用.next()方法,函数从暂停位置继续执行,一直到函数执行结束
//函数之前增加一个*,就是一个生成器函数
function * foo () {
console.log('1111')
yield 100
console.log('2222')
yield 200
console.log('3333')
yield 300
}
const generator = foo()
console.log(generator.next()) // 第一次调用,函数体开始执行,遇到第一个 yield 暂停
//1111 { value: 100, done: false }
console.log(generator.next()) // 第二次调用,从暂停位置继续,直到遇到下一个 yield 再次暂停
//2222 { value: 200, done: false }
console.log(generator.next()) // 。。。
console.log(generator.next())
// 第四次调用,已经没有需要执行的内容,{ value: undefined, done: true }
- 应用
// Generator 应用
// 案例1:发号器
function * createIdMaker () {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)//1
console.log(idMaker.next().value)//2
console.log(idMaker.next().value)//3
console.log(idMaker.next().value)//4
// 案例2:使用 Generator 函数实现 iterator 方法
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for (const item of all) {
yield item
}
}
}
for (const item of todos) {
console.log(item)
}
ES2016 概述
-
Array.prototype.includes
const arr = ['foo', 1, NaN, false]
// indexOf无法找到数组中的 NaN
console.log(arr.indexOf(NaN))//-1(下标)
// 能够查找 NaN
console.log(arr.includes(NaN))//true
// 直接返回是否存在指定元素
console.log(arr.includes('foo'))//true
-
指数运算符
console.log(Math.pow(2, 10)) //1024
console.log(2 ** 10)//1024
ES2017 概述
-
Object.values
const obj = {
foo: 'value1',
bar: 'value2'
}
console.log(Object.values(obj))//[ 'value1', 'value2' ]
-
Object.entries
console.log(Object.entries(obj))//[ [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]
//可以结合for...of遍历得到键,值
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
}
//foo value1 bar value2
-
String.prototype.padStart / String.prototype.padEnd 对齐文本
const books = {
html: 5,
css: 16,
javascript: 128
}
for (const [name, count] of Object.entries(books)) {
console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
}
-
在函数参数中添加尾逗号
const arr = [
100,
200,
300,
]
- async/await promise语法糖