[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...infor...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 }

对任意对象Objectclass实现迭代,务必要实现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]
        }
    }
}

添加新评论