ES新特性总结

ECMAScript的了解

ECMAScript也是一门脚本语言,一般缩写es,通常我们把它看做Js的标准化规范,但实际上js是ECMAScript的扩展语言,ECMAScript中只是提供了最基本的语法,只是停在了语言层面,实现不了实际应用当中的功能开发。

JS实现了ECMAScript语言的标准,在ECMAScript的基础上进行了扩展,使得我们在浏览器环境可以操作DOM、BOM,node环境可以读写文件。

总得来说:

  1. 浏览器下的JS包括:ECMAScript + Web APIs(DOM、BOM)
  2. NODE下的JS包括:ECMAScript + Node APIs(fs、net、etc.)
  • Javascript语言本身指的就是ECMAScript,从2015年开始ES保持每年一个大版本的迭代
  • ES2015开始不再以版本号命名,按照年份命名,所以很多人也把ES2015称为ES6
  • ECMAScript也是Js的语言本身。

ES2015概述

  • 也叫做ES6,用ES6去泛指ES2015之后的所有新标准
  • 解决原有语法上的一些问题或者不足,比如let、const、块级作用域
  • 对原有语法进行增强,比如解构、展开符、参数默认值、模板字符串
  • 全新的对象、全新的方法、全新的功能,比如Promise、Proxy
  • 全新的数据类型和数据结构,比如Set、Map

为了方便调试代码在项目下安装 nodemon,使得代码更改后终端能立即输出结果
执行命令:yarn nodemon 文件名称 (当文件更改时,终端能实时输出)

es2015新语法

let、const、块级作用域

作用域:某个成员能够起作用的范围。es2015之前只有 全局作用域 和 局部作用域,es2015增加了 块级作用域,包含在一个 {} 内

let、const、var的区别:

  1. 都是声明一个变量;
  2. let、const变量不能提升,var存在变量提升;
  3. let、const声明之后才能使用,在声明之前使用变量会报错,形成暂时性死区;
  4. const声明的同时必须赋值,一旦声明就不能再次修改,否则会报错,常用于声明一个只读的常量;

那么开发的过程中最佳实践:不用var,主用const,配合let

块级作用域的体现

if (true) {
    let foo = 'foo';
}
console.log(foo); // foo is not defined
// 这个时候i是一个全局变量,在第二层for循环里面的i把第一层的i给覆盖了,所以只输出了 0 1 2
for(var i=0; i<3; i++) {
    for(var i=0; i<3; i++) {
        console.log(i);
    }
}
// 0 1 2

/*
原有问题:
  当我们执行eles的元素onclick事件时,for循环早就执行完毕,这个时候的i为3,所以后面无论执行
  哪一个元素的onclick都是输出3
*/

var eles = [{}, {}, {}]

for(var i=0; i<eles.length; i++) {
    eles[i].onclick = function () {
        console.log(i);
     }
}
eles[2].onclick(); // 3
eles[1].onclick(); // 3

/*
怎么解决呢?--- 闭包实现,借助于函数作用域去摆脱全局作用域的影响
*/
for(var i=0; i<eles.length; i++) {
    eles[i].onclick = (function (i) {
        return function () {
            console.log(i);
        }
     })(i)
}
eles[2].onclick(); // 2
eles[1].onclick(); // 1

// 也可以借助于let的块级作用域来实现
for(let i=0; i<eles.length; i++) {
    eles[i].onclick = function () {
        console.log(i);
    }
}
eles[2].onclick(); // 2
eles[1].onclick(); // 1

数组解构

需要根据数组的位置提取对应的数据

// 之前,数组赋值
const arr = [100, 200, 300];
const a0 = arr[0];
const a1 = arr[1];
const a2 = arr[2];

// 用es6的解构 根据[]中变量名对应的位置分配arr中相对应位置的数值
const [a0, a1, a2] = arr;
console.log(a0, a1, a2); // 100 200 300

// 获取指定位置的数值
const [, , a3] = arr;
console.log('a3 =', a3); // a3 = 300

// 使用 ...rest 表示使用从当前位置开始的所有成员,最终结果放在一个数组里面(只能放在解构的最后位置)
const [a4, ...rest] = arr;
console.log('rest =', rest); // rest = [ 200, 300 ]

// 如果解构位置的成员下标小于数组的长度,那么只解构到当前的位置,后面的不会提取
const [a5] = arr;
console.log('a5 =', a5); // a5 = 100

