很多开发者并没有理解语言和平台之间的关系
- 解决原来语法上一些问题或者不足
- 对原有语法进行增强
- 全新的对象、全新的方法、全新的功能
- 全新的数据类型和数据结构
let 与块级作用域
// let 声明的成员只会在所声明的块中生效 -------------------------------------------
// if (true) {
// // var foo = 'zce'
// let foo = 'zce'
// console.log(foo)
// }
// 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()
// var elements = [{}, {}, {}]
// for (var i = 0; i < elements.length; i++) {
// elements[i].onclick = (function (i) {
// return function () {
// console.log(i)
// }
// })(i)
// }
// elements[0].onclick()
// var elements = [{}, {}, {}]
// for (let i = 0; i < elements.length; i++) {
// elements[i].onclick = function () {
// console.log(i)
// }
// }
// elements[0].onclick()
// for 循环会产生两层作用域 ----------------------------------
// for (let i = 0; i < 3; i++) {
// let i = 'foo'
// console.log(i)
// }
// let i = 0
// if (i < 3) {
// let i = 'foo'
// console.log(i)
// }
// i++
// if (i < 3) {
// let i = 'foo'
// console.log(i)
// }
// i++
// if (i < 3) {
// let i = 'foo'
// console.log(i)
// }
// i++
// let 修复了变量声明提升现象 --------------------------------------------
// console.log(foo)
// var foo = 'zce'
// console.log(foo)
// let foo = 'zce'
const
// const name = 'zce'
// 恒量声明过后不允许重新赋值
// name = 'jack'
// 恒量要求声明同时赋值
// const name
// name = 'zce'
// 恒量只是要求内层指向不允许被修改
// const obj = {}
// 对于数据成员的修改是没有问题的
// obj.name = 'zce'
// obj = {}
数组解构
// 数组的解构
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 [, rootdir] = path.split('/')
console.log(rootdir)
对象解构
// 对象的解构
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')
模板字符串
// 模板字符串
// 反引号包裹
// 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)
带标签的模板字符串
// 带标签的模板字符串
// 模板字符串的标签就是一个特殊的函数,
// 使用这个标签就是调用这个函数
// 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)
字符串扩展方法 startsWith 、endsWith、includes
// 字符串的扩展方法
const message = 'Error: foo is not defined.'
console.log(
// message.startsWith('Error')
// message.endsWith('.')
message.includes('foo')
)
函数参数的默认值
// 函数参数的默认值
// function foo (enable) {
// // 短路运算很多情况下是不适合判断默认参数的,例如 0 '' false null
// // enable = enable || true
// enable = enable === undefined ? true : enable
// console.log('foo invoked - enable: ')
// console.log(enable)
// }
// 默认参数一定是在形参列表的最后
function foo (enable = true) {
console.log('foo invoked - enable: ')
console.log(enable)
}
foo(false)
剩余参数
// 剩余参数
// function foo () {
// console.log(arguments)
// }
function foo (first, ...args) {
console.log(args)
}
foo(1, 2, 3, 4)
展开数组参数
// 展开数组参数
const arr = ['foo', 'bar', 'baz']
// console.log(
// arr[0],
// arr[1],
// arr[2],
// )
// console.log.apply(console, arr)
console.log(...arr)
箭头函数
// 箭头函数
// 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
// 箭头函数不会改变 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()
对象字面量的增强
// 对象字面量
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)
obj.method1()
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)
// 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)
// 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
)
Proxy
// 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)
// }
// })
// personProxy.age = 100
// personProxy.gender = true
// console.log(personProxy.name)
// console.log(personProxy.xxx)
// Proxy 对比 Object.defineProperty() ===============
// 优势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)
** Proxy 对比 Object.defineProperty()**
- Proxy 可以监视读写以外的操作
- Proxy 可以很方便的监视数组操作
- Proxy 不需要侵入对象
Reflect :统一提供一套用于操作对象的API
// Reflect 对象
// 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))
Promise
解决了传统异步编程中回调函数嵌套过深的问题
Class类
// 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()
静态成员 static
// static 方法
class 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()
类的继承 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()
Set 数据结构
// Set 数据结构
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)
// 弱引用版本 WeakSet
// 差异就是 Set 中会对所使用到的数据产生引用
// 即便这个数据在外面被消耗,但是由于 Set 引用了这个数据,所以依然不会回收
// 而 WeakSet 的特点就是不会产生引用,
// 一旦数据销毁,就可以被回收,所以不会产生内存泄漏问题。
Map 数据结构
// 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)
console.log(m.get(tom))
// m.has()
// m.delete()
// m.clear()
m.forEach((value, key) => {
console.log(value, key)
})
// 弱引用版本 WeakMap
// 差异就是 Map 中会对所使用到的数据产生引用
// 即便这个数据在外面被消耗,但是由于 Map 引用了这个数据,所以依然不会回收
// 而 WeakMap 的特点就是不会产生引用,
// 一旦数据销毁,就可以被回收,所以不会产生内存泄漏问题。
Symbol
场景1: 扩展对象,属性名冲突问题
// // shared.js ====================================
const cache = {}
// a.js =========================================
// cache['foo'] = Math.random()
cache['a_foo'] = Math.random()
// b.js =========================================
// cache['foo'] = '123'
cache['b_foo'] = '123'
console.log(cache)
场景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 数据类型
// 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 obj = {
// [Symbol()]: 123
// }
// console.log(obj)
// console.log(
// // Symbol() === Symbol()
// Symbol('foo') === Symbol('foo')
// )
// Symbol 全局注册表 ----------------------------------------------------
// const s1 = Symbol.for('foo')
// const s2 = Symbol.for('foo')
// console.log(s1 === s2)
// console.log(
// Symbol.for(true) === Symbol.for('true')
// )
// 内置 Symbol 常量 ---------------------------------------------------
// console.log(Symbol.iterator)
// console.log(Symbol.hasInstance)
// const obj = {
// [Symbol.toStringTag]: 'XObject'
// }
// console.log(obj.toString())
// Symbol 属性名获取 ---------------------------------------------------
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
// for (var key in obj) {
// console.log(key)
// }
// console.log(Object.keys(obj))
// console.log(JSON.stringify(obj))
console.log(Object.getOwnPropertySymbols(obj))
for…of 循环
作为遍历所有数据结构的统一方式
// 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 方法
// arr.forEach() // 不能跳出循环
// arr.some()
// arr.every()
// 遍历 Set 与遍历数组相同
// const s = new Set(['foo', 'bar'])
// for (const item of s) {
// console.log(item)
// }
// 遍历 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)
// }
可迭代接⼝(Iterator)
// 迭代器(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)
}
实现可迭代接口(Iterable)
// const obj = {
// [Symbol.iterator]: function () {
// return {
// next: function () {
// 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)
})
console.log('-------------------------------')
for (const item of todos) {
console.log(item)
}
Generator 函数 (惰性执行)
// Generator 函数
// function * foo () {
// console.log('zce')
// return 100
// }
// const result = foo()
// console.log(result.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 暂停
console.log(generator.next()) // 第二次调用,从暂停位置继续,直到遇到下一个 yield 再次暂停
console.log(generator.next()) // 。。。
console.log(generator.next()) // 第四次调用,已经没有需要执行的内容了,所以直接得到 undefined
Generator 应用
// 案例1:发号器
function * createIdMaker () {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
// 案例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、 Map Set WeakMap WeakSet的区别
-
Set:
成员唯一、无序且不重复 [value, value],键值与键名是一致的(或者说只有键值,没有键名) 可以遍历,方法有:add、delete、has 复制代码
-
WeakSet:
成员都是对象 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏 不能遍历,方法有add、delete、has 复制代码
-
Map:
本质上是键值对的集合,类似集合,成员键值不限于字符串 可以遍历,方法很多可以跟各种数据格式转换 复制代码
- WeakMap:
只接受对象作为键名(null除外),不接受其他类型的值作为键名 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的 不能遍历,方法有get、set、has、delete
Set、WeakSet都是元素的集合,而Map和WeakMap是键值对的集合。 Set和WeakSet的区别
- 里面的元素都唯一,Set的元素可以是任何值,而WeakSet的元素只能是对象。
- 对于基本类型的值,只要“===”为真,则这两个值相等(无法加入Set中)。对于引用类型(Set和WeakSet中),只有地址相同,它们才不唯一,而值相同的两个不同对象,它们是不同的。
- NaN被视为同一个值。
- WeakSet中的元素是弱引用,即随时会消失(被回收)。
Map和WeakMap的区别
- Map的键和值都可以是任意类型,而WeakMap的键只能是对象。
- Map和WeakMap的键必须唯一,判断方式是“===”和是否地址相同。
- Map视NaN为同一个键。
- WeakMap中的键值对的键是弱引用,WeakMap中的成员随时会消失。