EcmaScript新特性

一 ECMAScript简介

合格的前端开发都应该对它很熟悉,但是很多前端开发者并没有弄清楚语言和平台之间的关系,以JavaScript为例,很多人就都不清楚平时的代码哪些属于语言层面哪些属于平台层面。尽管照样写代码,但会阻碍进一步发展。所以系统化学习ECMAScript很有必要,对高质量的开发很有帮助。
JavaScript是ECMAScript的扩展语言,ECMAScript只提供了最基本的语法,并不能完成功能的开发。JavaScript实现了ECMAScript的基本语法,并且可以操作DOM和BOM。在Nodejs中则是包含ECMAScript和Node提供的api例如fs、net等等。
从2015年开始后每年一个迭代,其中ES2015和前一个版本间隔了6年,所以包含的新功能最多,ES2015及以后的版本被称为ES6
http://www.ecma-international.org/ecma-262/6.0/
新特性主要分为四类:
1、解决原有语法上的一些问题;
2、对原有语法进行增强;
3、全新的对象、全新的方法、全新的功能;
4、全新的数据类型;
在Nodejs中演示的话,nodemon小工具可以在修改完成代码后自动执行代码,全局安装或者安装在当前项目中都是一样的,执行时输入命令:yarn nodemon index.js

二 ES6

1、Let与块级作用域

作用域:某个成员可以起作用的范围。
在ES6之前,只有全局作用域和函数作用域,在ES6增加了块级作用域。在以前块是没有单独作用域的,这对于复杂的程序很不利。使用let来声明的变量具备了这个特点后就特别适合用于for循环中的计数器变量。

	for(let i=0;i< 3;i++){
	  console.log("out", i)
	  for(let i=0;i< 3;i++){
	    console.log(i)
	  }
	}
	var elements = [{}, {}, {}]
	
	for (var i = 0;i < elements.length; i++){
	  elements[i].onclick = (function (i){
	    return function (){
	      console.log(i)
	    }
	  })(i)
	}
	
	elements[1].onclick()    // 用var声明for循环中的计数变量的问题
	for (let i=0;i<3;i++){
	  let i = 'foo'
	  console.log(i)   // 执行三次
	}
	
	console.log(i)  // i is not defined

const只是在let的基础上,添加了禁止变量改变的限制,但是当const声明的变量为引用类型变量时,该变量的属性还是可以修改的。

使用变量的最佳实践:不用var,主用const,配合let

2、解构赋值(Destructuring)

使用解构赋值的方式声明变量时,变量的声明和赋值必须同时进行。
数组中的成员是按照顺序来解构的,对象中的成员是按照属性名来解构的。

3、模板字符串

支持使用${}语法来拼接字符串和换行。
当模板字符串带标签时,模板字符串会被自动处理成数组,代码示例如下:

	const str = console.log`hello world`  // ['hello world']
	
	const name = 'tom'
	const gender = true
	
	function myTagFunc(strings, ...args) {
	  console.log(strings)
	  console.log(args)
	  return strings[0] + args[0] + strings[1] + args[1] + strings[2]
	}
	
	const result = myTagFunc`hey ${name} cc ${gender} dd`  // ['hey', 'cc', 'dd']
	console.log(result) // 正常输出字符串

4、字符串的扩展方法

includes、startsWith、endsWith

	const message = 'Error: foo is not defined.'
	console.log(
	  message.startsWith('Error'),
	  message.endsWith('.'),
	  message.includes('foo')
	)

5、参数默认值

函数形参后加上等号和变量,带有默认值的形参一定要放在最后。

6、剩余参数

剩余操作符…只能出现在参数的最后一个,并且只能使用一次
剩余操作符…还可以完成数组展开的功能

7、箭头函数

() => {} 极大简化了回调函数的写法
function函数会将this指向调用这个函数的对象,而箭头函数不会改变this的指向(函数外的this是什么,里面的this就是什么)

8、对象字面量增强Enhanced object literals

计算属性名: let = {[Math.random()]: 123}
属性名和值相等的属性,只写变量名进去即可: let = {name}

