JavaScript Web:ECMAScript、BOM、DOM
JavaScript Node.js: ECMAScript、Node APIs:fs net etc等
通常 认为ESMAScript 是JS规范,实际上JS是ES的扩展语言 本身就ES,ES只提供了基本语法
09 ES 5
11 ES 5.1
15 ES 2015 ES6
图示
1、let 声明的成员只会在所声明的块中生效
2、let 应用场景:循环绑定事件,事件处理函数中获取正确索引
3、for 循环会产生两层作用域 所以可以解决上述问题
4、let 修复了变量声明提升现象
1、声明时必须要设置初始值
2、声明的成员不能被修改:声明过后不能重新去指向一个新的内存地址,而属性是可以修改的。如常量被赋值一个新的对象则会报错
推荐:不用var 主用const 配合使用let
const arr = [100, 200, 300]
1、按照位置分配 const [foo, bar, baz] = arr
2、逗号分隔 const [, , baz] = arr 通过结构获取数组指定位置的值
3、…只能放在解构的最后一个位置上使用 const [foo, …rest] = arr
4、如若大于则是undefined,如: const [foo, bar, baz, more] = arr
5、=设置默认值 const [foo, bar, baz = 123, more = ‘default value’] = arr
const obj = { name: ‘zce’, age: 18 }
1、通过属性名去匹配 const { name } = obj
2、同名会报错,可重命名、设置默认值 如:
// const name = 'tom'
const { name: objName = 'jack' } = obj
3、简化代码
const { log } = console
log('foo')
log('bar')
log('123')
1、支持换行
2、插值表达式${}
3、表达式的执行结果将会输出到对应位置
const msg = `hey, ${name} --- ${1 + 2} ---- ${Math.random()}`
1、标签:一个特殊的函数,使用标签就是调用这个函数
2、使用必须要先定义函数
3、打印模版字符串中内容分割过后的结果,因为模板字符串中可能会有嵌入的表达式.strings是按照表达式分割过后那些静态的内容。
4、函数的返回值就是带标签的模板字符串的返回值
const name = 'tom'
const gender = false
function myTagFunc (strings, name, gender) {
console.log(strings) // ["hey, ", " is a ", "."]
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2] // 返回正常值 'hey, tom is a woman.'
}
const result = myTagFunc`hey, ${name} is a ${gender}.`
1、includes() // 判断是否包含
2、startsWith() // 判断是否以…开头
3、endsWith() // 判断是否以…结尾
1、通过=号设置默认值:function foo (enable = true) {}
2、如果没有传递实参或者传递的是undefined时才会被使用
1、形参前加上…
2、这个形参会以数组的形式接收从当前位置往后所有的实参
3、只能出现在形参的最后一位,且只能使用一次
function foo (first, ...args) {
console.log(args)
}
1、把数组中的成员按照次序传入到参数列表中
const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr) // foo bar baz
console.log(...arr) // foo bar baz
1、()=>{return }
2、()=>
3、不会改变this的指向 始终指向当前作用域中的this
1、属性名与变量名相同,可以省略 : bar
2、方法可以省略 : function 直接 const obj = {a()}
3、上述方法中this,谁调用指向谁
4、通过 [] 让表达式的结果作为属性名
const bar = '345'
const obj = {
foo: 123,
// bar: bar
// 属性名与变量名相同,可以省略 : bar
bar,
// method1: function () {
// console.log('method111')
// }
// 方法可以省略 : function
method1 () {
console.log('method111')
// 这种方法就是普通的函数,同样影响 this 指向。
console.log(this)
},
// Math.random(): 123 // 不允许
// 通过 [] 让表达式的结果作为属性名
[bar]: 123
}
// obj[Math.random()] = 123
console.log(obj)
1、Object.assign 将多个源对象中的属性复制到一个目标对象中,源对象会覆盖目标对象中相同的属性
const result = Object.assign(target, source1, source2)
2、assign的返回值就是目标对象 true
3、可以应用在设置默认值等场景
4、Object.is
+0===-0 // true
NaN===NaN // false
Object.is(+0,-0) // false
Object.is(NaN,NaN) // true
1、监视对象的属性读写 ES5 Object.defineProperty VUE3以前就是利用这个方法完成双向数据绑定
2、new Proxy(需要代理的目标对象,代理的处理对象})
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)
}
})
1、Proxy 可以监视读写以外的操作 如:delete操作、对对象中方法的调用等 deleteProperty
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) // {name: "zce"}
2、Proxy 可以很方便的监视数组操作 set
// const list = []
// const listProxy = new Proxy(list, {
// set (target, property, value) {
// console.log('set', property, value) // property 就是索引
// 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)
全新的内置对象,提供了一套统一的对象操作API
1、属于一个静态类,不能通过new的方式去构建一个实例对象
2、只能调用这个静态类的静态方法如Reflect.get()和Math.randomNum类似
3、Reflect内部封装了一系列对对象的底层操作 14-1个。
4、成员方法是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(Reflect.has(obj, 'name')) // false
console.log(Reflect.deleteProperty(obj, 'age')) // true
console.log(Reflect.ownKeys(obj)) // ["foo", "bar"]
解决异步编程中回调函数嵌套过深的问题
链接: https://blog.csdn.net/weixin_38550182/article/details/112218464.
class Person {
// 当前我们这个类型的构造函数
constructor (name) {
this.name = name // 通过this访问当前类型的实例对象
}
// 为这个类型定义实例方法
say () {
console.log(`hi, my name is ${this.name}`)
}
}
const p = new Person('tom')
p.say()
// function Person (name) {
// this.name = name
// }
// Person.prototype.say = function () {
// console.log(`hi, my name is ${this.name}`)
// }
1、类型中的方法:实例方法vs静态方法
实例方法:需要通过这个类型构造的实例对象去调用
静态方法:则是通过类型本身去调用
以前实现静态方法是直接在构造函数对象上去挂载方法来去实现,因为在js中函数也是对象,也可以去添加一些方法成员
ES 2015中新增添加静态成员的 static 关键词
注意:静态方法是挂载到类型上的,所以在静态方法内部 this 就不会指向某一个实例对象 而是当前的类型
clas
s Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
static create (name) {
return new Person(name)
}
}
const tom = Person.create('tom')
tom.say()
class Person
{
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`) // hi, my name is jack
}
}
// Student中会拥有Person中所有的成员了
class Student extends Person {
// Student的构造函数
constructor (name, number) {
super(name) // 父类构造函数 / 这边好像是必须要有 不然报错
this.number = number
}
hello () {
super.say() // 调用父类成员
console.log(`my school number is ${this.number}`) // my school number is 100
}
}
const s = new Student('jack', '100')
s.hello()
1、全新的数据结构,可以理解成集合,与数组类似
2、Set内部成员不允许重复
3、它是一个类型,通过这个类型构造出来的实例就可以用来存放不同的数据 add
const s = new Set()
s.add(1).add(2).add(3).add(4).add(2) // Set(4) {1, 2, 3, 4}
// s.forEach(i => console.log(i)) // 1 2 3 4
// for (let i of s) {
// console.log(i)
// }
4、size // console.log(s.size) 类似length
5、has // console.log(s.has(100)) 返回true/false
6、delete // console.log(s.delete(3)) 返回true/false
7、clear // s.clear() 清除集合中的全部内容
8、// 应用场景:数组去重
const arr = [1, 2, 1, 3, 4, 1]
// const result = Array.from(new Set(arr)) // 转成数组
const result = […new Set(arr)] // 转成数组
console.log(result)
用来映射两个任意类型数据之前的对应关系
1、类似对象 本质上都是键值对集合,对象中的键只能是字符串类型,存放复杂结构的数据时会有些问题
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
console.log(obj) // {123: "value", true: "value", [object Object]: "value"}
console.log(Object.keys(obj)) // ["123", "true", "[object Object]"]
console.log(obj['[object Object]']) // value
2、用法
const m = new Map()
const tom = { name: ‘tom’ }
m.set(tom, 90)
console.log(m) // Map(1) { {…} => 90}
console.log(m.get(tom)) // get方法获取
3、
// m.has()
// m.delete()
// m.clear()
4、
m.forEach((value, key) => {
console.log(value, key)
})
一种全新的原始数据类型 最主要的作用就是为对象添加独一无二的属性名
1、用来表示一个独一无二的值
2、通过Symbol()函数来创建一个Symbol类型的数据
3、
Symbol()===Symbol() // false
Symbol===Symbol // true
4、
console.log(typeof Symbol()) // “symbol”
5、
console.log(Symbol()) // Symbol()
6、允许传入字符串,作为这个值的描述文本
Symbol('foo') //Symbol(foo)
7、可作为对象属性,为对象添加用不重复的键
8、也可以在计算属性名中使用
const obj = {
[Symbol()]: 123
}
console.log(obj) // Symbol(): 123}
9、Symbol 模拟实现私有成员
用Symbol来创建私有成员的属性名了,在这个对象的内部可以通过创建属性的Symbol()拿到对应的属性成员
外部文件没有办法创建相同的Symbol所以就不能直接访问到这个成员,只能够调用这个对象中普通名称的成员,这样就实现了所谓的私有成员
// a.js
const name = Symbol()
const person = {
[name]: 'zce',
say () {
console.log(this[name])
}
}
// 只对外暴露 person
// b.js
// 由于无法创建出一样的 Symbol 值,
// 所以无法直接访问到 person 中的「私有」成员
// person[Symbol()]
person.say()
10、未来还会出现BigInt 的原始数据类型 用来存放更长的数字
1、唯一性,每次都是全新的值
// console.log(
// // Symbol() === Symbol()
// Symbol(‘foo’) === Symbol(‘foo’)
// )
2、Symbol 全局注册表 全局复用一个相同的Symbol值 使用全局变量或者使用Symbol的静态方法for 来实现
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
console.log(
Symbol.for(true) === Symbol.for('true') // true
)
3、这个for方法维护的是字符串和Symbol的对应关系 会强制转换成字符串
// console.log(
// Symbol.for(true) === Symbol.for('true') // true
// )
4、提供了很多内置的Symbol常量, 用来作为内部方法的标识,这些标识符可以让自定义对象实现一些JS当中内置的接口
console.log(Symbol.iterator) // Symbol(Symbol.iterator)
console.log(Symbol.hasInstance) // Symbol(Symbol.hasInstance)
console.log({}.toString()) // [object Object] 这样的结果叫作对象的toString标签
// const obj = {
// [Symbol.toStringTag]: 'XObject' // 为对象实现迭代器时会经常用到
// }
// console.log(obj.toString()) // [object XObject]
5、for in和Object.keys方法拿不到属性名。 Object.keys只能获取字符串属性名
6、JSON.stringify(obj) 也会忽略掉Symbol属性
7、console.log(Object.getOwnPropertySymbols(obj)) 只能获取到Symbol属性名
全新的遍历方式
for遍历数组、for…in 遍历键值对、ES5函数式的遍历方法 数组的forEach
1、作为遍历所有数据结构的统一方式
2、// for…of 循环可以替代 数组对象的 forEach 方法
const arr = [100, 200, 300, 400]
// for (const item of arr) {
// console.log(item)
// }
// arr.forEach(item => {
// console.log(item)
// })
3、可以使用break,forEach不能跳出循环
// for (const item of arr) {
// console.log(item)
// if (item > 100) {
// break
// }
// }
4、
// forEach 无法跳出循环,必须使用 some 或者 every 方法
// arr.forEach() // 不能跳出循环
// arr.some() // 返回true 可终止遍历
// arr.every() // 返回false 可中止遍历
5、伪数组也可遍历 如arguments对象、节点列表
6、遍历 Set 与遍历数组相同
// const s = new Set([‘foo’, ‘bar’])
// for (const item of s) {
// console.log(item) // foo bar
// }
7、遍历 Map 可以配合数组解构语法,直接获取键值
const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const key of m) {
console.log(key) // ['foo','123'] ['bar', '345']
}
// 配合解构
for (const [key, value] of m) {
console.log(key, value) // foo 123 ...
}
8、 普通对象不能被直接 for…of 遍历
// const obj = { foo: 123, bar: 456 }
// for (const item of obj) {
// console.log(item)
// }
为了给各种各样的数据结构提供统一的遍历方式
1、ES中能够表示有结构的数据类型越来越多 数组 对象 set map
2、如果不理解接口可理解成一种规格标准,例如在ECMAScript任意一种数据类型都有toString方法,就是因为它们都实现了统一的规则标准,编程语言中更专业说法是它们都实现了统一的接口
3、如下这个三个可以被for…of遍历的对象中都有如下的方法iterator。iterator接口约定的是对象中必须要挂在一个叫做iterator的方法。这就是for…of循环的工作原理
3、实现Iterable接口就是for…of的前提
// 迭代器(Iterator)
1、所有可以被for…of循环遍历的数据类型都必须要实现iterator接口,也就是内部必须要挂在iterator方法。这个方法需要返回带有next方法的对象,不断调用这个next方法就可以实现对这个数据的遍历
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实现
while (true) {
const current = iterator.next()
if (current.done) {
break // 迭代已经结束了,没必要继续了
}
console.log(current.value)
}
1、为什么for…of循环可以作为遍历所有数据结构的统一方式了,因为它内部就是区调用被遍历对象的itarable方法,得到一个迭代器,从而去遍历内部所有的数据,这也就是iterable接口所约定的内容,只要对象实现了iterable接口,就可以实现用for…of遍历我们自己的对象。
// 实现可迭代接口(Iterable):约定内部必须要有一个用于返回迭代器的iterator的方法
// const obj = {
// [Symbol.iterator]: function () {
// iterator方法返回的这个对象,实现了迭代器接口iterator,约定内部必须要有一个用于迭代的next方法
// return {
// next: function () {
// 实现了迭代结果的接口iteration result value done
// return {
// value: 'zce',
// done: true
// }
// }
// }
// }
// }
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)
}
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
// 对外提供统一遍历访问接口
each: function (callback) {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all) {
callback(item)
}
},
// 提供迭代器(ES2015 统一遍历访问接口)
[Symbol.iterator]: function () {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
// for (const item of todos.life) {
// console.log(item)
// }
// for (const item of todos.learn) {
// console.log(item)
// }
// for (const item of todos.work) {
// console.log(item)
// }
todos.each(function (item) {
console.log(item)
})
for (const item of todos) {
console.log(item)
}
1、避免异步编程中回调嵌套过深。提供更好的异步编程解决方案
2、也实现了迭代器接口
function * foo () {
console.log('zce')
return 100
}
const result = foo()
console.log(result) // foo {}
console.log(result.next()) // zce {value:100,done:true}
3、特点:惰性执行
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 暂停
console.log(generator.next()) // 第二次调用,从暂停位置继续,直到遇到下一个 yield 再次暂停
console.log(generator.next()) // 。。。
console.log(generator.next()) // 第四次调用,已经没有需要执行的内容了,所以直接得到 undefined
// 案例1:发号器
function * createIdMaker () {
let id = 1
while (true) {
yield id++ // 每次调用时加1.不用担心死循环
}
}
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)
}
语言层面的模块化标准
1、2016.6发布
2、
// ECMAScript 2016
// 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)) // -1
// 直接返回是否存在指定元素
// console.log(arr.includes('foo')) // true
// 能够查找 NaN
// console.log(arr.includes(NaN)) // true
// 指数运算符
// console.log(Math.pow(2, 10))
console.log(2 ** 10)
1、
// ECMAScript 2017
// const obj = {
// foo: 'value1',
// bar: 'value2'
// }
// Object.values -----------------------------------------------------------
// console.log(Object.values(obj))
// Object.entries ----------------------------------------------------------
// console.log(Object.entries(obj))
// for (const [key, value] of Object.entries(obj)) {
// console.log(key, value)
// }
// console.log(new Map(Object.entries(obj)))
// Object.getOwnPropertyDescriptors ----------------------------------------
// const p1 = {
// firstName: 'Lei',
// lastName: 'Wang',
// get fullName () {
// return this.firstName + ' ' + this.lastName
// }
// }
// // console.log(p1.fullName)
// // const p2 = Object.assign({}, p1)
// // p2.firstName = 'zce'
// // console.log(p2) // 无效
// 获取对象当中属性的完整描述信息
// const descriptors = Object.getOwnPropertyDescriptors(p1) // 配合 es5中新增的getter seter使用
// // console.log(descriptors)
// 将描述信息定义到新的对象当中,这样就对gettersetter属性做到一个复制
// const p2 = Object.defineProperties({}, descriptors)
// p2.firstName = 'zce'
// console.log(p2.fullName)
// 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, count)
// // }
// for (const [name, count] of Object.entries(books)) {
// console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
// }
// 在函数参数中添加尾逗号 -----------------------------------------------------
// function foo (
// bar,
// baz,
// ) {
// }
// const arr = [
// 100,
// 200,
// 300,
// ]
// const arr = [
// 100,
// 200,
// 300,
// 400,
// ]
// const arr = [
// 100,
// 200,
// 300
// ]
// const arr = [
// 100,
// 200,
// 300,
// 400
// ]
// Async/Await Promise语法糖