ES6 个人总结的一些实用方法

234 阅读10分钟

1.ES6新增哪些内容

  • let const 变量声明
  • 模板字符串 ``
  • 变量的结构赋值
  • Set Map数据结构
  • Promise
  • async await 语法糖
  • Symbol 数据类型
  • Proxy
  • Reflect
  • Class 类
  • 箭头函数
  • 剩余参数
  • 函数参数默认值
  • 严格相等
  • ES2020 BigInt 大整数 基本数据类型

2. let const var 的区别

  • 作用域以及变量提升 let const是块级作用域,var是函数作用域
// let const 的块级作用域
{
    let l1 = 1;
    const s1 = 1
    console.log(l1);   // 1
    console.log(s1);   // 1
}
console.log(l1,s1);    // l1 is not defined
// var 的作用域影响,var在全局声明的变量,会被挂载到window上
var a = 1;
let b = 1;
const c = 1;
console.log(window.a, a);  // 1 , 1
console.log(window.b, b);  // undefind , 1
console.log(window.c, c);  // undefind , 1
if (true) {
    var d = 1;
    let e = 1;
    const f = 1;
    console.log(d, e, f);   // 1 , 1 , 1
}
console.log(window.d);  //1
console.log(window.e);  // undefind
console.log(e);  // e is not defind
// var 的函数作用域
var foo = 'aaa'
function change(){
    foo = 'ccc'
    var foo  = 'bbb'
    console.log(foo);   // bbb
    // 函数内的代码相当于 var foo ; foo = 'ccc'; foo = 'bbb';
}
change()
console.log(foo);   // aaa  var在函数内声明的变量不会影响到全局
  • 值的修改以及重复声明 var,let声明的变量的值可以任意改变,const声明的基本数据类型的值不能改变,引用数据类型的值可以修改其属性的值。
    var可以重复声明变量,const let 不对变量重复声明
// 暂时性死区
var tmp = 123; 
if (true) {
    tmp = 456; // Cannot access 'tmp' before initialization  初始化前无法访问当前变量
    let tmp; 
}
  • 变量提升 var创建的变量会被提升到该作用域的最顶部。const,let 不会
console.log(foo)   // undefined
var foo = 2
上面代码相当于
var foo
console.log(foo)
foo = 2
  • 使用场景
    • 一般的let 替换调var
    • 对于常数,匿名函数 箭头函数的时候使用const

3. 数值的扩展

3-1 Math对象的一些比较实用的小方法

  • Math.trunc(): 去除一个数的小数部分
  • Math.sign(): 判断一个数是正数、负数还是零
  • Math.cbrt(): 返回一个数的立方根

3-2 BigInt 数据类型

为了与number区别。BingInt类型的数据必须加后缀n

  const a = 123n;
  const b = 123;
  console.log(Number(a) === Number(b)); // true
  console.log(a == b); // true
  console.log(a === b);   // false
  console.log(a === BigInt(b));   // true
  console.log(typeof a); // bigint
  • bigint不能与普通数值进行混合运算

4. 函数的扩展

4-1 关于函数length属性

函数的length默认情况下返回不具有默认值的参数的个数,当指定了参数的默认值的时候,函数的length属性失真。

const foo = (a, b) => {
    console.log(a, b);
};
const bar = (a = 1, b) => {
    console.log(a, b);
};
const fn = (a, b = 1) => {
    console.log(a, b);
};
console.log(foo.length); // 2
console.log(bar.length); // 0 
console.log(fn.length);  // 1

4-2 有关函数的作用域

var x = 1;
function f(x, y = x) {
    console.log(y);
}
f(2); // 2
// 在这里相当于
// function f( let x ; y = x){}  

var x = 1;
function f(y = x) {  // let y = x;
    console.log(y);
}
f(); // 1

//  这样会报错
var x = 1;
function foo(x = x) {   // let x = x; x is not defined
// ...
}
foo(); // ReferenceError: x is not defined

