参考文档
ES6入门: ES6 前言 – ES6文档 (caibaojian.com)
SegmentFault:一文搞定ES6常用API_个人文章 – SegmentFault 思否
菜鸟教程:3.2.3 ES6 对象 | 菜鸟教程 (runoob.com)
知乎:JavaScript ES7~ES11新特性 – 知乎 (zhihu.com)
let 命令
- let作用域为区块内;
- let同作用域下只能声明一次;
- for循环中setTimeout适合使用let,如下:
for (var i = 0; i < 10; i++) { setTimeout(function(){ console.log(i); }); } // 输出十个 10 for (let j = 0; j < 10; j++) { setTimeout(function(){ console.log(j); }); } // 输出 0123456789
- let不存在变量提升,如下:
console.log(a); // ReferenceError: a is not defined let a = "apple"; console.log(b); // undefined var b = "banana";
const 命令
const 声明简单类型的变量等同于常量,可做只读保护;而声明复杂类型时需慎用,如Object、Array等,仍可修改其成员值(非直接修改常量),如下:
const OPTS = { config: { a: 1, b: 2 } } OPTS.config = {}; // Uncaught TypeError: Assignment to constant variable. OPTS.config.a = 111; // 注意:其成员仍允许修改
解构赋值
- 解构Array模型
// 基本 let [a, b, c] = [1, 2, 3]; // a = 1 // b = 2 // c = 3 // 可嵌套 let [a, [[b], c]] = [1, [[2], 3]]; // a = 1 // b = 2 // c = 3 // 可忽略 let [a, , c] = [1, 2, 3]; // a = 1 // c = 3 // 不完全解构 let [a = 1, b] = []; // a = 1, b = undefined // 剩余运算符 let [a, ...b] = [1, 2, 3]; // a = 1 // b = [2, 3] // 遍历字符串 let [a, b, c, d, e] = 'hello'; // a = 'h' // b = 'e' // c = 'l' // d = 'l' // e = 'o' // 字符串转数组 let [...a] = 'hello'; // a = ["h", "e", "l", "l", "o"] // 解构默认值 let [a = 2] = [undefined]; // a = 2 let [a = 3, b = a] = []; // a = 3, b = 3 let [a = 3, b = a] = [1]; // a = 1, b = 1 let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
- 解构Object模型
// 基本 let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; // foo = 'aaa' // bar = 'bbb' let { baz : foo } = { baz : 'ddd' }; // foo = 'ddd' // 可嵌套、可忽略 let obj = {p: ['hello', {y: 'world'}] }; let {p: [x, { y }] } = obj; // x = 'hello' // y = 'world' let obj = {p: ['hello', {y: 'world'}] }; let {p: [x, { }] } = obj; // x = 'hello' // 不完全解构 let obj = {p: [{y: 'world'}] }; let {p: [{ y }, x ] } = obj; // x = undefined // y = 'world' // 剩余运算符 let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}; // a = 10 // b = 20 // rest = {c: 30, d: 40} // 解构默认值 let {a = 10, b = 5} = {a: 3}; // a = 3; b = 5; let {a: aa = 10, b: bb = 5} = {a: 3}; // aa = 3; bb = 5;
箭头函数
- 使用=>定义箭头函数,如下:
let fun = () => { console.log('这是一个箭头函数'); } fun(); // 这是一个箭头函数
- 箭头函数可以闭包自执行,如下:
;(() => { console.log('这是一个闭包箭头函数'); })(); // 这是一个闭包箭头函数
- 箭头函数不可作为构造函数实例化对象,如下:
let Fun = () => { console.log('不可作为构造函数'); } new Fun(); // Uncaught TypeError: Fun is not a constructor
- 箭头函数没有arguments变量,如下:
let fun = () => { console.log(arguments); } fun(); // Uncaught ReferenceError: arguments is not defined
- 箭头函数可以再简写,如下:
// 只有一个参数的时候,可以省略括号 let fun = val => {}; // 方法体只有一条语句,可省略大括号和return let sum = num => num + num; sum(1); // 2
- 箭头函数不改变this指向(即便call或apply传入也改变不了),例如setTimeout在循环中,如react组件绑定事件等等,不再赘述;
模板字符串调用
// 示例 function fun() { console.log(arguments[0]); } // 调用方法1: fun(11, 22, 33); // 11 // 调用方法2: fun`abcd`; // ["abcd", raw: ["abcd"]]
数值扩展
- Number.EPSILON = 2.220446049250313e-16,是JS表示的最小精度,当JS进行小数运算的时候结果会不准确,可以使用Number.EPSILON进行参考,如下:
// JS中比较浮点数方法 function equal(a, b){ if(Math.abs(a-b) < Number.EPSILON){ return true; } else { return false; } } console.log(0.1 + 0.2 === 0.3); // false console.log(equal(0.1 + 0.2, 0.3)); // true
- Number.isNaN(number),检测一个数值是否为NaN,如下:
Number.isNaN(11111); // false Number.isNaN(NaN); // true
- Number.isInteger(number),判断一个数是否为整数,如下:
Number.isInteger(2.5); // false Number.isInteger(2.0); // true Number.isInteger(-2.0); // true Number.isInteger(0.00); // true
- Number.isFinite(number),检测一个数值是否为有限数,如下:
Number.isFinite(1/0); // false Number.isFinite(Infinity); // false Number.isFinite(0/1); // true
- Number.parseInt(string, ?radix) 和 Number.parseFloat(string),字符串转整数和字符串转浮点数,并且去掉不能转换的部分,如下:
Number.parseInt('12.12ABC'); // 12 Number.parseInt('ABC12.12'); // NaN Number.parseInt('11', 2); // 3 Number.parseFloat('12.12ABC'); // 12.12 Number.parseFloat('ABC12.12'); // NaN
对象方法扩展
- Object.is(value1, value2),判断两个值是否完全相等,几乎和===等价,如下:
console.log(Object.is(NaN, NaN)); // true console.log(NaN === NaN); // false,注意此处 var aa = {}; var bb = {}; console.log(aa === bb); // false console.log(Object.is(aa === bb)); // false
- Object.assign(target, source[…source[source1, source2, ?source3]]),用于合并对象,将一个或多个源对象的所有可枚举属性,复制到目标对象,如下:
let rec = {}; const conf = { target:'获取经书', person:['唐僧','悟空'] } // enumerable为false的属性,不会被复制 Object.defineProperty(conf, 'height' ,{ value: '188', enumerable: false }) Object.assign(rec,conf); console.log(rec); // {target: "获取经书", person: Array(2)} // 引用类型复制的是指针,会修改conf对象的person属性数据 rec.person.push('八戒'); console.log(conf); // {target: "获取经书", person: Array(3), height: "188"}
Symbol
ES6 引入一种新的原始数据类型 Symbol ,表示独一无二的值,方便用来定义对象的唯一属性名。ES6 数据类型除了 Number 、 String 、 Boolean 、 Object、 null 和 undefined ,还新增了 Symbol 。
- 基本用法:因 Symbol 是原始数据类型,不是对象,故不能用 new 命令。可接受一个字符串参数,即new Symbol(?string),为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分,如下:
let sy = Symbol("KK"); console.log(sy); // Symbol(KK) typeof(sy); // "symbol" // 相同参数 Symbol() 返回的值不相等 let sy1 = Symbol("kk"); sy === sy1; // false
- 作为属性名:由于每一个 Symbol 的值都不相等,所以 Symbol 作为对象的属性名,可保证属性不重名。
let sy = Symbol("key1"); // 写法1 let syObject = {}; syObject[sy] = "kk"; console.log(syObject); // {Symbol(key1): "kk"} // 写法2 let syObject = { [sy]: "kk" }; console.log(syObject); // {Symbol(key1): "kk"} // 写法3 let syObject = {}; Object.defineProperty(syObject, sy, {value: "kk"}); console.log(syObject); // {Symbol(key1): "kk"}
注意,1:Symbol 作为对象属性名时不能用 “.” 运算符,需用方括号。因为 “.” 运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性,如下:
let sy = Symbol("key1"); let syObject = {}; syObject[sy] = "kk"; syObject[sy]; // "kk" syObject.sy; // undefined
注意,2:Symbol 值作为属性名时,该属性是公有属性不是私有属性,可在类的外部访问。但不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到,如下:
let sy = Symbol("key1"); let syObject = {}; syObject[sy] = "kk"; console.log(syObject); for (let i in syObject) { console.log(i); // 无输出 } Object.keys(syObject); // [] Object.getOwnPropertySymbols(syObject); // [Symbol(key1)] Reflect.ownKeys(syObject); // [Symbol(key1)]
- 定义常量:Symbol 的值是唯一的,所以不会出现相同值的常量,如下:
const COLOR_RED = Symbol("red"); const COLOR_YELLOW = Symbol("yellow"); const COLOR_BLUE = Symbol("blue");
- Symbol.for(key: any):类似单例模式,首先在全局搜索被登记的 Symbol 中是否有该字符串参数为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索,如下:
let yellow = Symbol("Yellow"); let yellow1 = Symbol.for("Yellow"); yellow === yellow1; // false(新创建的检索的不同) let yellow2 = Symbol.for("Yellow"); yellow1 === yellow2; // true(检索的结果相同)
- Symbol.keyFor(sym: Symbol):返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记,如下:
let yellow1 = Symbol.for("Yellow"); Symbol.keyFor(yellow1); // "Yellow"
- Symbol.hasInstance,指向一个内部方法钩子,当对象使用instanceof运算符判断是否为该对象实例时会自动调用该方法,如下:
class MyArray { [Symbol.hasInstance](arr) { console.log('调用了Symbol.hasInstance内置方法1。'); return arr instanceof Array && arr[0] === 'neusofts'; } } ['neusofts', 123, true] instanceof new MyArray; // 注:是类实例 // 调用了Symbol.hasInstance内置方法1。 // true // 或者: Object.defineProperty(MyArray, Symbol.hasInstance, { value: function (arr) { console.log('调用了Symbol.hasInstance内置方法2。'); return arr instanceof Array && arr[0] === 'neusofts'; } }); ['neusofts', 123, true] instanceof MyArray; // 注:是类 // 调用了Symbol.hasInstance内置方法2。 // true // 上述结果可以说明:重新定义对象Symbol.hasInstance属性,当用instanceof操作符时,会触发Symbol.hasInstance属性上定义的方法,判断一个对象是否是构造函数的实例,完全可以使用自定义规则来判断。
- Symbol.iterator属性(迭代器),数组之所以可以使用for…of(自动判断结束循环)方式遍历,原因是数组原型上包含该属性(迭代器),数组调用该原型方法时,即可返回一个生成器对象,如下:
let arr = ['A', 'B', 'C', 'D']; let iterator = arr[Symbol.iterator](); iterator.next(); // {value: "A", done: false} iterator.next(); // {value: "B", done: false} iterator.next(); // {value: "C", done: false} iterator.next(); // {value: "D", done: false} iterator.next(); // {value: undefined, done: true} // 小结:Symbol.iterator符号被定义为对象的默认迭代器。内置对象和自定义对象都可以使用这个符号,以提供一个能返回迭代器的方法。当Symbol.iterator在一个对象上存在时,该对象就会被认为是可迭代对象。 // 自定义迭代器: const myObj = { name: [ 'AA', 'BB', 'CC', undefined, 'DD' ], [Symbol.iterator]: function () { let i = 0; return { next: () => { if (i < this.name.length) { const result = { value: this.name[i], done: false }; i++; return result; } else { return { value: undefined, done: true }; } } } } } for (let val of myObj) { console.log(val); } // AA // BB // CC // undefined // DD // 为了for-of更易用,ES6中的许多类型都具有默认的迭代器。所有的集合类型(数组、Map与Set)都具有迭代器。
生成器
- 生成器定义和调用,生成器是特殊函数,用于实现异步编程,可以迭代器的方式实现调用。生成器的定义用一个*标记在函数名称前,生成器函数返回的结果是迭代器对象,调用迭代器对象的next方法可得到yield语句后的值,如下:
// function generate(): Generator<"AA" | "BB" | "CC", void, unknown> function *generate() { yield 'AA'; yield 'BB'; yield 'CC'; } let iterator = generate(); // 使用next iterator.next(); // {value: "AA", done: false} iterator.next(); // {value: "BB", done: false} iterator.next(); // {value: "CC", done: false} iterator.next(); // {value: undefined, done: true} // 或者使用for...of for (let i of iterator) { console.log(i); } // AA // BB // CC
- 生成器函数传参,前面yield的执行结果是后面表达式的入参,如下:
function *generate(id) { console.log('拿到id' + id); let name = yield '通过id' + id + '拿到用户名'; let info = yield '通过id' + id + '和用户名,拿到用户资料'; } let iterator = generate(33); for (let i of iterator) { console.log(i); } // 拿到id33 // 通过id33拿到用户名 // 通过id33和用户名,拿到用户资料
- 异步回调场景,如下:
function wrapper(id) { var iterator; //模拟获取 用户数据 订单数据 商品数据 function getCustomer(id) { setTimeout(() => { let res = '用户数据 - ' + id; //调用 next 方法, 并且将数据传入 iterator.next(res); }, 1e3); } function getOrders(customer) { setTimeout(() => { let res = '订单数据 - ' + customer; iterator.next(res); }, 1e3); } function getGoods(orders) { setTimeout(() => { let res = '商品数据 - ' + orders; iterator.next(res); }, 1e3); } iterator = (function* () { console.log('拿到:' + id); let customer = yield getCustomer(id); console.log('拿到:' + customer); let orders = yield getOrders(customer); console.log('拿到:' + orders); let goods = yield getGoods(orders); console.log('拿到:' + goods); return goods; })(); iterator.next(); } wrapper('ID33'); // 拿到:ID33 // 拿到:用户数据 - ID33 // 拿到:订单数据 - 用户数据 - ID33 // 拿到:商品数据 - 订单数据 - 用户数据 - ID33
Promise
- 定义Promise对象,如下:
const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('返回成功!'); // reject('返回失败!'); }, 2e3); }); promise.then( success => console.log(success), // 调用resolve时,返回成功! fail => console.error(fail) // 调用reject时,返回失败! ).finally(() => { // 无参,无论成功失败都执行的语句 }); // 相关知识点: // Promise.prototype.finally(?onFinally); // 无论成功失败都执行的语句
- 用Promise封装读取文件,如下:
const fs = require('fs'); const promise = new Promise((resolve, reject) => { fs.readFile('license.txt', (err, data) => { err && reject(err); resolve(data); }); }); promise.then( data => data.toString(), err => console.log(err) );
- 用Promise封装Ajax,如下:
const promise = new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open('GET', '/test.api'); xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { // 判断响应状态码 200 ~ 299 if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response); } else { reject(xhr.status); } } } }); promise.then( resData => resData, errStatus => errStatus );
- Promise异步调用,如下:
// 异步函数aFun var aFun = function (data) { return new Promise((resolve) => { setTimeout(() => { resolve(data); console.log(data); }, 2e3); }); } // 异步函数bFun var bFun = function (data) { return new Promise((resolve) => { setTimeout(() => { resolve(data + 'b'); console.log(data + 'b'); }, 2e3); }); } // 异步函数cFun var cFun = function (data) { return new Promise((resolve) => { setTimeout(() => { resolve(data + 'c'); console.log(data + 'c'); }, 2e3); }); } async function queue(arr, paramInit) { let res = paramInit; for (let promise of arr) { res = await promise(res); } return await res; } queue([aFun, bFun, cFun], 'a').then(data => data); // a // ab // abc
Map 对象
Map 对象保存键值对。任何值(对象或者原始值)都可以作为一个键或一个值。
Maps 和 Objects 的区别:
- 键名:一个 Object 的键只能是字符串或者 Symbols,但Map允许任意值;
- 排序:Map 中键值是有序的(FIFO 原则),而添加到Object中的键则不是;
- 数量:Map 的键值对数量可从 size 属性获取,而 Object 的只能手动计算;
- 冲突:Object 原型链上的键名有可能和自定义的键名产生冲突。
Map中的key:
- key是字符串;
- key是对象,如下:
var myMap = new Map(); var keyObj = {}, myMap.set(keyObj, "和键 keyObj 关联的值"); myMap.get(keyObj); // "和键 keyObj 关联的值" myMap.get({}); // undefined, 此{}非彼keyObj
- key是函数,如下:
var myMap = new Map(); var keyFunc = function () {}, // 函数 myMap.set(keyFunc, "和键 keyFunc 关联的值"); myMap.get(keyFunc); // "和键 keyFunc 关联的值" myMap.get(function() {}) // undefined, 此function () {}非彼keyFunc
- key是NaN,如下:
var myMap = new Map(); myMap.set(NaN, "not a number"); myMap.get(NaN); // "not a number" var otherNaN = Number("foo"); myMap.get(otherNaN); // "not a number",虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),但NaN作为Map的键来说没什么区别。
Map遍历
- for…of 、解构方法,如下:
var myMap = new Map(); myMap.set(0, "zero"); myMap.set(1, "one"); for (var [key, value] of myMap) { console.log(key + " = " + value); } // 0 = zero // 1 = one for (var [key, value] of myMap.entries()) { console.log(key + " = " + value); } // 0 = zero // 1 = one for (var key of myMap.keys()) { console.log(key); } // 0 // 1 for (var value of myMap.values()) { console.log(value); } // zero // one
- forEach() 、箭头函数,如下:
var myMap = new Map(); myMap.set(0, "zero"); myMap.set(1, "one"); myMap.forEach((value, key) => { console.log(key + " = " + value); }, myMap); // 0 = zero // 1 = one
Map 操作
- 与Array转换,如下:
var kvArray = [["key1", "value1"], ["key2", "value2"]]; // 转为Map var myMap = new Map(kvArray); // Map(2) {"key1" => "value1", "key2" => "value2"} // 转为Array var outArray = Array.from(myMap); // (2) [Array(2), Array(2)] // 0: (2) ["key1", "value1"] // 1: (2) ["key2", "value2"]
- 克隆,如下:
var kvArray = [["key1", "value1"], ["key2", "value2"]]; var myMap1 = new Map(kvArray); var myMap2 = new Map(myMap1); console.log(myMap1 === myMap2); // false,Map对象会构造出新的对象
- 合并,如下:
// 合并2个二维数组 var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]); var second = new Map([[1, 'uno'], [2, 'dos']]); var merged = new Map([...first, ...second]); // Map(3) {1 => "uno", 2 => "dos", 3 => "three"} // 合并时,如有重复的键值,后面的会覆盖前面的
Set对象
Set 对象允许存储任何类型的唯一值,无论是原始值或者是对象引用。
特殊值:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
let mySet = new Set([+0, -0, undefined, undefined, NaN, NaN]); // Set(3) {0, undefined, NaN} 验证以上3项不重复 var o = {a: 1, b: 2}; mySet.add(o); mySet.add({a: 1, b: 2}); // Set(5) {0, undefined, NaN, {…}, {…}} 对象类型认为非同一个
- 类型转换Array,如下:
// Array转Set var mySet = new Set(["value1", "value2", "value3"]); // Set(3) {"value1", "value2", "value3"} // Set转Array var myArray = [...mySet]; // (3) ["value1", "value2", "value3"] // String转Set new Set('hello'); // Set(4) {"h", "e", "l", "o"}
- 数组去重,如下:
new Set([1, 2, 3, 4, 4, 'a', 'a', 'b']); // Set(6) {1, 2, 3, 4, "a", "b"}
- 并集,如下:
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var union = new Set([...a, ...b]); // {1, 2, 3, 4}
- 交集,如下:
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
- 差集,如下:
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var difference = new Set([...a].filter(x => !b.has(x))); // {1}
模块化
- 导出方式,如下:
// 逐一暴露 export let name = 'Neusofts'; export function action() { console.log('Neusofts'); } // 统一暴露 let name = 'Neusofts'; function action() { console.log('Neusofts'); } export { name, action }; // 默认暴露 export default { name: 'Neusofts', action: function () { console.log('Neusofts'); } }
- 导入方式,如下:
// 通用的导入方式 import * as All from './m1.js'; // 解构赋值形式 import { name, action } from './m2.js'; // 默认暴露引入方式 import obj from './m3.js';
其他
- class类
- function可设置默认值
附加内容
.
ES7新特性
- Array.prototype.includes(searchElement: any, fromIndex?: number),检测数组中是否包含某个元素,如下:
var arr = ['www', 'neusofts', 'com']; arr.includes('neusofts'); // true
- 指数操作符**,用来幂运算,功能与 Math.pow(x: number, y: number): number 相同,如下:
console.log(2 ** 3); // 8 Math.pow(2, 3); // 8
- async和await,改造Promise实现的Ajax的示例,如下:
function Ajax(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { // 判断响应状态码 200 ~ 299 if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response); } else { reject(xhr.status); } } } }); } async function getInfo() { let res1 = await Ajax('https://api.apiopen.top/getJoke'); console.log(res1); console.log('-'.repeat(43)); let res2 = await Ajax('https://api.apiopen.top/getJoke'); console.log(res2); } getInfo(); // text1_text1_text1_text1_text1_text1_text1 ... // ------------------------------------------- // text2_text2_text2_text2_text2_text2_text2 ...
提案
- 定义类的私有变量“#”,如下:
class Test { #mySelf = '私有变量'; fun(){ console.log(this.#mySelf); } } let test = new Test(); test.fun(); // 私有变量
- Optional Chaining(可选链式调用),如下:
// 需求:读取某对象下的某个属性 const data = { user: {}, }; // 直接读取,报错: console.log(data.user.address.street); // Uncaught TypeError: Cannot read property 'street' of undefined // 报错原因是 user 中没有 address 对象,然后我们这样判断: const street = data && data.user && data.user.address && data.user.address.street; console.log(street); // undefined // 这样的写法很不爽 // 新特性可以这样写( ?. 类似于angular5的安全操作符) console.log(data.user ?. address ?. street); // undefined
- Nullish coalescing(空值合并),如下:
// 我们判断空值一般这样: value != null ? value : 'default value'; // 或者这样: value || 'default value'; // 新特性可以这样写( ?? ): value ?? 'default value'; let a = 0; let b = a || "aaa"; let c = a ?? "aaa"; console.log("b的值是 " + b); // b的值是 aaa console.log("c的值是 " + c); // c的值是 0
- Pipeline operator(管道运算符),目前vscode和chrome尚未支持测试,代码示例如下:
// 举个例子,通过三个函数对字符串进行处理,通常是这样: function a(str) { return str + ", " + str; } function b(str) { return str[0].toUpperCase() + str.substring(1); } function c(str) { return str + '!'; } let result = c(b(a("hello"))); // "Hello, hello!" // 通过管道运算符,我们可以这样写: let result = "hello" |> a |> b |> c // "Hello, hello!"
近期评论