ES6语法知识

496 阅读35分钟

声明(let和const)

let 声明的就是变量,变量一旦初始化之后,还可以重新赋值

const声明的就是常量,常量一旦初始化,就不能重新赋值了,否则就会报错

注意事项

  • 使用 const 声明常量,一旦声明,就必须立即初始化,不能留到以后赋值
  • const 声明的常量,允许在不重新赋值的情况下修改它的值,比方说对象.属性名的方式
  • 默认使用 const,只有当确实需要改变变量的值的时候才使用let

let、const 与 var 的区别

1.重复声明

已经存在的变量或常量,又声明了一遍

var 允许重复声明,letconst 不允许

2.变量提升

var会提升变量的声明到当前作用域的顶部

3.暂时性死区

只要作用域内存在 letconst ,它们所声明的变量或常量就自动“绑定”这个区域,不再受到外部作用域的影响

4.window 对象的属性和方法

全局作用域中,var 声明的变量,通过 function 声明的函数,会自动变成 window 对象的属性或方法

5.块级作用域

var没有块级作用域,letconst可以形成块级作用域

应用

 body {
   padding: 50px 0 0 150px;
 }
 ​
 .btn {
   width: 100px;
   height: 100px;
   margin-right: 20px;
   font-size: 80px;
   cursor: pointer;
 }
 <button class="btn">0</button>
 <button class="btn">1</button>
 <button class="btn">2</button>
 // 1.var
 var btns = document.querySelectorAll('.btn')
 ​
 for (var i = 0; i < btns.length; i++) {
   btns[i].addEventListener(
     'click',
     function () {
       console.log(i) // 3 3 3...
     },
     false
   )
 }
 ​
 // 2.闭包
 // 利用立即执行函数形成闭包
 var btns = document.querySelectorAll('.btn')
 ​
 for (var i = 0; i < btns.length; i++) {
   ;(function (index) {
     btns[index].addEventListener(
       'click',
       function () {
         console.log(index) // 0 -> 0 1 -> 1 2 -> 2
       },
       false
     )
   })(i)
 }
 ​
 // 3.let/const
 //块级作用域
 let btns = document.querySelectorAll('.btn')
 ​
 for (let i = 0; i < btns.length; i++) {
   btns[i].addEventListener(
     'click',
     function () {
       console.log(i) // 0 -> 0 1 -> 1 2 -> 2
     },
     false
   )
 }

模板字符串

模板字符串与一般字符串的区别

 const person = {
   username: '小龙',
   age: 18,
   sex: 'male'
 }
 ​
 const info1 = '我的名字是:' + person.username + ', 性别:' + person.sex + ', 今年' + person.age + '岁了'
 console.log(info1)
 ​
 const info2 = `我的名字是:${person.username}, 性别:${person.sex}, 今年${person.age}岁了`
 console.log(info2)

和其他东西一起使用的时候,使用模板字符串,方便注入

其他情况下使用模板字符串或一般字符串都行

注意事项

1.输出多行字符串

 const info1 = '第1行\n第2行'
 console.log(info1)
 //第1行
 //第2行
 const info2 = `第1行\n第2行`
 const info3 = `第1行
 第2行`
 console.log(info2)
 //第1行
 //第2行
 console.log(info3)
 //第1行
 //第2行

模板字符串中,所有的空格、换行或缩进都会被保留在输出之中

2.输出 ` 和 \ 等特殊字符

 const info = `'`\`
 console.log(info) // '`\

3.模板字符串的注入

 const username = 'xiaohu'
 const person = { age: 18, sex: 'male' }
 const getSex = function (sex) {
   return sex === 'male' ? '男' : '女'
 }
 ​
 const info = `${username}, ${person.age + 2}, ${getSex(person.sex)}`
 console.log(info) // xiaohu, 20, 男

只要最终可以得出一个值的就可以通过 ${} 注入到模板字符串中

模板字符串应用

将数据动态添加到 ul 列表中

 <p>学生信息表</p>
 <ul id="list">
   <li style="list-style: none">信息加载中……</li>
 </ul>
 // 数据
 const students = [
   {
     username: '王五',
     age: 18,
     sex: 'male'
   },
   {
     username: 'ZhangSan',
     age: 28,
     sex: 'male'
   },
   {
     username: 'LiSi',
     age: 20,
     sex: 'female'
   }
 ]
 ​
 const list = document.getElementById('list')
 ​
 let html = ''
 ​
 for (let i = 0; i < students.length; i++) {
   html += `<li>我的名字是:${students[i].username},${students[i].sex},${students[i].age}</li>`
 }
 ​
 list.innerHTML = html

箭头函数

什么是箭头函数

1.认识箭头函数

 const add = (x, y) => {
   return x + y
 }
 console.log(add(1, 1))

2.箭头函数的结构

const/let 函数名 = 参数 => 函数体

3.如何将一般函数改写成箭头函数

 // 声明形式
 function add() {}
 ​
 // 声明形式->函数表达式形式
 const add = function () {}
 ​
 // 函数表达式形式->箭头函数
 const add = () => {}

箭头函数注意事项

1.单个参数

 // 单个参数可以省略圆括号
 const add = x => {
   return x + 1
 }
 console.log(add(1))
 ​
 // 无参数或多个参数不能省略圆括号
 const add = () => {
   return 1 + 1
 }
 const add = (x, y) => {
   return x + y
 }
 console.log(add(1, 1))

2.单行函数体

 // 单行函数体可以同时省略 {} 和 return
 const add = (x, y) => {
   return x + y
 }
 const add = (x, y) => x + y
 console.log(add(1, 1))
 ​
 // 多行函数体不能再化简了
 const add = (x, y) => {
   const sum = x + y
   return sum
 }

3.单行对象

 const add = (x, y) => {
   return {
     value: x + y
   }
 }
 ​
 // 如果箭头函数返回单行对象,可以在 {} 外面加上 (),让浏览器不再认为那是函数体的花括号
 const add = (x, y) => ({ value: x + y })
 ​
 console.log(add(1, 1))
 ​

箭头函数的 this 指向

先复习一下非箭头函数中的this指向

 // 1.全局作用域中的 this 指向
 console.log(this) // window
 ​
 // 2.一般函数(非箭头函数)中的 this 指向
 'use strict'
 function add() {
   console.log(this)
 }
 add() // 严格模式就指向 undefined 非严格模式下指向 undefined -> window 
 ​
 const calc = {
   add: add
 }
 calc.add() // calc
 const adder = calc.add
 adder() // undefined->window(非严格模式下)
 ​
 //点击事件
 document.onclick = function () {
   console.log(this)
 }
 document.onclick() // document
 ​
 //构造函数
 function Person(username) {
   this.username = username
   console.log(this) // p
 }
 ​
 const p = new Person('xiaozhang')
 ​
 // 只有在函数调用的时候 this 指向才确定,不调用的时候,不知道指向谁
 // this 指向和函数在哪儿调用没关系,只和谁在调用有关
 // 没有具体调用对象的话,this 指向 undefined,在非严格模式下,转向 window

说回箭头函数,箭头函数没有自己的 this,它会指向外层作用域的 this

 const calc = {
   add: () => {
     console.log(this)
   }
 }
 calc.add() // window
 ​
 // 2.练习
 // 'use strict'
 const calc = {
   add: function () {
     // this
     const adder = () => {
       console.log(this)
     }
     adder()
   }
 }
 calc.add() // calc
 // adder 的 this -> add 方法中的 this , add 方法被 calc 调用 -> add 方法中的 this 指向 calc
 // 所以 adder 的 this 指向 calc
 ​
 const addFn = calc.add
 addFn() // undefined->window
 // addFn 函数表达式直接调用,this 指向 window (非严格模式) , undefined(严格模式)

不适用箭头函数的场景

1.作为构造函数

 // 箭头函数没有 this 构造函数中的 this 指向实例对象 所以不能这么写
 const Person = () => {}
 new Person()

2.需要 this 指向调用对象的时候

 document.onclick = () => {
   console.log(this) //window
 }
 document.addEventListener(
   'click',
   () => {
     console.log(this) //window
   },
   false
 )

3.需要使用 arguments 的时候

 // 箭头函数中没有 arguments
 function add() {
   console.log(arguments)
 }
 add(1, 2, 3, 4, 5) // Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
 ​
 const add2 = () => console.log(arguments)
 add2() // Uncaught ReferenceError: arguments is not defined

箭头函数的应用

 <button id="btn">开始</button>
 <span id="result">0</span>
 const btn = document.getElementById('btn')
 const result = document.getElementById('result')
 ​
 //以前的做法
 // const timer = {
 //   time: 0,
 //   start: function () {
 //     // 提前保存一下 this 有时候也用 self
 //     var that = this
 //     btn.addEventListener(
 //       'click',
 //       function () {
 //         setInterval(function () {
 //           console.log(this)
 //           that.time++
 //           result.innerHTML = that.time
 //         }, 1000)
 //       },
 //       false
 //     )
 //   }
 // }
 // 箭头函数的做法
 // 箭头函数没有自己的 this ,利用这个特性,可以让它指向 start 方法
 const timer = {
   time: 0,
   start: function () {
     // this
     btn.addEventListener(
       'click',
       () => {
         // this
         setInterval(() => {
           console.log(this)
           this.time++
           result.innerHTML = this.time
         }, 1000)
       },
       false
     )
   }
 }
 timer.start()

解构赋值

解构赋值是什么

1.认识解构赋值

 // 1.认识解构赋值
 // const arr = [1, 2, 3]
 // const a = arr[0]
 // const b = arr[1]
 // const c = arr[2]
 // console.log(a, b, c) // 1 2 3
 ​
 const [a, b, c] = [1, 2, 3]
 console.log(a, b, c) // 1 2 3

2.什么是解构赋值

解析某一数据的结构,将我们想要的东西提取出来,赋值给变量或常量