深入一下函数的作用域

var x = 1;
function foo(x,y = function () {  x = 2;}) {
    var x = 3;
    y()    // y函数内的x的作用域是新生成的x ,与foo函数内部的x不同一个作用域
    console.log(x);
}
foo(); // 3
console.log(x); //1

4-3 箭头函数与普通函数的区别

(1)箭头函数没有自己的this对象。

(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

箭头函数的作用域: 指向距离最近的父级的作用域

window.s2 = 0
function Timer() {
    this.s1 = 0;
    this.s2 = 0;
    // 箭头函数
    setInterval(() => this.s1++, 1000);
    // 普通函数
    setInterval(function () {
      this.s2++;
    }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log("s1: ", timer.s1), 3100);
setTimeout(() => console.log("s2: ", timer.s2,window.s2), 3100);
// timer.s1 : 3
// timer.s2 : 0
// window.s2: 3

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);   // this 执行foo的this
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1

由于箭头函数没有自己的this,所以当然也就不能用call()apply()bind()这些方法去改变this的指向

globalThis.s = 21;

const obj = {
  s: 42,
  m: () => console.log(this.s)
};

obj.m() // 21

上面例子中,obj.m()使用箭头函数定义。JavaScript 引擎的处理方法是,先在全局空间生成这个箭头函数,然后赋值给obj.m,这导致箭头函数内部的this指向全局对象,所以obj.m()输出的是全局空间的21,而不是对象内部的42。上面的代码实际上等同于下面的代码。

globalThis.s = 21;
globalThis.m = () => console.log(this.s);

const obj = {
  s: 42,
  m: globalThis.m
};

obj.m() // 21

5.数组的扩展

5-1 数组扩展运算符常用技巧

// 数组的复制
const arr1 = [1,2,3];
const arr2 = [...arr1];

// 数组的合并
const arr3 = [ ...arr1 , ...arr2]

5-2 Array.from()

// 创建一个指定长度的,内容为0的数组
Array.from({length:3},x=>0);   // [0,0,0]

5-3 Array.find()/findIndex()

const temp = [1, 2, -1, 4];
const res = temp.find((cur, index, array) => {
// 相当于高阶函数中的参数 cur 当前值,index索引,array数组
    console.log(cur, index, array);
    return cur < 0;
});
console.log(res);
// find返回满足条件的第一个值,findIndex返回满足条件的第一个值的索引

image.png

5-4 Array.includes()

在以前判断一个值是否在一个数组内的方法是indexOf(),indexOf的缺点:

  1. 不够语义化,判断一个值是否存在需要判断是否等于-1
  2. 内部使用严格相等运算符,不能判断NaN

5-5 数组的拍平 flat() / flatMap()

flat用于嵌套的数组进行拉平,默认拉平一层,参数为一个整形,代表拉平的层数

flatMap 对原嵌套数组的每个元素成员先执行一个函数,然后再进行flatflatMap只能拍平一层嵌套数组。 两者都会返回一个新的数组,不会影响原来的数组

const temp = [1, [2, [3]]];
console.log(temp.flat()); // [1,2,Array(1)]  默认情况拍平一层
const faTemp = temp.flatMap((x) => [x, x * 2]);  
console.log(faTemp); // [1, 2, Array(2), NaN]

6. 对象的扩展

6-1 == & === 的缺点

NaN === NaN   // false
NaN == NaN    // false
+0 == -0      // true
+0 === -0     // true

从而引出Object.is(),用来判断两个值是否相等。

6-2 Object.assign()

对象的浅拷贝

const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

如果考虑到原型上的属性的话

const clone = (origin)=>{
    const originProto = Object.getPrototypeOf(origin)
    return Object.assign(Object.create(originProto),origin)
    // 浅拷贝
    // return Object.assign(origin)
}

7. 运算符的扩展

7-1 链运算符的扩展

// 错误的写法
const firstName = message.body.user.firstName || "default";
// 链运算符
const firrtName = message?.body?.user?.firstName || "default";
// 实用场景
// 1. 表单的验证(对于方法的判断)
if (From.checkVal?.() === false) {
    // 这里判断checkVal是否是个函数
    // 验证失败;
}

链式调用的注意点:

  1. 短路机制 (当不满足条件的时候不再往下执行)
  2. 括号的影响(对括号外没有影响)
  3. 以下场合实用会报错
// 构造函数
new a?.()
new a?.b()

// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`

// 链判断运算符的左侧是 super
super?.()
super?.foo

// 链运算符用于赋值运算符左侧
a?.b = c
  1. 右侧不得有十进制整数

7-2 null 判断运算符

const message = null;
const test = message ?? true;
// test = message || true
console.log(test);

8 Set Map数据结构

8.1 Set 数据结构

set类似于数组,但是成员是唯一的,没有重复的值。

8.1.1 数组的去重

[...new Set(array)]
// 字符串去重
[...new Set('abbbcc')].join('')

8.1.2 Set的属性和方法

属性:

size:返回实例的成员总数.

方法:

add(value): 添加某个值,返回Set结构本身

delete(value): 删除某个值,返回一个布尔值,表示是否删除成功

has(value):返回一个布尔值,判断该值是否为Set的成员

clear():清除所有成员,无返回值

8.1.3 Set的遍历

(1)keys()values()entries()

keys(),values()返回的是Set的值。

(2)forEach()

(3)for(let item of [...set])

8.2 weakSet 数据结构

WeakSet 的成员只能是对象,不能是其他类型的值。

WeakSet没有size属性,WeakSet不能遍历,但是WeakSet的方法于Set数据结构的方法相同。

8.3 Map数据结构

8.3.1 定义及初始化

Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应

const items = [
    [name,'chang']
]
const test = new Map(items)
console.log(test.get(name));   // chang

// 原理
items.forEach((key,value)=>{
    map.set(key,value)
})

8.3.2 Map数据类型转换

(1) Map转为数组

const map = new Map([[name,chang]])
const arr = [...map]

(2) 数组转为map

const map = new Map([[name,chang]])

(3) Map转为对象

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

const myMap = new Map()
  .set('yes', true)
  .set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }

(4) 对象转为Map

let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));

8.4 WeakMap 数据结构

WeakMapMap的区别有两点。

  1. WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。

  2. WeakMap的键名所指向的对象,不计入垃圾回收机制。(WeakMap键所指的值不会被回收)

9. Proxy代理

Proxy 用于修改某些操作的默认行为。

9.1 对基本对象的拦截

  const handler = {
    get: function (target, key, receiver) {
      // target 目标对象 , key 键 , receiver 接收者
      console.log("get", target, key, receiver);
      // return target[key];
      // 暂时还没能理解这个Reflect
      // return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value) {
      console.log("set", target, key, value);
      target[key] = value;
    },
  };
  // 一个原生对象
  const obj = {
    name: "chang",
  };
  // 给原生对象创建一层代理
  const proxy = new Proxy(obj, handler);
  // 对代理的取值
  console.log(proxy.name);
  // 对代理属性的变更
  proxy.age = 20;
  // 获取代理的新属性
  console.log(proxy.age);

9.2 对函数以及构造函数的拦截

  const handler = {
    get: function (target, key) {
      console.log("get:", target, key);
      return target[key];
    },
    apply: function (target, thisBinding, args) {
      // 方法调用拦截
      console.log("apply", target, thisBinding, args);
      return target(...args);
    },
    construct: function (target, args) {
      // target 构造函数, args 参数
      console.log("方法构造拦截",target,args);
      return new target(...args);
    },
  };

  const sums = (...args) => {
    return args.reduce((pre, cur) => {
      return pre + cur;
    }, 0);
  };

  const fproxy = new Proxy(sums, handler);
  console.log(fproxy(1, 2, 3)); // 触发apply 拦截  6
  console.log(fproxy.name); // 触发 get 拦截    sums

  // 对构造函数的拦截
  // 创建一个地址的构造函数
  class address {
    constructor(address) {
      ["province", "city", "county"].forEach((item) => {
        this[item] = address[item];
      });
    }
  }
  // 建造一个符合address构造函数的参数对象
  const chang = {
    province: "xx省",
    city: "xx市",
    county: "xx县",
  };
  // 对address构造函数设置代理
  const address_proxy = new Proxy(address, handler);
  // 初始化一个实例 
  const chang_address = new address_proxy(chang);
  console.log('chang',chang_address);     // chang address {province: "xx省", city: "xx市", county: "xx县"}

