module4-05-ES6新特性

ES6新特性

一、ECMAScript的发展过程与简介

  • ES6是2015年的新标准,要搞清楚ES6是泛指还是特指。特指的话是ES2015,泛指的话是指ES2015之后的版本

1.1 ES6概述

  • 解决原有语法上的一些问题或者缺陷(作用域问题)

  • 对原有语法进行增强(解构、展开、模板字符串)

  • 全新的对象、全新的方法、全新的功能(Promise、proxy、Object.asgn...)

  • 全新的数据类型和数据结构(Symbol、Set、Map)

1.2 学习ES2015准备工作

  • 准备一个支持es6的环境,如chrome最新版本或者nodejs

  • 可以在VScode软件中使用Chrome调试

    • 需要安装的插件

      • ① Debugger for Chrome

      • ② Live Server

      • ③ Browser Preview

    • 查看右下角port

    • 点击左上角运行-启动调试-chrome

    • 然后修改配置文件的端口为右下角port一致,在网页里面选择对应文件即可

二、ECMAScript2015 的新特性

2.1 let、const、块级作用域

  • 举一个例子,常见的for循环循环变量污染问题

var arr = []
for (var i = 0; i < 5; i++) {
   arr[i] = function () {
       console.log(i)
  }
}
arr[0]() // 5
arr[1]() // 5
  • 因为在ES6之前只有函数作用域和全局作用域,所以循环定义的i会暴露到全局也就是window里面,arr存的函数输出自然就是循环完之后的i值了

(1)块级作用域

  • 讲let与const之前不得不介绍块级作用域

  • 如:if语句中使用let跟var变量是不一样的

  • 可以把一个{}里面看成是块级作用域

  • 继续用for循环来举例

    • const arr = []
      for (let i = 0; i < 5; i++) {
         arr[i] = function () {
             console.log(i)
        }
      }
      arr[0]() // 0
      arr[1]() // 1
  • 说明在函数里面是有两层作用域的,所以可以把for循环看为

    • {
      let temp = 0,
      end = 5
      if (temp < end) {
      let i = temp
      arr[i] = function () {
      console.log(i)
      }
      temp++
      }
      if (temp < end) {
      let i = temp
      arr[i] = function () {
      console.log(i)
      }
      temp++
      }
      //...
      }
      arr[0]()
      arr[1]()

(2)let与const

  • ① 他们不会有变量提升

  • 后面声明了但是前面引用会有暂时性死区

  • const声明的变量指向的地址不可变,复杂数据类型可以继续修改属性,相当于let的只读效果

  • 最佳用法

    • 不用var,朱永const,配合let

2.2 数组与对象的解构

(1)数组的解构

  • ① 下面foo1写在赋值符左端的数组的第一个位置,代表把赋值符右边数组第一个下标的值传给foo1

  • ...语法是代表把剩余的参数全部放进一个数组中,如果后面没有参数了会返回一个空数组

  • ③ foo2中设置了默认值,即右边没有匹配的值才会使得foo2 = 1000

    • const arr = [100, 200, 300, 400]
      const [foo1, foo2 = 1000, ...rest]
      console.log(foo1) // [100]
      console.log(foo2) // [200]
      console.log(rest) // [300, 400]
    •  

(2)对象的解构

  • 使用方法跟数组解构类似,不同的是设置变量的命名问题

  • 设置变量命名需要与原函数的key值相等,但是会产生命名冲突(这个名字原先被声明过了)

    • 可以使用 : 符号获取这个符号左边的值然后声明 : 右边的变量,设置默认值如果有:则在右边用=来设置

    • const obj = {
         name: 'zs'
      }
      const name = 'ls'
      const { name: newName = 'ww' } = obj
      console.log(newName) // 'zs'
    •  

2.3 模板字符串、模板字符串标签函数

(1)模板字符串

  • 相对于普通的字符串模板字符串使用的是``来定义

  • 在这里面可以让字符串换行

  • 原本的字符串插值要用几个字符串拼接在一起,而模板字符串可以用插值表达式${},在这里面可以放变量,表达式,甚至调用函数

  • ex:

const name = 'foo'
const str = `hello, ${name}, ${1 + 1}`
// 'hello, foo, 2

(2)模板字符串标签函数

  • 模板字符串标签函数可以看成是用``代替函数调用符(),并传入参数

  • ex:

const name = 'zs'
const gender = true
function foo (strings, name, sex) {
   sex = sex ? 'man' : 'woman' // 可以在里面进行操作
   console.log(strings) // ['hi, ', ', ', '']
   console.log(name) // 'zs'
   console.log(sex) // ’man‘
}
foo`hi, ${name}, ${gender}`
  • 上面的例子表示模板字符串会被${}分割并组成一个数组,如果${}在开头或者结尾,则默认会多出一个空字符串 ‘’

  • 然后第二个参数开始表示插值表达式传入的值${},设置形参的时候不用名字相同

2.4 字符串扩展方法

  • includes():表示字符串是否包含某字符串

  • startsWith():表示字符串开头是否是某字符串

  • endWith():表示字符串结尾是否是某字符串

2.5 参数默认值

  • 我们平时为函数形参赋予默认值的时候是使用value = value || true其实是不严谨,比如如果传入一个false也会进行默认值赋值操作

  • 正确的做法是判断是否严格等于undefinedvalue = value === undefined ? true : value

  • 而ES6可以在定义形参的时候就设置好默认值

    • 不过设置默认值的形参建议放在参数列表的后面

function foo (value1 = 'value1', value2 = 'value2') {
   console.log(value1, value2)
}
foo('haha') // 'haha' 'value2'

2.6 ...操作符

  • 主要有两种作用,作剩余操作符展开操作符

(1)剩余操作符

  • 用作于获取的时候使用,使用特点是必须要放在最后一位,

  • ex:获取函数的参数

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

(2)展开操作符

  • 用作于传递数组的每一项作为参数,特点也是放在最后,可以传入伪数组

  • ex:传入函数参数并将arguments转换为数组

var arr = [1, 2, 3]
function foo () {
    console.log([...arguments])
}
foo(...arr)
// [1, 2, 3]

2.7 箭头函数

  • 箭头函数只能作为匿名函数,可以用函数表达式声明,建议用在传递的参数是函数上面

(1)箭头函数的语法

// 普通函数
const foo1 = function (a, b) {
    return false
}
// 箭头函数
// ① 当代码段只有一条表达式的时候
const foo2 = (a, b) => return false
// ② 当参数只有一个的时候
const foo2 = a => return false
const foo2 = (a, b) => {
    console.log(a, b)
    return false
}

(2)this指向

  • 不会指向调用者,会指向定义时候所在的this

  • 若想使用4种可以改变this的函数调用方法的话(构造函数调用、对象方法调用、事件绑定方法、定时器函数),建议先用普通函数包裹一个子调用的箭头函数,如下

var age = 20
const obj = {
    age: 18,
    foo1: () => {
        // 这里面的this指向的是foo1所在的this,因为obj没有块级作用域,所以指向window,而window.age = 20
        console.log(this.age)
    },
    foo2: function () {
        // 这里面的this被普通匿名函数给锁住了,在调用的时候只想调用者也就是obj,而obj.age = 18
        ;(() => {
            console.log(this.age)
        })();
    }
}
obj.foo1() // 20
obj.foo2() // 18

2.8 对象字面量加强与计算属性名

(1)对象字面量加强

  • 可以理解为ES5对象写法的语法糖

  • 属性值与方法的语法糖:

    • 注意:下面的sayHi实质是普通函数的简写,不是箭头函数

    • let name = 'zs'
      const obj = {
          name,
          sayHi () {
          	console.log(this.name)
          }
      }
      obj // { name: 'zs', sayHi: f }

(2)计算属性名

  • 在定义对象属性值或者调用的时候(在ES6之前只能调用使用)可以使用 [] 包裹的里面用表达式计算出属性名

    • 如果是全局变量的函数,需要用window[]来计算变量名调用