// 如果解构位置的成员下标大于数组的长度,那么下标不存在的地方提取的就是undefined
const [a6, a7, a8, a9] = arr;
console.log(a6, a7, a8, a9); // 100 200 300 undefined

// 解构可以设置默认值
const arr1 = [];
const [a10 = 600] = arr1;
console.log('a10 =', a10); // a10 = 600

// 项目中使用获取 url中对应的参数
let str = 'path/foo/dirname12133';
const [ , , dirname] = str.split('/');
console.log(dirname); // dirname12133

对象解构

需要根据属性名去匹配提取

const obj = {name: 'Sunny', age: 26, gender: true};
const {name, age, gender} = obj;
console.log(${name}今年${age}了!); // Sunny今年18了!

// 可以重命名,设置默认值
const {name: objName, age: objAge = 22} = obj;
console.log(${objName}今年${objAge}了!); // Sunny今年22了!

模板字符串  ``

  • 支持差值表达式嵌入变量/任何标准的js表达式 ${}
  • 支持换行
const str1 = hello world, this is a \string` \n`
console.log(str1);
带标签的模板字符串

const str2 = console.log`hello world`; // [ 'hello world' ]

作用:

  • 对我们的模板字符串进行加工
  • 文本的多语言化
  • 检查我们的字符串中是否存在不安全的字符
  • 实现一个小型的模板引擎
function myTagFunc(strs, name, gender) {
    console.log(name, gender, strs);
	const sex = gender ? 'man' : 'woman';
	return strs[0] + name + strs[1] + sex + strs[2];
}

/*
这样输出的原因是:
  模板字符串可能有嵌入的表达式,这个数组按照表达式分割过后的那些静态内容,是一个数组,
  另外这个myTagFunc函数中可以接收到这个模板字符串中的出现的表达式的返回值,在myTagFunc
  函数中接收一下能输出
*/

const result = myTagFunchey`${name} is a ${gender}`;
// Sunny true [ 'hey, ', ' is a ', '' ]


console.log(result); // hey, Sunny is a man

字符串的扩展方法

let message = 'Error: foo is not defined';
// includes: 判断字符串中是否包含某一个字符
console.log(message.includes('foo')); // true

// startsWith:判断字符串是否是以指定的字符开头
console.log(message.startsWith('Error')); // true

// endsWith:判断字符串是否是以指定的字符结尾
console.log(message.endsWith('defined')); // true

函数参数默认值

// 对于之前设置函数中传参默认值
function foo(isEdit) {
	isEdit = isEdit === undefined ? true : isEdit; // 设置默认值
	console.log('foo =', isEdit); // foo = true
}

// es6中设置默认值,可以直接在函数声明的时候设置
function foo(isEdit = true, isRead) {
	console.log('es6 foo =', isEdit, isRead); // es6 foo = true undefined
}
foo();

剩余参数

// 之前我们获取函数的所有参数可以用arguments,但是它是一个伪数组,需要转换为数组
function restFunc1() {
  	// arguments是一个伪数组,需要转换为数组
	console.log(arguments, Array.from(arguments)); 
}
restFunc1(1, 2, 3); // [Arguments] { '0': 1, '1': 2, '2': 3 } [ 1, 2, 3 ]


// 用剩余参数直接在函数()中...args就可以获取到从当前位置开始的所有传参,
// 是一个数组,只能放在形参的最后一位,并且只能使用一次
function restFunc2(...args) {
	console.log('args =', args); // arguments是一个伪数组,需要转换为数组
}
restFunc2(1, 2, 3); // args = [ 1, 2, 3 ]

展开操作符 ...

// 展开数组参数:大大简化我们的代码
console.log(...arr); // 100 200 300

箭头函数

  • 简化了函数的定义
  • 代码更简短,易读
  • 箭头函数里面没有this的机制,所以不会改变this的指向,(在箭头函数外面this是什么,那么箭头函数里面拿到的this就是什么,任何情况都不会发生改变)
const add = n => n + 1;
console.log(add(100)); // 101