9、Object.assign

多个源对象中的属性复制到一个目标对象当中: targetObj = Object.assign({}, source1, …)
常用语复制对象和给对象类型的配置项添加默认值功能

	const source1 = {
	  a: 123,
	  b: 123
	}
	
	const source2 = {
	  a: 456,
	  c: 456
	}
	
	const target = {}
	const result = Object.assign(target, source1, source2)
	
	console.log(target)
	console.log(target === result)   //true

10、Object.is

Object.is(NaN, NaN), Object.is(+0, -0)

11、Proxy

(1)Proxy用法

	const person = {
	  name: 'ZXX',
	  age: 20
	}
	
	const personProxy = new Proxy(person, {
	  get(target, property){
	    console.log('get', target, property)
	    return property in target ? target[property] : 'default'
	  },
	  set(target, property, value){
	    console.log('set', target, property, value)
	
	    if(property === 'age'){
	      if(!Number.isInteger(value)){
	        throw new TypeError(`${value} is not an int`)
	      }
	    }
	    target[property] = value
	  }
	})
	
	personProxy.gender = 'male'
	
	console.log(personProxy.name)
	console.log(personProxy.gender)
	
	personProxy.age = '19'  // 会触发报错

(2)Object.defineProperty和Proxy区别

Object.defineProperty只能监视属性的读写,Proxy能够监视到更多对象操作。
Proxy是以非侵入的方式监管了对象的读写

	// Proxy监视对象
	
	const person = {
	  name: 'ZXX',
	  age: 20
	}
	
	const personProxy = new Proxy(person, {
	  deleteProperty(target, property){
	    console.log('delete', property)
	    delete target[property]
	  }
	})
	
	delete personProxy.age
	console.log(personProxy)

	// Proxy监视数组
	
	const list = []
	const listProxy = new Proxy(list, {
	  set(target, property, value){
	    console.log('set', property, value)
	    target[property] = value
	    return true // 表示设置成功
	  }
	})
	
	listProxy.push(100)

12、Reflect 统一的对象操作API

Reflect是一个静态类,不能通过new操作符来新建对象,用法类似于Math对象。Reflect内部封装了一系列对对象的底层操作,成员方法就是Proxy处理对象的默认实现。

	const obj = {
	  foo: '123',
	  bar: '456'
	}
	
	const proxy = new Proxy(obj, {
	  get (target, property) {
	    console.log('watch logic-')
	    return Reflect.get(target, property)
	  }
	})
	
	console.log(proxy.foo)

Reflect存在的意义就是同意提供一套用于操作对象的API

	const obj = {
	  name: 'zce',
	  age: 18
	}
	
	// console.log('name' in obj)
	// console.log(delete obj['age'])
	// console.log(Object.keys(obj))
	
	// Reflect目前有十三个方法
	// Reflect可以替代掉之前的多种写法,尽量使用
	
	console.log(Reflect.has(obj, 'name'))
	console.log(Reflect.deleteProperty(obj, 'age'))
	console.log(Reflect.ownKeys(obj))

13、Promise

promise也是ES6新增的

14、Class类

	class Person {
	  constructor(name, age) {
	    this.name = name
	  }
	
	  say () {
	    console.log(`my name is ${this.name}`)
	  }
	}
	
	const p = new Person('张三')
	p.say()

15、静态方法

static关键词,需要注意的是:静态方法是挂载在类型上的,所以在静态方法当中的this不会指向某个实例,而是指向这个类型。

	class Person {
	  constructor(name) {
	    this.name = name
	  }
	
	  say () {
	    console.log(`my name is ${this.name}`)
	  }
	
	  static create(name){
	    return new Person(name)
	  }
	}
	
	Person.create('张三').say()

16、类的继承

	class Person {
	  constructor(name) {
	    this.name = name
	  }
	
	  say () {
	    console.log(`my name is ${this.name}`)
	  }
	}
	
	class Student extends Person {
	  constructor(name, number){
	    super(name)  // super关键字之前不能出现this关键字
	    this.number = number
	  }
	
	  hello () {
	    super.say()
	    console.log(`my school number is ${this.number}`)
	  }
	}
	
	const s = new Student('jack', '100')
	s.hello()