9.3 应用实例

9.3.1 数组负数索引

  const createArray = (...args) => {
    const handler = {
      get: function (target, key) {
        const index = Number(key);
        if (index < 0) {
          return target[target.length + index];
        } else {
          return target[index];
        }
      },
    };
    return new Proxy(args, handler);
  };

  const arr = createArray(1, 2, 3);
  console.log(arr[-2]); //2
  console.log(arr[2]); //3

10 Reflect

10.1 Reflect的设计

  1. Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
  2. 修改某些Object方法的返回结果。
  3. Object操作都变成函数行为。
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。

自我理解:Reflect就是proxy的简写,Reflect与Proxy对象的方法一一对应。有了Reflect不必再new Proxy(),这样复杂,直接调用Reflect的方法就可以。

10.3 Reflect 的静态方法

Reflect对象一共有 13 个静态方法。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

Reflect.apply

const ages = [11, 33, 12, 54, 18, 96];

// 旧写法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);

// 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);

10.3.1 Reflect.ownKeys()

var myObject = {
  foo: 1,
  bar: 2,
  [Symbol.for('baz')]: 3,
  [Symbol.for('bing')]: 4,
};

// 旧写法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']

Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]

// 新写法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]

11. Promise

11.1 方法

  1. Promise.prototype.then()

  2. Promise.prototype.catch()

  3. Promise.prototype.finally()

  4. Promise.all()

将多个Promise实例,包装成一个新的Promise实例。 入参为一个数组,返回一个数组。

const p = Promise.all([p1, p2, p3]);
// 1. 只有p1 p2 p3的状态都变为fulfilled的时候,p的状态才会变为fulfilled.
// 2. 只要其中一个rejected,p的状态就会变为rejected.
  1. Promise.race()

race:比赛。只要其中的一个实例率先改变状态,那么p的状态就跟着改变。

  1. Promise.any()

只要其中的一个实例变为fulfilled状态,p的状态就会变为fulfilled状态;

如果所有参数实例都变为rejected状态,包装实例就会编程rejected状态;

12 Iterator

  const obj = {
    [Symbol.iterator]: function () {
      return {
        next: function () {
          return {
            value: 1,
            done: true,
          };
        },
      };
    },
  };
  console.log(obj);  // {Symbol(Symbol.iterator): ƒ}
  const iter = obj[Symbol.iterator]()
  console.log(iter.next());  // {value: 1, done: true}

原生具备iterator接口的数据结构如下:

String : 基本类型 String

Array:引用类型 数组

Set: Set

Map:Map

arguments: 普通函数的参数

NodeList:节点列表

typedArray:类型化数组

// TypedArray 关键字需要替换为底部列出的构造函数。
new TypedArray(); // ES2017中新增
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);

// TypedArray 指的是以下的其中之一:

Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();

12.1 结合generator函数使用

  let obj = {
    *[Symbol.iterator](){
      yield '1'
      yield '2'
      yield '3'
      yield '4'
      yield '5'
    }
  };

  for (const item of obj) {
    console.log(item);
  }

12.2 将对象构造成可遍历的对象

  let obj = {
    name: "change",
    age: 20,
  };

  function* entire(obj) {
    for (let key of Object.keys(obj)) {
      yield [key, obj[key]];
    }
  }

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

