ES6新特性

本文介绍了ECMAScript的发展过程,重点讲解了ES6(2015)的关键新特性,包括let和const、箭头函数、Class、Promise、Generator、模板字面量、默认参数、解构分配等,旨在帮助读者更好地理解和掌握JavaScript的新特性。
摘要由CSDN通过智能技术生成

一、ECMAScript和JavaScript

​ 实际上JavaScript是ECMAScript的扩展语言,ECMAScript只是提供了最基本的语法(约定了代码该如何编写,例如怎样定义变量和函数,如何实现循环语句等)。JavaScript实现了ECMAScript标准,并且在这个基础上进行了扩展,使得我们可以在浏览器中操作DOM和BOM,在Node环境中进行读写文件等操作。

在浏览器环境中,JavaScript = ECMAScript + Web APIs【BOM/DOM】

在Node环境中,JavaScript = ECMAScript + Node APIs【fs/net/etc.】

二、ECMAScript 的发展过程

ECMAScript 的发展过程

三、ECMAScript 2015 的新特性

对原有语法进⾏增强,更便捷,更易用(例如解构,展开、参数默认值、模板字符串等等)

解决原有语法上的⼀些问题或者缺陷(例如let、const块级作用域)

全新的对象、全新的⽅法、全新的功能(例如promise、proxy、Object.assign方法等等)

全新的数据类型和数据结构(例如symbol、map、set等等)

1、let和const

1、let的使用和注意事项

<1>let 的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

{
  let a = 10;
  var b = 1;
}

console.log(a) // ReferenceError: a is not defined.
console.log(b) // 1

<2>var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined; let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

<3>在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)

if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

<4>let不允许在相同作用域内,重复声明同一个变量。

2、const 的使用和注意事项

<1>const声明一个只读的常量。一旦声明,常量的值就不能改变,声明时必须立即初始化,不能留到以后赋值。

ps:这里的不能修改是指存储的内存地址,修改常量中的属性成员是可以的。

const foo;
// SyntaxError: Missing initializer in const declaration

<2>作用域与let命令相同:只在声明所在的块级作用域内有效。

<3>const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

2、箭头函数

箭头函数 Arrow functions

箭头函数与 this(箭头函数中没有this的机制, 所以箭头函数不会改变this的指向)

const person = {
	name: 'zhangsan',
	// sayHi: function() {
	// 	console.log(`hi, my name is ${this.name}`)
	// }
	sayHi: () => { console.log(`hi, my name is ${this.name}`) },
	sayHiAsync: function() {
		// const _this = this
		// setTimeout(() => {
		// 	console.log(_this.name)
		// }, 1000)
		// 在工作中用到_this = this 的情况,都可以用箭头函数去避免
		setTimeout(() => {
			console.log(this.name)
		}, 1000)
	}
}
// person.sayHi()	// hi, my name is zhangsan
person.sayHi()	// hi, my name is undefined
person.sayHiAsync()	// zhangsan

3、Class(类)

// 创建类
class Person {
    // 构造函数
    constructor(name) {
    	this.name = name
    }
    // 实例方法
    say() {
    	console.log(`my name is ${this.name}`)
    }
}
const p = new Person('tom')
p.say()			// my name is tom

静态成员 static

// 创建类
class Person {
    // 构造函数
    constructor(name) {
    	this.name = name
    }
    // 实例方法
    say() {
    	console.log(`my name is ${this.name}`)
    }
    // 静态方法 挂载到类上面的,所以this总是指向类
    static create(name) {
    	return new Person(name)
    }
}
// 静态方法是使用类直接调用的
const p = Person.create('jack')
p.say()		// my name is jack

类的继承 extends

class Person {
	constructor(name) {
		this.name = name
	}
	say() {
		console.log(`my name is ${this.name}`)
	}
}
// 使用extends 实现继承
class Student extends Person {
	constructor(name, number) {
		// super始终指向父类,调用super就是调用父类的构造函数
		super(name)
		this.number = number
	}
	hello () {
		// super始终指向父类,调用super实例方法就是调用父类的实例方法
		super.say(name)
		console.log(`my school number is ${this.number}`)
	}
}
const p = new Student('jack', '11')
p.hello()