数组解构赋值的原理

 // 1.模式(结构)匹配
 ;[] = [1, 2, 3]
 ​
 // 2.索引值相同的完成赋值
 const [a, b, c] = [1, 2, 3]
 console.log(a, b, c) // 1 2 3
 ​
 // 不取的,可以直接用逗号跳过
 const [a, [, , b], c] = [1, [2, 4, 5], 3]
 console.log(a, b, c) // 1 5 3

数组解构赋值的默认值

 // 1.默认值的基本用法
 const [a, b] = [] // undefined undefined
 const [a, b] = [undefined, undefined] // undefined undefined
 ​
 const [a = 1, b = 2] = [] // 1 2
 console.log(a, b)
 ​
 // 2.默认值的生效条件
 // 只有当一个数组成员严格等于(===)undefined 时,对应的默认值才会生效
 const [a = 1, b = 2] = [3, 0] // 3 0
 const [a = 1, b = 2] = [3, null] // 3 null
 const [a = 1, b = 2] = [3] // 3 2
 console.log(a, b)
 ​
 // 3.默认值表达式
 // 如果默认值是表达式,默认值表达式是惰性求值的
 const func = () => {
   console.log('我被执行了')
   return 2
 }
 // const [x = func()] = [1] // 1
 const [x = func()] = [] // 2
 console.log(x)

数组解构赋值的应用

 // 1.常见的类数组的解构赋值
 // arguments
 function func() {
   const [a, b] = arguments
   console.log(a, b) // 1 2
 }
 // func()
 func(1, 2)
 ​
 // NodeList
 console.log(document.querySelectorAll('p')) // [p, p, p]
 const [p1, p2, p3] = document.querySelectorAll('p')
 console.log(p1, p2, p3) // <p>1</p> <p>2</p> <p>3</p>
 ​
 // 2.函数参数的解构赋值
 const array = [1, 1]
 // const add = arr => arr[0] + arr[1]
 const add = ([x = 0, y = 0]) => x + y
 console.log(add(array)) // 2
 console.log(add([])) // 0
 ​
 // 3.交换变量的值
 let x = 1
 let y = 2
 ​
 // let tmp = x
 // x = y
 // y = tmp
 // console.log(x, y)
 ​
 ;[x, y] = [y, x] // 2 1
 console.log(x, y)

对象解构赋值的原理

 // 1.模式(结构)匹配
 // {}={}
 ​
 // 2.属性名相同的完成赋值
 const { age, username } = { username: 'xiaofeng', age: 18 }
 // const { age: age, username: username } = { username: 'xiaofeng', age: 18 }
 console.log(age, username) // 18 "xiaofeng"
 ​
 // 取别名
 const { age: age, username: uname } = { username: 'xiaofeng', age: 18 }
 console.log(age, uname) // 18 "xiaofeng"

对象解构赋值的注意事项

 // 1.默认值的生效条件
 // 对象的属性值严格等于 undefined 时,对应的默认值才会生效
 const { username = 'ZhangSan', age = 0 } = { username: 'lisi' }
 console.log(username, age) // lisi 0
 ​
 // 2.默认值表达式
 // 如果默认值是表达式,默认值表达式是惰性求值的
 ​
 // 3.将一个已经声明的变量用于解构赋值
 // 如果将一个已经声明的变量用于对象的解构赋值,整个赋值需在圆括号中进行
 let x = 2
 ;({ x } = { x: 1 })
 console.log(x) // 1
 ​
 // 4.可以取到继承的属性
 const { toString } = {}
 console.log(toString) // ƒ toString() { [native code] }
 ​

对象解构赋值的应用

 // 1.函数参数的解构赋值
 // 以前的做法
 // const logPersonInfo = user => console.log(user.username, user.age)
 // 现在的做法
 const logPersonInfo = ({ age = 0, username = 'ZhangSan' }) => console.log(username, age)
 logPersonInfo({ username: 'lisi', age: 18 }) // lisi 18
 // 相当于 { age, username:username }={ username: 'lisi', age: 18 }
 logPersonInfo({}) // ZhangSan 0
 ​
 // 2.复杂的嵌套
 const obj = {
   x: 1,
   y: [2, 3, 4],
   z: {
     a: 5,
     b: 6
   }
 }
 const { x, y, z } = obj
 console.log(x, y, z) // 1 [2, 3, 4] {a: 5, b: 6}
 const {
   y,
   y: [, yy],
   z,
   z: { b }
 } = obj
 console.log(yy, y, z, b) // 3 (3) [2, 3, 4] {a: 5, b: 6} 6
 // [, yy] = [2, 3, 4] yy 是 y 数组的第二个元素

其它数据类型的解构赋值

 // 1.字符串的解构赋值
 ​
 // 数组形式的解构赋值
 const [a, b, , , c] = 'hello'
 console.log(a, b, c) // h e o
 ​
 // 对象形式的解构赋值
 const { 0: a, 1: b, length } = 'hello'
 console.log(a, b, length) // h e 5
 console.log('hello'.length) // 5
 ​
 // 字符串既可以按数组形式来解构赋值,也可以按对象形式来解构赋值
 ​
 // 2.数值和布尔值的解构赋值
 // 先将等号右边的值转为对象
 console.log(new Number(123)) // Number {123}
 const { a = 1, toString } = 123
 console.log(a, toString) // 1 ƒ toString() { [native code] }
 ​
 const { b = 2, toString } = true
 console.log(b, toString) // 2 ƒ toString() { [native code] }
 ​
 // 3.undefined 和 null 的解构赋值
 // 由于 undefined 和 null 无法转为对象,所以对它们进行解构赋值,都会报错
 const { toString } = undefined 
 // es6.js:25 Uncaught TypeError: Cannot destructure property 'toString' of 'undefined' as it is undefined.
 const { toString } = null 
 // Uncaught TypeError: Cannot destructure property 'toString' of 'null' as it is null.

对象字面量的增强

属性和方法的简洁表示法

 // 1.对象字面量是什么
 // 实例化构造函数生成对象
 const person = new Object()
 person.age = 18
 person.speak = function () {}
 ​
 // 对象字面量
 const person = {
   age: 18,
   speak: function () {}
 }
 ​
 // 2.属性的简洁表示法
 // 键名和变量或常量名一样的时候,可以只写一个
 const age = 18
 const person = {
   // age: age
   age
 }
 console.log(person) // {age: 18}
 ​
 // 3.方法的简洁表示法
 // 方法可以省略冒号和 function 关键字
 const person = {
   // speak: function () {}
   speak() {}
 }
 console.log(person) // {speak: ƒ}

方括号语法

 // 1.方括号语法的用法
 const prop = 'age'
 const person = {}
 ​
 // 相当于 person.prop = 18
 person[prop] = 18
 ​
 // 方括号语法可以写在对象字面量中
 const person = {
   [prop]: 18
 }
 ​
 console.log(person) // {age: 18}
 ​
 // 2.方括号中可以放什么
 // [值或通过计算可以得到值的(表达式)] 比如变量 函数 字符串 字符串拼接
 const prop = 'age'
 const func = () => 'age2'
 const person = {
   // [prop]: 18
   // [func()]: 18
   // ['sex']: 'male'
   ['s' + 'ex']: 'male'
 }
 console.log(person) // {sex: "male"}
 ​
 // 3.方括号语法和点语法的区别
 // 点语法是方括号语法的特殊形式
 // const person = {}
 // person.age 等价于 person['age']
 ​
 // 属性名由数字、字母、下划线以及 $ 构成,并且数字还不能打头的时候可以使用点语法
 // age18_$ √
 // 18age ×
 // 合法标识符可以用来作为变量或常量名
 ​
 // 当你的属性或方法名是合法标识符时,可以使用点语法,其他情况下请使用方括号语法

函数参数默认值

函数参数的默认参数值是什么

 // 1.认识函数参数的默认值
 // 调用函数的时候传参了,就用传递的参数;如果没传参,就用默认值
 multiply(2, 1)
 multiply(2)
 ​
 // 2.函数参数默认值的基本用法
 const multiply = (x, y) => {
   if (typeof y === 'undefined') {
     y = 1
   }
 ​
   return x * y
 }
 // 新的写法
 const multiply = (x, y = 1) => x * y
 console.log(multiply(2)) // 2

函数参数默认值的注意事项

 // 1.默认值的生效条件
 // 不传参数,或者明确的传递 undefined 作为参数,只有这两种情况下,默认值才会生效
 const multiply = (x, y = 1) => x * y
 console.log(multiply(2, 0)) // 0
 console.log(multiply(2, null)) // 0
 console.log(multiply(2, undefined)) // 2
 console.log(multiply(2)) // 2
 ​
 // 2.默认值表达式
 // 如果默认值是表达式,默认值表达式是惰性求值的
 // 3.设置默认值的小技巧
 // 函数参数的默认值,最好从参数列表的右边开始设置
 // const multiply = (x = 1, y) => x * y
 // console.log(multiply(undefined, 2)) // 2 第一个参数如果是默认值,想使用默认值需要传 undefined
 ​
 const multiply = (x, y = 1) => x * y
 console.log(multiply(2)) // 2
 ​
 // 3.设置默认值的小技巧
 // 函数参数的默认值,最好从参数列表的右边开始设置
 // const multiply = (x = 1, y) => x * y
 // console.log(multiply(undefined, 2)) // 2 第一个参数如果是默认值,想使用默认值需要传 undefined
 ​
 const multiply = (x, y = 1) => x * y
 console.log(multiply(2)) // 2

函数参数默认值的应用

 // 1.接收很多参数的时候
 // 不推荐
 const logUser = (username = 'ZhangSan', age = 0, sex = 'male') => console.log(username, age, sex)
 logUser('lisi', 18, 'male') // lisi 18 male
 logUser() // ZhangSan 0 male
 ​
 // 2.接收一个对象作为参数
 // 推荐 解构赋值
 const logUser = ({ username = 'zhangsan', age = 0, sex = 'male' } = {}) => console.log(username, age, sex)
 logUser({
   username: 'lisi',
   age: 18,
   sex: 'male'
 }) // lisi 18 male
 logUser({}) // zhangsan 0 male
 logUser() // zhangsan 0 male