17、Set数据结构

类似数组,但是不可以存在重复值

	const s = new Set()
	s.add(1).add(2).add(3).add(2)
	
	console.log(s, Array.from(s))
	s.forEach(item => console.log('forEach', item))
	
	for(let i of s){
	  console.log('of', i)
	}
	
	console.log('size', s.size)
	console.log('has', s.has(100))
	console.log('delete', s.delete(3))
	console.log('deleted', s)
	
	s.clear()
	console.log('cleared', s)
	
	const arr = [11, 2, 1, 4, 6, 6, 4, 6]
	const result = new Set(arr)  // 去重成功
	
	console.log('result', result)
	console.log('arrFilt', Array.from(result), '或', [...result])

18、Map数据结构

	const obj = {}
	obj[true] = 'value'
	obj[123] = 'value'
	obj[{a:1}] = 'value'
	
	console.log(Object.keys(obj))   // [ '123', 'true', '[object Object]' ]
	
	const m = new Map()
	const tom = {name: 'tom'}
	const jack = {name: 'jack'}
	m.set(tom, 90)
	m.set(jack, 100)
	
	console.log('m', m)
	m.forEach((value, key) => {
	  console.log('forEach', value, key)
	})
	console.log('get', m.get(tom))
	
	console.log('has', m.has(tom))
	m.delete(tom)
	console.log('deleted', m)
	m.clear()
	console.log('cleared', m)
	
	let oMap = new Map([["a","b"]])
	oMap.get('a')   // b

19、Symbol

Symbol() === Symbol() // false
为对象添加独一无二的属性标识符, 截止到ES2019已经存在7种数据类型,将来还会增加BigInt数据类型用于存储更长的数字。

(1)Symbol数据类型

	//示例1
	const obj = {
	  [Symbol()]: 'cc'
	}
	
	obj[Symbol()] = '1234'
	obj[Symbol()] = 'aab'
	
	console.log(obj)
	//示例2
	const name1 = Symbol()
	const name2 = Symbol()
	
	const person = {
	  [name1]: 'zce',
	  [name2]: 'bb',
	  say(){
	    console.log(this[name1])
	  }
	}
	
	person.say()
	console.log(name2, person[name2])

(2)Symbol 静态方法

// 静态方法1
	const s1 = Symbol.for('foo')
	const s2 = Symbol.for('foo')
	console.log(s1 === s2)  // true
	
	console.log(Symbol.for(true) === Symbol.for('true'))  // 首先会转化为字符串


// 静态方法2
	console.log(Symbol.iterator)
	console.log(Symbol.hasInstance)
	
	const obj = {
	  [Symbol.toStringTag]: 'XObject',
	  foo: 'aa'
	}
	console.log(obj.toString())                       // [object XObject]
	console.log(Object.keys(obj))                     // ['foo']
	console.log(Object.getOwnPropertySymbols(obj))    // [ Symbol(Symbol.toStringTag) ]
	console.log(JSON.stringify(obj))                  // {"foo":"aa"}

20、for-of循环

直接获取元素而不是下标, 可以使用break直接中止循环。同样也适用于函数中的arguments对象、操作dom时的dom元素列表等类似数组的对象,使用方法同数组。可以作为遍历所有数据结构的统一方式。

	// for-of循环
	
	const arr = [100, 200, 300, 400]
	
	for (const item of arr){
	  console.log('arr', item)
	  if(item > 200){
	    break
	  }
	}
	
	const m = new Map()
	m.set('foo', '123')
	m.set('bar', '456')
	for(const item of m){
	  console.log('Map', item)  // 当遍历Map对象时,可以得到数组方式提供的键和值
	}
	
	const obj = {foo:123, bar: 456}
	for(const item of obj){
	  console.log(item)
	}

21、可迭代接口

