EcmaScript新特性
- 一 ECMAScript简介
- 二 ES6
- 1、Let与块级作用域
- 2、解构赋值(Destructuring)
- 3、模板字符串
- 4、字符串的扩展方法
- 5、参数默认值
- 6、剩余参数
- 7、箭头函数
- 8、对象字面量增强Enhanced object literals
- 9、Object.assign
- 10、Object.is
- 11、Proxy
- 12、Reflect 统一的对象操作API
- 13、Promise
- 14、Class类
- 15、静态方法
- 16、类的继承
- 17、Set数据结构
- 18、Map数据结构
- 19、Symbol
- 20、for-of循环
- 21、可迭代接口
- 22、迭代器模式
- 23、生成器函数Generator
- 24、ES Modules
- 25、ES2016 概述
- 26、ES2017 概述
一 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')}`)
}
声明:以上内容均为学习笔记,如涉及侵权,请联系我删帖。如需转载,请注明文章出处。