剩余参数

剩余参数是什么

 // 1.认识剩余参数
 const add = (x, y, z, ...args) => {}
 ​
 // 2.剩余参数的本质
 const add = (x, y, ...args) => {
   console.log(x, y, args)
 }
 add() // undefined undefined []
 add(1) // 1 undefined []
 add(1, 2) // 1 2 []
 add(1, 2, 3, 4, 5) // 1 2 (3) [3, 4, 5]
 ​
 // 剩余参数永远是个数组,即使没有值,也是空数组

剩余参数的注意事项

 // 1.箭头函数的剩余参数
 // 箭头函数的参数部分即使只有一个剩余参数,也不能省略圆括号
 const add = (...args) => {}
 ​
 // 2.使用剩余参数替代 arguments 获取实际参数
 const add = function () {
   console.log(arguments) // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
 }
 const add = (...args) => {
   console.log(args)
 }
 add(1, 2) // [1, 2]
 ​
 // 3.剩余参数的位置
 // 剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错
 const add = (x, y, ...args,z) => {
   console.log(args) // Uncaught SyntaxError: Rest parameter must be last formal parameter
 }
 ​

剩余参数的应用

 // 1.完成 add 函数
 const add = (...args) => {
   let sum = 0
 ​
   for (let i = 0; i < args.length; i++) {
     sum += args[i]
   }
   // reduce
   return sum
 }
 console.log(add()) // 0
 console.log(add(1, 1)) // 2
 console.log(add(1, 2, 3)) // 6
 ​
 // 2.与解构赋值结合使用
 // 剩余参数不一定非要作为函数参数使用
 const [num, ...args] = [1, 2, 3, 4]
 console.log(num, args) // 1 [2, 3, 4]
 ​
 // 和数组解构赋值结合起来使用
 const func = ([num, ...args]) => {
   console.log(...args)
 }
 func([1, 2, 3]) // 2 3
 ​
 // 剩余元素(报错提示是元素) 和解构赋值结合来用就不适合称之为参数
 // 必须是最后一个 否则报错 Uncaught SyntaxError: Rest element must be last element
 const { x,...z ,y  } = { a: 3, x: 1, y: 2, b: 4 }
 ​
 const func = ({ x, y, ...z }) => {
   console.log({ ...z }) 
 }
 func({ a: 3, x: 1, y: 2, b: 4 }) // {a: 3, b: 4}

展开运算符

数组展开运算符的基本用法

 // 1.认识展开运算符
 // Math.min 只接受参数列表,不接受数组
 console.log(Math.min([3, 1, 2])) // NaN
 console.log(Math.min(3, 1, 2)) // 1
 ​
 // [3, 1, 2]->3, 1, 2
 ​
 // 2.数组展开运算符的基本用法
 console.log(Math.min(...[3, 1, 2]))
 // 相当于
 // console.log(Math.min(3, 1, 2))

区分剩余参数和展开运算符

 // 1.根本区别
 // 展开运算符
 // [3,1,2]->3,1,2
 ​
 // 剩余参数
 // 3,1,2->[3,1,2]
 ​
 // 2.区分剩余参数和展开运算符
 // 剩余参数
 const add = (...args) => {
   console.log(args) // [1, 2, 3]
   // 展开运算符
   console.log(...args) // 1 2 3
   console.log(...[1, 2, 3]) // 1 2 3
   console.log(1, 2, 3) // 1 2 3
 }
 add(1, 2, 3)
 ​
 console.log([...[1, 2, 3], 4]) // [1, 2, 3, 4]
 // [1, 2, 3]->1,2,3

数组展开运算符的应用

 // 1.复制数组
 const a = [1, 2]
 const b = a
 a[0] = 3
 console.log(b) // [3,2]
 ​
 const c = [...a]
 a[0] = 5
 console.log(a) // [5, 2]
 console.log(c) // [3, 2]
 ​
 // 2.合并数组
 const a = [1, 2]
 const b = [3]
 const c = [4, 5]
 ​
 console.log([...a, ...b, ...c]) // [1, 2, 3, 4, 5]
 console.log([...b, ...a, ...c]) // [3, 1, 2, 4, 5]
 console.log([1, ...b, 2, ...a, ...c, 3]) // [1, 3, 2, 1, 2, 4, 5, 3]
 ​
 // 3.字符串转为数组
 // 字符串可以按照数组的形式展开
 console.log(...'alex') // a l e x
 console.log('a', 'l', 'e', 'x') // a l e x
 console.log([...'alex']) // ["a", "l", "e", "x"]
 console.log('alex'.split('')) // ["a", "l", "e", "x"]
 ​
 // 4.常见的类数组转化为数组
 // arguments
 function func() {
   console.log([...arguments])
 }
 func(1, 2)
 ​
 // NodeList
 console.log(document.querySelectorAll('p'))
 console.log([...document.querySelectorAll('p')].push)

对象展开运算符的基本用法

 // 1.展开对象
 // 对象不能直接展开,必须在 {} 中展开
 const apple = {
   color: '红色',
   shape: '球形',
   taste: '甜'
 }
 console.log(...apple) // Uncaught TypeError: Found non-callable @@iterator
 console.log([...apple]) //  Uncaught TypeError: apple is not iterable
 ​
 // 对象的展开:把属性罗列出来,用逗号分隔,放到一个 {} 中,构成新对象
 console.log({ ...apple }) // {color: "红色", shape: "球形", taste: "甜"}
 console.log({ ...apple } === apple) // false
 ​
 // 2.合并对象
 const apple = {
   color: '红色',
   shape: '球形',
   taste: '甜'
 }
 const pen = {
   color: '黑色',
   shape: '圆柱形',
   use: '写字'
 }
 console.log({ ...pen }) // {color: "黑色", shape: "圆柱形", use: "写字"}
 console.log({ ...apple, ...pen }) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
 // 新对象拥有全部属性,相同属性,后者覆盖前者
 ​
 console.log({ pen, apple }) // {pen: {…}, apple: {…}}
 console.log({ ...pen, apple }) // {color: "黑色", shape: "圆柱形", use: "写字", apple: {…}}

对象展开运算符的注意事项

 // 1.空对象的展开
 // 如果展开一个空对象,则没有任何效果
 console.log({ ...{} }) // {}
 console.log({ ...{}, a: 1 }) // {a: 1}
 ​
 // 2.非对象的展开
 // 如果展开的不是对象,则会自动将其转为对象,再将其属性罗列出来
 console.log({ ...1 }) // {}
 console.log(new Object(1)) // Number {1}
 console.log({ ...undefined }) // {}
 console.log({ ...null }) // {}
 console.log({ ...true }) // {}
 ​
 // 如果展开运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象
 console.log({ ...'hello' }) // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
 console.log([...'hello']) // ["h", "e", "l", "l", "o"]
 console.log(...'hello') // h e l l o
 ​
 console.log({ ...[1, 2, 3] }) // {0: 1, 1: 2, 2: 3}
 ​
 // 3.对象中对象属性的展开
 // 不会展开对象中的对象属性
 const apple = {
   feature: {
     taste: '甜'
   }
 }
 const pen = {
   feature: {
     color: '黑色',
     shape: '圆柱形'
   },
   use: '写字'
 }
 console.log({ ...apple }) // {feature: {…}}
 console.log({ ...apple, ...pen }) // {feature: {…}, use: "写字"}

对象展开运算符的应用

 // 1.复制对象
 const a = { x: 1, y: 2 }
 // const b = a // 复制引用地址
 ​
 const c = { ...a } // 复制对象
 console.log(c, c === a) // {x: 1, y: 2} false
 ​
 // 2.用户参数和默认参数
 // 第一种
 const logUser = ({ username = 'ZhangSan', age = 0, sex = 'male' } = {}) => {
   console.log(username, age, sex) // {x: 1, y: 2} false
 }
 ​
 //第二种
 const logUser = userParam => {
   const defaultParam = {
     username: 'ZhangSan',
     age: 0,
     sex: 'male'
   }
 ​
   const param = { ...defaultParam, ...userParam }
   console.log(param.username) // ZhangSan
   const { username, age, sex } = { ...defaultParam, ...userParam }
   console.log(username, age, sex) // ZhangSan 0 male
 }
 logUser()

Set

Set 是什么

 // 1.什么是 Set
 // 集合
 ​
 // [1, 2]
 // 数组是一系列有序的数据集合
 ​
 // Set 是一系列无序、没有重复值的数据集合
 ​
 // 2.理解 Set
 const s = new Set()
 s.add(1)
 s.add(2)
 ​
 // Set 中不能有重复的成员
 s.add(1)
 console.log(s) // Set(2) {1, 2}
 ​
 // Set 没有下标去标示每一个值,所以 Set 是无序的,也不能像数组那样通过下标去访问 Set 的成员

Set 实例的方法和属性

 // 1.方法
 // add
 const s = new Set()
 s.add(1).add(2).add(2)
 console.log(s) // Set(2) {1, 2}
 ​
 // // has
 // console.log(s.has(1)) // true
 // console.log(s.has(3)) // false
 ​
 // // delete
 // s.delete(1)
 // console.log(s) // Set(1) {2}
 ​
 // // 使用 delete 删除不存在的成员,什么都不会发生,也不会报错
 // s.delete(3)
 // console.log(s) // Set(1) {2}
 ​
 // // clear
 // s.clear()
 // console.log(s) // Set(0) {}
 ​
 // forEach
 s.forEach(function (value, key, set) {
   // Set 中 value = key
   console.log(value, key, set === s) // 1 1 true // 2 2 true
   console.log(this) // document
 }, document)
 console.log(s)
 ​
 // 按照成员添加进集合的顺序遍历
 ​
 // 2.属性
 // size
 console.log(s.size) // 2
 console.log(s) // Set(2) {1, 2}