const person = {
    name: 'Tom',
    age: 26,
  
  	// 普通函数的this指向它的调用者
    sayHi: function() {
        console.log(`My name is ${this.name}!`);
    },
  
		// 箭头函数的this一开始就定义好的,指向当前作用域里面的this,指向window
    sayHi1: () => {
        console.log(`My name is ${this.name}!`);
    },
  
    sayHiAsync: function() {
        // setTimeout里面的回调最终会放在全局作用域中被调用, 所以this.name为undefined
        setTimeout(function () {
            console.log(`My name is ${this.name}!`);
        }, 1000);
    },
    sayHiAsync1: function() {
        const _this = this; // 保存当前作用域中的this
        setTimeout(function () {
            console.log(`My name is ${_this.name}!`);
        }, 1000);
    },
    sayHiAsync2: function() {
        // 箭头函数的this指向当前作用域里面的this
        setTimeout(() => {
            console.log(`My name is ${this.name}!`);
        }, 1000);
    }
}
person.sayHi(); // My name is Tom!
person.sayHi1(); // My name is undefined!
person.sayHiAsync(); // My name is undefined!
person.sayHiAsync1(); // My name is Tom!
person.sayHiAsync2(); // My name is Tom!

对象字面量的增强

写法上面的不同,任意属性名

const foo1 = 'outer foo';

// 传统的写法
const obj1 = {
    foo1: 'foo',
    bar: bar,
    method: function() {
        console.log(this.foo1);
    }
}
// es2015的写法,使得代码更加简短
const obj2 = {
    foo1: 'foo',
    bar,
    method() {
        console.log(this, this.foo1);
    },
    // 任意属性名
    [Math.random()]: 123
}
obj1.method(); // { foo1: 'foo', bar: 'bar', method: [Function: method] }  foo
obj2.method(); // { foo1: 'foo', bar: 'bar', method: [Function: method] }  foo

对象扩展方法

  • Object.assign

依次将多个源对象中的属性复制到一个目标对象中。如果源对象属性名和目标对象属性名有重名,那么源对象中的属性就会覆盖掉目标对象的属性 (从源对象中取,放到目标对象)
第一个参数:目标对象 (所有源对象都会复制到目标对象中)

  • 后面的参数:源对象
  • 返回值:目标对象

这个方法的使用通俗点来说就是用后面对象的属性覆盖并且复制到目标对象中

const target = {
    a: 456,
    c: 456
}
const source1 = {
    a: 123,
    b: 123
}
const source2 = {
    b: 789,
    d: 789
}
const result1 = Object.assign(target, source1, source2);

console.log('result1 =', result1);
// result1 = { a: 123, c: 456, b: 789, d: 789 }

console.log('target === result ==>', target === result1);
// target === result ==> true


// 比较常用于复制一个对象
function copyObj(obj) {
    const copyObj = Object.assign({}, obj); // 用于复制一个对象
    copyObj.name = 'copyObj name';
    console.log(copyObj);
}
const obj6 = {name: 'obj6 name'};
copyObj(obj6);
console.log(obj6.name); // obj6 name
  • Object.is

用于判断两个值是否相等

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

监视属性的读写

可以用es5提供的Object.defineProperty来劫持属性的setter和getter,可以捕获到对象当中属性的读写情况

es2015增加了Proxy代理对象:相当于一个门卫,无论你进去还是出来门卫都知道,可以轻松监视到数据的读写过程

new Proxy()的参数介绍

第一个参数:我们需要代理的目标对象
第二个参数:是一个对象,我们代理的处理对象,常用的有get和set两个方法
get函数中有两个参数:

  • target:为代理的目标对象
  • property:为外部访问的属性名

set默认接收三个参数:

  • target:代理目标对象
  • property:写入的属性名称
  • value:写入的属性值
// 基本使用
let person6 = {
    name: 'Sunny',
    age: 26
}
// 代理对象
const personProxy = new Proxy(person6, {
    // 接收两个参数:target为代理的目标对象 property为外部访问的属性名
    get(target, property) {
        // console.log(target, property);
        return property in target ? target[property] : undefined; // 返回值为外部访问属性得到的结果
    },
    // 默认接收三个参数:代理目标对象、写入的属性名称、写入的属性值
    set(target, property, value) {
        if (property === 'age') {
            if (!Number.isInteger(value)) throw new TypeError(`${value} is not an int`);
        } else if (property === 'gender') {
            target[property] =value ? '是' : '否';
        } else {
            target[property] = value;
        }
        console.log('set ===', target, property, value);
    }
})
personProxy.gender = true; // set === { name: 'Sunny', age: 26 } gender true
personProxy.age = 123; // set === { name: 'Sunny', age: 26, gender: '是' } age 123
console.log(personProxy.name); // Sunny
console.log(personProxy.gender); // 是

