ECMAScript新特性(es6 )(1-2-1)

首先要区分语言和平台之间的关系,语言本身是指ECMAScript,平台是指浏览器或者node,在平时我们浏览器开发里js就是ECMAScript。
浏览器的组成部分
image.png
node.js的组成部分
image.png
在ES5.1之后版本我们统称为ES6
image.png
主要说明如下:
**• let 和 const
• Arrow functions
• Classes(类)
• Generators
• Map 和 Set
• for ... of 循环
• Symbol
• Modules
• Template literals(模板字⾯量)
• Default parameters(默认参数)
• Enhanced object literals(对象字⾯量增强)
• Destructuring assignments(解构分配)
• Spread operator(展开操作符)
• Proxy和Reflect**

这些新增API和对象主要是进行:
**• 对原有语法进⾏增强
• 解决原有语法上的⼀些问题或者缺陷
• 全新的对象、全新的⽅法、全新的功能
• 全新的数据类型和数据结构**

let和const

let 声明的成员只会在所声明的块中生效
if (true) {
let foo = 'zce'
console.log(foo)
}

let经典应用场景

var elements = [{}, {}, {}]
for (let i = 0; i < elements.length; i++) {
  elements[i].onclick = function () {
    console.log(i)
  }
}

因为产生自己的循环体的块级作用域,所以可以代替闭包。

我们看下面例子来解析一下运行
for (let i = 0; i < 3; i++) {
let i = 'foo'
console.log(i)
}

如下:
let i = 0

if (i < 3) {
let i = 'foo'
console.log(i)
}

i++

if (i < 3) {
let i = 'foo'
console.log(i)
}

i++

if (i < 3) {
let i = 'foo'
console.log(i)
}

i++
其实是这样的,所以我们也就可以解释在这其中for 循环会产生两层作用域。
let 还修复了变量声明提升现象。
const 整体和let类似,

  • 恒量声明过后不允许重新赋值
  • 恒量要求声明同时赋值

const name
name = 'zce' //会报错

  • 恒量只是要求内层指向不允许被修改
  • 对于对象数据成员的修改是没有问题的

Arrow functions

箭头函数的 简易写法,可以去看下阮一峰老师的es6,这里主要标明几个箭头函数的特征。

  • 箭头函数绑定父作用域的this
  • 箭头函数this不可修改
  • 箭头函数不能作为构造函数,不能使用new
  • 箭头函数没有arguments, caller, callee
  • 箭头函数没有原型属性//prototype 为undefined
  • 箭头函数在ES6 class中声明的方法为实例方法,不是原型方法
class Super{
    sayName =()=>{
        //do some thing here
    }
}

var a = new Super()
var b = new Super()
a.sayName === b.sayName //false

class

es6里实现class关键字,来代替es5里的构造函数的方式生成类。
es6的extends关键词来进行继承,底层是使用es5寄生组合式继承的封装。
使用extends来继承,constructor中使用this必须要先实例化父类,使用super关键词实例化,其他方法里也可通过super调用父类成员。

class里的constructor相当于es5的实例化,然后之外定义的属性函数,相当于定义在原型上(不包含箭头函数)

static关键词 静态属性,可以不实例化类可直接访问,里面的this指向类本身,不是实例化后的对象。

// extends 继承

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}

class Student extends Person {
  constructor (name, number) {
    super(name) // 父类构造函数
    this.number = number
  }

  hello () {
    super.say() // 调用父类成员
    console.log(`my school number is ${this.number}`)
  }
}

const s = new Student('jack', '100')
s.hello()

Generators

Generators函数调用会返回一个iterator对象,可以继续调用next()方法来依次返回Generators函数yield关键词之后的表达式结果,结果是个对象{value:'结果值',done:布尔值(是否结束true/false)},yield可以暂停当前函数,然后再通过外面iterator调用next方法继续往下走。

代码如下:

function * createIdMaker () {
  let id = 1
  while (true) {
    yield id++
  }
}

const idMaker = createIdMaker()

console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)

Map和Set

Map

Map对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。构造函数Map可以接受一个数组作为参数。

map和object区别

  • 一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值。
  • Map中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  • Map支持for of 循环,obj需要自己添加Symbol.iterator

