wallleap / myblogs

blog, Issue
https://myblog.wallleap.cn
4 stars 1 forks source link

一文总结 ES 新特性 #47

Open wallleap opened 4 years ago

wallleap commented 4 years ago

title: 一文总结 ES 新特性 date: 2020-04-28 23:33 updated: 2020-04-28 23:33 cover: //cdn.wallleap.cn/img/pic/cover/202302NoyYdY.jpg category: 技术杂谈 tags:

总结并列出 ES 5 及之后的新特性

一、理解 ES

  1. 全称: ECMAScript (/ˈɛkməskrɪpt/)

    • 它是一种由 ECMA 组织(前身为欧洲计算机制造商协会)制定和发布的脚本语言规范
    • 我们学习的 JavaScript 就是 ECMA 的实现
  2. JS 包含三个部分:

    • ECMAScript(JS 基础、核心部分)

    • 扩展 → 浏览器端

      • BOM(浏览器对象模型)
      • DOM(文档对象模型)
    • 扩展 → 服务器端

      • Node.js
  3. ES 的几个重要版本

    • 之前的为 ES 3(ES 4 夭折了,没发布)
    • ES 5:2009 年发布
    • ES 6(ES 2015):2015 年发布,也称为 ECMA 2015——重点(之后的 ES 就以年份命名)
    • ES 7(ES 2016):2016 年发布,也称为 ECMA 2016

二、ES 5 新特性

ES 5 对 JS 进行了修修补补,大概可以分为以下几类:

1、严格模式

理解:

目的/作用:

使用:

语法和行为改变:

<script>
  'use strict'
  var username = 'luwang'
  // name = 'luwang' 在严格模式下不用 var 声明变量会报错
  console.log(username)

  function Person(name, age) {
    this.name = name
    this.age = age
  }
  new Person('luwang', 23)
  // Person('luwang', 23) //没有 new 会报错

  var str = 'web'
  eval('var str = "HTML"; alert(str)') // HTML
  alert(str) // web 即开启严格模式之后不会污染全局作用域

  var obj = {
    username: 'luwang',
    username: 'luwang'  // 定义重名了
  }
</script>

2、JSON 序列化和反序列化

<script>
  var obj = { username: 'luwang' }
  obj = JSON.stringify(obj)
  console.log(typeof obj)
  obj = JSON.parse(obj)
  console.log(typeof obj)
</script>

3、Object 扩展

ES 5 给 Object 扩展了一些静态方法

常用的两个:

create

Object.create(prototype[, descriptors]):创建一个新的对象,返回值是新对象

var obj = {username: 'luwang', age:23}
var obj1 = {}
obj1 = Object.create(obj, {  // obj的属性为obj1的原型
  sex: {
    value: '男',
    writable: true,  // 默认false
    configurable: true,
    enumerable: true
  }
})
console.log(obj1.sex)
obj1.sex = 'nan'
console.log(obj1.sex)
delete obj1.sex
console.log(obj1)
for(var i in obj1){
  console.log(i)
}

defineProperties

Object.defineProperties(object, descriptors):为指定对象定义扩展多个属性

var obj = { username: 'luwang', age:23 }
var obj1 = {}
var obj2 = { firstName: 'lu', lastName: 'wang' }
Object.defineProperties(obj2, {
  fullName: { // 此方法在原型中
    get: function(){ // 获取扩展属性的值
      console.log('get方法被调用')
      return this.firstName + ' ' + this.lastName
    },
    set: function(data){ // 监听扩展属性,当扩展属性发生变化的时候会自动调用,自动调用后会讲变化的值作为实参注入到set函数
      console.log('set方法被调用,', data)
      var names = data.split(' ') // 根据空格拆分为数组
      this.firstName = names[0]
      this.lastName = names[1]
    }
  }
})
console.log(obj2.fullName) // get会自动调用 
obj2.fullName = 'lu wang'  
console.log(obj2.fullName)

惰性求值:点击才给值(什么时候要什么时候给),会再次调用 get 方法

惰性求值

对象本身也有两个方法:

var obj = {
  firstName: 'lu', 
  lastName: 'wang',
  get fullName(){
    return this.firstName + ' ' + this.lastName
  },
  set fullName(data){
    var names = data.split(' ')
    this.firstName = names[0]
    this.lastName = names[1]
  }
}
console.log(obj)
obj.fullName = 'lu wang'
console.log(obj.fullName)

4、Array 扩展

Array.isArray(arr):判断一个对象 arr 是否是数组

Array.prototype.indexOf(value):得到值在数组中的第一个下标

Array.prototype.lastIndexOf(value):得到值在数组中的最后一个下标

Array.prototype.forEach(function(item, index, array){}[, asThis]):遍历数组,返回值是 undefined

Array.prototype.map(function(item, index, array){}[, asThis]):遍历数组返回一个新的数组(每一项是回调函数的返回值)

Array.prototype.filter(function(item, index, array){}[, asThis]):遍历过滤出一个子数组,返回一个由条件为 true 的元素组成的新数组

Array.prototype.reduce(function(accumulator, item, index, array)[, initValue])Array.prototype.reduce 对数组中的每个元素按序执行一个提供的回调函数,每次执行回调函数会将之前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值