proxy 相比于 Object.defineProperty有什么优势呢?
Object.defineProperty只能监听属性的读写;

Object.defineProperty监视数组的操作,一般都是重写数组的操作方法(自定义的方法,覆盖掉数组原型对象上面的比如push、pop这些方法,以此用来劫持对这个方法的调用过程)特定的方式单独去定义对象当中那些需要被监视的属性。对已存在对象我们要监听它的属性需要做很多额外的操作。

而Proxy更加强大:

1、能监听到delete操作、对对象方法的调用
2、更好的支持数组对象的监听

Proxy是以非侵入的方式监管了对象的读写。已经定义好的对象,我们不需要对对象本身做任何的操作就可以监视到内部成员的读写。

const personProxy1 = new Proxy(person6, {
    // 在外部对代理对象进行delete操作时,会自动执行 (target:代理对象、key:要删除的属性名称)
    deleteProperty(target, key) {
        console.log(target, key);
    }
})
delete personProxy1.name; // { name: 'Sunny', age: 26 } name

// proxy对数组的监视
const list = [];
const listProxy = new Proxy(list, {
    // key为数组下标 value为下标对应的值
    set(target, key, value) {
        console.log('set', key, value);
        target[key] = value;
        return true; // 表示写入成功
    }
})
// 当往数组中push值的时候,set能监听到数组的变化,并输出对应的下标和值
listProxy.push(100);
// set 0 100
// set length 1
listProxy.push(100);
// set 1 100
// set length 2

Reflect

统一提供了一套用于操作对象的API

ES2015中提出的全新的内置对象,统一的对象操作API,属于一个静态类,不能通过new的方式去构建一个实例对象,只能调用这个静态类里面的静态方法,比如Reflect.get

内部封装了一系列对对象的底层操作(13个),Reflect成员方法就是Proxy处理对象的默认实现。这个是什么意思呢?通过代码能清楚的知道:

// 假如监听一个对象的属性,但是没有写get、set方法读写属性,但是其实它的底层默认就调用的是Reflect.get,和Reflect.set
const myProxy1 = new Proxy(person6, {
    get(target, property) {
        return Reflect.get(target, property)
    },
    set(target, property, value) {
        return Reflect.set(target, property, value)
    }
})


// 依此表明我们在自定义get、set逻辑时,更标准的做法是先去实现自己所需要的监视逻辑,最后再去返回调用Reflect中对应的get或者set方法的结果
const myProxy2 = new Proxy(person6, {
    get(target, property) {
        console.log('这里写一系列的监视逻辑!');
        return Reflect.get(target, property)
    },
    set(target, property, value) {
        return Reflect.set(target, property, value)
    }
})
console.log(myProxy2.name);

Reflect的使用

// 原先我们操作对象
console.log('name' in person6); // true
console.log(delete obj['age']); // true
console.log(Object.keys(person6)); // [ 'name', 'age' ]

// 使用Reflect操作对象,写法上面比较统一
console.log(Reflect.has(person6, 'name')); // true
console.log(Reflect.deleteProperty(person6, 'age')); // true
console.log(Reflect.ownKeys(person6)); // [ 'name', 'age' ]

Promise

内置对象,全新的异步编程解决方案,解决了传统异步编程中回调函数嵌套过深的问题

可以参考看 异步编程 · 语雀

Class类

类型当中的方法分为:实例方法 和 静态方法

  • 实例方法:通过这个类型构造的实例对象去调用 比如:new Person().getName()
  • 静态方法:直接通过类型本身去调用,静态方法的this指向这个类型 比如:Person.create()  静态方法用static声明

类的继承 extends:能够抽象出来相似类型之间的重复代码,提高代码的复用率

里面有一个super()对象,始终指向父类,调用super()的作用就是调用父类的构造函数

class Person {
    // 当前类型的构造函数
    constructor(name) {
        this._name = name;
    }
    // 实例方法
    getName() {
        console.log(`Hi, my name is ${this._name}`);
    }
    // 静态方法
    static create(name) {
        return new Person(name);
    }
}
const p = new Person('tom');
p.getName() // 实例方法的调用 tom

const p2 = Person.create('Sunny') // 静态方法的调用
p2.getName(); // Sunny