let fnName = 'foo'
const obj = {
    [fnName] () {
        console.log(`我的函数名叫做${arguments.callee.name}`)
    }
}
// obj[fnName]也可以是ES6之前就可以实现的
obj.foo()
function foo () {
    console.log('我是foo函数调用的')
}
// [fnName]()这种方法是错误的
window[fnName]()

2.9 Object新增方法

  • 接下来介绍一下Object.assign()Object.is()

(1)Object.assgin()

  • 这个是用于对象的浅拷贝的,下面我会介绍几种应用场景

  • 语法:

    • const result Object.assign(obj1, obj2)
      result === obj1 // true
      // obj2的值会覆盖到obj1, 有同名字的key就会
      // 即使不使用返回值, obj1也已经发生改变了
  • 当传入参数是引用类型而且不想函数内部引用会修改函数外部的值

    • 但是如果里面的值还有引用类型的话,需要深拷贝才可而已

const obj = {
    name: 'zs',
    oobj: {
    aaa: 1
}
}
function foo (obj) {
    const newObj = Object.assign({}, obj)
    newObj.name = 'ls'
   	newObj.oobj.aaa = 2
    console.log(obj)
    console.log(newObj)
}
foo(obj)
// 下面oobj.aaa都改成了2
// obj --- { name: 'zs', oobj: { aaa: 2 } }
// newObj --- { name: 'ls', oobj: { aaa: 2 } }
  • ② 构造函数传入参数的时候,将参数的值传入到this中

function Student (options) {
    Object.assign(this, options)
}

(2)Object.is()

  • 这个用于更精确的全等===,但还是推荐是用===

  • ① 比如 -0 与 +0 使用全等符号是相等的,但是我们期望它不等

    • console.log(-0 === +0) // true
      console.log(Object.is(-1, +0)) // false
  • ② NaN与NaN是不全等的,也不等于,在这个方法里面会返回true

    • console.log(NaN === NaN, NaN == NaN) // false false
      console.log(Object.is(NaN, NaN))

2.10 class类、静态方法、类的继承

(1)class类

  • 可以当成是构造函数的语法糖,在一个class里面设置完

  • 语法:

    • constructor相当于定义构造函数的代码段

    • 里面的方法比如sayHi是设置在prototype里面的

class Person {
    constructor (name, age) {
        this.name = name
        this.age = age
    }
    sayHi () {
        console.log(`Hi, my name is ${this.name}`)
    }
}

(2)静态方法

  • 构造函数有静态方法和实例方法,静态方法是构造函数对象调用的。实例方法,是实例对象调用的

  • 语法:

    • 静态方法里面的this指向的是构造函数对象

    • 甚至可以直接new this()调用

class Person {
    constructor (name, age) {
        this.name = name
        this.age = age
    }
    sayHi () {
        console.log(`Hi, my name is ${this.name}`)
    }
    static create (name. age) {
        console.log(this)
        return new Person(name, age)
        // return new this(name, age) 也是正确的
    }
}

(3)类的继承

  • 类似于组合继承,不过有一个super方法可以根据我们的需求赋值

  • 语法:

    • 主要是使用super来继承父类的属性添加到实例对象中,super是一定要调用的

class Person {
    constructor (name, age) {
        this.name
        this.age
    }
    sayHi () {
        console.log(`Hi, my name is ${this.name}`)
    }
}
// 继承
class Student extends Person {
    constructor (name, age, number) {
        super(name, age) // 一定要调用
        this.number = number
    }
    hello () {
        console.log()
    }
}
  • 其中用super继承属性是存在于实例对象中

  • 继承的方法是存在于__proto__.__proto__之中

  • 定义的方法存在于__proto__之中

2.11 Set

  • Set是一种新的数据结构,叫做集合

  • 它可以像数组一样存储变量、赋值。可以用来作数据去重

const s = new Set()
s.add(1).add(2).add(3).add(4)
console.log(s) // Set(4) {1, 2, 3, 4}

Set的属性和方法