12.3 for...in 遍历数组的缺点

  • 数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
  • for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
  • 某些情况下,for...in循环会以任意顺序遍历键名。

13. Generator生成器函数

13.1 斐波那契数列

  function* fibonacci() {
    let [pre, cur] = [0, 1];
    for(;;){
      yield cur;
      [pre, cur] = [cur, pre + cur];
    }
  }
  for (const item of fibonacci()) {
    if (item > 100) break;
    console.log(item);
  }

13.2 Generator 异步处理应用

ES6发布以前的异步处理方法: 回调函数、事件监听、发布/订阅、Promise对象

14. Async

async 其实就是generator的语法糖

async 在 generator函数上的改进:

  1. 自带执行,与普通函数一样,一行一行的执行

  2. 更好的语义

  3. 返回值是Promise,相比于更加方便。Generator返回值是iterator

14.1 延时输出

  asyncPrint = async (message, time) => {
    await new Promise((resolve, reject) => {
      setTimeout(resolve, time);
    });
    console.log(message);
  };

  asyncPrint("hello world", 5000);

14.2 使用形式

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

14.3 await

await后面是一个Promise对象,返回该对象的结果,如果不是Promise对象,就直接返回对应的值。

  • 一个休眠的方法
  // 休眠的方法
  const sleep = (time) => {
    return new Promise((resolve) => setTimeout(resolve, time));
  };
  // 间隔输出
  const intervalOutPut = async (time) => {
    for (let i = 0; i < 5; i++) {
      console.log(i);
      await sleep(time)
    }
  };
  intervalOutPut(1000)

14.4 使用的注意点

  • 最好把await命令放在try...catch代码块中

  • 多个await命令后面的异步操作,如果不存在继发关系,最好让他们同时触发

let [foo, bar] = await Promise.all([getFoo(), getBar()]);
  • await命令只能用在async函数中,在普通函数中会报错

  • async函数可以保留运行堆栈

const a = async () => {
  await b();
  c();
};

上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()c()报错,错误堆栈将包括a()

15. Class 类

15.1 一个基本的Class定义

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');

person.sayName(); // "张三"

15.2 使用时的注意点

  • 严格模式

  • 不存在提升

  • name属性

  • Generator 方法

class Foo {
  constructor(...args) {
    this.args = args;
  }
  * [Symbol.iterator]() {
    for (let arg of this.args) {
      yield arg;
    }
  }
}

for (let x of new Foo('hello', 'world')) {
  console.log(x);
}
// hello
// world
  • this指向(默认指向类的实例)

15.3 静态属性和方法

加上static关键字,就表示该方法不会呗继承,只能通过类直接调用。

静态方法里的this指向的是类,而不是实例,所以在静态方法里不能使用到类内的属性和方法。

  class Foo {
    static prop = 1; // 定义一个prop静态属性(本身的属性,不绑定到this上)
    static classMethod() {
      return "hello";
    }
  }

  Foo.classMethod(); // 'hello'
  console.log(Foo.prop);      // 1
  var foo = new Foo();
  console.log(foo.prop);      // undefined
  foo.classMethod();
  // Uncaught TypeError: foo.classMethod is not a function

15.4 私有属性和方法

  class Foo {
    #message = "message"; // 私有属性

    // 公共方法
    classMethod() {
      this._say(); // 调用私有方法
      console.log(this.#message); // 打印私有属性
      return "hello";
    }
    // 私有方法
    _say = function () {
      console.log("say");
    };
  }
  // Foo._say();
  //  Foo._say is not a function
  // console.log(Foo.#message);
  // Uncaught SyntaxError: Private field '#message' must be declared in an enclosing class

  // 创建一个类的实例
  var foo = new Foo();
  // 方法调用
  foo.classMethod();      // say   \n   message

15.5 new.target()

这个属性是可以用确定构造函数是怎么调用的。

function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

15.6 类的继承以及super关键字

只有在使用super关键字后才可以使用thissuper

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}