// 类的继承,Student拥有Person类中的所有成员, super对象始终指向父类
class Student extends Person {
    constructor(name, number) {
        // super对象,始终指向父类,调用super()的作用就是调用父类的构造函数
        super(name);
        this._number = number;
    }
    hello() {
        super.getName();
        console.log(`My school number is ${this._number}`);
    }
}
const s = new Student('jack', 123456);
s.hello(); // Hi, my name is jack   My school number is 123456

Set数据结构

可以理解为集合,内部成员唯一,是一个类型,通过new个实例可以用来存放不重复的数据

常用方法:

  1. 通过 add 方法往集合中添加数据,因为这个方法返回这个集合本身,所以可以多次链式调用add
  2. 遍历集合 使用集合对象的 forEach 方法、for-of循环 new Set().forEach(i => log(i));  for(let i of new Set()) {log(i)}
  1. size获取整个集合的长度 new Set().size
  2. has 判断集合当中是否存在某一个特定的值 new Set().has(key);
  1. delete 用于删除集合当中的某一个值 new Set().delete(key);
  2. clear 用于清除整个集合 new Set().clear();
set.add(1).add(2).add(3).add(4).add(1);

console.log('set add =', set); // set add = Set(4) { 1, 2, 3, 4 }

console.log('set size =', set.size); // set size = 4

console.log('set has 2 =', set.has(2)); // set has 2 = true

set.delete(4);
console.log('delete after =', set); // delete after = Set(3) { 1, 2, 3 }

set.clear();
console.log('set clear after=', set); // set clear after= Set(0) {}

// 最常用的就是实现数组的去重
const array = [1, 2, 3, 4, 1, 2];
const uniqueArr = [...new Set(array)];
console.log(uniqueArr); // [ 1, 2, 3, 4 ]

Map数据结构

严格意义上面的键值对集合,用来映射两个任意类型数据之间的对应关系

对象中的键值只能是字符串/Symbol,而Map中的键可以是任何类型

常用方法:

  • set():存数据
  • get():获取数据
  • has():是否有某一个属性
  • delete()
  • clear()
  • size
  • map的forEach遍历
const mapObj = {};
mapObj[true] = 'value';
mapObj[123] = 'value';
mapObj[{name: '123'}] = 'value';
console.log(Object.keys(mapObj)) // [ '123', 'true', '[object Object]' ] 内部键值tostring自动转换为字符串类型

let map = new Map();
const tom = {name: 'tom'};

map.set(tom, 90);
console.log(map); // Map(1) { { name: 'tom' } => 90 }

console.log(map.size); // 1

console.log(map.get(tom)) // 90

console.log(map.has(tom)) // true

map.delete(tom)
console.log(map) // Map(0) {}

map.set(tom, 90);
map.clear();

map.set(tom, 90).set(true, 1000);
console.log(map) // Map(2) { { name: 'tom' } => 90, true => 1000 }

map.forEach((value, key) => {
    console.log(value, key);
})

Symbol

  1. 一种全新的原始数据类型 (表示一个独一无二的值)。
  2. 通过Symbol创建的任意值都是唯一的,不会重复。
  1. 最主要的作用就是为对象添加一个独一无二的属性名。

注意点:

  • 唯一性:每次创建的Symbol是一个唯一值,不管它的描述文本是否相同,他都是不相等的,比如:log(Symbol('foo') === Symbol('foo')) ==> false
  • Symbol.for()静态方法可以接收一个字符串作为参数,相同的字符串一定会返回相同Symbol类型的值。因为内部维护了一个全局的注册表(字符串 和 Symbol之间的关系),使我们的字符串和Symbol值形成了一一对应的关系,如果传入的不是一个字符串,内部会转换为字符串,所以下面的情况都为true
log(Symbol.for('true') === Symbol.for(true)) ==> true
log(Symbol.for('foo') === Symbol.for('foo')) ==> true
  • for in 、Object.keys是获取不到Symbol定义的属性名的,JSON.stringify序列化对象Symbol定义的属性也是获取不到的 (因此特别适合作为对象的私有属性)
const symbol = Symbol()

console.log(typeof symbol); //symbol

console.log(Symbol() === Symbol()) // false

const obj10 = {};
obj10[Symbol()] = 123;
obj10[Symbol()] = 456;
console.log(obj10); // { [Symbol()]: 123, [Symbol()]: 456 }

