前言
一直没有把ES6标准系统地进行学习,虽然很多新特性已经用上了,但也该系统地学习一下
还是阮一峰讲得全面些:ECMAScript 6 入门 | 阮一峰 很多新特性,多用就记住了
ES6简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言
ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准
let和const
let 声明变量,const声明常量(内存地址所保存的数据不得改动)
1、块级作用域
var a = 2;{ let a = 1; console.log(a); // 1}console.log(a); // 22、同一个块内不允许覆盖、重复声明
var a = 1;var a = 2;console.log(a); // 2let b = 1;let b = 2; // 'b' has already been declared{ let b = 2; console.log(b); // 2}3、没有变量提升
console.log(a); // undefinedvar a = 1;console.log(b); // Cannot access 'b' before initializationlet b = 2;4、暂时性死区 只要块级作用域内存在let命令,它所声明的变量就绑定这个区域,不再受外部的影响
只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
console.log(typeof a); // undefinedconsole.log(typeof b); // undefinedvar b = 1;console.log(typeof x); // ReferenceErrorlet x = 2;Tip: **1、**循环中的 var 与 let:
var 会提升进行声明,数组中所有函数访问的都是同一个 i
var a = [];for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); };}a[6](); // 10变量 i 是 let 声明的,当前的 i 只在本轮循环有效,所以每一次循环的 i 都是一个新的变量,但在循环中,JavaScript 引擎会记住每一次 let 的值,下一次创建的时候,直接在这个值上递进
var a = [];for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); };}a[6](); // 6**2、**const 声明的对象仍然是可写的,可以使用 Object.freeze() 冻结对象
当对象嵌套时,需要递归冻结
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } });};解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值
数组解构
**1、**只要等号两边的模式相同,左边的变量就会被赋予对应的值
let [a, b, c, ...d] = [1, 2, 3, 4, 5, 6, 7, 8];console.log(a); // 1console.log(d); // [ 4, 5, 6, 7, 8 ]**2、**如果解构不成功,变量的值就等于undefined
let [a] = [];console.log(a); // undefinedlet [b, c] = [1];console.log(c); // undefined**3、**解构赋值允许指定默认值,只有当一个数组成员严格等于undefined,默认值才会生效
let [b, c = 2] = [1];console.log(c); // 2如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined
let [x = 1] = [undefined];x // 1let [x = 1] = [null];x // null如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值
function f() { console.log('123');}let [a = f()] = [1]; // 不执行f()let [b = f()] = []; // 123对象解构
**1、**解构提取对象中的属性
const obj = { a: 1, b: 2,}let { a, b } = obj // 变量名与属性名需相同console.log(a) // 1变量名与属性名不一致,需写成下面这样
const obj = { a: 1}let { a : x } = objconsole.log(x) // 1**2、**嵌套结构的对象
const obj = { p: [ 'Hello', { y: 'World' } ]};let { p, p: [x, { y }] } = obj;console.log(x); // Helloconsole.log(y); // World**3、**剩余运算符,将其它属性展开到一个对象中存储
const obj = { a: 1, b: 2,}const a = { ...obj, b: 3, // 可以覆盖 c: 4, // 默认值}console.log(a.a, a.b, a.c); // 1 3 4**4、**可以取到继承的属性
const obj1 = {};const obj2 = { a: 1 };Object.setPrototypeOf(obj1, obj2);const { a } = obj1;console.log(a); // 1函数参数解构
函数的参数也可以使用解构赋值
function add([x, y]){ return x + y;}add([1, 2]); // 3[[1, 2], [3, 4]].map(([a, b]) => a + b); // [ 3, 7 ]技巧
**1、**交换变量的值
let x = 1;let y = 2;let [x,y] = [y,x];**2、**函数返回多个值
// 返回一个数组function example() { return [1, 2, 3];}let [a, b, c] = example();
// 返回一个对象function example() { return { foo: 1, bar: 2 };}let { foo, bar } = example();函数的扩展
1、带参数默认值的函数
function f(a, b = 1) { }function f(a, b = fun()) { }2、rest 参数 获取函数的多余参数,不需要使用 arguments 对象,rest 参数之后不能再有其他参数
function f(a, ...b) { console.log(a, b); // 1 [2, 3]}f(1, 2, 3);3、name 属性 返回函数名
function foo() {}foo.name // "foo"箭头函数
使用“箭头”(=>)定义函数
var f = v => v;// 等同于var f = function (v) { return v;};多种写法
var f = v => v;var f = v => { return v };var f = (a, b) => a + b;简化回调函数
[1,2,3].map(function (x) { return x * x;});[1,2,3].map(x => x * x);箭头函数的特性: 1、没有自己的 this 2、不可以当作构造函数 3、不可以使用 arguments 对象 4、不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数
对象的扩展
1、属性简洁表示
let [a, b] = [1, 2];const obj = { a, b, fun(){ } // 简写对象方法}2、属性名表达式 属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串 属性名表达式与简洁表示法,不能同时使用
obj['a' + 'bc'] = 123; // obj.abc = 123let propKey = 'foo';let obj = { [propKey]: true, ['a' + 'bc']: 123};Object方法
1、is()
判断两个值是否相同,解决了 NaN 问题
Object.is(null,undefined); // falseObject.is(NaN,NaN); // trueNaN === NaN // false2、assign()
合并多个对象,并返回第一个参数的引用,冲突属性后面覆盖前面
let obj1 = { a: 1 }let obj2 = { a: 2, b: 3 }let obj3 = Object.assign(obj1, obj2); // {a: 2, b: 3}console.log(obj3 === obj1); // true3、keys() 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键名
4、values() 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值
5、entries() 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组
6、fromEntries() entries() 的逆操作,用于将一个键值对数组转为对象
7、hasOwn() 判断某个属性是否为自身的属性
数组的扩展
1、扩展运算符 ...
扩展运算符将一个数组转为用逗号分隔的参数序列
console.log(...[1, 2, 3]) // 1 2 3console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5function add(x, y) { return x + y }add(...[4, 38]) // 42替代用到 apply() 和 push() 的某些操作
Math.max.apply(null, [11, 33, 22]) // 33Math.max(...[11, 33, 22]) // 33let arr1 = [0, 1, 2]let arr2 = [3, 4, 5]arr1.push.apply(arr1, arr2)arr1.push(...arr2)Array方法
1、Array.from() 将两类对象转为真正的数组:类似数组的对象和可遍历的对象(包括 ES6 新增的 Set 和 Map)
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3};console.log([].slice.call(arrayLike)) // [ 'a', 'b', 'c' ]console.log(Array.from(arrayLike)) // [ 'a', 'b', 'c' ]接收第二个参数,回调函数,对每个元素进行处理
let arrayLike = { '0': 1, '1': 2, length: 2};console.log(Array.from(arrayLike, item => item * 2)) // [ 2, 4 ]2、Array.of() 将一组任意类型的值,转换为数组
Array.of(1, 2) // [1, 2]Array(3) // [, , ,]Array.of(3) // [3]实例方法
1、copyWithin(target, start = 0, end = this.length) 将指定位置的成员复制到其他位置(会覆盖),然后返回当前数组
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
[1,2,3,4,5].copyWithin(0,3) // [4, 5, 3, 4, 5]2、find() 找出第一个符合条件的数组成员。传入一个回调函数,没有符合条件的则返回 undefined
[1, -2, -5, 10].find(n => n < 0) // -2[1, -2, -5, 10].find(function(value, index, arr) { return value < 0;})findIndex() 返回第一个符合条件的数组成员的位置,没有符合条件的则返回 -1
findLast() findLastIndex() 从数组的最后一个成员开始,依次向前检查
可以接受第二个参数,用来绑定回调函数的this对象
3、fill() 使用给定值,覆盖填充一个数组
可以接受第二、三个参数,用于指定填充的起始位置和结束位置
['a', 'b', 'c'].fill(7) // [7, 7, 7]new Array(3).fill(7) // [7, 7, 7]若填充对象,数组中填充的是该对象的引用
4、entries() keys() values() 用于遍历数组,都返回一个遍历器对象
keys() 是键名的遍历,values() 是键值的遍历,entries() 是键值对的遍历
for (let index of ['a', 'b'].keys()) { console.log(index); // 0 // 1}
for (let elem of ['a', 'b'].values()) { console.log(elem); // 'a' // 'b'}
for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); // 0 "a" // 1 "b"}5、includes() 判断数组中是否包含某个值,可选第二个参数表示搜索的起始位置,默认为0
[1, 2, 3].includes(2) // true[1, 2, NaN].includes(NaN) // true6、flat() 将多维数组解构为一维,传入参数表示总共解构多少层,默认 1
该方法返回一个新数组,对原数据没有影响
如果不管有多少维,都要转成一维数组,可以用 Infinity 作为参数。 如果原数组有空位,flat()方法会跳过空位
[1, 2, [3, 4]].flat() // [1, 2, 3, 4][1, 2, [3, 4, [5, 6]]].flat() // [1, 2, 3, 4, [5, 6]][1, 2, [3, 4, [5, 6]]].flat(2) // [1, 2, 3, 4, 5, 6][1, [2, [3]]].flat(Infinity) // [1, 2, 3][1, 2, , 4, 5].flat() // [1, 2, 4, 5]flatMap() 传入一个遍历函数,先执行 map() 再 flat() 只能展开一层数组。
可选第二个参数,用来绑定遍历函数里面的this
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()[2, 3, 4].flatMap((x) => [x, x * 2])// [2, 4, 3, 6, 4, 8]7、at() 返回指定索引上的值,允许负索引
该方法不仅可用于数组,也可用于字符串和类型数组
let arr = [1, 2, 3]arr[arr.length - 1] // 3arr.at(-1) // 38、toReversed() toSorted() toSpliced() with()
含义和用法完全一样,但不改变原数组,而返回一个原数组的拷贝:
toReversed() 对应 reverse(),用来颠倒数组成员的位置。
toSorted() 对应 sort(),用来对数组成员排序。
toSpliced() 对应 splice(),用来在指定位置,删除指定数量的成员,并插入新成员。
with(index, value) 对应 splice(index, 1, value),用来将指定位置的成员替换为新的值。
9、group((item, index, array) => {}) 数组成员分组
根据分组函数的运行结果,将数组成员分组,分组函数的返回值应该是字符串(或者可以自动转为字符串),以作为分组后的组名
还未实装的提案
let arr = [1,2,3,4,5];let result = arr.group(item => { return item % 2 === 0 ? 'even': 'odd';});console.log(result);运算符的扩展
1、指数运算符**
多个指数运算符连用时,是从最右边开始计算的
2 ** 2 // 42 ** 3 ** 2 // 相当于 2 ** (3 ** 2)let b = 4;b **= 3; // 等同于 b = b * b * b;2、链判断运算符?.
如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在
// 错误的写法const firstName = message.body.user.firstName || 'default';// 正确的写法const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default';使用 ?. 运算符简化写法
在链式调用的时候判断左侧的对象是否为 null 或 undefined。如果是,则不再往下运算,直接返回 undefined
const firstName = message?.body?.user?.firstName || 'default';判断对象方法是否存在,如果存在就立即执行:
obj.fun?.()3、NULL 判断运算符??
使用 || 运算符指定默认值时,只要属性的值为 null 或 undefined,默认值就会生效,但是属性的值如果为空字符串或 false 或 0,默认值也会生效
Null 判断运算符 ?? 类似 ||,但是只有运算符左侧的值为 null 或 undefined 时,才会返回右侧的值
与链判断运算符 ?. 配合使用,为 null 或 undefined 的值设置默认值。
const a = obj?.a ?? 10;Symbol类型
Symbol 是新的基本数据类型,表示独一无二的值
let s = Symbol();console.log(typeof s) // symbolconsole.log(Symbol('sss') == Symbol('sss')) //falseSymbol 值通过 Symbol() 函数生成,允许接受一个字符串作为参数,表示对 Symbol 实例的描述。这主要是为了在控制台显示,或者转为字符串时,比较容易区分。
如果 Symbol 的参数是一个对象,就会调用该对象的 toString() 方法,将其转为字符串,然后再生成一个 Symbol 值
let s = Symbol('sss');console.log(s) // Symbol(sss)console.log(s.toString()) // Symbol(sss)
const obj = { toString() { return 'abc'; }}s = Symbol(obj);console.log(s) // Symbol(abc)Symbol 值也可以转为布尔值(true),但是不能转为数值
let s = Symbol();console.log(Boolean(s)) // trueconsole.log(Number(s)) // TypeError作为属性名
现在,对象的属性名可以是字符串或 Symbol 值
由于每一个 Symbol 值都是不相等的,这意味着只要 Symbol 值用于对象的属性名,就能保证不会出现同名的属性
let s = Symbol('123');const obj = { a: 1, [s]: 's'}obj[Symbol('123')] = 123console.log(obj) // { a: 1, [Symbol(123)]: 's', [Symbol(123)]: 123 }console.log(obj[s]) // sSymbol 值作为对象属性名时,用点运算符无法获取该属性
let s = Symbol('s');const obj = { [s]: 's', s: 123};console.log(obj.s) // 123console.log(obj[s]) // sSymbol 值作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回,所以通常可以当做私有属性来使用
Object.getOwnPropertySymbols() 方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值
Reflect.ownKeys() 方法可以返回所有类型的键名,包括常规键名和 Symbol 键名
const obj = { a: 1, [Symbol('s')]: 's',};for (let key in obj) { console.log(key); // a}console.log(Object.keys(obj)); // [ 'a' ]
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(s) ]console.log(Reflect.ownKeys(obj)); // [ 'a', Symbol(s) ]Symbol方法
1、Symbol.for() 将生成的 Symbol 值登记在全局环境中供搜索,若有相同描述的 Symbol 值则复用返回
Symbol() 写法没有登记机制
其登记机制可以用在不同的 iframe 或 service worker 中取到同一个值
let s1 = Symbol('s');let s2 = Symbol.for('s');let s3 = Symbol.for('s');console.log(s1 === s2); // falseconsole.log(s2 === s3); // true2、Symbol.keyFor() 返回一个已登记的 Symbol 类型值的参数 key
let s1 = Symbol('s');let s2 = Symbol.for('s');console.log(Symbol.keyFor(s1)) // undefinedconsole.log(Symbol.keyFor(s2)) // sMap对象
Map对象保存键值对,元素会保持其插入时的顺序。
Map的键可以是任意数据类型,包括函数、对象或任意基本类型。
在需要进行很多新增操作,且需要储存许多数据的时候,使用 Map 会更高效
var m = new Map();//或传入一个嵌套数组m = new Map([ ['x', 1], ['y', 2]]);Object与Map增删改查基本操作。
var o = {};var m = new Map();//添加o.x = 1;m.set('x', 1);//修改o.x = 2;m.set('x', 2);//递增o.x++;m.set('x', m.get('x')+1);//获取o.x;m.get('x');//删除delete o.x;map.delete('x');Map的键值对个数可以通过size属性获取
var m = new Map();m.set('x', 1);m.set('y', 2);console.log(m);//Map(2) {'x' => 1, 'y' => 2}console.log(m.size);//2Map的方法
基本方法:
- **
get():**获取元素 - **
set():**设置元素 - **
has():**检查是否有指定key - **
clear():**清空map - **
delete():**删除指定元素
遍历方法:
keys():提取键并返回键的迭代器MapIterator对象values():提取值并返回值的迭代器MapIterator对象entries():提取键值对并返回取键值对的迭代器MapIterator对象- **
forEach():**传入回调函数(value, key)=>{}
var m = new Map([ ['x', 1], ['y', 2]]);
console.log(m.keys());//MapIterator {'x', 'y'}console.log(m.values());// MapIterator {1, 2}console.log(m.entries());// MapIterator {'x' => 1, 'y' => 2}
//迭代器可以用for-of遍历for (let [key, value] of m.entries()) { console.log(key, value);//x 1, y 2}
m.forEach((value, key) => { console.log(key, value);//x 1, y 2})Set对象
Set是唯一值的集合,与map类似,map存放的是键值对,而set只存放唯一值。
创建set对象:
var s = new Set();var s = new Set([1,2,3]);set对象与数组也很像,可以互相转换
var arr1 = [1, 2, 3];var s = new Set(arr1);//数组转为set对象,会去重var arr2 = [...s];//set对象转为数组可利用set值唯一的特性做数组去重、并集、交集、差集操作
//1、去重var s = new Set([1,2,3,3,2,1]);var arr = [...s];//[1,2,3]
//2、并集var arr1 = [1, 2, 3];var arr2 = [2, 3, 4];var s = new Set([...arr1, ...arr2]); // {1, 2, 3, 4}var arr = [...s];// [1,2,3,4]
//3、交集,arr1和arr2共有的元素var arr1 = [1, 2, 3];var arr2 = [2, 3, 4];var s1 = new Set(arr1);//把数组转为set对象方便操作var s2 = new Set(arr2);var result = arr1.filter(x => s2.has(x));//[2, 3]
//4、差集,arr1去除arr2中的元素var arr1 = [1, 2, 3];var arr2 = [2, 3, 4];var s1 = new Set(arr1);//把数组转为set对象方便操作var s2 = new Set(arr2);var result = arr1.filter(x => !s2.has(x));//[1]Set值个数可以通过size属性获取
var s = new Set([1,2,3]);console.log(s);//Set(3) {1, 2, 3}console.log(s.size);//3Set的方法
- **
add():**添加新元素 - **
delete():**删除指定元素 - **
clear():**清空所有元素 - **
has():**判断是否存在某值 - **
forEach():**遍历每个元素,传入回调函数 - **
keys():**返回一个 Iterator 对象,这个对象以插入Set 对象的顺序包含了原 Set 对象里的每个元素 - **
values():**同keys() - **
entries():**返回 [value, value] 形式的数组迭代器对象,value 是给定集合中的每个元素,迭代器对象元素的顺序即集合对象中元素插入的顺序
add()、delete()、clear()、has() :
var s = new Set([1,2,3]);s.add(4);console.log(s);//Set(4) {1, 2, 3, 4}s.delete(2);s.has(2);//falses.clear();console.log(s.size);// 0**keys()、values() :**返回一个 Iterator 对象,这个对象以插入Set 对象的顺序包含了原 Set 对象里的每个元素
var s = new Set([1,2,3]);var setIter = s.values();console.log(setIter);//SetIterator {1, 2, 3}console.log(setIter.next().value); // 1console.log(setIter.next().value); // 2console.log(setIter.next().value); // 3**entries() :**返回 [value, value] 形式的数组迭代器对象,value 是给定集合中的每个元素,迭代器对象元素的顺序即集合对象中元素插入的顺序
var s = new Set([1,2,3]);var setIter = s.entries();console.log(setIter);//SetIterator {1 => 1, 2 => 2, 3 => 3}console.log(setIter.next().value); // [1, 1]console.log(setIter.next().value); // [2, 2]console.log(setIter.next().value); // [3, 3]**forEach() :**遍历每个元素,传入回调函数,参数:回调函数、thisArg执行回调函数时可以当作this来使用。 回调函数参数:值(key)、值(value)、set对象
var s = new Set([1,2,3]);s.forEach((key,value,set)=>{ console.log(key,value);//1 1, 2 2, 3 3});迭代/遍历器Iterator
ES6之后,表示“集合”的数据结构,在原先 Array 和 Object 基础上,又增加了 Map 和 Set
遍历器 Iterator 用于规范统一地对集合进行遍历操作,是一个能访问数据的接口
Iterator 的遍历过程: (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。 (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。 (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。 (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息(一个包含 value、done 属性的对象) value:当前成员的值 done:布尔值,表示遍历是否结束
总之,遍历器使得不同的数据结构都可以被 for-of 遍历,只要实现了 Symbol.iterator
Symbol.iterator
Symbol.iterator 是一个预定义好的、类型为 Symbol 的特殊值,是 Iterator 接口
一个数据结构只要具有 Symbol.iterator 属性,就是可遍历的,也就是可以用 for-of 进行遍历
Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器
const obj = { [Symbol.iterator] : function () { return { next: function () { return { value: 1, done: true }; } }; }};原生具备 Iterator 接口的数据结构:Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象
let arr = ['a', 'b', 'c'];let iter = arr[Symbol.iterator]();iter.next() // { value: 'a', done: false }iter.next() // { value: 'b', done: false }iter.next() // { value: 'c', done: false }iter.next() // { value: undefined, done: true }// for-of 遍历let arr = ['a', 'b', 'c'];for(let item in arr) { console.log(item);}生成器Generator
生成器是一种函数,function关键字与函数名之间有一个星号 *,函数体内部使用yield表达式
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是一个遍历器对象
yield 关键字会暂停函数的执行,调用遍历器对象的 next 方法,能让函数执行到下一个 yield 暂停处(或是return)
总之 Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
function* fun() { console.log(1); yield 'qx'; console.log(2); yield 'chuckle'; console.log(3);}const f = fun();console.log(f.next())console.log(f.next())console.log(f.next())// 1// { value: 'qx', done: false }// 2// { value: 'chuckle', done: false }// 3// { value: undefined, done: true }next() 会返回一个包含 value、done 属性的对象,其中 value 值是 yield 后面表达式的值
next() 可以传入参数,yield 是将其返回后,函数再继续执行到下一个 yield
function* fun() { let x = yield; console.log(x); let y = yield; console.log(x+y);}const f = fun();console.log(f.next(1))console.log(f.next(2))console.log(f.next(3))// { value: undefined, done: false }// 2// { value: undefined, done: false }// 5// { value: undefined, done: true }使用 Generator 函数能够很方便地实现 Symbol.iterator,其目的也正是如此
function* objectEntries(obj){ // 获取对象的所有 key 保存到数组 [name, age] const propKeys = Object.keys(obj); for(const propkey of propKeys){ yield [propkey, obj[propkey]] }}const obj = { name: 'chuckle', age: 19}// 把 Generator 生成器函数赋值给对象的Symbol.iterator属性, 为该对象加上遍历器接口obj[Symbol.iterator] = objectEntries;// objectEntries(obj) 等价于 obj[Symbol.iterator](obj)for(let [key, value] of objectEntries(obj)){ console.log(`${key}: ${value}`);}// name: chuckle// age: 19Generator的应用
1、异步代码同步化
function* main(){ let res = yield request('https://v1.hitokoto.cn/'); console.log('1:'+res); res = yield request('https://v1.hitokoto.cn/'); console.log('2:'+res); res = yield request('https://v1.hitokoto.cn/'); console.log('3:'+res);}const ite = main();ite.next();
function request(url){ fetch(url) .then(res=>res.json()) .then(data=>{ ite.next(data.hitokoto) })}// 1:自己永远是孤单的,但你可以让其他人变得不孤单。// 2:我就是小偷,专门来偷走哥哥的心// 3:电脑的处理器总比执行单元小。2、正确的执行顺序
function* main(){ fun1(); yield fun2(); fun3();}let ite = main();ite.next();// 1 2 3
function fun1(){ console.log('1');}function fun2(){ setTimeout(()=>{ console.log('2'); ite.next(); },1000)}function fun3(){ console.log('3');}for-in 和 for-of
for-in(ES5) 和 for-of(ES6) 都是遍历数据的语法。
for-in 用于遍历对象的可枚举的属性(名称),会顺着原型链查找可枚举属性。
let obj = { name: '张三', age: 18}Object.prototype.sex = "男"
for(let item in obj) { console.log(item) // name、age、sex}而 for-of 用于遍历可迭代对象,即实现了 Symbol.iterator 接口的对象,会顺着原型链查找 Symbol.iterator 属性。
Array,Map,Set,String 等都实现了该接口,可以直接遍历其值。
let arr = ['a', 'b', 'c']for(let item of arr) { console.log(item) // a、b、c}Promise
之前已经记过笔记了:Promise异步编程
Class类
在 ES5 中生成实例对象的传统方法是通过构造函数
function Person(name, age) { this.name = name; this.age = age;}Person.prototype.sayName = function () { console.log(this.name);}let p = new Person('chuckle', 18);p.sayName() // chuckleES6新增了class语法糖,类本质上是函数,类本身指向其构造函数 constructor(),所以类的构造函数也就是类本身
class A { }console.log(typeof A === 'function'); // true构造函数和普通函数容易混淆,现在,可以通过 Class 关键字来声明一个类了,其内置的 constructor() 在实例化对象时会立即被调用
用法还是大差不差,并且和之前一样,类方法都在原型上
class Person { // 实例化的时候会立即被调用 constructor(name, age){ this.name = name; this.age = age; } //等同于Person.prototype.sayName = function sayName(){} sayName(){ console.log(this.name); }}let p = new Person('chuckle', 18);p.sayName() // chuckle由于类中定义的方法都在其构造函数的原型上,所以可以用 Object.assign() 向类添加多个方法
class Person { constructor(){}}Object.assign(Person.prototype, { toString(){}, toValue(){}});constructor函数
constructor() 方法是类的默认方法,在创建实例对象时,自动调用
默认返回实例对象(即this),也可以指定返回(return)另外一个对象
constructor() { return Object.create(null);}如果一个实例属性不需要接受外部的传参赋值,可以不在 constructor 方法中定义
class Person { constructor(name, age){ this.name = name; this.age = age; } country = 'china';}let p = new Person('chuckle', 18);console.log(p)// Person { country: 'china', name: 'chuckle', age: 18 }Class表达式
可以写成表达式的形式
const MyClass = class { name = 'chuckle'; SayName() { console.log(this.name); }};const myClass = new MyClass();myClass.SayName() // chuckle可以写出立即执行的 Class
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); }}('张三');person.sayName(); // 张三Static
在类成员前,加上static关键字,就表示静态属性或方法
静态即不会被实例继承,而是直接通过类来调用
静态方法可以与非静态方法重名
class Class{ name = 'chuckle'; static name = 'qx'; static SayName() { console.log(this.name); // 静态方法的this指类本身,只能访问静态属性 }};Class.SayName() // qxnew Class().SayName(); // TypeError: myClass.SayName is not a function父类的静态成员,可以被子类继承
class Foo { static num = 123; static classMethod() { return 'hello'; }}class Bar extends Foo {}Bar.classMethod() // 'hello'Bar.num // 123私有方法和属性
私有,即只能在类的内部访问的方法和属性,外部不能访问,有利于代码的封装
早期并没有途径实现真正的私有,只能通过特殊的命名规则加以区别
class Class { foo () {} // 公有 _fun() {} // 加上_表示私有}或者借助 Symbol 来让成员不易被获取,但仍然可以被 Reflect.ownKeys() 等方法获取到
let s1 = Symbol('name');let s2 = Symbol('fun')class Class { name = 'qx'; [s1] = 'chuckle'; [s2](){ console.log(this.name); };}const myClass = new Class()console.log(myClass) // Class { name: 'qx', [Symbol(name)]: 'chuckle' }myClass[s2]() // qxconsole.log(Object.getOwnPropertySymbols(myClass)) // [ Symbol(name) ]ES2022 正式为 class 添加了私有成员,方法是在属性或方法名之前使用 # 表示
class Class { #count = 0; get value(){ return this.#count; } increment() { this.#count++; }}const myClass = new Class();myClass.increment();console.log(myClass.value) // 1console.log(myClass.#count) // SyntaxError: Private field '#count' must be declared in an enclosing classconsole.log(myClass.count) // undefined不管在类的内部或外部,如果读取一个不存在的私有成员,会报错
私有成员前面,也可以加上 static 关键字,表示这是一个静态的私有成员
类的继承
Class 通过 extends 关键字实现继承,写法比 ES5 的原型链继承,要清晰和方便很多
class Animal { constructor(name, age){ this.name = name; this.age = age; } sayName(){ return this.name; } sayAge(){ return this.age; }}
class Dog extends Animal{ constructor(name, age, color){ // 子类的构造函数必须执行一次 super() 函数 super(name, age); // 等同于 Animal.call(this,name,age); 继承父类的属性 this.color = color; } // 子类自己的方法 sayColor(){ return `${this.name}是${this.age}岁了,它的颜色是${this.color}`; } // 重写父类的方法 sayName(){ // super 相同于 Animal return this.name + super.sayAge + this.color; }}
let d1 = new Dog('小黄', 28, 'red');console.log(d1.sayAge()); // 调用继承父类的方法console.log(d1.sayColor()); // 调用子类自己的方法console.log(d1.sayName()); // 调用重写父类的方法继承的原型链
类的继承是原型链继承,子类的原型的__proto__指向父类的原型,详见:原型与原型链
同时为了静态方法能够继承,子类的构造函数的__proto__指向父类的构造函数
class A { name = 'a' getName() { console.log(this.name) }}class B extends A { name = 'b'}// 继承实例方法console.log(Object.getPrototypeOf(B.prototype) === A.prototype) // true// 继承静态的类方法console.log(Object.getPrototypeOf(B) === A) // true两条原型链:
B.__proto__ === AB.prototype.__proto__ === A.prototypesuper
super 关键字,既可以当作函数使用,也可以当作对象使用
super 作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次 super() 函数 调用super()的作用是形成子类的this对象,任何对子类 this 的操作都要放在 super() 的后面
super() 相当于 A.prototype.constructor.call(this)(在子类的this上运行父类的构造函数)
class A {}class B extends A { constructor() { super(); this.name = 'qx'; }}super 作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类
定义在父类实例上的方法或属性,无法通过 super 调用
class A { p() { return 2; }}
class B extends A { constructor() { super(); console.log(super.p()); // 2 }}
let b = new B();Module模块化
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量,而 CommonJS 和 AMD 模块,都是运行时的
// CommonJS模块let { stat, exists, readfile } = require('fs');// ES6模块import { stat, exists, readFile } from 'fs';cES6 模块主要有两个命令构成:export 和 import
export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量
const name = '张三';const age = 18;const sayName = function() { console.log(fristName); }export {name, age, sayName}
import {name, age, sayName} from './modules/index.js';默认暴露 export default 为模块指定默认输出,import命令可以为该匿名函数指定任意名字
export default function(){ console.log('chuckle');}
import sayName from './modules/index.js'import { default as sayName } from './modules/index.js'import sayName, { name } from './modules/index.js'sayName(); // chuckle整体加载:模块暴露的内容都会作为该对象的成员
import * as obj from './modules/index.js';在浏览器环境中,需要给 script 标签加上 type='module' 属性
<script type='module'> import { name } from './modules/index.js';</script>动态import()
使用 import() 函数动态引入模块
if(true){ import('./modules').then(mod=>{ console.log(mod) // mod对象中包含模块暴露的成员 })}
评论