4、Promise

Promise ⼀种更优的异步编程解决⽅案,解决了传统异步编程中回调函数嵌套过深的问题。详细介绍参见

5、Generators

避免异步编程中回调嵌套过深,提供更好的异步编程方案

⽣成器 Generator

// 在普通函数的基础上加上 * 号就是生成器函数
function * foo() {
	console.log(111111)
	// yield 暂停执行 和 return 作用类似,将结果返回出去
	yield 1000
	console.log(2222222)
	yield 2000
}
const fn = new foo()
console.log(fn.next())
// 111111
// {value: 1000, done: false}
console.log(fn.next())
// 2222222
// {value: 2000, done: false}
console.log(fn.next())
// {value: undefined, done: true}

⽣成器应⽤ Generator

// 1、实现发号器
function * createMaker() {
	let id = 1
	while(true) {
		yield id++
	}
}
const addID = createMaker()
console.log(addID.next().value)
console.log(addID.next().value)
// 2、实现对象的 iterator 方法
const obj = {
	life: ['吃饭', '睡觉', '打豆豆'],
	work: ['html', 'css', 'javascript'],
	learn: ['语文', '数学', '外语'],
	[Symbol.iterator]: function * () {
		const all = [...this.life, ...this.work, ...this.learn]
		for (const item of all) {
			yield item
		}
	}
}
for (const item of obj) {
	console.log(item)
}

6、Modules

ES Modules 语⾔层⾯的模块化标准,将在模块化开发中介绍

7、模板字面量

模板字符串字⾯量 Template literals(支持换行,支持插值表达式)

const name = 'zhangsan'
const msg = `hello, ${name}! --- ${ 1+ 2 } === ${Math.random()}`
console.log(msg) 		// hello, zhangsan! --- 3 === 0.24367109177674284

模板字符串标签函数 Tagged templates(使用标签函数,必须先定义标签函数,实际上就是使用标签函数对模板字符串进行加工)

// 标签函数中接收到的参数是数组
// const str = console.log`hello world` 		// [ 'hello world' ]
const name = 'zhangsan'
const age = 18
const sex = 1
// 标签函数接收到的第一个参数是所有静态内容组成的数组,后面的参数为插值表达式中的值
function tagFun(str, name, age, sex) {
	// console.log(str, name, age, sex)
	// 对模板字符串进行操作并返回操作后的结果
	return str[0] + name + str[1] + age + str[2] + sex
}
const r = tagFun`hello, ${name}, year is ${age} sex is ${sex}`
// [ 'hello, ', ', year is ', ' sex is ', '' ] zhangsan 18 1
console.log(r) 	// hello, zhangsan, year is 18 sex is 1

字符串的扩展⽅法(includes/startsWith/endsWith)

const str = "Error: foo is not defined."
console.log(str.startsWith('Error'))		// true
console.log(str.endsWith('.'))		// true
console.log(str.includes('is'))		// true

8、默认参数

参数默认值 Default parameters

如果有多个参数,带有默认值的参数要放到最后

// function fun (enable) {
// 	enable = enable === undefined ? true : enable
// 	console.log(enable)
// }
// fun()		// true
// fun(1)		// 1
// 上面等价于下面
// function fun (enable = true) {
// 	console.log(enable)
// }
// fun()		// true
// fun(1)		// 1

// 如果有多个参数,带有默认值的参数要放到最后
function fun (a, enable = true) {
	console.log('a', a, 'enable', enable)
}
fun(1, false)		// a 1 enable false

9、对象字⾯量增强

对象字⾯量增强 Enhanced object literals

const name = "zhangsan"
const obj = {
	// name: name,
	name,			// 如果属性名和变量名一致,可以简写
	age: 18,
	// methods: function() {
	// 	console.log(1111)
	// }
	methods() {		// : function 可以简写为一个()
		console.log(1111)
	},
	// Math.random(): 123,
	// 计算属性名
	[Math.random()]: 123
}
console.log(obj)

