一、ECMAScript 2015
1. ES2015共有三种作用域
- 全局作用域
- 函数作用域
- 块级作用域(新增)
2、变量声明:let const
let const都是块级作用域,let是变量,const是常量,
let、const、var 三者的区别:const:定义的变量不可以修改,当前块级作用域内,无法重复声明;var:1、只存在于当前作用域中,2、var命令会发生“变量提升”现象,可以在声明之前使用,值为undefined,且可以重复声明,3、全局声明的变量时,是顶层对象的属性。let:当前块级作用域内,不能重复声明,必须先声明,再调用
let 在 for 循环中的表现
for (var i = 0; i < 3; i++) {
for (var i = 0; i < 3; i++) {
console.log(i)
}
console.log('内层结束 i = ' + i)
}
for (var i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i)
}
console.log('内层结束 i = ' + i)
}
let 应用场景:循环绑定事件,事件处理函数中获取正确索引
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
console.log(i)
}
}
elements[2].onclick() //3
var elements = [{}, {}, {}]
for (let i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
console.log(i)
}
}
elements[0].onclick() //0
3、数组解构
方括号[]中的变量按顺序匹配数组元素
const arr = [100, 200, 300]
const foo = arr[0]
const bar = arr[1]
const baz = arr[2]
console.log(foo, bar, baz)
const [foo, bar, baz] = arr
console.log(foo, bar, baz)
const [, , baz] = arr
console.log(baz)
const [foo, ...rest] = arr
console.log(rest)
const [foo, bar, baz, more] = arr
console.log(more)
const [foo, bar, baz = 123, more = 'default value'] = arr
console.log(bar, more)
const path = '/foo/bar/baz'
const tmp = path.split('/')
const rootdir = tmp[1]
const [a, rootdir] = path.split('/') // ['','foo','bar','baz']
console.log(rootdir)
4、对象解构
const obj = { name: 'zce', age: 18 }
const { name } = obj
console.log(name)
const name = 'tom'
const { name: objName } = obj
console.log(objName)
const name = 'tom'
const { name: objName = 'jack' } = obj
console.log(objName)
const { log } = console
log('foo')
log('bar')
log('123')
5、模板字符串
// 反引号包裹
const str = `hello es2015, this is a string`
// 允许换行
const str = `hello es2015,
this is a \`string\``
console.log(str)
const name = 'tom'
// 可以通过 ${} 插入表达式,表达式的执行结果将会输出到对应位置
const msg = `hey, ${name} --- ${1 + 2} ---- ${Math.random()}`
console.log(msg)
6、 模板字符串标签函数
模板字符串的标签就是一个特殊的函数,
使用这个标签就是调用这个函数
// 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)
7、字符串的扩展方法
includes()
startsWith()
endsWith()
const message = 'Error: foo is not defined.'
console.log(
// message.startsWith('Error')
// message.endsWith('.')
message.includes('foo')
)
8、函数参数的默认值
// 带有默认值的参数要放在最后
function foo(enable = true) {
console.log(enable)
}
foo(true) //true
foo(false) //false
foo() // true
9、剩余参数
function foo (first, ...args) {
console.log(args)
}
foo(1, 2, 3, 4)
10、展开数组参数
const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr) // foo bar baz
console.log(...arr) // foo bar baz
11、箭头函数
箭头函数不会改变this指向,this为上层作用域的this
function inc (number) {
return number + 1
}
// 最简方式
const inc = n => n + 1
// 完整参数列表,函数体多条语句,返回值仍需 return
const inc = (n, m) => {
console.log('inc invoked')
return n + 1
}
console.log(inc(100))
const arr = [1, 2, 3, 4, 5, 6, 7]
arr.filter(function (item) {
return item % 2
})
// 常用场景,回调函数
arr.filter(i => i % 2)
// 箭头函数不会改变 this 指向
const person = {
name: 'tom',
// sayHi: function () {
// console.log(`hi, my name is ${this.name}`)
// }
sayHi: () => {
console.log(`hi, my name is ${this.name}`)
},
sayHiAsync: function () {
// const _this = this
// setTimeout(function () {
// console.log(_this.name)
// }, 1000)
console.log(this)
setTimeout(() => {
// console.log(this.name)
console.log(this)
}, 1000)
}
}
person.sayHiAsync()
12、 对象字面量增强
属性名与变量名相同,可以省略 : bar
对象方法可以直接写函数形式:method1(){}
使用方括号的方式计算动态属性名
const bar = 111
const obj = {
foo: 123,
// bar: bar,
bar, // 同上一行效果
// method1: function () {
// console.log(`method1: ${this}`)
// },
method2 () {
// 直接写一个方法,同上面的冒号属性
console.log(`method2: ${this}`)
},
[Math.random()]: 123, // 计算属性名
}
console.log(obj)
13、对象扩展方法
Object.assign(target, source):将多个源对象中的属性复制到一个目标对象中
// 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)
console.log(result === target)
// 应用场景
function func (obj) {
// obj.name = 'func obj'
// console.log(obj)
const funcObj = Object.assign({}, obj)
funcObj.name = 'func obj'
console.log(funcObj)
}
const obj = { name: 'global obj' }
func(obj)
console.log(obj)
14、代理对象:Proxy
ES5中有一个Object.defineProperty,Vue2就是通过这个实现数据双向绑定
ES6提供了Proxy,可以监视对象的读写过程,Vue3.0通过Proxy实现数据绑定
// Proxy 对比 Object.defineProperty() ===============
const person = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person, {
// 监视属性读取
get (target, property) {
return property in target ? target[property] : 'default'
// console.log(target, property)
// return 100
},
// 监视属性设置
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)
// 优势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 不需要侵入对象 --------------------------
const person = {}
Object.defineProperty(person, 'name', {
get () {
console.log('name 被访问')
return person._name
},
set (value) {
console.log('name 被设置')
person._name = value
}
})
Object.defineProperty(person, 'age', {
get () {
console.log('age 被访问')
return person._age
},
set (value) {
console.log('age 被设置')
person._age = value
}
})
// person.name = 'jack'
// console.log(person.name)
// Proxy 方式更为合理
const person2 = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person2, {
get (target, property) {
console.log('get', property)
return target[property]
},
set (target, property, value) {
console.log('set', property, value)
target[property] = value
}
})
personProxy.name = 'jack'
console.log(personProxy.name)
15、Reflect 统一的对象操作API
Reflect属于静态类(如Math),不能new,只能调用静态方法:Reflect.get()。Reflect内部封装了一系列对对象的底层操作。Reflect成员方法就是Proxy处理对象的默认实现
const obj = {
foo: '123',
bar: '456'
}
const proxy = new Proxy(obj, {
get (target, property) {
console.log('watch logic~')
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))
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))
16、 类 关键词 Class
// function Person (name) {
// this.name = name
// }
// Person.prototype.say = function () {
// console.log(`hi, my name is ${this.name}`)
// }
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
const p = new Person('tom')
p.say()
静态方法, this 指向当前类型,而不是实例
class Person {
constructor(name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
static create(name) {
// this 指向当前类型,而不是实例
console.log(this) // [Function: Person]
return new Person(name)
}
}
const tom = Person.create('tom')
tom.say() // hi, my name is tom
继承,关键词 extends
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()
// hi, my name is jack
// my school number is 100
17、Set 数据结构
弱引用版本 WeakSet
差异就是 Set 中会对所使用到的数据产生引用
即便这个数据在外面被消耗,但是由于 Set 引用了这个数据,所以依然不会回收
而 WeakSet 的特点就是不会产生引用,
一旦数据销毁,就可以被回收,所以不会产生内存泄漏问题。
const s = new Set()
s.add(1).add(2).add(3).add(4).add(2)
// console.log(s)
// s.forEach(i => console.log(i))
// for (let i of s) {
// console.log(i)
// }
// console.log(s.size)
// console.log(s.has(100))
// console.log(s.delete(3))
// console.log(s)
// s.clear()
// console.log(s)
// 应用场景:数组去重
const arr = [1, 2, 1, 3, 4, 1]
// const result = Array.from(new Set(arr))
const result = [...new Set(arr)]
console.log(result)
18、数据结构 Map
弱引用版本 WeakMap
差异就是 Map 中会对所使用到的数据产生引用
即便这个数据在外面被消耗,但是由于 Map 引用了这个数据,所以依然不会回收
而 WeakMap 的特点就是不会产生引用,
一旦数据销毁,就可以被回收,所以不会产生内存泄漏问题。
// 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)
console.log(m.get(tom))
// m.has()
// m.delete()
// m.clear()
m.forEach((value, key) => {
console.log(value, key)
})
19、原始数据类型 Symbol
最主要的作用就是为对象添加独一无二的属性名
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // symbol
console.log(Symbol() === Symbol()) // false
console.log(Symbol('foo')) // Symbol(foo)
console.log(Symbol('bar')) // Symbol(bar)
console.log(Symbol('baz')) // Symbol(baz)
const obj = {}
obj[Symbol()] = 111
obj[Symbol()] = 2
console.log(obj) // { [Symbol()]: 111, [Symbol()]: 2 }
const name = Symbol()
const person = {
[name]: 'jal', // 作为私有成员防止被访问
say(){
console.log(this[name])
}
}
person.say()// jal
console.log(person[Symbol()]) // undefined
// console.log(person[name]) // jal
截止到ES2019一共定义了6种原始类型,和一个object类型,未来还会增加一个bigint的原始类型(stage-4阶段)标准化过后就是8种数据类型了
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(
s1 === s2, // true
// Symbol.for的参数会被转化为字符串
Symbol.for(true) === Symbol.for('true'), // true
)
const obj2 = {
// 为对象实现迭代器时会用到
[Symbol.toStringTag]: 'XObject'
}
console.log(obj2.toString()) // [object Object] [object XObject]
const obj3 = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
for(var key in obj3) {
console.log(key)
}
// foo
console.log(Object.keys(obj3)) // [ 'foo' ]
console.log(JSON.stringify(obj3)) // {"foo":"normal value"}
console.log(Object.getOwnPropertySymbols(obj3)) // [ Symbol() ]
20、 for … of 作为遍历所有数据结构的统一方式
// for ... of 循环, 可以使用break
const arr = [1, 2, 3, 4]
for (const item of arr) { // item为每个对象实例
console.log(item)
}
// 相当于
// arr.forEach(item => {
// console.log(item)
// })
可以使用break终止循环
// arr.forEach ,但是这个方法不能终止遍历
// 为了终止遍历,我们之前,我们曾使用
// arr.some() 返回true
// arr.every() 返回false
for(const item of arr) {
console.log(item)
if(item > 1)break
}
遍历集合Set
const s = new Set(['foo', 'bar'])
for(const item of s) {
console.log(item)
}
// foo bar
遍历集合Map
const m = new Map()
m.set('foo', '123')
m.set('bar', '34')
for(const item of m) {
console.log(item)
}
// [ 'foo', '123' ] [ 'bar', '34' ]
// 解构键和值
for(const [key, value] of m) {
console.log(key,value)
}
// foo 123
// bar 34
遍历对象,报错了:TypeError: obj is not iterable
const obj = {name: 'jal', age: 22}
for(const item of obj) {
console.log(item) // TypeError: obj is not iterable
}
21、迭代器(Iterator)
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())
// console.log(iterator.next())
while (true) {
const current = iterator.next()
if (current.done) {
break // 迭代已经结束了,没必要继续了
}
console.log(current.value)
}
二、ECMAScript 2016
1、数组的includes方法
// Array.prototype.includes -----------------------------------
// const arr = ['foo', 1, NaN, false]
// 找到返回元素下标
// console.log(arr.indexOf('foo'))
// 找不到返回 -1
// console.log(arr.indexOf('bar'))
// 无法找到数组中的 NaN
// console.log(arr.indexOf(NaN))
// 直接返回是否存在指定元素
// console.log(arr.includes('foo'))
// 能够查找 NaN
// console.log(arr.includes(NaN))
// 指数运算符 ---------------------------------------------------
// console.log(Math.pow(2, 10))
console.log(2 ** 10)
三、ECMAScript 2017
Object.values(obj)
获取对象所有的值数组
const obj = {
name: 'jal',
age: 20
}
// 对象的值组成的数组
console.log(Object.values(obj)) // [ 'jal', 20 ]
获取对象的键值数组 Object.entries(obj)
// 对象的键值数组, 可以for...of 这个对象了
console.log(Object.entries(obj)) // [ [ 'name', 'jal' ], [ 'age', 20 ] ]
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
}
// name jal
// age 20
console.log(new Map(Object.entries(obj))) // Map(2) { 'name' => 'jal', 'age' => 20 }
获取对象的详细描述 Object.getOwnPropertyDescriptors(obj)
const p1 = {
firstName: 'Ji',
lastName: 'Ailing',
get fullName() {
return this.firstName + ' '+ this.lastName
}
}
const p2 = Object.assign({}, p1)
p2.firstName = 'zce'
console.log(p2) // { firstName: 'zce', lastName: 'Ailing', fullName: 'Ji Ailing' }
const descriptors = Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
/*
{
firstName: { value: 'Ji', writable: true, enumerable: true, configurable: true },
lastName: {
value: 'Ailing',
writable: true,
enumerable: true,
configurable: true
},
fullName: {
get: [Function: get fullName],
set: undefined,
enumerable: true,
configurable: true
}
}
*/
const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'zce'
console.log(p3.fullName) // zce Ailing
用指定字符串填充目标字符串的头部或者尾部,直到达到指定的长度为止 padEnd/padStart
const books = {
html: 5,
css: 16,
javascript: 128
}
for(const [key, value] of Object.entries(books)) {
console.log(key, value)
}
// html 5
// css 16
// javascript 128
for(const [key, value] of Object.entries(books)) {
console.log(`${key.padEnd(16, '-')}|${value.toString().padStart(3, '0')}`)
}
// html------------|005
// css-------------|016
// javascript------|128
在函数参数中添加尾逗号
function foo (
bar,
baz,
) {
}
const arr = [
10,
20,
30,
]