Set 构造函数的参数

 // 1.数组
 const s = new Set([1, 2, 1])
 console.log(s) // Set(2) {1, 2}
 ​
 // // 2.字符串、arguments、NodeList、Set 等
 console.log(new Set('hi')) // Set(2) {"h", "i"}
 function func() {
   console.log(new Set(arguments))
 }
 func(1, 2, 1) // {1, 2}
 ​
 console.log(new Set(document.querySelectorAll('p'))) // Set(3) {p, p, p}
 ​
 const s = new Set([1, 2, 1])
 console.log(new Set(s) === s) // false set当做参数相当于复制了一个 set 
 console.log(s) // {1, 2}

Set 的注意事项

 // 1.判断重复的方式
 const s = new Set([NaN, 2, NaN])
 ​
 console.log(1 === 1) // true
 console.log(NaN === NaN) // false
 ​
 // Set 对重复值的判断基本遵循严格相等(===)
 // 但是对于 NaN 的判断与 === 不同,Set 中 NaN 等于 NaN
 console.log(s) // Set(2) {NaN, 2}
 ​
 const s = new Set()
 s.add({}).add({})
 console.log({} === {}) // false
 console.log(s) // Set(2) {{…}, {…}}
 ​
 // 2.什么时候使用 Set
 // ① 数组或字符串去重时
 // ② 不需要通过下标访问,只需要遍历时
 // ③ 为了使用 Set 提供的方法和属性时(add delete clear has forEach size 等)

Set 的应用

 // 1.数组去重
 const s = new Set([1, 2, 1])
 console.log(s) // Set(2) {1, 2}
 ​
 console.log(...s) // 1 2
 console.log([...s]) // [1, 2]
 console.log([...new Set([1, 2, 1])]) // [1, 2]
 ​
 // 2.字符串去重
 const s = new Set('abbacbd')
 console.log([...s].join('')) // abcd
 console.log(s) // Set(4) {"a", "b", "c", "d"}
 ​
 console.log([...new Set('abbacbd')].join('')) // abcd
 ​
 // 3.存放 DOM 元素
 console.log(document.querySelectorAll('p')) // NodeList(3) [p, p, p]
 const s = new Set(document.querySelectorAll('p'))
 s.forEach(function (elem) {
   elem.style.color = 'red'
   elem.style.backgroundColor = 'yellow'
 })

Map

Map 是什么

 // 1.认识 Map
 // 映射
 ​
 // Map 和对象都是键值对的集合
 ​
 // 键->值,key->value
 const person = {
   name: 'xiaolong',
   age: 18
 }
 ​
 const m = new Map()
 m.set('name', 'xiaolong')
 m.set('age', 18)
 console.log(m) // Map(2) {"name" => "xiaolong", "age" => 18}
 ​
 // 2.Map 和对象的区别
 // 对象一般用字符串当作键
 const obj = {
   name: 'xiaolong',
   true: 'true',
   [{}]: 'object'
 }
 console.log(obj) // {name: "xiaolong", true: "true", [object Object]: "object"}
 console.log({}.toString()) // [object Object]
 ​
 // 基本数据类型:数字、字符串、布尔值、undefined、null
 // 引用数据类型:对象([]、{}、函数、Set、Map 等)
 // 以上都可以作为 Map 的键
 const m = new Map()
 m.set('name', 'xiaolong')
 m.set(true, 'true')
 m.set({}, 'object')
 m.set(new Set([1, 2]), 'set')
 m.set(undefined, 'undefined')
 console.log(m) // Map(5) {"name" => "xiaolong", true => "true", {…} => "object", Set(2) => "set", undefined => "undefined"}

Map 实例的属性和方法

 // 1.方法
 // set
 const m = new Map()
 ​
 // 使用 set 添加的新成员,键如果已经存在,后添加的键值对覆盖已有的
 m.set('age', 18).set(true, 'true').set('age', 20)
 console.log(m) // Map(2) {"age" => 20, true => "true"}
 ​
 // get
 console.log(m.get('age')) // 20
 // get 获取不存在的成员,返回 undefined
 console.log(m.get('true'))
 console.log(m.get(true)) // true
 ​
 // has
 console.log(m.has('age')) // true
 console.log(m.has('true')) // false
 ​
 // delete
 m.delete('age')
 console.log(m) // Map(1) {true => "true"}
 m.delete('name')
 // 使用 delete 删除不存在的成员,什么都不会发生,也不会报错
 console.log(m) // Map(1) {true => "true"}
 ​
 // clear
 m.clear()
 console.log(m) // Map(0) {}
 ​
 const m = new Map()
 ​
 // 使用 set 添加的新成员,键如果已经存在,后添加的键值对覆盖已有的
 m.set('age', 18).set(true, 'true').set('age', 20)
 ​
 // forEach
 m.forEach(function (value, key, map) {
   console.log(value, key, map === m) // 20 "age" true // true true true
   console.log(this) // document // document
 }, document)
 ​
 // 2.属性
 // size
 // 对象没有类似的属性
 console.log(m.size) // 2

Map 构造函数的参数

 // 1.数组
 console.log(new Map(['name', 'xiaohu', 'age', 18]))
 // Uncaught TypeError: Iterator value name is not an entry object
 // 只能传二维数组,而且必须体现出键和值
 console.log(
   new Map([
     ['name', 'xiaohu'],
     ['age', 18]
   ])
 ) // Map(2) {"name" => "xiaohu", "age" => 18}
 ​
 // 2.Set、Map 等
 // Set 中也必须体现出键和值
 const s = new Set([
   ['name', 'xiaohu'],
   ['age', 18]
 ])
 console.log(new Map(s)) // Map(2) {"name" => "xiaohu", "age" => 18}
 console.log(s) // Set(2) {Array(2), Array(2)}
 ​
 // Map
 // 复制了一个新的 Map
 const m1 = new Map([
   ['name', 'xiaohu'],
   ['age', 18]
 ])
 console.log(m1) // Map(2) {"name" => "xiaohu", "age" => 18}
 const m2 = new Map(m1)
 console.log(m2, m2 === m1) // Map(2) {"name" => "xiaohu", "age" => 18} false

Map 的注意事项

 // 1.判断键名是否相同的方式
 // 基本遵循严格相等(===)
 // 例外就是 NaN,Map 中 NaN 也是等于 NaN
 console.log(NaN === NaN) // false
 const m = new Map()
 m.set(NaN, 1).set(NaN, 2)
 console.log(m) // Map(1) {NaN => 2}
 ​
 // 2.什么时候使用 Map
 // 如果只是需要 key -> value 的结构,或者需要字符串以外的值做键,使用 Map 更合适
 ​
 // 只有模拟现实世界的实体时,才使用对象

Map 的应用

 const [p1, p2, p3] = document.querySelectorAll('p')
 console.log(p1, p2, p3) // <p>1</p><p>2</p><p>3</p>
 // 第一种使用map给p标签添加颜色
 const m = new Map()
 m.set(p1, 'red')
 m.set(p2, 'green')
 m.set(p3, 'blue')
 console.log(m) // Map(3) {p => "red", p => "green", p => "blue"}
 // 第二种使用map给p标签添加颜色
 const m = new Map([
   [
     p1,
     {
       color: 'red',
       backgroundColor: 'yellow',
       fontSize: '40px'
     }
   ],
   [
     p2,
     {
       color: 'green',
       backgroundColor: 'pink',
       fontSize: '40px'
     }
   ],
   [
     p3,
     {
       color: 'blue',
       backgroundColor: 'orange',
       fontSize: '40px'
     }
   ]
 ])
 ​
 m.forEach((propObj, elem) => {
   for (const p in propObj) {
     elem.style[p] = propObj[p]
   }
 })
 ​
 console.log(m) //Map(3) {p => {…}, p => {…}, p => {…}}

Iterator

Iterator是什么

 // 1.Iterator 的作用
 // Iterator:遍历器(迭代器)
 // for() 接触最多的遍历
 // [1,2].forEach
 // new Set().forEach
 ​
 // Iterator 也是用来遍历的
 ​
 // 2.寻找 Iterator
 ​
 const it = [1, 2][Symbol.iterator]()
 console.log(it) // Array Iterator {}
 ​
 // 3.使用 Iterator
 const it = [1, 2][Symbol.iterator]()
 console.log(it.next()) // {value: 1, done: false}
 console.log(it.next()) // {value: 2, done: false}
 console.log(it.next()) // {value: undefined, done: true}
 console.log(it.next()) // {value: undefined, done: true}
 ​
 // it:可遍历对象(可迭代对象)
 // Symbol.iterator:可遍历对象的生成方法
 ​
 // 4.什么是 Iterator
 // Symbol.iterator(可遍历对象的生成方法) -> it(可遍历对象) -> it.next() -> it.next() -> ...(直到 done 为 true)

Iterator 解惑

 // 1.为什么需要 Iterator 遍历器
 // 遍历数组:for 循环和 forEach 方法
 // 遍历对象:for in 循环
 ​
 // Iterator 遍历器是一个统一的遍历方式
 ​
 console.log([][Symbol.iterator]()) // Array Iterator {}
 console.log({}[Symbol.iterator]) // undefined -> 对象可以手动添加 Iterator
 ​
 // 2.如何更方便的使用 Iterator
 // Symbol.iterator->it->next()
 ​
 // 我们一般不会直接使用 Iterator 去遍历
 // for..of