对象扩展⽅法 Object.assign

const target = {
	a: 1,
	b: 2
}
const obj1 = {
	c: 3,
	d: 4
}
const obj2 ={
	a: 11,
	e: 5
}
//  Object.assign 接收多个对象,第一个对象是目标对象,后面对象的属性值都会存入到或者覆盖到目标对象中
const r = Object.assign(target, obj1, obj2)
console.log(target)		//{ a: 11, b: 2, c: 3, d: 4, e: 5 }
console.log(target === r)		// true

// 使用Object.assign复制对象,修改对象内的成员不会影响原始数据
const newObj = Object.assign({}, r)
newObj.a = 123
console.log(r)			// { a: 11, b: 2, c: 3, d: 4, e: 5 }
console.log(newObj)		// { a: 123, b: 2, c: 3, d: 4, e: 5 }

对象扩展⽅法 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
)
// 建议还是使用三等去判断

10、解构分配

数组的解构 Destructuring(根据数组的位置进行提取)

const arr = [1, 2, 3, 4]
// 从数组中取出数据给变量赋值
const [a, b, c, d] = arr
// 提取某个位置的数据给变量赋值
const [, , e] = arr
// 剩余参数的提取
const [f, ...g] = arr
// 如果数组中没有值返回undefined,还可以给定默认值
const [, , , , h, i = 'default' ] = arr
console.log(a, b, c, d)		// 1 2 3 4
console.log(e)		// 3
console.log(f)		// 1
console.log(g)		// [2, 3, 4]
console.log(h)		// undefined
console.log(i)		// default

对象的解构 Destructuring(根据对象的属性名进行提取)

对象解构和数组一样,没有匹配到的成员发挥undefined,也可以给定默认值

const obj = { name: 'zhangsan', age: 18 }
// 使用对象的属性名进行提取
const { name } = obj
console.log(name)  	// zhangsan
// 使用重命名方式解决命名冲突
const { name: new_name } = obj
console.log(new_name)  	// zhangsan
// 可以在重命名后继续给定默认值
const { name: new_name2 = 'lisi' } = obj
console.log(new_name2)  	// zhangsan

11、展开操作符

剩余参数 Rest parameters

function fn () {
	// arguments 中 是伪数组
	console.log(arguments)
}
fn(1, 2, 3)		// [Arguments] { '0': 1, '1': 2, '2': 3 }
function fn2 (a, ...arg) {
	// 使用展开操作符 得到的arg 是数组 取代Arguments去接收无限参数
	// 只可以存放在形参的位置,只可以放在最后,并且只可以使用1次
	console.log(arg)
}
fn2(1, 2, 3)	 	// [ 1, 2, 3 ]

展开数组 Spread

const arr = ['a', 'b', 'c']
// console.log(
// 	arr[0],
// 	arr[1],
// 	arr[2]
// )
// 对于参数不固定的时候,这种通过下标的方式便行不通了
// 之前可以使用apply的方式, apply第一个参数是this指向,后面的参数是需要传递的参数
console.log.apply(console, arr)
// 使用展开操作符更便捷
console.log(...arr)

12、for … of 循环

​ 普通的for循环,适合遍历数组;for in 适合遍历键值对;还有有许多函数式的遍历方法,比如数组对象的forEach方法,这些方法都有一定的局限性。ES6引入了for of 循环,用于遍历所有数据结果的统一方式

可迭代接⼝ Iterable(实现iterable接口是for…of的前提)