常用api

  • set(key, val): 向Map中添加新元素
  • get(key): 通过键值查找特定的数值并返回
  • has(key): 判断Map对象中是否有Key所对应的值,有返回true,否则返回false
  • delete(key): 通过键值从Map中移除对应的数据
  • clear(): 将这个Map中的所有元素删除
  • size:返回Map对象中所包含的键值对个数

遍历方法

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

map支持 for of 遍历,内部的Symbol.iterator是entries

Set

Set对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set 本身是一个构造函数,用来生成Set 数据结构。Set函数可以接受一个数组(或者具有 iterable 接口(迭代器)的其他数据结构)作为参数,用来初始化(带有Symbol.iterator)。
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

  • +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复
  • undefined 与 undefined 是恒等的,所以不重复
  • NaN 与 NaN 是不恒等的,但是在 Set 中认为NaN与NaN相等,所有只能存在一个,不重复。

set常用方法

  • add(value):添加某个值,返回 Set 结构本身(可以链式调用)。
  • delete(value):删除某个值,删除成功返回true,否则返回false
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。
  • size:返回Set实例的成员总数。

遍历方法

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回键值对的遍历器。
  • forEach():使用回调函数遍历每个成员。

由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

for...of循环

for of 循环本意是统一 js中循环的标准,我们查看可以使用for of循环的对象,
image.png
解释:里面都有一个Symbol.iterator方法,这个of循环首先调用这个方法,它返回一个对象(然后就是循环对象),这个对象内部有next方法,方法返回一个对象{value:'值',done:布尔值//是否结束},每次循环就是调用这个next方法,等done为true的时候循环结束。
这是不是很像Generator函数,所以说Generator调用的结果也是可以用of循环的,比如map和set结构都有这个Symbol.iterator方法,,所以它也是可以循环的,但是对象是没有这个方法的,所以需要我们手动添加一下

const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],
  [Symbol.iterator]: function * () {
    const all = [...this.life, ...this.learn, ...this.work]
    for (const item of all) {
      yield item
    }
  }
}

for (const item of todos) {
  console.log(item)
}

因为我们看到 它和Generator函数很像,所以我们用它辅助实现一下,
给[Symbol.iterator]定义一个生成器函数,返回一个可迭代对象,然后我们使用 yield 函数来辅助实现,因为它返回的和我们要的那个对象是一样的。

Symbol