for...of 的用法

 // 1.认识 for...of
 const arr = [1, 2, 3]
 // 第一种
 const it = arr[Symbol.iterator]()
 console.log(it.next()) // {value: 1, done: false}
 console.log(it.next()) // {value: 2, done: false}
 console.log(it.next()) // {value: 3, done: false}
 console.log(it.next()) // {value: undefined, done: true}
 // 第二种
 let next = it.next()
 console.log(next) // {value: 1, done: false}
 while (!next.done) {
   next = it.next()
   console.log(next) // {value: 2, done: false} // {value: 3, done: false} // {value: undefined, done: true}
 }
 ​
 // 第三种
 for (const item of arr) {
   console.log(item) // 1 // 2 // 3
 }
 // for...of 循环只会遍历出那些 done 为 false 时,对应的 value 值
 ​
 // 2.与 break、continue 一起使用
 const arr = [1, 2, 3]
 for (const item of arr) {
   if (item === 2) {
     // break
     continue
   }
   console.log(item) // 1  3
 }
 ​
 // 3.在 for...of 中取得数组的索引
 const arr = [1, 2, 3]
 ​
 // keys() 得到的是索引的可遍历对象,可以遍历出索引值
 console.log(arr.keys()) // Array Iterator {}
 for (const key of arr.keys()) {
   console.log(key) // 0 // 1 // 2
 }
 // values() 得到的是值的可遍历对象,可以遍历出值
 for (const value of arr.values()) {
   console.log(value) // 1 // 2  // 3
 }
 for (const value of arr) {
   console.log(value) // 1 // 2  // 3
 }
 ​
 // entries() 得到的是索引+值组成的数组的可遍历对象
 for (const entries of arr.entries()) {
   console.log(entries) // [0, 1] // [1, 2] // [2, 3]
 }
 // 解构赋值
 for (const [index, value] of arr.entries()) {
   console.log(index, value) // 0 1 // 1 2 // 2 3
 }

原生可遍历和非原生可遍历

 // 1.什么是可遍历
 // 只要有 Symbol.iterator 方法,并且这个方法可以生成可遍历对象,就是可遍历的
 ​
 // 只要可遍历,就可以使用 for...of 循环来统一遍历
 ​
 // 2.原生可遍历的有哪些
 // 数组
 // 字符串
 // Set
 // Map
 // arguments
 // NodeList
 ​
 for (const item of [1, 2, 3]) {
   console.log(item) // 1 // 2 // 3
 }
 for (const item of 'hi') {
   console.log(item) // h // i
 }
 for (const item of new Set([1, 2])) {
   console.log(item) // 1 // 2
 }
 ​
 for (const item of new Map([
   ['hello', 'world'],
   [1, 2]
 ])) {
   console.log(item) // ["hello", "world"] // [1, 2]
 }
 ​
 for (const elem of document.querySelectorAll('p')) {
   console.log(elem)
   elem.style.color = 'red' // <p style="color: red;">1</p> // <p style="color: red;">2</p> // <p style="color: red;">3</p>
 }
 ​
 // 3.非原生可遍历的有哪些
 // 一般的对象
 const person = { sex: 'male', age: 18 }
 console.log(person[Symbol.iterator]()) // Uncaught TypeError: person[Symbol.iterator] is not a function
 ​
 //手写对象的 iterator 方法
 person[Symbol.iterator] = () => {
   let index = 0
   return {
     next() {
       index++
 ​
       if (index === 1) {
         return {
           value: person.age,
           done: false
         }
       } else if (index === 2) {
         return {
           value: person.sex,
           done: false
         }
       } else {
         return {
           done: true
         }
       }
     }
   }
 }
 for (const item of person) {
   console.log(item) // 18 // male
 }
 ​
 ​
 // 有 length 和索引属性的对象
 const obj = {
   0: 'xiaohu',
   1: 'male',
   length: 2
 }
 ​
 // 直接拿数组的方法过来使用
 // obj[Symbol.iterator] = Array.prototype[Symbol.iterator]
 ​
 // 自己手写
 obj[Symbol.iterator] = () => {
   let index = 0
 ​
   return {
     next() {
       let value, done
       if (index < obj.length) {
         value = obj[index]
         done = false
       } else {
         value = undefined
         done = true
       }
 ​
       index++
 ​
       return {
         value,
         done
       }
     }
   }
 }
 for (const item of obj) {
   console.log(item) // xiaohu // male
 }

使用了 Iterator 的场合

 // 原生可遍历的
 // Array 数组
 // String 字符串
 // Set
 // Map
 // 函数的 arguments 对象
 // NodeList 对象
 ​
 // for...of
 ​
 // 1.数组的展开运算符
 console.log(...[1, 2, 3]) // 1 2 3
 console.log(1, 2, 3) // 1 2 3
 console.log(...'str') // s t r
 console.log(...new Set([1, 2, 3])) // 1 2 3
 console.log(...{}) // 对象没有iterator,直接使用会报错 Uncaught TypeError: Found non-callable @@iterator
 ​
 // 2.数组的解构赋值
 // const [a, b] = [1, 2] // 1 2
 // const [a, b] = [...[1, 2]] // 1 2
 // const [a, b] = 'hi' // h i
 // const [a, b] = [...'hi'] // h i
 // const [a, b] = [...new Set([3, 4])] // 3 4
 ​
 console.log(a, b)
 ​
 // 3.Set 和 Map 的构造函数
 // new Set(iterator)
 // new Map(iterator)

ES6的新增方法

字符串的新增方法

includes()

 // 判断字符串中是否含有某些字符
 // 1.基本用法
 console.log('abc'.includes('a')) // true
 console.log('abc'.includes('ab')) // true
 console.log('abc'.includes('bc')) // true
 console.log('abc'.includes('ac')) // false
 ​
 // 2.第二个参数
 // 表示开始搜索的位置,默认是 0
 console.log('abc'.includes('a')) // true
 console.log('abc'.includes('a', 0)) // true
 console.log('abc'.includes('a', 1)) // false
 ​
 // 3.应用
 // https://www.imooc.com/course/list
 // https://www.imooc.com/course/list?c=fe&sort=pop&name=value
 let url = 'https://www.imooc.com/course/list?'
 const addURLParam = (url, name, value) => {
   url += url.includes('?') ? '&' : '?'
 ​
   url += `${name}=${value}`
 ​
   return url
 }
 url = addURLParam(url, 'c', 'fe')
 console.log(url) // https://www.imooc.com/course/list?&c=fe
 url = addURLParam(url, 'sort', 'pop')
 console.log(url) // https://www.imooc.com/course/list?&c=fe&sort=pop

padStart() 和 padEnd()

 // 补全字符串长度
 // 1.基本用法
 console.log('x'.padStart(5, 'ab')) // ababx
 console.log('x'.padEnd(5, 'ab')) // xabab
 console.log('x'.padEnd(4, 'ab')) // xaba
 ​
 // 2.注意事项
 // 原字符串的长度,等于或大于最大长度,不会消减原字符串,字符串补全不生效,返回原字符串
 console.log('xxx'.padStart(2, 'ab')) // xxx
 console.log('xxx'.padEnd(2, 'ab')) // xxx
 ​
 // 用来补全的字符串与原字符串长度之和超过了最大长度,截去超出位数的补全字符串,原字符串不动
 console.log('abc'.padStart(10, '0123456789')) // 0123456abc
 console.log('abc'.padEnd(10, '0123456789')) // abc0123456
 ​
 // 如果省略第二个参数,默认使用空格补全长度
 console.log('x'.padStart(4)) // '   x'
 console.log('x'.padEnd(4)) // 'x   '
 ​
 // 3.应用
 // 显示日期格式
 // 2020
 // 10
 // 10
 ​
 // 2020-10-10
 // 2020-01-01
 ​
 console.log('10'.padStart(2, 0)) // 10
 console.log('1'.padStart(2, 0)) // 01

trimStart() 和 trimEnd()

 // 清除字符串的首或尾空格,中间的空格不会清除
 ​
 // 1.基本用法
 const s = '  a b c  '
 console.log(s) // '  a b c  '
 console.log(s.trimStart()) // 'a b c  '
 console.log(s.trimLeft()) // 'a b c  '
 console.log(s.trimEnd()) // '  a b c'
 console.log(s.trimRight()) // '  a b c'
 console.log(s.trim()) // 'a b c'
 ​
 // 2.应用
 const usernameInput = document.getElementById('username')
 const btn = document.getElementById('btn')
 btn.addEventListener(
   'click',
   () => {
     console.log(usernameInput.value)
 ​
     // 验证
     console.log(usernameInput.value.trim())
     if (usernameInput.value.trim() !== '') {
       // 可以提交
       console.log('可以提交')
     } else {
       // 不能提交
       console.log('不能提交')
     }
 ​
     // 手动提交
   },
   false
 )

数组的新增方法

includes()

 // 1.基本用法
 // 判断数组中是否含有某个成员
 console.log([1, 2, 3].includes('2')) // false
 console.log([1, 2, 3].includes(2)) // true
 // 第二个参数表示搜索的起始位置,默认值是 0
 console.log([1, 2, 3].includes(2, 2)) // false
 // 基本遵循严格相等(===),但是对于 NaN 的判断与 === 不同,includes 认为 NaN === NaN
 console.log(NaN === NaN) // false
 console.log([1, 2, NaN].includes(NaN)) // true
 ​
 // 2.应用
 // 去重
 // [1, 2, 1]
 ​
 const arr = []
 for (const item of [1, 2, 1]) {
   if (!arr.includes(item)) {
     arr.push(item)
   }
 }
 console.log(arr) // [1, 2]

Array.from()

 // 将其他数据类型转换成数组
 ​
 // 1.基本用法
 console.log(Array.from('str')) // ["s", "t", "r"]
 ​
 // 2.哪些可以通过 Array.from() 转换成数组
 // 2.1.所有可遍历的
 // 数组、字符串、Set、Map、NodeList、arguments
 console.log(Array.from(new Set([1, 2, 1]))) // [1, 2]
 console.log([...new Set([1, 2, 1])]) // [1, 2]
 ​
 // 2.2.拥有 length 属性的任意对象
 const obj = {
   0: 'a',
   1: 'b',
   name: 'xiaolong',
   length: 3
 }
 console.log(Array.from(obj)) // ["a", "b", undefined] 只会转化 number key 的 value ,其它 undefined
 console.log([...obj]) // Uncaught TypeError: obj is not iterable 对象不可遍历,所以报错
 ​
 // 3.第二个参数
 // 作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组
 console.log(
   [1, 2].map(value => {
     return value * 2
   })
 ) // [2, 4]
 console.log(Array.from('12', value => value * 2)) // [2, 4]
 console.log(Array.from('12').map(value => value * 2)) // [2, 4]
 ​
 // 4.第三个参数
 Array.from(
   '12',
   value => {
     console.log(this) // window // window
   },
   document
 )
 Array.from(
   '12',
   function () {
     console.log(this) //document // document
   },
   document
 )

