我的ES6总结教程
[TOC]
解构
数组解构
// 赋值
let [a, b, c] = [1, 2, 3];
// 还可以解构基于Iterator的数据结构
let [x, y, z] = new Set(['a', 'b', 'c']);
// 默认值 x='a', y='b
let [x, y = 'b'] = ['a', undefined];
对象解构
// 变量必须与属性同名,才能取到正确的值
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
const { log } = console;
log('hello') // hello
字符串解构
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
函数参数解构
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
圆括号
作用:
// 错误的写法
let x;
{x} = {x: 1};
// 正确的写法
let x;
({x} = {x: 1});
解构唯一可以使用圆括号:赋值语句的非模式部分(左侧部分),可以使用圆括号。
只有在赋值,并且没有声明语句时可用。否则全部不可用!
[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
函数的扩展
rest 参数
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
// 注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
// 报错
function f(a, ...b, c) {
// ...
}
箭头函数
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
// 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
// 如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
let fn = () => void doesNotReturn();
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
注意:由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
数组的扩展
拓展运算符
作用:扩展运算符是三个点(...),将一个数组转为用逗号分隔的参数序列。
Math.max(...[14, 3, 77])
// 复制数组
const a1 = [1, 2];
// 写法一 解开并重新成组
const a2 = [...a1];
// 写法二
const [...a2] = a1;
// 合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
// 转换Iterator结构为数组
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
// 转换Map Set为数组
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
对象的拓展
属性表达式
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
扩展运算符
// 拷贝作用
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
// 分解数组为对象
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
链判断运算符
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
null判断运算符
??行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
??和||的区别
a ?? b
a !== undefined && a !== null ? a : b
对于undefined和null, ??操作符的工作原理与||操作符相同
> undefined ?? 'default'
'default'
> null ?? 'default'
'default'
除了 undefined 和 null的其它虚值,?? 不会返回默认值。
> false ?? 'default'
false
> '' ?? 'default'
''
> 0 ?? 'default'
0
参考文档:传送门
**注意:
双竖线判断的是逻辑性的(相当于两个等号==,而不是三个等号===),
双问号的判断是为空判断的(相当于三个等号===,会进行类型判断)。**
Symbol
概念:
Symbol是新的数据类型,表示独一无二的值。它是JavaScript语言的第七种数据类型。
凡是属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
定义属性名
在对象里面定义属性时,要使用[]
的方式进行定义,不能使用.
操作符定义,因为会被js把.
操作符理解为字符串类型。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
// 不可使用.操作符
const mySymbol = Symbol();
const a = {};
// 使用.操作符会让js理解为是字符串
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
属性名的遍历
使用Object.getOwnPropertySymbols()
方法,才可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。(返回Symbol属性数组)
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
// [Symbol(a), Symbol(b)]
其它方式:
Reflect.ownKeys()
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]
Symbol.for(),Symbol.keyFor()
Symbol.for和Symbol的区别:
Symbol的参数为该对象的描述而已,所以即便参数的字符串相同,但是对象是不一样的。
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
Symbol.for()的参数会搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值
这里可以用用户名来理解:注册一个用户名,比如叫admin,用户名是唯一性的。Symbol.for()就是用于获取有没有注册过该用户名,如果有就提取出来使用,如果没有则新建这个用户名并保存。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
Symbol.keyFor()
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key
。只支持由Symbol.for()注册的对象。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
// 没有使用Symbol.for注册的所以获取不到
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
Symbol.hasInstance
// 狗类
// function Dog(){
// }
// 狗类
class Dog{
// 用中括号,包括起来之后,它就形成了一个方法,可以进行控制instanceof操作符的返回结果
// 重写了Dog类被使用instanceof的方法,可以操纵返回值。
// 参数就是instanceof操作符前面的对象
// dog instanceof Dog 里面的dog
[Symbol.hasInstance](obj){
// 返回一个bool类型的值,代表instanceof的返回值(逻辑型)
return obj instanceof Array;
}
}
var dog = new Dog();
// 判断当前的对象是否属于Dog类
if (dog instanceof Dog){
// true
}
// True
[1,2,3] instanceof Dog
Symbol.iterator
使当前的类,或对象(Object)支持遍历,类似于数组,支持for..of。
let obj = {
// 用来实现该对象,支持遍历迭代。
*[Symbol.iterator](){
yield 1;
yield 2;
}
}
for(let a of obj){
console.log(a);
}
// 1
// 2
yield
类似于return
,但是不会终止当前方法的执行,它只是为了迭代使用时,返回当前所属的一个值。
Iterator(遍历器,迭代器)
Iterator是一种接口,任何数据结构只要实现 Iterator 接口,就可以使用遍历、迭代。
Iterator里含有一个next
方法,每次调用它,都会将游标向下移动一次(可以理解为数组下标+1)。next
方法每次调用会有返回值,返回一个对象,其中有value和done的属性。value是值,done是bool代表是否遍历完成。
let obj = next();
obj: {
value:1,
done: true/false
}
在next
方法中返回的done和value缺一不可:
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
// 使用range方法可以返回一个RangeIterator对象,该对象支持迭代。
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
注意:解构和拓展运算符,都需要支持Iterator接口
实现Iterator的重要代码 generator和iterator+yield
yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
我们要实现一个支持遍历的方法或对象,先要实现它的Iterator接口,并使用generator和yield提供的简写方式,这样会变得更简单。
// 当你使用yield的实现时,即无需实现next方法,它将自动实现,
// 但是yield必须要在generator里才能使用
let generator = function* () {
yield 1;
// yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
for(let x of iterator){
console.log(x);
}
遍历器对象的 return(),throw()
next()
方法是必须部署的,return()
方法和throw()
方法是否部署是可选的。
如果for...of
循环提前退出(通常是因为出错,或者有break
语句),就会调用return()
方法。
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}
Generator
Generator 函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
yield的返回值
yield
可以返回值,这个值,就是从next
方法传递进去的参数。
function* f() {
for(var i = 0; true; i++) {
// 在这里我们可以看到yield返回了一个bool类型的值,
// 这个值来自于下面next的参数。
// g.next(true)
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
对任意对象Object
或class
实现迭代,务必要实现Symbol.iterator
接口方法,才可以实现迭代#
号代表私有成员属性
class Cat{
constructor(){
this.#index = 0;
}
add(item){
this[this.#index] = item;
this.#index++;
}
*[Symbol.iterator](){
for(let i = 0; i < this.index; i++){
yield this[i]
}
}
}
使用private
class Cat{
#index = 0
add(item){
this[this.#index] = item;
this.#index++;
}
*[Symbol.iterator](){
for(let i = 0; i < this.#index; i++){
yield this[i]
}
}
}