let/const

ES2015 前没有块级作用域,用 var 在花括号内声明一个变量,在花括号外也能访问到。

  1. {
  2. let a = 125;
  3. var b = 521;
  4. }
  5. a // Uncaught ReferenceError: a is not defined
  6. b // 521
  1. for 循环中,如果使用 var 来控制循环有时候会产生意料之外的结果。
  1. for (var i = 0; i <10; i++) {
  2. setTimeout(function() { // 同步注册回调函数到 异步的 宏任务队列。
  3. console.log(i); // 执行此代码时,同步代码for循环已经执行完成
  4. }, 0);
  5. }
  6. // 输出 10个10
  7. // i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
  8. for (let i = 0; i < 10; i++) {
  9. setTimeout(function() {
  10. console.log(i); // i 是循环体内局部作用域,不受外界影响。
  11. }, 0);
  12. }
  13. // 输出 0-9
  14. for (let i = 0; i < 3; i++) {
  15. for (let i = 0; i < 3; i++) {
  16. console.log(i);
  17. }
  18. }
  19. // 输出 3次 0 1 2

变量提升

var 生命的变量可以先使用再声明,因为 var 声明的变量相当于在作用域的最前面先赋予 undefined 的值,称之为变量提升;let 必须先生命再使用。

  1. console.log(foo) // 输出 undefined,不报错
  2. var foo = '1'

暂时性死区

  1. var tmp=521;
  2. if(true){
  3. tmp='abc';//ReferenceError: tmp is not defined
  4. let tmp;
  5. }
  6. // 代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

重复声明

  1. var i = 1;
  2. var i = 2; // 不报错
  3. let j = 1;
  4. let j = 2; // 报已经变量存在的错误

let 和 const

  1. const let 的特性相同,区别在于 const 声明的是不可改变的常量。但是如果用 const 声明复杂对象,那么可以改变这个对象的属性值,因为对象常量的内存地址并没有改变。
  1. const i = 4;
  2. i = 5; // 报错
  3. const a = { b: 1, c: 2 }
  4. a.b = 3 // 不会报错,因为 a 的内存地址没有改变
  5. a = { d: 6 } // 报错

数组解构

  1. // 数组的解构
  2. const arr = [100, 200, 300]
  3. // const [foo, bar, baz] = arr
  4. // console.log(foo, bar, baz)
  5. // foo = 100, bar = 200, baz = 300
  6. // const [, , baz] = arr
  7. // console.log(baz)
  8. // baz = 300
  9. // const [foo, ...rest] = arr
  10. // console.log(rest)
  11. // rest = [200, 300]
  12. // const [foo, bar, baz, more] = arr
  13. // console.log(more)
  14. // more = undefined
  15. // 设置默认值
  16. // const [foo, bar, baz = 123, more = 'default value'] = arr
  17. // console.log(baz, more)
  18. // baz = 300, more = 'default value'
  19. const path = '/foo/bar/baz'
  20. // const tmp = path.split('/')
  21. // const rootdir = tmp[1]
  22. const [, rootdir] = path.split('/')
  23. console.log(rootdir)

对象解构

  1. // 对象的解构
  2. const obj = { name: 'zce', age: 18 }
  3. // const { name } = obj
  4. // console.log(name)
  5. // 重命名
  6. // const name = 'tom'
  7. // const { name: objName } = obj
  8. // console.log(objName)
  9. // 设置默认值
  10. // const name = 'tom'
  11. // const { name: objName = 'jack' } = obj
  12. // console.log(objName)
  13. const { log } = console
  14. log('foo')
  15. log('bar')
  16. log('123')

模板字符串

  1. // 模板字符串
  2. // 反引号包裹
  3. // const str = `hello es2015, this is a string`
  4. // 允许换行
  5. // const str = `hello es2015,
  6. // this is a \`string\``
  7. // console.log(str)
  8. const name = 'tom'
  9. // 可以通过 ${} 插入表达式,表达式的执行结果将会输出到对应位置
  10. const msg = `hey, ${name} --- ${1 + 2} ---- ${Math.random()}`
  11. console.log(msg)

带标签的模板字符串

  1. const name = 'tom'
  2. const gender = false
  3. function myTagFunc (strings, name, gender) {
  4. // console.log(strings, name, gender)
  5. // return '123'
  6. const sex = gender ? 'man' : 'woman'
  7. return strings[0] + name + strings[1] + sex + strings[2]
  8. }
  9. const result = myTagFunc`hey, ${name} is a ${gender}.`
  10. console.log(result)
  11. // 输出 hey, tom is a woman.
  12. // 字符串会在模板那里分隔, strings 是 ['hey, ', ' is a ', '.', raw: ['hey, ', ' is a ', '.']]