find() 和 findIndex()

 // find():找到满足条件的一个立即返回
 // findIndex():找到满足条件的一个,立即返回其索引
 ​
 // 1.基本用法
 console.log(
   [1, 5, 10, 15].find((value, index, arr) => {
     console.log(this) // window // window // window
     return value > 9
   }, document) // 10
 )
 console.log(
   [1, 5, 10, 15].find(function (value, index, arr) {
     console.log(this) // document // document // document
     return value > 9
   }, document) // 10
 )
 console.log(
   [1, 5, 10, 15].findIndex((value, index, arr) => {
     return value > 9
   }, document) // 2
 )
 ​
 // 2.应用
 const students = [
   {
     name: '张三',
     sex: '男',
     age: 16
   },
   {
     name: '李四',
     sex: '女',
     age: 22
   },
   {
     name: '王二麻子',
     sex: '男',
     age: 32
   }
 ]
 console.log(students.find(value => value.sex === '女')) // {name: "李四", sex: "女", age: 22}
 console.log(students.findIndex(value => value.sex === '女')) // 1

对象的新增方法

Object.assign()

 // 用来合并对象
 ​
 // 1.基本用法
 // Object.assign(目标对象, 源对象1,源对象2,...): 目标对象
 ​
 const apple = {
   color: '红色',
   shape: '圆形',
   taste: '甜'
 }
 const pen = {
   color: '黑色',
   shape: '圆柱形',
   use: '写字'
 }
 console.log(Object.assign(apple, pen)) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
 ​
 // Object.assign 直接合并到了第一个参数中,返回的就是合并后的对象
 console.log(apple) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
 console.log(Object.assign(apple, pen) === apple) // true
 ​
 // 可以合并多个对象
 console.log(Object.assign({}, apple, pen)) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
 console.log(apple) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
 console.log({ ...apple, ...pen }) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
 ​
 // 2.注意事项
 // 2.1.基本数据类型作为源对象
 // 与对象的展开类似,先转换成对象,再合并
 console.log(Object.assign({}, undefined)) // {}
 console.log(Object.assign({}, null)) // {}
 console.log(Object.assign({}, 1)) // {}
 console.log(Object.assign({}, true)) // {}
 console.log(Object.assign({}, 'str')) // {0: "s", 1: "t", 2: "r"}
 ​
 // 2.2.同名属性的替换
 // 后面的直接覆盖前面的
 const apple = {
   color: ['红色', '黄色'],
   shape: '圆形',
   taste: '甜'
 }
 const pen = {
   color: ['黑色', '银色'],
   shape: '圆柱形',
   use: '写字'
 }
 console.log(Object.assign({}, apple, pen)) // {color: ["黑色", "银色"], shape: "圆柱形", taste: "甜", use: "写字"}
 ​
 // 3.应用
 // 合并默认参数和用户参数
 const logUser = userOptions => {
   const DEFAULTS = {
     username: 'ZhangSan',
     age: 0,
     sex: 'male'
   }
 ​
   const options = Object.assign({}, DEFAULTS, userOptions)
   //   const options = Object.assign({}, DEFAULTS, undefined) 不传参 undefined 转换成对象是 {}
   console.log(options)
 }
 logUser() // {username: "ZhangSan", age: 0, sex: "male"}
 logUser({}) // {username: "ZhangSan", age: 0, sex: "male"}
 logUser({ username: 'wangwu' }) // {username: "wangwu", age: 0, sex: "male"}

Object.keys()、Object.values() 和 Object.entries()

 // 1.基本用法
 const person = {
   name: 'xiaolong',
   age: 18
 }
 ​
 console.log(Object.keys(person)) // ["name", "age"]
 console.log(Object.values(person)) // ["xiaolong", 18]
 console.log(Object.entries(person)) // [["name", "age"],["xiaolong", 18]]
 ​
 // 2.与数组类似方法的区别
 console.log([1, 2].keys()) // Array Iterator {}
 console.log([1, 2].values()) // Array Iterator {}
 console.log([1, 2].entries()) // Array Iterator {}
 console.log(person.keys) // undefined
 ​
 // 数组的 keys()、values()、entries() 等方法是实例方法,返回的都是 Iterator
 // 对象的 Object.keys()、Object.values()、Object.entries() 等方法是构造函数方法,返回的是数组
 // Object.keys() 方法之前就有,ES6 补全了 value 和 entries 方法,为了保证一致性 , Object 的三个方法采用 ES6 之前的做法
 ​
 // 3.使用 for...of 循环遍历对象
 const person = {
   name: 'xiaohu',
   age: 18
 }
 for (const key of Object.keys(person)) {
   console.log(key) // name // age
 }
 for (const value of Object.values(person)) {
   console.log(value) // xiaohu  // 18
 }
 for (const entries of Object.entries(person)) {
   console.log(entries) // ["name", "xiaohu"] // ["age", 18]
 }
 for (const [key, value] of Object.entries(person)) {
   console.log(key, value) // name xiaohu // age 18
 }
 ​
 // Object.keys()/values()/entires() 并不能保证顺序一定是你看到的样子,这一点和 for in 是一样的

Promise

Promise 是什么

 // 1.认识 Promise
 // Promise 是异步操作的一种解决方案
 // 回调函数
 document.addEventListener(
   'click',
   () => {
     console.log('这里是异步的')
   },
   false
 )
 console.log('这里是同步的')
 ​
 // 2.什么时候使用 Promise
 // Promise 一般用来解决层层嵌套的回调函数(回调地狱 callback hell)的问题
 // 运动
 const move = (el, { x = 0, y = 0 } = {}, end = () => {}) => {
   el.style.transform = `translate3d(${x}px, ${y}px, 0)`
 ​
   el.addEventListener(
     'transitionend',
     () => {
       // console.log('end');
       end()
     },
     false
   )
 }
 const boxEl = document.getElementById('box')
 ​
 document.addEventListener(
   'click',
   () => {
     move(boxEl, { x: 150 }, () => {
       move(boxEl, { x: 150, y: 150 }, () => {
         move(boxEl, { y: 150 }, () => {
           // console.log('object');
           move(boxEl, { x: 0, y: 0 })
         })
       })
     })
   },
   false
 )
 * {
   padding: 0;
   margin: 0;
 }
 ​
 #box {
   width: 300px;
   height: 300px;
   background-color: red;
   transition: all 0.5s;
 }
 <div id="box"></div>

Promise 的基本用法

 // 1.实例化构造函数生成实例对象
 console.log(Promise)
 ​
 // Promise 解决的不是回调函数,而是回调地狱
 const p = new Promise(() => {})
 ​
 // 2.Promise 的状态
 const p = new Promise((resolve, reject) => {
   // Promise 有 3 种状态,一开始是 pending(未完成),执行 resolve,变成 fulfilled(resolved),已成功
   // 执行 reject,变成 rejected,已失败
 ​
   // Promise 的状态一旦变化,就不会再改变了
 ​
   // pending->fulfilled
   // resolve()
 ​
   // pending->rejected
   reject()
 })
 ​
 // 3.then 方法
 p.then(
   () => {
     console.log('success')
   },
   () => {
     console.log('error')
   }
 )
 ​
 // 4.resolve 和 reject 函数的参数
 const p = new Promise((resolve, reject) => {
   // Promise 有 3 种状态,一开始是 pending(未完成),执行 resolve,变成 fulfilled(resolved),已成功
   // 执行 reject,变成 rejected,已失败
 ​
   // Promise 的状态一旦变化,就不会再改变了
 ​
   // pending->fulfilled
   resolve('succ')
   resolve({ username: 'xiaolong' })
 ​
   // pending->rejected
   reject('reason')
   reject(new Error('reason'))
 })
 p.then(
   data => {
     console.log('success', data)
   },
   err => {
     console.log('error', err)
   }
 )
 ​
 console.log(p)

Promise的实例方法

then()

 // 1.什么时候执行
 // pending->fulfilled 时,执行 then 的第一个回调函数
 // pending->rejected 时,执行 then 的第二个回调函数
 ​
 // 2.执行后的返回值
 // then 方法执行后返回一个新的 Promise 对象
 const p = new Promise((resolve, reject) => {
   resolve()
   // reject()
 })
 const p2 = p
   .then(
     () => {},
     () => {}
   )
   .then()
   .then()
 ​
 console.log(p, p2, p === p2) // Promise {<fulfilled>: undefined} Promise {<pending>} false
 ​
 // 3.then 方法返回的 Promise 对象的状态改变
 const p = new Promise((resolve, reject) => {
   // resolve()
   reject()
 })
 p.then(
   () => {
     // console.log('success')
   },
   () => {
     console.log('err') // err
 ​
     // 在 then 的回调函数中,return 后面的东西,会用 Promise 包装一下
     // return undefined
     // 等价于
     // return new Promise(resolve => {
     //   resolve(undefined)
     // })
 ​
     return 123
     // return new Promise(resolve => {
     //   resolve(123)
     // })
 ​
     // 默认返回的永远都是成功状态的 Promise 对象
     // return new Promise((resolve, reject) => {
     //   reject('reason')
     // })
   }
 )
   .then(
     data => {
       console.log('success2', data) // success2 123
 ​
       // return undefined
       return new Promise(resolve => {
         resolve(undefined)
       })
     },
     err => {
       console.log('err2', err)
     }
   )
   .then(
     data => {
       console.log('success3', data) // success3 undefined
     },
     err => {
       console.log('err3', err)
     }
   )
 ​
 // 4.使用 Promise 解决回调地狱
 // 运动
 const move = (el, { x = 0, y = 0 } = {}, end = () => {}) => {
   el.style.transform = `translate3d(${x}px, ${y}px, 0)`
 ​
   el.addEventListener(
     'transitionend',
     () => {
       end()
     },
     false
   )
 }
 const boxEl = document.getElementById('box')
 ​
 const movePromise = (el, point) => {
   return new Promise(resolve => {
     move(el, point, () => {
       resolve()
     })
   })
 }
 ​
 document.addEventListener(
   'click',
   () => {
     movePromise(boxEl, { x: 150 })
       .then(() => {
         return movePromise(boxEl, { x: 0, y: 0 })
       })
       .then(() => {
         return movePromise(boxEl, { x: 150, y: 150 })
       })
       .then(() => {
         return movePromise(boxEl, { y: 150 })
       })
   },
   false
 )
 ​