// 模拟实现对象的私有成员
const privateName = Symbol();
const person10 = {
    privateName: 'Sunny',
    say() {
        console.log(this.privateName);
    }
}
person10.say(); // Sunny
console.log(Symbol.for('foo') === Symbol.for('foo')) // true

for...of循环

之前的循环遍历:

  1. for循环适合遍历数组
  2. for...in循环适合遍历键值对
  3. 一些数组对象的遍历方法:forEach、map

但是这些遍历的方式都会有一定的局限性

es6提出了一个新的遍历 for...of,以后将作为遍历所有数据结构的统一方式

for...of的基本用法

1. for...of循环遍历可以使用break关键字来终止循环;但是forEach循环是不能终止遍历的:

以前我们要随时终止数组的遍历要用数组的实例方法some、every,通过some回调函数中返回true, every回调函数中返回false都能终止遍历。
2. for...of遍历数组可以输出数组的每一个元素;
3. 一些伪数组也能使用for...of遍历;
4. Set 和 Map 对象也能for...of遍历;
5. for...of只能遍历有可迭代接口的数据,直接遍历普通对象会报错,obj is not iterable, 为什么呢?

es中能够表示有结构的数据类型越来越多,为了提供一种统一的遍历方式,es2015提出了Iterable接口(可迭代接口,可以理解为一种规格标准)。
比如任何一个数据类型都有一个toString()方法,说明了他们都实现了统一的规格标准(统一的接口)
可迭代接口:就是一种可以被for...of循环统一遍历访问的规格标准,只要这个数据结构实现了可迭代接口,就能够被for...of循环遍历

for...of的内部工作原理

内部去调用被遍历对象的Iterator方法得到一个迭代器,从而去遍历内部所有的数据 (这个也是Iterator接口所约束的内容)。只要我们的对象实现了Iterator接口,那么就能被for...of循环遍历

let arr = [100, 200, 300, 400];

// for...of break终止循环
for (let item of arr) {
    if (item > 200) {
        break;
    }
    console.log(item);
} // 100 200


// 使用数组解构来直接获取key value

let map = new Map();
const tom = {name: 'tom'};
map.set(tom, 90);

for (const [key, value] of map) {
    console.log(key, value);
}

在对象上面添加一个迭代器接口

// 在对象上面添加一个迭代器接口
const objIterator = {
    store: ['foo', 'bar', 'baz'],
    [Symbol.iterator]: function () {
        let index = 0;
        const self = this;
        return {
            next: function () {
                const result = {
                    value: self.store[index],
                    done: index >= self.store.length
                }
                index++;
                return result;
            }
        }
    },
    name: 'Name',
    number: 210000
}
for (const item of objIterator) {
    console.log(item);
}
// foo bar baz

迭代器设计模式

对外提供统一遍历接口,让外部不在关心这个数据内部的结构是怎样的

场景:两人协同开发任务清单应用

// a文件
const todos = {
    life: ['吃饭', '看书', '运动'],
    learn: ['前端', '后端', '大数据'],
    work: ['写代码', '过需求'],
  
    each: function(callback) {
        const all = [...this.life, ...this.learn, ...this.work];
        for(const item of all) {
            callback(item);
        }
    },
  
    [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
                }
            }
        }
    }
}


// b文件 需要遍历todos,并输出,对于这样三个for循环,代码有点冗余,可以在todos中写一个each方法

for (const item of todos.life) {
    console.log(item);
}
for (const item of todos.learn) {
    console.log(item);
}
for (const item of todos.work) {
    console.log(item);
}

console.log('----------------------------');

// 调用todos的each方法
todos.each(item => {
    console.log(item);
})
console.log('----------------------------');

// 调用模拟iterator方法
for (let item of todos) {
    console.console.log(item);
}

Generator生成器函数

出现的目的是为了能够在复杂的逻辑代码中避免异步编程中回调嵌套过深的问题,从而提供更好的异步编程解决方案

语法:

  1. 在普通的函数名称前面增加一个 *
  2. 返回值为一个生成器对象
  1. 声明的Generator函数第一次调用并不会立即执行,直到调用next()方法才会执行
  2. 遇到yield会暂停代码的执行,直到遇到下一个next()才会继续向下执行