// 所有被for...of循环遍历的数据都必须实现iterator接口
const arr = ['a', 'b', 'c']
const iterator = arr[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

const arr2 = new Set([1, 2, 3])
const iterator2 = arr2[Symbol.iterator]()
console.log(iterator2.next())
console.log(iterator2.next())
console.log(iterator2.next())
console.log(iterator2.next())

实现可迭代接⼝ Iterable

// 对象内部必须挂载一个iterator方法
// 这个方法需要返回一个对象,这个对象内部需要有next方法
// next方法需要返回一个对象,这个对象有两个属性成员,一个是value,一个done
const obj = {
	name: 'zhagnsan',
	age: 20,
	sex: '男',
	[Symbol.iterator]: function () {
		let index = 0
		const arr = [this.name, this.age, this.sex]
		return {
			next () {
				const result = {
					value: arr[index],
					done: index >= arr.length
				}
				index++
				return result
			}
		}
	}
}
for (const item of obj) {
	console.log(item)
}

迭代器模式 Iterator

// 迭代器模式对外部提供统一遍历接口,让外部不用再关心数据内部的结构是怎样的
const obj = {
	life: ['吃饭', '睡觉', '打豆豆'],
	learn: ['html', 'css', 'javascript'],
	work: ['搬砖', '喝茶', '上厕所'],
	[Symbol.iterator]: function () {
		let index = 0
		const all = [...this.life, ...this.learn, ...this.work]
		return {
			next() {
				const result = {
					value: all[index],
					done: index >= all.length
				}
				index++
				return result
			}
		}
	}
}
for (const item of obj) {
	console.log(item)
}

13、Map 和 Set

Set 数据结构【set内部的成员是不允许重复的】

const arr = new Set()
// add方法会返回集合本身,所以可以链式调用
arr.add(1).add(2).add(3).add(4).add(2)
// console.log(arr)
// 可以使用循环方法进行遍历
// arr.forEach(i => console.log(i))
// for (let i of arr) console.log(i)
// size查询长度,has查询是否包含特定值,delete删除指定值,clear清空全部内容
console.log(arr.size)
console.log(arr.has(100))
console.log(arr.delete(1))
console.log(arr)
arr.clear()
console.log(arr)
// 最常用的是为数组去重
const array = [1, 2, 3, 2, 5, 3]
// const r = Array.from(new Set(array))
const r = [...new Set(array)]
console.log(r)

Map 数据结构【与对象类似,本质上都是键值对的集合】

const obj = []
obj[123] = 'value'
obj[true] = 'value'
obj[{a: 1}] = 'value'
console.log(Object.keys(obj))
//  ["123", "true", "[object Object]"]  都被toString()转换为了字符串

// 如果用对象作为键存储数据,就会出现问题,所得到的所有结果都是一样的
// map就可以解决这个问题
const m = new Map()
const tom = { name: 'tom' }
const jack = { name: 'jack' }
m.set(tom, 90)
m.set(jack, 96)
console.log(m)
// 可以使用get方法获取指定值
console.log(m.get(jack))

// 和set一样,map实例也具有has方法,delete方法,clear方法
m.has(tom)
m.delete(jack)
m.clear()
console.log(m)

14、Proxy

Proxy 代理对象(门卫,专门为对象设置访问代理器)

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为

<1>get()

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

var person = {
  name: "张三"
};

var proxy = new Proxy(person, {
  get: function(target, propKey) {
    if (propKey in target) {
      return target[propKey];
    } else {
      throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
    }
  }
});

proxy.name // "张三"
proxy.age // 抛出一个错误

上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是person对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作

<2>set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy保证age的属性值符合要求。

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // 对于满足条件的 age 属性以及其他属性,直接保存
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错

<3>deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。

var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    delete target[key];
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}

var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property

上面代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错

Proxy vs. Object.defineProperty()

Proxy 代理对象

15、Reflect

1、一个静态类,统⼀的对象操作 API(内部封装了一系列对对象的底层操作)

2、Reflect中的方法名与Proxy对象中的处理对象的方法成员是完全一致的

// Reflect成员方法就是Proxy处理对象的默认实现
const obj = {
	foo: 123,
	bar: 456
}
const p = new Proxy(obj, {
	get(target, propert) {
		return Reflect.get(target, propert)
	}
})
console.log(p.foo) 	// 123

Reflect

3、Reflect存在的意义:统一提供了一套用于操作对象的API

16、Symbol

⼀种全新的原始数据类型,表示一个独一无二的值,最主要的作用就是为对象添加独一无二的属性名