symbol 是一种基本数据类型 (primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。

每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。更进一步的解析见—— glossary entry for Symbol

使用场景:扩展对象,属性名冲突问题

一般使用第三方库扩展时,我们预防覆盖方法名,可以使用Symbol对象做key,因为两个 Symbol 永远不会相等。
由于无法创建出一样的 Symbol 值, 所以我们可以用它创建「私有」成员

更多的方法可以查看mdn
Symbol

Modules

模块 es6中的import语句为变量,函数,和类创建的是只读绑定,标识符只有在导出的模块中可以修改,即使是导入绑定的模块也无法更改绑定的值。
一些简单的语法可以在mdn上查看
这里贴一个以前的异步模块

var me =(function hello(name){
    function greeting(){
        console.log('hello'+name)
    }
    return {//public API
        greeting:greeting
    }
})('kyle')
console.log(me.greeting())

Template literals(模板字⾯量)

// 反引号包裹
const str = hello es2015, this is a string
允许换行,可以通过${}插入表达式,返回得结果会输出到对应位置
xxx${...}xxx

**带标签的模板字符串,模板字符串的标签就是一个特殊的函数,
使用这个标签就是调用这个函数**

const name = 'tom'
const gender = false

function myTagFunc (strings, name, gender) {
  // console.log(strings, name, gender)
  // return '123'
  const sex = gender ? 'man' : 'woman'
  return strings[0] + name + strings[1] + sex + strings[2]
}

const result = myTagFunc`hey, ${name} is a ${gender}.`

console.log(result)
hey, tom is a woman.

就是把字符串根据变量分割成一个数组,然后后续就是字符串中的变量依次传递.

Default parameters(默认参数)

在es5中给函数的默认参数只能

function foo (enable) {
  // 短路运算很多情况下是不适合判断默认参数的,例如 0 '' false null
  // enable = enable || true
  enable = enable === undefined ? true : enable
  console.log('foo invoked - enable: ')
  console.log(enable)
}

//这样去赋值现在es6中我们可以

function foo (enable = true) {
  console.log('foo invoked - enable: ')
  console.log(enable)
}

//包括解构的时候可以 const {sex='默认'}=props;
甚至函数可以
function foo({sex='1'}={}){
    console.log(sex)
}

Enhanced object literals(对象字⾯量增强)

这一篇比较简单看代码把

// 对象字面量

const bar = '345'

const obj = {
  foo: 123,
  // bar: bar
  // 属性名与变量名相同,可以省略 : bar
  bar,
  // method1: function () {
  //   console.log('method111')
  // }
  // 方法可以省略 : function
  method1 () {
    console.log('method111')
    // 这种方法就是普通的函数,同样影响 this 指向。
    console.log(this)
  },
  // Math.random(): 123 // 不允许
  // 通过 [] 让表达式的结果作为属性名
  [bar]: 123
}

// obj[Math.random()] = 123

console.log(obj)
obj.method1()
// Object.assign 方法

// const source1 = {
//   a: 123,
//   b: 123
// }

// const source2 = {
//   b: 789,
//   d: 789
// }

// const target = {
//   a: 456,
//   c: 456
// }

// const result = Object.assign(target, source1, source2)

// console.log(target)
// console.log(result === target)

// 应用场景

function func (obj) {
  // obj.name = 'func obj'
  // console.log(obj)

  const funcObj = Object.assign({}, obj)
  funcObj.name = 'func obj'
  console.log(funcObj)
}

const obj = { name: 'global obj' }

func(obj)
console.log(obj)
// Object.is

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

Destructuring assignments(解构分配)和Spread operator(展开操作符)

数组的解构

const arr = [100, 200, 300]
const [foo, bar, baz] = arr;
可以直接这样去解构,对应下表0,1,2
如果不要前面的可以
[,,baz]
这样就是只取2位置得了

经典的排序算法 交换位置可以:
const [arr[1],arr[0]]=[arr[0],arr[1]]
然后展开操作符
const [foo, ...rest] = arr;
取第一个 参数之后所有的返回一个数组。
展开操作符只能放在最后。

取出来的值也可以给定默认值
例如
const [foo, bar, baz = 123, more = 'default value'] = arr;

对象的解构:
const obj = { name: 'zce', age: 18 }
const { name } = obj;
相当于{name:name}
可以这样去解构,包括对象当参数传递时也可以这样解构
然后也可以给定默认值,前面有例子。
这个解构变量名字的声明和正常声明相反,是在右边,如下

const { name: objName = 'jack' } = obj;
name是从obj解构出来的属性名,objName是我们新的变量名来接收这个值,并且给了它一个默认值。

然后就是展开操作

const {name,...args}=obj;
image.png
不要在乎这些报错
这个解构展开就是说,除了解构指明的属性名之外,剩余的属性组成一个新对象,用剩余参数名称接受。

Proxy和Reflect

代理对象proxy和defineProperty的对比
image.png

优势:

  1. Proxy 可以监视读写以外的操作
  2. Proxy 可以很方便的监视数组操作
  3. Proxy 不需要侵入对象

一个简单的例子

const person2 = {
  name: 'zce',
  age: 20
}

const personProxy = new Proxy(person2, {
  get (target, property) {
    console.log('get', property)
    return target[property]
  },
  set (target, property, value) {
    console.log('set', property, value)
    target[property] = value
  }
})

personProxy.name = 'jack'

console.log(personProxy.name)

更多的api的使用方式可以在mdn中查看
Proxy的使用方式

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

它提供了对对象的统一操作,之前对象的操作不统一,
const obj = {
name: 'zce',
age: 18
}

console.log('name' in obj)
console.log(delete obj['age'])
console.log(Object.keys(obj))

console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))

具体提供了哪些方法,也可以直接去mdn中查看
Reflect

该内容借鉴于 拉钩大前端训练营

阅读 152

推荐阅读

世界核平

0 人关注
28 篇文章
专栏主页