字符串扩展方法

  1. // 字符串的3个扩展方法
  2. const message = 'Error: foo is not defined.'
  3. console.log(
  4. message.startsWith('Error'),
  5. message.endsWith('.'),
  6. message.includes('foo')
  7. )

参数默认值

  1. // 函数参数的默认值
  2. // 第一种
  3. function zce (enable) {
  4. // 短路运算很多情况下是不适合判断默认参数的,例如 0 '' false null
  5. // enable = enable || true
  6. enable = enable === undefined ? true : enable
  7. console.log('foo invoked - enable: ')
  8. console.log(enable)
  9. }
  10. // 第二种
  11. // 默认参数一定是在形参列表的最后
  12. function foo (enable = true) {
  13. console.log('foo invoked - enable: ')
  14. console.log(enable)
  15. }
  16. foo(false)

扩展运算符 … 的作用

接收剩余参数

… 接收剩余的参数,返回一个数组,接收剩余参数必须在最后。

  1. function foo (first, ...args) {
  2. console.log(args) // [2, 3, 4]
  3. }
  4. foo(1, 2, 3, 4)

数组展开

  1. // 展开数组参数
  2. const arr = ['foo', 'bar', 'baz']
  3. console.log(...arr)
  4. // 输出 foo bar baz

数组、对象拷贝

需要注意的是,… 是浅拷贝,如果是基本数据类型的数组和一层的对象则相互不会影响,如果是对象数组和多层对象则会影响。

  1. var arr = [1,2,3,4,5]
  2. var [ ...arr2 ] = arr
  3. arr[2] = 5
  4. console.log(arr) // [1, 2, 5, 4, 5]
  5. console.log(arr2) // [1, 2, 3, 4, 5]

箭头函数

箭头函数不会改变 this,因为它本身没有 this 的机制。
如果箭头右边不写花括号,相当于返回右边的值,如果写了花括号,想要返回值就需要写 return。

  1. const arr = [1, 2, 3, 4, 5, 6, 7]
  2. // 常用场景,回调函数
  3. arr.filter(i => i % 2)
  4. // 或者这么写
  5. arr.filter(i =>{ return i % 2 })

对象

对象字面量的增强

  1. // 对象字面量
  2. const bar = '345'
  3. const obj = {
  4. foo: 123,
  5. // bar: bar
  6. // 属性名与变量名相同,可以省略 : bar
  7. bar,
  8. // method1: function () {
  9. // console.log('method111')
  10. // }
  11. // 方法可以省略 : function
  12. method1 () {
  13. console.log('method111')
  14. // 这种方法就是普通的函数,同样影响 this 指向。
  15. console.log(this)
  16. },
  17. // Math.random(): 123 // 不允许
  18. // 通过 [] 让表达式的结果作为属性名
  19. [bar]: 123
  20. }
  21. // obj[Math.random()] = 123
  22. console.log(obj)
  23. obj.method1()

对象扩展方法

Object.assign

将多个源对象的属性复制到一个第一个对象。如果有同一个属性名,保留后传入的。

  1. const source1 = {
  2. a: 123,
  3. b: 123
  4. }
  5. const source2 = {
  6. b: 789,
  7. d: 789
  8. }
  9. const target = {
  10. a: 456,
  11. c: 456
  12. }
  13. const result = Object.assign(target, source1, source2)
  14. console.log(target)
  15. console.log(result === target)
  16. // 输出 {a: 123, c: 456, b: 789, d: 789} true

Object.assign 是一层的深拷贝,如果是多层的对象,它还是浅拷贝。下面的 obj 的 name 属性没有被改变,但是更深一层的 address.city 被改变了。

  1. function func (obj) {
  2. const funcObj = Object.assign({}, obj)
  3. funcObj.name = 'funcObj'
  4. funcObj.address.city = 'guangzhou'
  5. console.log(funcObj) // 输出 address: {city: 'guangzhou', country: 'china'} name: "funcObj"
  6. }
  7. const obj = { name: 'global obj', address: { city: 'chaozhou', country: 'china' } }
  8. func(obj)
  9. console.log(obj) //输出 address: {city: 'guangzhou', country: 'china'} name: "global obj"

Object.is

用来判断两个值是否相等。

  1. // Object.is
  2. console.log(
  3. // 0 == false // => true
  4. // 0 === false // => false
  5. // +0 === -0 // => true
  6. // NaN === NaN // => false
  7. // Object.is(+0, -0) // => false
  8. // Object.is(NaN, NaN) // => true
  9. )