ES中能够表示有结构的数据类型越来越多,为了统一遍历各种数据类型的数据,ES2015提供了Iterable(可迭代)接口。对象类型的值无法直接进行for-of循环操作的,原因就是对象类型没有默认绑定这个Iterable接口。
console.log( Object.prototype[Symbol.iterator] ) // false

	const arr = ['foo', 'first', 'cc']
	
	let iterator = arr[Symbol.iterator]()
	
	console.log(iterator.next())
	console.log(iterator.next())
	
	for(const item of arr){
	  console.log('arr', item)
	}
	
	for(const item of iterator){  // 继续iterator的next步骤继续运行
	  console.log('iterator', item)
	}

(1)实现可迭代接口

新建的对象中如果有[Symbol.iterator]属性并且符合迭代器功能,便可以实现for-of循环。

	const obj = {                     // 实现了可迭代接口的这个对象被称为iterable
	  [Symbol.iterator]: function(){  // 计算属性名的方式定义到对象当中
	    return {                      // 实现了next方法的这个返回值被称为Iterator
	      next: function (){         // 自定义实现for-of循环逻辑
	        return {                  // 反回了value和done属性的对象,被称为IterationResult
	          value: "zxc",           // 暂时先返回一个固定值,正式使用时不会这样写
	          done: true
	        }
	      }
	    }
	  }
	}
	
	for(const item of obj){     // 此时for-of循环便不再报错
	  console.log('aa')
	}

添加上想要的返回值逻辑之后:

	const obj = {                     // 实现了可迭代接口的这个对象被称为iterable
	  store: ['foo', 'bar', 'baz'],
	  [Symbol.iterator]: function(){  // 计算属性名的方式定义到对象当中
	    let index = 0
	    const self = this
	    return {                      // 实现了next方法的这个返回值被称为Iterator
	      next: function (){         // 自定义实现for-of循环逻辑
	        const result = {                  // 反回了value和done属性的对象,被称为IterationResult
	          value: self.store[index],           // 返回一个想要的结果,逻辑可以自由控制
	          done: index >= self.store.length
	        }
	        index ++
	        return result
	      }
	    }
	  }
	}
	
	for(const item of obj){     // 此时for-of循环便可以正常输入我们想要的结果
	  console.log(item)
	}

22、迭代器模式

对自定义对象添加可迭代接口,让其可以实现了for-of循环,这就是迭代器模式。优势:调用者可以忽略该模式对象的内部结构,减少代码耦合。

	const todos = {
	  life: ['吃饭','睡觉', '打豆豆'],
	  learn: ['语文', '数学', '外语'],
	  work: ['喝茶']  // 代码改动时添加的属性
	}
	
	for (const item of todos.life){
	  console.log(item)
	}
	
	for(const item of todos.learn){
	  console.log(item)
	}
	
	// 因为没有对todo.work属性做遍历,所以代码改动时添加的work属性值就无法得到

代码可以优化为:

	const todos = {
	  life: ['吃饭','睡觉', '打豆豆'],
	  learn: ['语文', '数学', '外语'],
	  work: ['喝茶']// 代码改动时添加的属性
	  each: function(callback){
	    const all = [...this.life, ...this.learn, ...this.work]
	    for(const item of all){
	      callback(item)
	    }
	  }
	}
	
	todos.each(function(item){  // 此处调用在todos对象修改属性时无需改动
	  console.log(item)
	})

用迭代器模式来做优化:(语言层面的方法可以使这一操作的效率更高,更能适用于多种数据结构)

	const todos = {
	  life: ['吃饭','睡觉', '打豆豆'],
	  learn: ['语文', '数学', '外语'],
	  work: ['喝茶'],  // 代码改动时添加的属性
	  [Symbol.iterator]: function () {
	    const all = [...this.life, ...this.learn, ...this.work]
	    let index = 0
	    return {
	      next: function(){
	        return {
	          value: all[index],
	          done: index++ >= all.length   // 一定要将index值做+1防止死循环
	        }
	      }
	    }
	  }
	}
	
	for(let item of todos){
	  console.log(item)
	}