Array.prototype.reduceRight:从右到左

Array.prototype.some(function(){item, index, array}[, asThis]):数组中是否至少有一个元素符合回调函数给定的条件,有则返回 true,没有返回 false

Array.prototype.every(function(item, index, array){}[, asThis]):判断一个数组内的所有元素是否都能通过指定函数的测试,返回布尔值(回调函数有三个参数,第一个为当前遍历的对象、第二个为当前的下标、第三个为数组本身,方法还接受第二个参数,将作为 this

var arr = [2,4,5,1,6,7,4,3,9]
console.log(arr.indexOf(4))
console.log(arr.lastIndexOf(4))
arr.forEach(function(item, index){
  console.log(item, index)
})
var arr1 = arr.map(function(item, index){
  return item + 10
})
console.log(arr, arr1)
arr.filter(function(item, index){
  return item > 3
})
arr.every(function(item) {
  return item > 10
})  // false

5、Function 扩展

函数新增 bind 方法,Function.prototype.bind(asThis),将函数内的 this 绑定为 asThis,并将函数返回

强制绑定 this 使用 call/applybind

var obj = {username: 'luwang'}
function foo(){
  console.log(this)
}
foo() // this-->window 全局
// call 和 apply 不传参的时候是一样的
foo.call(obj) // this-->{username: 'luwang'} obj 对象
foo.apply(obj) // this-->{username: 'luwang'} obj 对象
// bind 的特点: 绑定完 this 不会立即调用当前的函数,而是将函数返回
// var bar = foo.bind(obj)
// bar()
foo.bind(obj)()

// 传入参数的形式
var obj1 = {age: 23}
function fun(data){
  console.log(this, data)
}
fun(22) // window  22
// call 直接从第二个参数开始,依次传入
fun.call(obj1, 21) // {age: 23} 21
// 第二参数必须是数组,传入放在数组里
fun.apply(obj1, [20]) // {age: 23} 20

// bind 传参的方式同 call 一样
fun.bind(obj1, 18)()

🔖 面试题:区别 bind()call()apply()

fn.bind(asThis, args):指定函数中的 this,并返回函数(不会立即调用),一般用在回调函数绑定其他对象的 this

var obj = {username: 'luwang'}
setTimeout(function(){
  console.log(this) // Window
}, 1000)
setTimeout(function(){
  console.log(this) // Window
}.bind(obj), 1000)

fn.call(asThis, args):指定函数中的 this,并调用函数

fn.apply(asThis, [args]):指定函数中的 this,并调用函数

总结:三个都可以给 fn 指定 thisbind 不会调用 fncallapply 都会调用 fncall 的其他参数依次以逗号分隔,apply 的其他参数以数组形式传递

6、Date扩展

7、其他

三、ES 6 新特性

ES 6 新增了很多特性,让 JS 变得非常好用

1、2 个声明变量的新关键字

ES 6 中新增了块作用域,{} 包裹的地方就是一个块,ES 5 中没有块级作用域(只有全局和函数作用域)

let 关键字

作用:与 var 相似,用于声明一个变量

特点:

应用:

循环遍历加监听

<br/><button>按钮1</button><br/><br/>
<button>按钮2</button><br/><br/>
<button>按钮3</button>
<script>
  var btns = document.getElementsByTagName('button')
  for(var i = 0; i < btns.length; i++){
    var btn = btns[i]
    btn.onclick = function(){
      alert(i)
    }
  }
  /*
  * 一直会显示3
  * 点击事件对应的是回调函数,回调函数又称勾子函数,回调函数会被放到事件队列中,等主线程上的代码执行完毕之后再通过钩子一样的形式,勾出来执行
  * 以前的方式是通过闭包,立即执行函数(自己的作用域)
  */
  for(var i = 0; i < btns.length; i++){
    var btn = btns[i]
    ;(function(i){  // 声明的形参
      btn.onclick = function(){
      alert(i)
    }
    })(i)   // 传的实参
  }
  /*
  * 闭包利用的是函数作用域的特点
  * 因此可以直接使用let
  */
  for(let i = 0; i < btns.length; i++){ // let,在块作用域内有效
    var btn = btns[i]
    btn.onclick = function(){
      alert(i)
    }
  }
</script>

使用 let 代替 var 是趋势

const 关键字

作用:定义一个常量

特点

应用

保存不用改变的数据

const PI = 3.1415926

let 和 const 声明的变量都是有块级作用域的

let 和 const 声明变量在块作用域内都有暂时性死区,即在声明之前使用该变量会报错

2、变量(对象)的解构赋值

理解:

  1. 从对象或数组中提取数据,并赋值给多个变量

  2. 将包含多个数据的对象/数组一次赋值给多个变量

数据源:对象/数组

目标:{a, b}/[a, b]

对象的解构赋值:let {n, a} = {n:'tom', a:12} 把对象中的值赋值出来(根据属性名 key)

数组的解构赋值:let[a, b] = [1, 'luwang'] (根据下标)

用途:数组匹配、对象匹配、参数匹配(给多个形参赋值)

let obj = {
  username: 'luwang',
  age: 23
}
// let username = obj.username
// let age = obj.age
// console.log(username, age)
// let {username, age} = obj // 对象,因此需要以对象的形式来接收 只需要一个就写一个,不需要按顺序
// console.log(username, age)
let {age} = obj
console.log(age)

// [b, a] = [a, b]
let arr = [1, 3, 5,'abc', true]
// let [a, b, c, d, e] = arr
// console.log(a, b, c, d, e)
// let [a, b] = arr
// console.log(a, b)
let [,,a, b] = arr
console.log(a, b)

function foo({username, age}){ // {username, age} = obj
  console.log(username, age)
}
foo(obj)

3、各种数据类型的扩展

(1) 字符串

模板字符串

let obj = {username: 'luwang', age: 23}
/*
* 之前的写法:简单拼串
* 缺点:可能会拼错,效率低。比如,url携带10个参数,动态拼起来
*/
let str = 'My name is ' + obj.username + ', age is '+ obj.age
console.log(str)
/*
* ES6提供的模板字符串
*/
str = `My name is ${obj.username} age is ${obj.age}`

字符串支持 Unicode

新增一些方法

let str = 'asdfghjkklqwrtyuiopzxcvbnm123467890'
console.log(str.includes('t')) // true
console.log(str.includes('abc')) // false
console.log(str.startsWith('a')) // true
console.log(str.endsWith('0')) // true
console.log(str.repeat(2)) // asdfghjkklqwrtyuiopzxcvbnm123467890asdfghjkklqwrtyuiopzxcvbnm123467890

(2) 数值

二进制与八进制表示法:二进制用 0b,八进制用 0o

新增方法:

console.log(0b1010)
console.log(0o12)
console.log(Number.isFinite(Infinity))
console.log(Number.isNaN(NaN))
console.log(Number.isInteger(123.1))
console.log(Number.isInteger(123.0))
console.log(Number.parseInt('123abc123')) // 123
console.log(Number.parseInt('a123abc123')) // NN
console.log(Math.trunc(123.123)) // 123

(3) 对象

简化的对象写法(短语法)

let name = 'Tom';
let age = 12;
/* 正常情况 */
let obj = {
  name: name,
  age: age,
  getName: function(){
    retrun this.name
  }
}
console.log(obj)
/* key和value相同,可以省略 */
let person = {
  name,  // 同名的属性可以不写
  age,
  setName (name) { // 可以省略函数的function
    this.name = name
  }
}

属性名支持表达式,需要用方括号括起来:

const name = ['ha', 'hi', 'ho']
function yes() {
  return 'Yes'
}
const obj = {
  [name+'llo']: 'luwang',
  ['not'+yes()]: 1
}

属性简写和解构赋值结合使用:

const getUserAction = ({id, name}) => {
  const { data: res } = await axios.get(`http://localhost:3000/user/${id}`)
  res.nickname = name
  return res
}

复制对象

Object.assign(target, source1, source2..):将源对象的属性复制到目标对象上

let obj = {}
let obj1 = {username:'a', age: 20}
let obj2 = {sex: '男'}
// Object.assign(obj, obj1)
// console.log(obj) // {username: "a", age: 20}
Object.assign(obj, obj1, obj2)
console.log(obj) // {username: "a", age: 20, sex: "男"}

判断是否相等

Object.is(v1, v2):判断2个数据是否完全相等

console.log(0 == -0) // true
console.log(NaN == NaN) //false
console.log(Object.is(0, -0)) // false
console.log(Object.is(NaN, NaN)) // true

__proto__ 属性

__proto__ 属性:隐式原型属性。ES 6 中能直接操作__proto__属性,但是不推荐使用

let obj = {}
let obj1 = {salary: 5000000}
obj.__proto__ = obj1
console.log(obj)
console.log(obj.salary)

(4) 数组

<button>測試1</button><br>
<button>測試2</button><br>
<button>測試3</button>
<script>
  let btns = document.getElementsByTagName('button')
  // 偽數組 不能使用forEach(數組的方法)
  Array.from(btns).forEach(function(item, index){
    console.log(item)
  })

  let arr = Array.of(1, 4, 'abc', true)
  console.log(arr)

  let arr2 = [2,3,4,2,5,7,3,6]
  console.log(arr2.find(function(item, index){
    return item > 4
  }))
  console.log(arr2.findIndex(function(item, index){
    return item > 4
  }))
</script>

(5) 函数

Ⅰ、箭头函数

让函数写法更简便

基本语法:

使用场景:多用来定义回调函数

特点:

let fun = function(){console.log('fun')}
fun()
// 1、没有形参
let fun1 = () => console.log('fun1')
fun1()

// 2、只有一个形参
let fun2 = (a) => console.log(a)
// 可省略() let fun2 = a => console.log(a)
fun2('aaa')

// 3、两个及两个以上的形参
let fun3 = (x,y) => console.log(x, y)
fun3(1, 2)

// I、函数体只有一条语句或表达式,{}可以省略-->会自动返回语句执行的结果或表达式的结果
let foo = (x, y) => x + y
// let foo = (x, y) => {return x + y}
console.log(foo(1, 3))

// II、函数体不止一条语句或者表达式, {}不可以省略
let foo2 = (x, y) => {
  console.log(x, y)
  return x + y
}
console.log(foo2(3, 5))

// 箭头函数的this
<br/><button id="btn1">按钮1</button><br/><br/>
<button id="btn2">按钮2</button><br/><br/>
<button id="btn3">按钮3</button>
<script>
  let btn1 = document.getElementById('btn1')
  let btn2 = document.getElementById('btn2')
  let btn3 = document.getElementById('btn3')
  btn1.onclick = function(){
    console.log(this) // <button id="btn1">按钮1</button>
  }
  btn2.onclick = () => {
    console.log(this)  // Window
  }
  let obj = {
    name: '箭头函数',
    getName: function(){
      btn3.onclick = () => {
        console.log(this) // {name: "箭头函数", getName: ƒ}
      }
    }
  }
  obj.getName()
  let obj1 = {
    name: '箭头函数',
    getName: () => {
      btn3.onclick = () => {
        console.log(this) // Window
      }
    }
  }
  obj.getName()
</script>

Ⅱ、参数处理

3 点运算符/点点点运算符

第一种用法:在函数中,rest(可变)参数

// arguments
function foo(a, b){
  console.log(arguments)
  // arguments.callee() 调用自身,相当于foo(参数)
  /* arguments.forEach(function(item, index){ // 会报错,伪数组并没有数组的一般方法
    console.log(item, index)
  }) */
}
foo(2,5)

参数

// 点点点运算符
function foo(...value){
  console.log(arguments)
  console.log(value) // 就是一个正常的数组
  value.forEach(function(item, index){
    console.log(item, index)
  })
}
foo(2,5)

function foo(a, ...value){// ...value只能放在最后面
  console.log(arguments)
  // arguments.callee()
  console.log(value) // 使用的时候不用加...
  value.forEach(function(item, index){
    console.log(item, index)
  })
}
foo(2, 3, 5, 7) // 最前面的就是a,value就不包括它了

第二种用法——扩展/展开运算符,可以分解出数组或对象中的数据

let arr = [1, 6]
let arr1 = [2, 3, 4, 5]
arr = [1, ...arr1, 6]
console.log(arr) // (6) [1, 2, 3, 4, 5, 6]  数组
console.log(...arr) // 1 2 3 4 5 6  每项值

Ⅲ、形参的默认值

// 定义一个点的坐标的构造函数
function Point(x, y){
  this.x = x
  this.y = y
}
let point = new Point(50, 20)
console.log(point) // Point {x: 50, y: 20}
// 忘记传参
let point1 = new Point()
console.log(point1) // Point {x: undefined, y: undefined}

/* 
* 因此会有需求,在忘记传参的时候使用默认值
* 在形参的位置赋默认值
*/
function Point(x = 0, y = 0){
  this.x = x
  this.y = y
}
let point = new Point(50, 20)
console.log(point) // Point {x: 50, y: 20}
// 忘记传参,使用默认值
let point1 = new Point()
console.log(point1) // Point {x: 0, y: 0}

(6) 正则表达式

4、新增数据类型

(1) Symbol 类型

前言:ES 5 中对象的属性名都是字符串,容易造成重名,污染环境

概念:ES 6 中添加了一种原始数据类型 symbol(已有的数据类型:String、Number、boolean、null、undefined、对象)

特点:

使用:

// 创建symbol属性值
let symbol = Symbol()
console.log(symbol)  // Symbol()
let obj = {username:'kobe', age:39}
// 可以添加symbol属性——但是得用另一种方式
obj.gender = '男'
obj[symbol] = 'hello'
console.log(obj)  // {username: "kobe", age: 39, gender: "男", Symbol(): "hello"}

//let symbol2 = Symbol()
//let symbol3 = Symbol()
// 并不相同,值是唯一的
//console.log(symbol2, symbol3, symbol2 == symbol3)  // Symbol() Symbol() false

// 可以传参,这样就能很明显看出不同了
let symbol2 = Symbol('one')
let symbol3 = Symbol('two')
console.log(symbol2, symbol3, symbol2 == symbol3)  // Symbol(one) Symbol(two) false

// 可以用来定义常量
const Person_key = Symbol('person_key')
console.log(Person_key)  // Symbol(person_key)

// 等同于在指定的数据结构上部署了Iterator接口
// 当使用for of去遍历某一个数据结构的时候,首先去找Symbol.itearator,找到了就去遍历,没有找到就不能遍历
let targetData = {
  [Symbol.iterator]: function(){
    let nextIndex = 0
    return{
      next: function(){
        return nextIndex < this.length ? {value: this[nextIndex++], done: false} : {value: undefined, done: true}
      }
    }
  }
}
// 使用三点运算符、解构赋值,默认会去调用Iterator接口
let arr2 = [1,6]
let arr3 = [2,3,4,5]
arr2 = [1,...arr3,6]
console.log(arr2)
let [a,b] = arr2
console.log(a,b)

(2) Set/Map 容器结构

容器: 能保存多个数据的对象,同时必须具备操作内部数据的方法

任意对象都可以作为容器使用,但有的对象不太适合作为容器使用(如函数)

Set 的特点:保存多个 value,value 是不重复 ====>数组元素去重

Map 的特点:保存多个 key-value,key 是不重复,value 是可以重复的

API:

// let set = new Set()
let set = new Set([1,2,4,5,2,3,6]) // 重复的会去除
console.log(set)
set.add(7)
console.log(set.size, set)
console.log(set.has(8))
console.log(set.has(7))
set.delete(7)
console.log(set.size, set)
set.clear()
console.log(set.size, set)

// let map = new Map()
let map = new Map([['username', 'aaa'], ['age', 35], ['sex', 'female']]) // 二维数组,且只能有两值(一个是key,一个是value)
map.set('other', 'shuoming')
console.log(map.size, map)
map.delete('other')
console.log(map)
console.log(map.has('username'))
map.clear()
console.log(map)

(3)WeakSet 和 WeakMap 类型

(4)TypedArray 类型

5、class 类

ES6 中新增的语法,用于实现面向对象编程

通过 class 关键字定义类,实现类的继承

(1) 之前实现继承

回顾:原型、构造函数、构造函数+原型——继承

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.showMe = function() {
  console.log(this.name)
}
let person = new Person('kobe', 39)
person.showMe()
console.log(person)

继承

(2) class 定义类

在类中通过 constructor() 定义构造方法(相当于构造函数)

// 定义一个 Person 类
class Person { // 类声明
// const Person = class { // 类表达式,类表达式的名称是可选的
  // 类的构造方法,只能有一个
  constructor(name, age) { // constructor(){} 里面的参数是生成实例传入的参数
    // 在构造方法中通过 this 给实例对象添加属性
    this.name = name
    this.age = age
  }
  // 在类中定义的方法,都是添加到类的原型对象上的
  showMe(){
    console.log(this.name) // this 指向实例对象,所以可以通过 this 来访问实例对象的属性
  }
}
let person = new Person('kobe', 39)  // new Person() 会自动调用 constructor() 构造方法,创建实例对象,并且将参数传递给 constructor() 构造方法
console.log(person)
person.showName()

继承

(3) class 类的继承

// 父类
class Person{
  constructor(name, age){
    this.name = name
    this.age = age
  }
  showMe(){
    console.log('调用父类的方法')
    console.log(this.name, this.age)
  }
  add(){
    console.log('父类的一般方法')
  }
}
let person = new Person('kobe', 39)
person.showMe()

// 子类:继承父类
class StarPerson extends Person{
  constructor(name, age, salary){
    // 如果子类中定义了构造方法,那么子类的构造方法中必须调用 super(),否则会报错
    super(name, age) // 调用父类的构造方法,将参数传递给父类的构造方法
    this.salary = salary // 子类自己的属性
  }
  showMe(){
    // 可以通过 super 来调用父类的方法
    super.add() // 调用父类的一般方法
    console.log('子类的重写方法')
    console.log(this.name, this.age, this.salary)
  }
}
let p1 = new StarPerson('wade', 36, 10000000)
console.log(p1)
p1.showMe()

(4) class 类的静态方法

// 定义一个 Person 类
class Person{
  constructor(name, age){
    this.name = name
    this.age = age
  }
  showMe(){
    console.log('调用 showMe 方法')
    console.log(this.name, this.age)
  }
  // 静态方法
  static showName(){
    console.log('调用 Person 的静态方法')
    console.log(this) // this 指向类本身
  }
}
let person = new Person('kobe', 39)
person.showMe()
Person.showName() // 通过类来调用静态方法

(5) class 类的 getter 和 setter 方法

// 定义一个 Person 类
class Person{
  constructor(name, age){
    this.name = name
    this.age = age
  }
  showMe(){
    console.log('调用 showMe 方法')
    console.log(this.name, this.age)
  }
  // 静态方法
  static showName(){
    console.log('调用 Person 的静态方法')
    console.log(this) // this 指向类本身
  }
  // getter 方法
  get info(){
    console.log('调用 getter 方法')
    return this.name + ' ' + this.age
  }
  // setter 方法
  set info(value){
    console.log('调用 setter 方法')
    let arr = value.split(' ')
    this.name = arr[0]
    this.age = arr[1]
  }
}
let person = new Person('kobe', 39)
person.showMe()
Person.showName() // 通过类来调用静态方法
console.log(person.info) // 通过实例对象来调用 getter 方法
person.info = 'wade 36' // 通过实例对象来调用 setter 方法
console.log(person.info)

(6) class 类的静态属性

// 定义一个 Person 类
class Person{
  constructor(name, age){
    this.name = name
    this.age = age
  }
  showMe(){
    console.log('调用 showMe 方法')
    console.log(this.name, this.age)
  }
  // 静态方法
  static showName(){
    console.log('调用 Person 的静态方法')
    console.log(this) // this 指向类本身
  }
  // getter 方法
  get info(){
    console.log('调用 getter 方法')
    return this.name + ' ' + this.age
  }
  // setter 方法
  set info(value){
    console.log('调用 setter 方法')
    let arr = value.split(' ')
    this.name = arr[0]
    this.age = arr[1]
  }
  // 静态属性
  static info = '这是一个静态属性'
}
let person = new Person('kobe', 39)
person.showMe()
Person.showName() // 通过类来调用静态方法
console.log(person.info) // 通过实例对象来调用 getter 方法
person.info = 'wade 36' // 通过实例对象来调用 setter 方法
console.log(person.info)
console.log(Person.info) // 通过类来调用静态属性

(7) 私有类字段(属性和方法)

// 定义一个 Person 类
class Person{
  constructor(name, age){
    this.name = name
    this.age = age
  }
  showMe(){
    console.log('调用 showMe 方法')
    console.log(this.name, this.age)
  }
  // 静态方法
  static showName(){
    console.log('调用 Person 的静态方法')
    console.log(this) // this 指向类本身
  }
  // getter 方法
  get info(){
    console.log('调用 getter 方法')
    return this.name + ' ' + this.age
  }
  // setter 方法
  set info(value){
    console.log('调用 setter 方法')
    let arr = value.split(' ')
    this.name = arr[0]
    this.age = arr[1]
  }
  // 静态属性
  static info = '这是一个静态属性'
  // 私有类字段
  #money = 10000000
  #showMoney(){
    console.log('调用私有类方法')
    console.log(this.#money)
  }
}
let person = new Person('kobe', 39)
person.showMe()
Person.showName() // 通过类来调用静态方法
console.log(person.info) // 通过实例对象来调用 getter 方法
person.info = 'wade 36' // 通过实例对象来调用 setter 方法
console.log(person.info)
console.log(Person.info) // 通过类来调用静态属性
console.log(person.money) // undefined
person.showMoney() // 报错

6、Promise 对象

理解:

let request = new XMLHttpRequest()
request.responseType = 'json'
request.open("GET", url)
request.send()

(1) Promise 的基本使用

ES 6 中定义实现 API(使用 Promise 基本步骤):

// 1. 创建promise对象
let promise = new Promise((resolve, reject) => { 
  // 初始化 promise 状态为 pending
  // 执行异步操作 
  if(异步操作成功) { // 调用成功的回调
    resolve(result);  // 修改 promise 状态为 fullfilled
  } else { // 调用失败的回调
    reject(errorMsg);   // 修改 promise 的状态为 rejected
  } 
}) 
// 2. 调用 promise 对象的 then()
promise.then(function(
  result => console.log(result), 
  errorMsg => alert(errorMsg)
))

例子:

// 1、创建 promise 对象
let promise = new Promise((resolve, reject) => {
  // 初始化 promise 状态  pending:初始化
  console.log('11111111')
  // 执行异步操作,通常是发送 Ajax 请求,开启定时器
  setTimeout(() => {
    console.log('3333333')
    // 根据异步任务的返回结果去修改 promise 的状态
    // 异步任务执行成功
    // resolve('哈哈,') // 修改 promise 的状态为 fullfilled:成功
    // 异步任务执行失败
    reject('555, ') // 修改 promise 的状态为 rejsected:失败
  }, 2000)
})
console.log('222222222')
// 2. 调用 promise 对象的 then()
promise
  .then((data) => { // 成功的回调
    console.log(data, '成功了~~~')
  }, (error) => { // 失败的回调
    console.log(error, '失败了……')
})

例如:新闻、新闻的评论,只发新闻的内容;在接着根据新闻的 id 拿取这个新闻下的评论

// 定义获取新闻的功能函数
function getNews(url){
  let promise = new Promise((resolve, reject) => {
    // 状态:初始化
    // 执行异步任务
    let xmlHttp = new XMLHttpRequest()
    // 绑定监听 readyState
    /* xmlHttp.onreadystatechange = function(){
      if(xmlHttp.readyState === 4 && xmlHttp.status == 200){
        // 请求成功
        console.log(xmlHttp.responseText)
        // 修改状态
        resolve(xmlHttp.responseText) // 修改 promise 的状态为成功
      }else{
        // 请求失败
        reject('暂时没有新闻内容')
      }
    } --> 逻辑有问题*/
    xmlHttp.onreadystatechange = function(){
      if(xmlHttp.readyState === 4){
        if(xmlHttp.status == 200){
          // 请求成功
          // console.log(xmlHttp.responseText)
          // 修改状态
          resolve(xmlHttp.responseText) // 修改promise的状态为成功
        }else{
          // 请求失败
          reject('暂时没有新闻内容')
        }
      }
    }

    // open 设置请求得方式以及 url
    xmlHttp.open('GET', url)
    // 发送
    xmlHttp.send()
  })
  return promise
}
getNews('http://localhost:3000/news?id=2')
  .then((data) => {
    console.log(data)
    // 发送请求获取评论内容准备 url
    let commentsUrl = JSON.parse(data).commentsUrl
    let url = 'http://localhost:3000' + commentsUrl
    // 发送请求
    return getNews(url)
  },(error) => {
    console.log(error)
  })
  .then((data) => {
    console.log(data)
  }, () => {

})

(2) Promise 的链式调用

生成的 Promise 对象可以进行链式调用,即 then() 方法返回的是一个新的 Promise 对象,可以继续调用 then() 方法

// 1、创建 promise 对象
let promise = new Promise((resolve, reject) => {
  // 初始化 promise 状态  pending:初始化
  console.log('11111111')
  // 执行异步操作,通常是发送 Ajax 请求,开启定时器
  setTimeout(() => {
    console.log('3333333')
    // 根据异步任务的返回结果去修改 promise 的状态
    // 异步任务执行成功
    // resolve('哈哈,') // 修改 promise 的状态为 fullfilled:成功
    // 异步任务执行失败
    reject('555, ') // 修改 promise 的状态为 rejsected:失败
  }, 2000)
})
console.log('222222222')
// 2. 调用 promise 对象的 then()
promise
  .then((data) => { // 成功的回调
    console.log(data, '成功了~~~')
  }, (error) => { // 失败的回调
    console.log(error, '失败了……')
})

7、Iterator 迭代器

概念:iterator 是一种接口机制,为各种不同的数据结构提供统一的访问机制

作用:

工作原理

原生具备 Iterator 接口的数据,可用 for...of 遍历

扩展理解

for-of 循环:可以遍历任何容器(Set、Map)、数组、对象、伪/类对象、字符串、可迭代的对象

let set = new Set([1, 2, 4, 3, 4, 5]) 
for(let i of set){
  console.log(i)
}

// 可以用 Set 给数组去重
let arr = [1,2,4,5,5,6,2]
let arr1 = arr
arr = [] // 保留数组类型
let set = new Set(arr1)
for(let i of set){
  arr.push(i)
}
console.log(arr)

例如

// 模拟指针对象(遍历器对象)
function myIterator(arr){// Iterator接口
let nextIndex = 0 // 记录指针的位置
  return{
    next: function(){// 遍历器对象
      return nextIndex < arr.length ? {value: arr[nextIndex++], done: false} : {value: undefined, done: true}
    }
  }
}
// 准备一个数据
let arr =[1,4,65,'abc']

let iteratorObj = myIterator(arr)
console.log(iteratorObj.next()) // {value: 1, done: false}
console.log(iteratorObj.next()) // {value: 4, done: false}
console.log(iteratorObj.next()) // {value: 65, done: false}
console.log(iteratorObj.next()) // {value: "abc", done: false}
console.log(iteratorObj.next()) // {value: undefined, done: true}

// 将iterator接口部署到指定的数据类型上,可以使用for of去循环遍历
// 数组、字符串、argument、set容器、map容器
for(let i of arr){
  console.log(i)
}// 1 4 65 abc

let str = 'abcdefg'
for(let i of str){
  console.log(i)
}// a b c d e f g

function fun(){
  for(let i of arguments){
    console.log(i)
  }
}
fun(1,4,5,'abc') // 1 4 5 abc

// let obj = {username:'kobe', age: 39}
// for(let i of obj){
//   console.log(i)
// }// Uncaught TypeError: obj is not iterable 不可迭代

8、Generator 生成器

概念:

特点:

function* generatorExample(){
    let result = yield 'hello'  // 状态值为hello
    yield 'generator'  // 状态值为generator
}

generator 函数返回的是指针对象,而不会执行函数内部逻辑

function* generatorExample(){
  console.log('开始执行')
  let result = yield 'hello'  // 状态值为hello
  yield 'generator'  // 状态值为generator
}
generatorExample() // 调用并不会执行函数内部逻辑

调用 next 方法函数,内部逻辑开始执行,遇到 yield 表达式停止,返回 {value: yield后的表达式结果/return后的返回结果(如果没写,返回undefined),done: boolean值(后面还有返回false,没有返回true)}

function* generatorExample(){
  console.log('开始执行')
  let result = yield 'hello'  // 状态值为hello,会执行,停止 测试yield console.log('会执行')
  console.log('下次调用next执行')
  yield 'generator'  // 状态值为generator
  console.log('下次调用next执行')
  return '返回的结果'
}
let MG = generatorExample() // 返回的是指针对象
console.log(MG.next()) // 执行,遇到yield停止
console.log(MG.next('可以拿到这个值')) // 再次调用next,往下执行,可以传参
console.log(MG.next()) // 再次调用next,往下执行,返回true

再次调用 next 方法会从上一次停止时的 yield 处开始,直到最后

yield 语句返回结果通常为 undefined,当调用 next 方法时传参内容会作为启动时 yield 语句的返回值

补充:

let obj = {username:'kobe', age: 39}
obj[Symbol.iterator] = function* myTest(){
  yield 1
  yield 2
  yield 3
}
for(let i of obj){
  console.log(i)
}

例如:

// 要比使用Promise更好
function getNews(url){
  $.get(url, function(data){ // 前面引入了jQuery
    console.log(data)
    let url = 'http://localhost:3000' + data.commentsUrl
    SX.next(url) // 放在这里也可以往下移,并且这里参数传输更方便
  })
}
function* sendXml(){
  let url = yield getNews('http://localhost:3000/news?id=3') // 如果这里出错,后面评论也不会再执行了
  yield getNews(url)
}
// 获取遍历器对象
let SX = sendXml()
SX.next()

9、模块

导入

语法:

/* 默认导出的导入 */
import defaultExport from "module-name";

/* 导入全部,设置别名 */
import * as name from "module-name";

/* 导入对象中单个或多个 */
import { export } from "module-name";
import { export as alias } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";

/* 导入默认导出的和对象的 */
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";

/* 可以导入其他文件 */
import "module-name";

/* 动态导入 */
var promise = import("module-name");//这是一个处于第三阶段的提案。

导出

在创建 JavaScript 模块时,export 语句用于从模块中导出实时绑定的函数、对象或原始值,以便其他程序可以通过 import 语句使用它们

// 导出单个特性
export let name1, name2, …, nameN; // 也可以用 var, const
export let name1 = …, name2 = …, …, nameN;
export function FunctionName(){...}
export class ClassName {...}

// 导出列表
export { name1, name2, …, nameN };

// 重命名导出
export { variable1 as name1, variable2 as name2, …, nameN };

// 解构导出并重命名
export const { name1, name2: bar } = o;

// 默认导出
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };

// 导出模块合集
export * from …; // does not set the default export
export * as name1 from …; // Draft ECMAScript® 2O21
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;

10、其他

五、ES 2016

新增了两个新特性

1、指数操作

指数运算符(幂): **

console.log(3 ** 3)  // 27 (3 的 3 次方)

2、数组新方法

Array.prototype.includes(value): 判断数组中是否包含指定 value

let arr = [1,2,3,'abc']
console.log(arr.includes('a'))  // false

六、ES 2017

1、async/await 函数

概念:真正意义上去解决异步回调的问题,同步流程表达异步操作

本质:Generator 的语法

语法:

async function foo(){
  await 异步操作;
  await 异步操作;
}

特点:

// async基本使用
async function foo(){
  return new Promise(resolve => {
    // setTimeout(function(){
    //   resolve()
    // }, 2000)
    // 可以写成下方这种
    setTimeout(resolve, 2000)
  })
}
async function test(){
  console.log('开始执行', new Date().toTimeString())
  await foo()
  console.log('执行完毕', new Date().toTimeString())
}
test()

// async 里 await 返回值
function test2(){
  return 'xxx'
}
async function asyncPrint(){
  /* let result = await test2()
  console.log(result) // 普通函数没有返回值
  */
  /*
  let result = await Promise.resolve()
  console.log(result) // Promise对象成功状态返回undefined
  */
  let result = await Promise.resolve('promise')
  console.log(result) // Promise对象成功状态传参返回参数 promise
  result = await Promise.reject('失败了……')
  console.log(result) // 失败状态,返回出错,且能将参数返回 Uncaught (in promise) 失败了……
}
asyncPrint()