Proxy

Proxy 类可以避免直接去操作一个对象,而通过代理的方式对对象的属性值进行查询和修改。这么做主要有三个作用:

  • 拦截和监视外部对对象的访问
  • 降低函数或类的复杂度
  • 在复杂操作前对操作进行校验或对所需资源进行管理
  1. // Proxy 对象
  2. const person = {
  3. name: 'zce',
  4. age: 20
  5. }
  6. const personProxy = new Proxy(person, {
  7. // 监视属性读取
  8. get (target, property) {
  9. return property in target ? target[property] : 'default'
  10. // console.log(target, property)
  11. // return 100
  12. },
  13. // 监视属性设置
  14. set (target, property, value) {
  15. if (property === 'age') {
  16. if (!Number.isInteger(value)) {
  17. throw new TypeError(`${value} is not an int`)
  18. }
  19. }
  20. target[property] = value
  21. // console.log(target, property, value)
  22. }
  23. })
  24. personProxy.age = 100
  25. personProxy.gender = true
  26. console.log(personProxy.name)
  27. console.log(personProxy.xxx)

对比 Object.defineProperty,Proxy 具有以下优势:

  1. 可以进行读写以外的操作,比如删除 ```javascript const person = { name: ‘zce’, age: 20 }

const personProxy = new Proxy(person, { deleteProperty (target, property) { console.log(‘delete’, property) delete target[property] } })

delete personProxy.age

  1. 2. 可以很方便地监视数组的操作
  2. ```javascript
  3. const list = []
  4. const listProxy = new Proxy(list, {
  5. set (target, property, value) {
  6. console.log('set', property, value)
  7. target[property] = value
  8. return true // 表示设置成功
  9. }
  10. })
  11. listProxy.push(100)
  12. listProxy.push(100)
  1. 不需要侵入对象 ```javascript 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)

  1. <a name="PKvx2"></a>
  2. ## Reflect
  3. Reflect 内部封装了一系列对对象的底层操作,都是静态方法,Reflect 成员方法就是 Proxy 处理对象的默认实现。
  4. ```javascript
  5. // Reflect 对象
  6. // const obj = {
  7. // foo: '123',
  8. // bar: '456'
  9. // }
  10. // const proxy = new Proxy(obj, {
  11. // get (target, property) {
  12. // console.log('watch logic~')
  13. // return Reflect.get(target, property)
  14. // }
  15. // })
  16. // console.log(proxy.foo)
  17. const obj = {
  18. name: 'zce',
  19. age: 18
  20. }
  21. // console.log('name' in obj)
  22. // console.log(delete obj['age'])
  23. // console.log(Object.keys(obj))
  24. console.log(Reflect.has(obj, 'name')) // 判断是否拥有某个属性
  25. console.log(Reflect.deleteProperty(obj, 'age')) // 删除某个属性
  26. console.log(Reflect.ownKeys(obj)) // 返回键的数组

Promise

class

ES2015 之前,定义一个类是通过函数定义的方式,定义方法是通过原型链。ES2015 引入了 class,定义一个类变得更加简单了。

  1. // class 关键词
  2. // function Person (name) {
  3. // this.name = name
  4. // }
  5. // Person.prototype.say = function () {
  6. // console.log(`hi, my name is ${this.name}`)
  7. // }
  8. class Person {
  9. constructor (name) {
  10. this.name = name
  11. }
  12. say () {
  13. console.log(`hi, my name is ${this.name}`)
  14. }
  15. }
  16. const p = new Person('tom')
  17. p.say()

静态

引入 static 关键字,一些方法和变量的访问,就不需要先实例化一个对象,再调用了。

  1. // static 方法
  2. class Person {
  3. constructor (name) {
  4. this.name = name
  5. }
  6. say () {
  7. console.log(`hi, my name is ${this.name}`)
  8. }
  9. static create (name) {
  10. return new Person(name)
  11. }
  12. }
  13. const tom = Person.create('tom')
  14. tom.say()

继承

extends 关键字使得一个类可以从另一个类继承属性和方法。

  1. // extends 继承
  2. class Person {
  3. constructor (name) {
  4. this.name = name
  5. }
  6. say () {
  7. console.log(`hi, my name is ${this.name}`)
  8. }
  9. }
  10. class Student extends Person {
  11. constructor (name, number) {
  12. super(name) // 父类构造函数
  13. this.number = number
  14. }
  15. hello () {
  16. super.say() // 调用父类成员
  17. console.log(`my school number is ${this.number}`)
  18. }
  19. }
  20. const s = new Student('jack', '100')
  21. s.hello()