catch()

 // 1.有什么用
 then(
   data => {},
   err => {}
 )
 then(data => {})
 ​
 // catch 专门用来处理 rejected 状态
 // catch 本质上是 then 的特例
 then(null, err => {})
 ​
 // 2.基本用法
 new Promise((resolve, reject) => {
   // resolve(123)
   reject('reason')
 })
   .then(data => {
     console.log(data)
   })
   // .then(null, err => {
   //   console.log(err)
   // })
   .catch(err => {
     console.log(err) // reason
 ​
     // return undefined
     throw new Error('reason') // Error: reason
   })
   .then(data => {
     console.log(data)
   })
   .catch(err => {
     console.log(err)
   })
 ​
 // catch() 可以捕获它前面的错误
 // 一般总是建议,Promise 对象后面要跟 catch 方法,这样可以处理 Promise 内部发生的错误

finally()

 // 1.什么时候执行
 // 当 Promise 状态发生变化时,不论如何变化都会执行,不变化不执行
 new Promise((resolve, reject) => {
   // resolve(123)
   reject('reason')
 })
   .finally(data => {
     console.log(data)
   })
   .catch(err => {})
 ​
 // 2.本质
 // finally() 本质上是 then() 的特例
 ​
 new Promise((resolve, reject) => {
   // resolve(123)
   reject('reason')
 })
   .finally(data => {
     console.log(data)
   })
   .catch(err => {})
 // 等同于
 new Promise((resolve, reject) => {
   // resolve(123)
   reject('reason')
 })
   .then(
     result => {
       return result
     },
     err => {
       return new Promise((resolve, reject) => {
         reject(err)
       })
     }
   )
   .then(data => {
     console.log(data)
   })
   .catch(err => {
     console.log(err)
   })

Promise.resolve() 和 Promise.reject()

 // 1.Promise.resolve()
 // 是成功状态 Promise 的一种简写形式
 new Promise(resolve => resolve('foo'))
 // 简写
 Promise.resolve('foo')
 ​
 // 参数
 // 一般参数
 Promise.resolve('foo').then(data => {
   console.log(data) // foo
 })
 ​
 // Promise
 // 当 Promise.resolve() 接收的是 Promise 对象时,直接返回这个 Promise 对象,什么都不做
 const p1 = new Promise(resolve => {
   setTimeout(resolve, 1000, '我执行了')
   // 相当于
   //   setTimeout(() => {
   //     resolve('我执行了')
   //   }, 1000)
 })
 Promise.resolve(p1).then(data => {
   console.log(data) // 我执行了
 })
 // 等价于
 // p1.then(data => {
 //   console.log(data) // 我执行了
 // })
 console.log(Promise.resolve(p1) === p1) // true
 ​
 // 当 resolve 函数接收的是 Promise 对象时,后面的 then 会根据传递的 Promise 对象的状态变化决定执行哪一个回调
 new Promise(resolve => resolve(p1)).then(data => {
   console.log(data) // 我执行了
 })
 // 具有 then 方法的对象
 // 立即执行对象里的then()
 // 对象里面的then()带有两个形式参数分别表示 resolve, reject 函数
 //   function func(obj) {
 //     obj.then(1, 2)
 //   }
 //   func({
 //     then(resolve, reject) {
 //       console.log(a, b)
 //     }
 //   })
 // 等价于
 const thenable = {
   then(resolve, reject) {
     resolve('data') // data
     // reject('reason')
   }
 }
 Promise.resolve(thenable).then(
   data => console.log(data),
   err => console.log(err)
 )
 console.log(Promise.resolve(thenable)) // Promise {<pending>} 后面的then不能返回 ,通过对象里的 resolve 和 reject 函数调用决定then执行什么
 ​
 // 2.Promise.reject()
 // 失败状态 Promise 的一种简写形式
 new Promise((resolve, reject) => {
   reject('reason') // Uncaught (in promise) reason
 })
 // 等价于
 // Promise.reject('reason') // Uncaught (in promise) reason
 ​
 // 参数
 // 不管什么参数,都会原封不动地向后传递,作为后续方法的参数
 const p1 = new Promise(resolve => {
   setTimeout(resolve, 1000, '我执行了')
 })
 Promise.reject(p1).catch(err => console.log(err)) // Promise {<pending>}
 ​
 new Promise((resolve, rejcet) => {
   resolve(123)
 })
   .then(data => {
     // return data
     // return Promise.resolve(data) 简写形式
 ​
     return Promise.reject('reason')
   })
   .then(data => {
     console.log(data)
   })
   .catch(err => console.log(err)) // reason

Promise.all()

 // 1.有什么用
 // Promise.all() 关注多个 Promise 对象的状态变化
 // 传入多个 Promise 实例,包装成一个新的 Promise 实例返回
 ​
 // 2.基本用法
 const delay = ms => {
   return new Promise(resolve => {
     setTimeout(resolve, ms)
   })
 }
 ​
 const p1 = delay(1000).then(() => {
   console.log('p1 完成了')
 ​
   // return 'p1'
   return Promise.reject('reason')
 })
 const p2 = delay(2000).then(() => {
   console.log('p2 完成了')
 ​
   return 'p2'
   // return Promise.reject('reason')
 })
 ​
 // Promise.all() 的状态变化与所有传入的 Promise 实例对象状态有关
 // 所有状态都变成 resolved,最终的状态才会变成 resolved
 // 只要有一个变成 rejected,最终的状态就变成 rejected
 ​
 const p = Promise.all([p1, p2])
 p.then(
   data => {
     console.log(data)
   },
   err => {
     console.log(err)
   }
 )
 ​
 // p1 完成了 // reason // p2 完成了

Promise.race() 和 Promise.allSettled()

 const delay = ms => {
   return new Promise(resolve => {
     setTimeout(resolve, ms)
   })
 }
 const p1 = delay(1000).then(() => {
   console.log('p1 完成了')
 ​
   return 'p1 成功了直接输出最终 data'
   // return Promise.reject('reason')
 })
 const p2 = delay(2000).then(() => {
   console.log('p2 完成了')
 ​
   // return 'p2'
   return Promise.reject('reason')
 })
 ​
 // 1.Promise.race()
 // Promise.race() 的状态取决于第一个完成的 Promise 实例对象,如果第一个完成的成功了,那最终的就成功;如果第一个完成的失败了,那最终的就失败
 const racePromise = Promise.race([p1, p2])
 racePromise.then(
   data => {
     console.log(data)
   },
   err => {
     console.log(err)
   }
 )
 // p1 完成了 // p1 成功了直接输出最终 data // p2 完成了
 ​
 // 2.Promise.allSettled()
 // Promise.allSettled() 的状态与传入的Promise 状态无关
 // 永远都是成功的
 // 它只会忠实的记录下各个 Promise 的表现
 const allSettledPromise = Promise.allSettled([p1, p2])
 allSettledPromise.then(data => {
   console.log('succ', data) // succ (2) [{status: "fulfilled", value: "p1"}, {status: "rejected", reason: "reason"}]
 })

Promise 的注意事项

 // 1.resolve 或 reject 函数执行后的代码
 // 推荐在调用 resolve 或 reject 函数的时候加上 return,不再执行它们后面的代码
 new Promise((resolve, reject) => {
   // return resolve(123)
   return reject('reason')
 })
 ​
 // 2.Promise.all/race/allSettled 的参数问题
 // 参数如果不是 Promise 数组,会将不是 Promise 的数组元素转变成 Promise 对象
 Promise.all([1, 2, 3]).then(datas => {
   console.log(datas)
 })
 // 等价于
 // Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then(datas => {
 //   console.log(datas)
 // })
 ​
 // 不只是数组,任何可遍历的都可以作为参数
 // 数组、字符串、Set、Map、NodeList、arguments
 Promise.all(new Set([1, 2, 3])).then(datas => {
   console.log(datas)
 })
 ​
 // 3.Promise.all/race/allSettled 的错误处理
 const delay = ms => {
   return new Promise(resolve => {
     setTimeout(resolve, ms)
   })
 }
 ​
 const p1 = delay(1000).then(() => {
   console.log('p1 完成了')
 ​
   // return 'p1'
   return Promise.reject('reason')
 })
 // .catch(err => {
 //   console.log('p1', err)
 // })
 ​
 const p2 = delay(2000).then(() => {
   console.log('p2 完成了')
 ​
   return 'p2'
   // return Promise.reject('reason')
 })
 //   .catch(err => {
 //     console.log('p2', err)
 //   })
 ​
 const allPromise = Promise.all([p1, p2])
 allPromise
   .then(datas => {
     console.log(datas)
   })
   .catch(err => console.log(err))
 ​
 // 错误既可以单独处理,也可以统一处理
 // 一旦被处理,就不会在其他地方再处理一遍
 ​

Promise 的应用

 // 异步加载图片
 const loadImgAsync = url => {
   return new Promise((resolve, reject) => {
     const img = new Image()
     img.onload = () => {
       resolve(img)
     }
 ​
     img.onerror = () => {
       reject(new Error(`Could not load image at ${url}`))
     }
 ​
     img.src = url
   })
 }
 ​
 const imgDOM = document.getElementById('img')
 loadImgAsync('https://gitee.com/lulalalula/images/raw/master/img/20210517151714.png')
   .then(img => {
     setTimeout(() => {
       imgDOM.src = img.src
     }, 1000)
   })
   .catch(err => {
     console.log(err)
   })
     <img src="https://gitee.com/lulalalula/images/raw/master/img/20210517151713.png" alt="" id="img" />