获取新闻内容案例

// async 比 generator 又更简单
async function getNews(url){
  return new Promise((resolve, reject) => {
    $.ajax({ // 前面已经引入jQuery
      method: 'GET',
      url,  // 这是ES6中简写
      /* success: function(data){
        resolve()
      },
      error: function(error){
        reject() 
      }*/
      // 简写
      success: data => resolve(data),
      error: error => reject(error)
    })
  })
}
async function sendXml(){
  let result = await getNews('http://localhost:3000/news?id=7')
  console.log(result) // {id: "7", title: "news title1...", content: "news content1...", commentsUrl: "/comments?newsId=7"}
  result = await getNews('http://localhost:3000' + result.commentsUrl)
  console.log(result)
}
sendXml()

改进一下,由于这种写法 error 并不会显示错误信息

<script src="./jquery-3.1.0.min.js"></script>
<script>
  async function getNews(url){
    return new Promise((resolve, reject) => {
      $.ajax({
        method: 'GET',
        url,
        success: data => resolve(data),
        // error: error => reject(error)
        error: error => resolve(false) // 不用reject,而是返回false
      })
    })
  }
  async function sendXml(){
    let result = await getNews('http://localhost:30010/news?id=7')
    console.log(result) // {id: "7", title: "news title1...", content: "news content1...", commentsUrl: "/comments?newsId=7"}
    if(!result){ // 出错就弹窗
      alert('暂时没有新闻……')
    }
    result = await getNews('http://localhost:3000' + result.commentsUrl)
    console.log(result)
  }
  sendXml()
</script>

给个模板

const onError = reason => {
  handleError(reason)  // 具体操作
  throw reason
}
async function fetchSome() {
  showLoading()  // Loading 处理
  const response = await axios.get('/xxx')  // 成功的
    .catch(onError)  // 失败的
    .finally(hideLoading)  // 处理 Loading
  /* doSomething */
  console.log(response)
}
fetchSome()

2、对象

3、字符串填充

padStart()padEnd() 分别可以在字符串位数不足的时候向头部和尾部进行填充内容

第一个参数是总位数,第二个参数为位数不足时填充的内容

例如:

let str = 1
str.padStart(2, '0')  // 01
str.padEnd(4, '*')  // 1***

4、参数可以有多余的逗号

function f(p1, p2, p3,) {}

5、其他

后续好用的新特性

问号

可选链?.

let a = user && user.name && user.name.firstName
let a = user?.name?.firstName

双问号??

let a = a || b  // 有个问题,0 是 false
let a = a ?? b  // undefined、null

两个结合使用

let a = user?.name ?? 'default' // 如果 user.name 存在就取 user.name,否则取 'default'

由于现在开发都会用到 Babel 转译代码,所以可以放心使用这些新特性