Set

Set 是 JavaScript 一种全新的数据结构,类似于数组,但是它的成员的值是不可重复的。Set 经常用于数组的去重。

  1. const s = new Set()
  2. s.add(1).add(2).add(3).add(4).add(2)
  3. const arr = [1, 2, 1, 3, 4, 1]
  4. // const result = Array.from(new Set(arr))
  5. const result = [...new Set(arr)]
  6. console.log(result) // [1,2,3,4]

Map

Map 也是一种新的数据结构,往里面放每一个成员,都要赋予一个键。与普通对象赋值方式不同,Map不会把键的类型转成字符串,它的键的类型任意,可以是一个对象,这是对象赋值做不到的。

  1. const m = new Map()
  2. const tom = { name: 'tom' }
  3. m.set(tom, 90)
  4. console.log(m)
  5. console.log(m.get(tom))
  6. // 常用方法
  7. // m.has()
  8. // m.delete()
  9. // m.clear()
  10. m.forEach((value, key) => {
  11. console.log(value, key)
  12. })

Symbol

Symbol 可以创建一个完全独一无二的值,在如今大量使用第三方模块的情况下,使用 Symbol 作为对象的键可以避免键名的冲突,也可以创建私有成员。

  1. // Symbol 数据类型
  2. // 场景1:扩展对象,属性名冲突问题
  3. // // shared.js ====================================
  4. const cache = {}
  5. // a.js =========================================
  6. cache['a_foo'] = Math.random()
  7. // b.js =========================================
  8. cache['b_foo'] = '123'
  9. console.log(cache)
  10. // =========================================================
  11. const s = Symbol()
  12. console.log(s)
  13. console.log(typeof s)
  14. 两个 Symbol 永远不会相等
  15. console.log(
  16. Symbol() === Symbol() // false
  17. )
  18. Symbol 描述文本
  19. console.log(Symbol('foo'))
  20. console.log(Symbol('bar'))
  21. console.log(Symbol('baz'))4
  22. // 使用 Symbol 为对象添加用不重复的键
  23. const obj = {}
  24. obj[Symbol()] = '123'
  25. obj[Symbol()] = '456'
  26. console.log(obj)
  27. 也可以在计算属性名中使用
  28. const obj = {
  29. [Symbol()]: 123
  30. }
  31. console.log(obj)
  1. // 案例2:Symbol 模拟实现私有成员
  2. // a.js ======================================
  3. const name = Symbol()
  4. const person = {
  5. [name]: 'zce',
  6. say () {
  7. console.log(this[name])
  8. }
  9. }
  10. // 只对外暴露 person
  11. // b.js =======================================
  12. // 由于无法创建出一样的 Symbol 值,
  13. // 所以无法直接访问到 person 中的「私有」成员
  14. // person[Symbol()]
  15. person.say()

Symbol 的常用方法:

  1. // Symbol 补充
  2. console.log(
  3. // Symbol() === Symbol()
  4. Symbol('foo') === Symbol('foo') // false
  5. )
  6. // Symbol 全局注册表 ----------------------------------------------------
  7. const s1 = Symbol.for('foo')
  8. const s2 = Symbol.for('foo')
  9. console.log(s1 === s2) // true
  10. console.log(
  11. Symbol.for(true) === Symbol.for('true')
  12. ) // true for 会把传入的参数转成字符串
  13. // 内置 Symbol 常量 ---------------------------------------------------
  14. console.log(Symbol.iterator)
  15. console.log(Symbol.hasInstance)
  16. const obj = {
  17. [Symbol.toStringTag]: 'XObject'
  18. }
  19. console.log(obj.toString())
  20. // Symbol 属性名获取 ---------------------------------------------------
  21. const obj = {
  22. [Symbol()]: 'symbol value',
  23. foo: 'normal value'
  24. }
  25. // for (var key in obj) {
  26. // console.log(key)
  27. // }
  28. // console.log(Object.keys(obj))
  29. // console.log(JSON.stringify(obj))
  30. console.log(Object.getOwnPropertySymbols(obj)) // [Symbol()] 可以获取到Symbol键

for of 循环

所有可被迭代的数据类型都可以使用 for..of 遍历,普通对象不能用 for…of 遍历。

  1. // 遍历 Map 可以配合数组结构语法,直接获取键值
  2. const m = new Map()
  3. m.set('foo', '123')
  4. m.set('bar', '345')
  5. for (const [key, value] of m) {
  6. console.log(key, value)
  7. }