s.add() // 添加值
s.forEach() // 与数组的同理
s.size // 相当于length
s.has() // 是否含有某一项
s.delete // 删除某一项
  • 里面有遍历器,可以使用for of遍历

  • 也可以转换成数组运算

    • ① Array.from()

    • ② [...s]

2.12 Map

  • Map是一种新的数据结构,叫做字典

  • 它跟对象的用法非常相像,他的key可以是任意类型的数据而object会把数据都变成string类型再传入到key

Map的属性和方法

const map = new Map()
const a = { a: 1}
map.set(a, 100)
console.log(map) // Map(1) {{a: 1} => 100}
console.log(map.get(a)) // 100
// 方法与Set类似
map.has()
map.delete()
map.clear()
map.forEach()
...

2.13 Symbol

  • 是ES2015的一种新的基本数据类型

  • 可以优秀的解决命名冲突问题,为对象添加私有变量(外部访问不到)

  • 语法:

    • ① Symbol()

    • ② Symbol(参数)

const s = Symbol('123')
const b = Symbol('123')
s === b // false

Symbol的一些用法

  • ① 传入字符串识别是否为同一Symbol(Symbol.for)

    • 传入的如果不是字符串,会转换成字符串再传入

    • 区分是否是for方法,即使传入的字符串相同不都是for方法的话不会返回同一个值

const s1 = Symbol.for('123')
const s2 = Symbol.for(123)
const s3 = Symbol(’123‘)
const
console.log(s1 === s2) // true
console.log(s2 === s3) // false
  • ② 作为对象的私有变量

    • 即使使用JSON转化,也会识别不出来,所以非常隐蔽

    • 可以使用Object.getOwnPropertySymbols()来获取,但是只能获取到symbol值

const obj = {
    foo: 1,
    [Symbol()]: 'symbol'
}
console.log(obj) // {foo: 1}
Object.keys(obj) // ['foo']
JSON.stringify(obj) // {"foo":"1"}
Object.getOwnPropertySymbols(obj) // [Symbol()]
  • ③ 为object类型的toString添加标识

    • 使用到了Symbol.toStringTag

    • 如果传入的value还是object类型则还是[object object]

const obj1 = {}
console.log(obj1.toString()) // [object object]
const obj2 = {[Symbol.toStringTag]: 'hahaha'}
console.log(obj2.toString()) // [object hahaha]

2.14 for of 遍历

  • for if的出现是为了作为遍历所有数据结构的统一方式

  • 在次之前我们遍历的方式有

    • 对象:for in

    • 数组:forEach...

  • 这些方法都有一定的局限性只适用于局部数据

    • 比如forEach不能用break打断

    • for in 不能遍历集合Set

  • for of可以遍历大部分数据类型包括set、map等,且相对于forEach可以设置break打断

  • 但是目前obj还是能适配for of遍历

const s = new Set(['foo1', 'foo2' ,'foo3'])
for (const item of s) {
    console.log(item)
}
// foo1 foo2 foo3
const m = new Map()
m.set('a', 1)
m.set('b', 2)
for (const item of m) {
    console.log(item)
}
// 会变成数组形式来表现Map的对应关系
// ['a', 1] ['b', 2]

 

2.15 ES2015的其它内容

  • 可迭代接口

  • 迭代器模式

  • 生成器

  • Proxy 代理对象

  • Reflect 统一的对象操作API

  • Promise 异步解决方法

  • ES Modules 语言层面的模块化标准

2.16 ES2016概述

  • 主要增加了两个新特性数组的includes,指数运算符

(1)Array.prototype.includes

  • 类似于ES2015中String的includes如果含有则返回true

  • 它比indexOf的优点之一在于可以检测是否含有NaN

const arr = [1, 2, 3, NaN, 5]
console.log(arr.indexOf(NaN)) // -1
console.log(arr.includes(NaN)) // true

(2)**指数运算符

  • 在这之前,指数运算是多个相乘或者Math.pow()实现的

console.log(Math.pow(2, 3)) // 8
console.log(2 ** 3) // 8
原文地址:https://www.cnblogs.com/lezaizhu/p/14236456.html