Class

Class 是什么

 // 1.认识 Class
 // 人类:类
 // 具体的人:实例、对象
 ​
 // 类可以看做是对象的模板,用一个类可以创建出许多不同的对象
 ​
 // 2.Class 的基本用法
 // 类名一般大写
 // class Person {}
 ​
 class Person {
   // 实例化时执行构造方法,所以必须有构造方法,但可以不写出来
   constructor(name, age) {
     console.log('实例化时执行构造方法')
     // this 代表实例对象,上面定义的是实例属性/方法
     this.name = name
     this.age = age
 ​
     // 一般在构造方法中定义属性,方法不在构造方法中定义
     // this.speak = () => {}
   }
 ​
   // 各实例共享的方法
   speak() {
     console.log('speak')
   }
 }
 // Person()
 const zs = new Person('ZS', 18)
 const ls = new Person('LS', 28)
 console.log(zs.name)
 console.log(zs.age)
 console.log(zs.speak)
 zs.speak()
 console.log(ls.name)
 console.log(ls.age)
 console.log(ls.speak)
 console.log(zs.speak === ls.speak)
 }
 ​
 // 3.Class 与构造函数
 class Person {
   constructor(name, age) {
     this.name = name
     this.age = age
   }
 ​
   speak() {
     console.log('speak')
   }
 ​
   // run(){}
 }
 Person.prototype.run = function () {}
 ​
 console.log(typeof Person)
 console.log(Person.prototype.speak)
 ​
 // function Person(name, age) {
 //   this.name = name
 //   this.age = age
 // }
 // Person.prototype.speak = function () {}

Class 的两种定义形式

 // 1.声明形式
 // class Person {
 //   constructor() {}
 ​
 //   speak() {}
 // }
 ​
 // 2.表达式形式
 //构造函数
 // function Person() {}
 // const Person = function () {}
 //类
 const Person = class {
   constructor() {
     console.log('constructor')
   }
 ​
   speak() {}
 }
 new Person()
 ​
 // (function () {
 //   console.log('func')
 // })()
 // func()
 ​
 // 立即执行的匿名类
 new (class {
   constructor() {
     console.log('constructor')
   }
 })()

实例属性、静态方法和静态属性

 // 1.实例属性
 // 方法就是值为函数的特殊属性
 class Person {
   //实例属性
   age = 18
   sex = 'male'
   getSex = function () {
     return this.sex
   }
 ​
   constructor(name, sex) {
     this.name = name
     // 上面的 age = 18 相当于 this.age = 18
     // this.sex = sex
   }
 ​
   // speak() {
   //   this.age = 18
   // }
 }
 ​
 const p = new Person('xiaohu')
 console.log(p.name) // xiaohu
 console.log(p.age) // 18
 console.log(p.sex) // male
 console.log(p.getSex()) // male
 ​
 // 2.静态方法
 // 类的方法
 class Person {
   constructor(name) {
     this.name = name
   }
 ​
   speak() {
     console.log('speak')
     console.log(this)
   }
 ​
   static speak() {
     console.log('人类可以说话')
 ​
     // this 指向类
     console.log(this)
   }
 }
 ​
 const p = new Person('xiaohu')
 p.speak() // speak  // Person {name: "xiaohu"}
 ​
 Person.speak() // 人类可以说话 // Class Person{...}
 ​
 // 3.静态属性
 // 类的属性
 class Person {
   constructor(name) {
     this.name = name
   }
 ​
   // 不要这么写,目前只是提案,有兼容性问题
   static version = '1.0'
   // 折中的办法是写成类方法
   static getVersion() {
     return '1.0'
   }
 }
 Person.version = '1.0'
 ​
 const p = new Person('xiaohu')
 console.log(p.name) // xiaohu
 ​
 console.log(Person.version) // 1.0
 console.log(Person.getVersion()) // 1.0

私有属性和方法

 // 1.为什么需要私有属性和方法
 // 一般情况下,类的属性和方法都是公开的
 // 公有的属性和方法可以被外界修改,造成意想不到的错误
 class Person {
   constructor(name) {
     this.name = name
   }
 ​
   speak() {
     console.log('speak')
   }
 ​
   getName() {
     return this.name
   }
 }
 const p = new Person('xiaoliu')
 console.log(p.name) // xiaoliu
 p.speak() // speak
 ​
 // ....
 p.name = 'zs'
 console.log(p.name) // zs
 ​
 // 2.模拟私有属性和方法
 // 2.1._ 开头表示私有
 class Person {
   constructor(name) {
     this._name = name
   }
 ​
   speak() {
     console.log('speak')
   }
 ​
   getName() {
     return this._name
   }
 }
 const p = new Person('xiaohu')
 console.log(p.name) //undefined 约定俗成加_就是私有方法,调用name访问不到
 p.name = 'zd'
 console.log(p.getName()) // xiaohu
 ​
 // 2.2.将私有属性和方法移出类
 ;(function () {
   let name = ''
 ​
   class Person {
     constructor(username) {
       // this.name = name
       name = username
     }
 ​
     speak() {
       console.log('speak')
     }
 ​
     getName() {
       return name
     }
   }
   // 将类添加到全局作用域中
   window.Person = Person
 })()
 ;(function () {
   const p = new Person('xiaohu')
   console.log(p.name) // undefined
   console.log(p.getName()) // xiaohu
 })()

extends

 // 1.子类继承父类
 class Person {
   constructor(name, sex) {
     this.name = name
     this.sex = sex
 ​
     this.say = function () {
       console.log('say')
     }
   }
 ​
   speak() {
     console.log('speak')
   }
 ​
   static speak() {
     console.log('static speak')
   }
 }
 Person.version = '1.0'
 ​
 class Programmer extends Person {
   constructor(name, sex) {
     super(name, sex)
   }
 }
 ​
 const zs = new Programmer('zs', '男')
 console.log(zs.name) // zs
 console.log(zs.sex) // 男
 zs.say() // say
 zs.speak() // speak
 Programmer.speak() // static speak
 console.log(Programmer.version) // 1.0
 ​
 class Person {
   constructor(name, sex) {
     this.name = name
     this.sex = sex
 ​
     this.say = function () {
       console.log('say')
     }
   }
 ​
   speak() {
     console.log('speak')
   }
 ​
   static speak() {
     console.log('static speak')
   }
 }
 Person.version = '1.0'
 ​
 // 2.改写继承的属性或方法
 class Programmer extends Person {
   constructor(name, sex, feature) {
     // this 操作不能放在 super 前面
     super(name, sex)
 ​
     this.feature = feature
   }
 ​
   hi() {
     console.log('hi')
   }
 ​
   // 同名覆盖
   speak() {
     console.log('Programmer speak')
   }
 ​
   static speak() {
     console.log('Programmer static speak')
   }
 }
 Programmer.version = '2.0'
 ​
 const zs = new Programmer('zs', '男', '秃头')
 console.log(zs.name) // zs
 console.log(zs.sex) // 男
 console.log(zs.feature) // 秃头
 zs.say() // say
 zs.speak() // Programmer speak
 zs.hi() // hi
 Programmer.speak() // Programmer static speak
 console.log(Programmer.version) // 2.0

super

 // 1.作为函数调用
 // 代表父类的构造方法,只能用在子类的构造方法中,用在其他地方就会报错
 // super 虽然代表了父类的构造方法,但是内部的 this 指向子类的实例
 class Person {
   constructor(name) {
     this.name = name
     console.log(this) 
   }
 }
 class Programmer extends Person {
   constructor(name, sex) {
     super(name, sex) 
   }
 }
 new Person('xiaolong') // Person {name: "xiaolong"}
 new Programmer('xiaohu') // Programmer {name: "xiaohu"}
 ​
 // 2.作为对象使用
 // 2.1.在构造方法中使用或一般方法中使用
 // super 代表父类的原型对象 Person.prototype
 // 所以定义在父类实例上的方法或属性,是无法通过 super 调用的
 // 通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例
 class Person {
   constructor(name) {
     this.name = name
     console.log(this) // this 指向 Person {name: "xiaolong"}
   }
 ​
   speak() {
     console.log('speak')
     console.log(this)
   }
 ​
   static speak() {
     console.log('Person speak')
     console.log(this)
   }
 }
 ​
 class Programmer extends Person {
   constructor(name, sex) {
     super(name, sex) // Programmer {name: "xiaohu"} super作为函数调用,代表父类构造方法,this 指向当前子类实例
     console.log(super.name) // undefined 因为 super 指向父类 prototype ,父类实例的属性无法调用
     super.speak()
     // speak      spuer 作为对象调用的父类原型上的方法
     // Programmer {name: "xiaohu"} 通过 super 对象调用父类方法,this 指向当前子类实例
   }
 ​
   speak() {
     super.speak()
     console.log('Programmer speak')
   }
 ​
   // 2.2.在静态方法中使用
   // 指向父类,而不是父类的原型对象
   // 通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,而不是子类的实例
   static speak() {
     super.speak()
     console.log('Programmer speak')
   }
 }
 new Person('xiaolong')
 new Programmer('xiaohu')
 Programmer.speak()
 // Person speak
 // class Programmer extends Person {...}
 // Programmer speak
 ​
 // 3.注意事项
 // 使用 super 的时候,必须显式指定是作为函数还是作为对象使用,否则会报错
 class Person {
   constructor(name) {
     this.name = name
   }
 ​
   speak() {
     console.log('speak')
   }
 }
 class Programmer extends Person {
   constructor(name, sex) {
     super(name, sex)
 ​
     console.log(super())
     console.log(super.speak)
   }
 }

参考:

慕课前端课程

扩展:

1.5万字概括ES6全部特性(已更新ES2020)

ES6 完全使用手册

ES6、ES7、ES8、ES9、ES10新特性一览