23、生成器函数Generator

在复杂的异步代码中减少回调函数,避免嵌套过深,提供更好的异步编程解决方案。
生成器函数可以帮我们返回生成器对象,惰性执行。

	// Generator函数
	
	function * foo (){
	  console.log('111')
	  yield 100
	  console.log('222')
	  yield 200
	  console.log('333')
	  yield 300
	}
	
	const result = foo()
	console.log(result.next())   // {value: 100, done: false}
	
	for(const item of result){   // 继续result的步骤继续执行
	  console.log(item)
	}

模拟实现一个发号器:

	function * createIdMaker () {
	  let id = 1
	  while (true) {
	    yield id++
	  }
	}
	
	let oIdMaker = createIdMaker()
	console.log(oIdMaker.next().value)
	console.log(oIdMaker.next().value)
	console.log(oIdMaker.next().value)
	console.log(oIdMaker.next().value)

使用Generator函数来实现自定义对象的迭代器会更方便

	const todos = {
	  life: ['吃饭','睡觉', '打豆豆'],
	  learn: ['语文', '数学', '外语'],
	  work: ['喝茶'],  // 代码改动时添加的属性
	  [Symbol.iterator]: function * () {     // 使用generator函数做优化
	    const all = [...this.life, ...this.learn, ...this.work]
	    for (const item of all){
	      yield item
	    }
	  }
	}
	
	for(let item of todos){
	  console.log(item)
	}

24、ES Modules

语言层面的模块化标准


25、ES2016 概述

ECMAScript 2016,发布于2016年6月,仅包含两个小功能:
1、数组实例对象的inclueds方法,相较于之前的indexOf实现方法,更方便并且可以判断出NaN。
2、多了一个指数运算符“**”,例如2的10次方 console.log(2 ** 10)。对于数学幂运算是个很好的方式。


26、ES2017 概述

ECMAScript 2017,发布于2017年6月,包含以下小功能: 1、 Object.values() 返回对象中所有值组成的数组。2、Object.entries() 返回对象中由健值对组成的小数组而组成的数组,方便配合转化Map类型的对象。3、Object.getOwnPropertyDescriptors()。4、字符串填充方法 String.prototype.padStart / String.prototype.padEnd。5、允许函数最后一个参数后存在尾逗号,很小的变化。6、Async/Await函数成为了标准化函数,本质就是promise的语法糖,相当于then

	const obj = {
	  foo: 'value1',
	  bar: 'value2'
	}
	// Object.values
	console.log(Object.values(obj))
	// Object.entries
	console.log(Object.entries(obj))
	for(const [key, value] of Object.entries(obj)){
	  console.log(key, value)
	}
	console.log(new Map(Object.entries(obj)))
	
	// Object.getOwnPropertyDescriptors()
	
	const p1 = {
	  firstName: "Lei",
	  lastName: 'Wang',
	  get fullName(){
	    return this.firstName + " " + this.lastName
	  }
	}
	
	console.log(p1.fullName)
	const p2 = Object.assign({}, p1) // p1当中的fullName方法被当成了普通属性来复制了
	p2.firstName = 'zxc' // 只能修改firstName属性,fullName不会变
	console.log(p2)  // { firstName: 'zxc', lastName: 'Wang', fullName: 'Lei Wang' }
	
	const descriptors = Object.getOwnPropertyDescriptors(p1)
	const p3 = Object.defineProperties({}, descriptors)  // 如此复制对象可以将get,set属性一并复制
	p3.firstName = 'zce'
	console.log(p3.fullName) // 正常输出 zce Wang
	
	// String.prototype.padStart / String.prototype.padEnd  字符串填充方法
	
	const books = {
	  html: 5,
	  css: 16,
	  javascript: 128
	}
	
	for (const [name, count] of Object.entries(books)){
	  console.log(name, count)
	}
	for (const [name, count] of Object.entries(books)){
	  console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
	}

声明:以上内容均为学习笔记,如涉及侵权,请联系我删帖。如需转载,请注明文章出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不甜的糖果

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值