迭代

ES2015 提供了 Iterable 接口,一些数据类型实现了这个接口,如Array,Set,Map。for…of 循环的本质就是调用这些数据类型的迭代器方法。

  1. const set = new Set(['foo', 'bar', 'baz'])
  2. const iterator = set[Symbol.iterator]() // set 的迭代器
  3. while (true) {
  4. const current = iterator.next() // 迭代器实现了 next()
  5. if (current.done) {
  6. break // 迭代已经结束了,没必要继续了
  7. }
  8. console.log(current.value)
  9. }

想要 for…of 可以遍历一个普通对象,那么就实现它的迭代器。

  1. // 迭代器设计模式
  2. // 场景:你我协同开发一个任务清单应用
  3. // 我的代码 ===============================
  4. const todos = {
  5. life: ['吃饭', '睡觉', '打豆豆'],
  6. learn: ['语文', '数学', '外语'],
  7. work: ['喝茶'],
  8. // 提供统一遍历访问接口
  9. each: function (callback) {
  10. const all = [].concat(this.life, this.learn, this.work)
  11. for (const item of all) {
  12. callback(item)
  13. }
  14. },
  15. // 提供迭代器(ES2015 统一遍历访问接口)
  16. [Symbol.iterator]: function () {
  17. const all = [...this.life, ...this.learn, ...this.work]
  18. let index = 0
  19. return {
  20. next: function () {
  21. return {
  22. value: all[index],
  23. done: index++ >= all.length
  24. }
  25. }
  26. }
  27. }
  28. }
  29. // 你的代码 ===============================
  30. // for (const item of todos.life) {
  31. // console.log(item)
  32. // }
  33. // for (const item of todos.learn) {
  34. // console.log(item)
  35. // }
  36. // for (const item of todos.work) {
  37. // console.log(item)
  38. // }
  39. todos.each(function (item) {
  40. console.log(item)
  41. })
  42. console.log('-------------------------------')
  43. for (const item of todos) {
  44. console.log(item)
  45. }

生成器 Generator

一种新的异步编程方案。

  1. function * foo () {
  2. console.log('1111')
  3. yield 100
  4. console.log('2222')
  5. yield 200
  6. console.log('3333')
  7. yield 300
  8. }
  9. const generator = foo()
  10. console.log(generator.next()) // 第一次调用,函数体开始执行,遇到第一个 yield 暂停
  11. console.log(generator.next()) // 第二次调用,从暂停位置继续,直到遇到下一个 yield 再次暂停
  12. console.log(generator.next()) // 。。。
  13. console.log(generator.next()) // 第四次调用,已经没有需要执行的内容了,所以直接得到
  1. // Generator 应用
  2. // 案例1:发号器
  3. function * createIdMaker () {
  4. let id = 1
  5. while (true) {
  6. yield id++
  7. }
  8. }
  9. const idMaker = createIdMaker()
  10. console.log(idMaker.next().value)
  11. console.log(idMaker.next().value)
  12. console.log(idMaker.next().value)
  13. console.log(idMaker.next().value)
  14. // 案例2:使用 Generator 函数实现 iterator 方法
  15. const todos = {
  16. life: ['吃饭', '睡觉', '打豆豆'],
  17. learn: ['语文', '数学', '外语'],
  18. work: ['喝茶'],
  19. [Symbol.iterator]: function * () {
  20. const all = [...this.life, ...this.learn, ...this.work]
  21. for (const item of all) {
  22. yield item
  23. }
  24. }
  25. }
  26. for (const item of todos) {
  27. console.log(item)
  28. }

ES Modules

ES2016

Array.includes()

用于判断数组是否包含指定元素。可以找到 NaN 元素。

  1. const arr = ['foo', 1, NaN, false]
  2. // 直接返回是否存在指定元素
  3. console.log(arr.includes('foo'))
  4. // 能够查找 NaN
  5. console.log(arr.includes(NaN))

ES2017

Object.values()

遍历对象的属性值。

Object.entries()

遍历对象的属性名和值的对。

  1. const obj = {
  2. foo: 'value1',
  3. bar: 'value2'
  4. }
  5. console.log(Object.values(obj)) // ['value1', 'value2']
  6. console.log(Object.entries(obj)) // [ ['foo', 'value1'], ['bar', 'value2'] ]
  7. for (const [key, value] of Object.entries(obj)) {
  8. console.log(key, value)
  9. } // foo value1 bar value2