// 1、Symbol创建的数据的类型就是symbol
const s = Symbol()
console.log(s)			// Symbol()
console.log(typeof s)	// symbol

// 2、Symbol创建的每一个数据都是不同的
console.log(Symbol() === Symbol())		// false
///3、Symbol允许创建数据的时候传入字符串,作为描述文本
console.log(Symbol('foo'))		// Symbol(foo)
console.log(Symbol('bar'))		// Symbol(bar)

///4、对象可以使用Symbol类型的值作为属性名
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) 		// { [Symbol()]: '123', [Symbol()]: '456' }

///5、也可以使用计算属性名的方式在对象字面量当中使用Symbol作为属性名
const obj2 = {
	[Symbol()]: '111'
}
console.log(obj2)		// { [Symbol()]: '111' }

// 6、模拟实现对象的私有成员
// 在a.js文件中
const name = Symbol()
const person = {
	[name]: 'tom',
	say() {
		console.log(this[name])
	}
}
// 在b.js文件中
person.say() 		// tom

四、ECMAScript 2016

1、数组实例对象的includes方法,用于检查数组中是否包含指定元素变得更加简单

const arr = ['foo', 1, NaN, false]
// 传统的检测
console.log(arr.indexOf('foo'))		// 0 
console.log(arr.indexOf('bar'))		// -1 
console.log(arr.indexOf(NaN))		// -1   --=检查不到NaN
console.log(arr.indexOf(false))		// 3
// 通过includes检测
console.log(arr.includes('foo'))		// true 
console.log(arr.includes('bar'))		// false 
console.log(arr.includes(NaN))		// true   --可以检测到NaN
console.log(arr.includes(false))		// true

2、指数运算符

console.log(Math.pow(2, 10))		// 以前求2的10次方
console.log(2 ** 10)		// 运用指数运算符求2的10次方

五、ECMAScript 2017

1、Object对象的三个扩展方法【Object.value()、Object.entries()、Object.getOwnPropertyDescriptors()】

const obj = {
	foo: 'v1',
	bar: 'v2'
}

// 1、与Object.keys()类似,Object.values()返回对象中值组成的数组
console.log(Object.values(obj)) 	// [ 'v1', 'v2' ]

// 2、Object.entries()方法是以数组的方式返回对象中的键值对
console.log(Object.entries(obj)) 	// [ [ 'foo', 'v1' ], [ 'bar', 'v2' ] ]
for (let [key, value] of Object.entries(obj)) {
	console.log(key, value)
}
// foo v1
// bar v2
// 将entries对象转换为Map对象
console.log(new Map(Object.entries(obj))) 	// Map(2) { 'foo' => 'v1', 'bar' => 'v2' }

// 3、Object.getOwnPropertyDescriptors()获取对象中完整的属性描述信息
// ES5之后可以为对象定义getter和setter属性,但是这种属性不能通过Object.assign()方法去复制的
const p1 = {
	firstName: 'lei',
	lastName: 'wang',
	get fullName() {
		return this.firstName + ' ' + this.lastName
	}
}
// const p2 = Object.assign({}, p1)
// p2.firstName = 'hua'
// console.log(p2.fullName) 	// lei wang  --fullName函数作为普通字符串被复制
// 获取p1的详细描述
const des = Object.getOwnPropertyDescriptors(p1)
const p2 = Object.defineProperties({}, des)
p2.firstName = 'hua'
console.log(p2.fullName) 	// hua wang

2、两个字符串填充方法【padEnd、padStart】

const books = {
	html: 5,
	css: 10,
	javascript: 15
}
// 对其输出的字符串长度,为输出的数字位数不足时导0
for (let [name, count] of Object.entries(books)) {
	console.log(`${name.padEnd(16, '- ')} | ${count.toString().padStart(3, '0')}`)
}
// html- - - - - -  | 005
// css- - - - - - - | 010
// javascript- - -  | 015

3、允许函数的参数列表最后一位添加结束的逗号【定义函数和调用函数都允许】

4、Aysnc/Await【本质上是promise的语法糖】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值