function * foo() {
    console.console.log(111);
    yield 100;
    console.console.log(222);
    yield 200;
    console.console.log(333);
    yield 300;
}
const g = foo();
g.next(); // 111
g.next(); // 222
g.next(); // 333
console.console.log(g.next()); // { value: undefined, done: true }


// 案例实现:发号器
function * createIdMaker() {
    let id = 1;
    while(true) {
        yield id++;
    }
}
const idMaker = createIdMaker();
console.log(idMaker.next()); // { value: 1, done: false }
console.log(idMaker.next()); // { value: 2, done: false }



// 生成器函数实现iterator接口函数
const todos1 = {
    life: ['吃饭', '看书', '运动'],
    learn: ['前端', '后端', '大数据'],
    [Symbol.iterator]: function * () {
        const alls = [...this.life, ...this.learn];
        for (const item of alls) {
            yield item;
        }
    }
}
for (let item of todos1) {
    console.console.log(item);
}

ES Modules

语言层面的模块化标准

ES2016

发布于2016年6月,是一个小版本,增加了两个新功能

数组实例对象的includes方法:Array.property.includes

传统的我们用indexOf来判断数组中是否有某一个元素,找到了,输出下标,找不到返回-1,但是判断不了NaN的情况,都是输出-1,而includes能判断出来NaN,直接返回true/false

指数运算符

之前借助于Math.pow(2, 5), 语言本身的运算符:console.log(2 ** 10) 表示2的10次方

ES2017

ECMAScript的第八个版本 发布于2017年6月,也只是一个小版本

Object.values:返回对象中所有值组成的数组

let obj11 = {foo: '123', bar: '456'};

console.log(Object.values(obj11)); // [ '123', '456' ]
Object.entries:数组的形式返回我们对象的所有的键值对
console.log(Object.entries(obj11)); 
// [ [ 'foo', '123' ], [ 'bar', '456' ] ]

// 使得我们可以直接用for of循环去遍历对象,先将对象转换为数组的形式,然后使用for...of遍历
for (let [key, value] of Object.entries(obj11)) {
    console.log(`key = ${key}, value = ${value}`);
}
/*
key = foo, value = 123
key = bar, value = 456
*/

// Map的构造函数就是需要这样的一个数组,那么可以借用Object.entries()将对象转换为Map类型的对象
const map11 = new Map(Object.entries(obj11));
console.log(map11); // Map(2) { 'foo' => '123', 'bar' => '456' }

Object.getOwnPropertyDescriptors:获取属性的完整描述信息,就是为了配合ES2015中的get、set使用

// es2015后我们可以为对象定义get、set属性,但是这种属性不能通过Object.assign()方法完全复制的,
const p11 = {
    first: 'Yang',
    last: 'yt',
    get fullName() {
        return `${this.first} ${this.last}`;
    }
}

const p22 = Object.assign({}, p11);
console.log(p22.fullName); // Yang yt

p22.first = 'Chen';
console.log(p22.fullName); //Yang yt
// fullName还是 Yang yt 出现这种情况的原因是因为Object.assign把 fullName当做一个属性复制的,
// 这个时候就可以使用Object.getOwnPropertyDescriptors来获取对象当中属性的完整描述信息

const descriptors = Object.getOwnPropertyDescriptors(p11);
console.log('descriptors =', descriptors);
/*
输出:
descriptors = {
  first: {
    value: 'Yang',
    writable: true,
    enumerable: true,
    configurable: true
  },
  last: { value: 'yt', writable: true, enumerable: true, configurable: true },
  fullName: {
    get: [Function: get fullName],
    set: undefined,
    enumerable: true,
    configurable: true
  }
}
*/
const p22 = Object.defineProperties({}, descriptors);
p22.first = 'Chen';
console.log(p22.fullName); // Chen yt

String.prototype.padStart / String.prototype.padEnd

字符串填充方法,用指定的字符串去填充字符串的开始和结束位置,直到达到我们的指定长度为止

const books = {
    html: 5,
    css: 16,
    javascript: 300
}
for (let [name, count] of Object.entries(books)) {
    // console.log(key, value);
    console.log(`${name.padEnd(16, '*')} ${count.toString().padStart(3, '0')}`);
}
/*
html************ 005
css************* 016
javascript****** 300
*/

在函数参数中添加尾逗号

function foo11(a, b, c,) {
    console.log(a, b, c);
}
foo11(1, 2, 3, ); // 1 2 3

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值