ES&next

181 阅读17分钟

主讲:麓一

发展历史

  • 浏览器脚本:Javascript
  • 服务器脚本:PHP / ASP / JSP

我们需要了解的内容/重点:

  • ECMAScript 是一个标准,JavaScript 是标准的实现;

  • ECMAScriptJavaScript 的规格;

  • ECMAScript 的方言,还有 JScriptActionScript

  • 浏览器端对于语言特性的实现,有一些滞后;

  • 浏览器在用户侧的升级,也有一些滞后;

ECMAScript / JavaScript -> ES6

babel

  • 浏览器的版本,和语言本身有差异,所以我要对语言,进行编译降级。

babel 6

  • ES 的标准:

    • Stage 0 - Strawman(展示阶段)
    • Stage 1 - Proposal(征求意见阶段)
    • Stage 2 - Draft(草案阶段)
    • Stage 3 - Candidate(候选人阶段)
    • Stage 4 - Finished(定案阶段)
  • es 的标准每年都会变,babel 6 的问题;

    • 使用的使用要注入不同的版本:babel-preset-stage-2babel-preset-2016 等等;

babel 7

preset-env:

  • targets : {}

  • 根据用户的一些浏览器的特性,在运行时动态的引入这些 polyfill (垫片);bundle size 最小。

  • polyfill 是一个概念:垫片。在一个只支持 es5 的浏览器中,去运行 es6

在没有任何配置选项的情况下,babel-preset-envbabel-preset-latest(或者 babel-preset-es2015babel-preset-es2016babel-preset-es2017 全部)的行为完全相同。

{
  "presets": ["env"]
}

例如只包含了支持每个浏览器 最后两个版本Safari 大于等于 7 版本所需的 polyfill 和代码转换。

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": ["last 2 versions", "safari >= 7"]
        }
      }
    ]
  ]
}

如果目标开发 Node.js 而不是浏览器应用的话,你可以配置 babel-preset-env 仅包含特定版本所需的 polyfilltransform

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "node": "6.10"
        }
      }
    ]
  ]
}

方便起见,你可以使用 "node": "current" 来包含用于运行 BabelNode.js 最新版所必需的 polyfilltransform

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}

函数解析

面试题:new 一个箭头函数会如何?

  • 会报错,提示:function is not a constructor
  • babel 编译时,会把 this 转成 (void 0)
const TestArrowFunc = () => {
  this.age = 18;
  this.name = 'luyi';
};

const t = new TestArrowFunc();
// 报错:TestArrowFunc is not a constructor

/* 
  1、会报错,提示:function is not a constructor;
  2、babel 编译时,会把 this 转成 (void 0);

  "use strict";

  var _this = void 0;

  var TestArrowFunc = function TestArrowFunc() {
    _this.age = 18;
    _this.name = "luyi";
  };

  var t = new TestArrowFunc();
*/

哪些不能用箭头函数?

  • arguments
  • yield
  • 构造函数的原型方法上;

arguments、callee、caller

  • arguments对象是所有(非箭头)函数中都可用的局部变量(类数组对象);

  • calleearguments 对象的属性,arguments.callee 指向参数所属的当前执行的函数;

  • callee 属性的初始值就是正被执行的 Function 对象;

  • callee 表示对函数对象本身的引用;

  • callee 拥有 length 属性,arguments.length实参长度arguments.callee.length形参长度

  • caller 属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null

const Person = function (age, name) {
  this.age = age;
  this.name = name;

  /* 
    arguments 是类数组对象,不能直接调用数组上面的方法
    caller 属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null
  */

  // arguments 是类数组对象,不能直接调用数组上面的方法
  console.log(arguments); // [Arguments] { '0': 18, '1': 'luyi', '2': 'teacher' }
  let argumentsArr = Array.prototype.slice.call(arguments, 2);
  console.log(argumentsArr); // [ 'teacher' ]

  // callee 是 arguments 对象的属性,arguments.callee 指向参数所属的当前执行的函数
  console.log(arguments.callee); // [Function: Person]
  console.log(arguments.callee === Person); // true

  // callee 拥有 length 属性,arguments.length 是实参长度,arguments.callee.length 是形参长度;
  console.log(arguments.length); // 3
  console.log(arguments.callee.length); // 2

  // caller 属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null
  console.log(Person.caller); // [Function: test]
  console.log(arguments.callee.caller); // [Function: test]
};

function test() {
  const p = new Person(18, 'luyi', 'teacher');
}

test();

变量的解构赋值

数组

let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo, bar, baz); // 1 2 3

let [x, , y] = [1, 2, 3];
console.log(x, y); // 1 3

let [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]

let [foo2] = [];
console.log(foo2); // undefined

let [foo3 = true] = [];
console.log(foo3); // true

// ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效
let [x2 = 1] = [undefined];
console.log(x2); // 1

let [x3 = 1] = [null];
console.log(x3); // null

对象

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
console.log(foo, bar); // aaa bbb

let { baz } = { foo: 'aaa', bar: 'bbb' };
console.log(baz); // undefined

// 重命名
let { foo2: baz2 } = { foo2: 'aaa', bar2: 'bbb' };
console.log(baz2); // aaa
// console.log(foo2); // foo2 is not defined

let obj = { p: ['Hello', { y: 'World' }] };

let {
  p: [x, { y }],
} = obj;
console.log(x); // Hello
console.log(y); // World

const node = {
  loc: {
    start: {
      line: 1,
      column: 5,
    },
  },
};

let {
  loc,
  loc: { start },
  loc: {
    start: { line },
  },
} = node;
console.log(loc); // { start: { line: 1, column: 5 } }
console.log(start); // { line: 1, column: 5 }
console.log(line); // 1

// 默认值生效的条件是,对象的属性值严格等于 undefined
var { x2 = 3 } = { x2: undefined };
console.log(x2); // 3

var { x3 = 3 } = { x3: null };
console.log(x3); // null

模板字符串

const consoleList = function (student, teacher) {
  console.log(`hello ${student}, I am ${teacher}, nice 2 meet U`);
  // hello my students, I am luyi, nice 2 meet U
  console.log('hello ' + student + ', I am ' + teacher + ', nice 2 meet U');
  // hello my students, I am luyi, nice 2 meet U

  // 模板字符串支持换行
  console.log(`hello ${student}, 
    I am ${teacher}, 
    nice 2 meet U`);
  /* 
    hello my students,
      I am luyi,
      nice 2 meet U
  */
};

consoleList('my students', 'luyi');
const stu = 'my students';
const tea = 'luyi';

const consoleString = function (stringTemplate, ...restVal) {
  console.log(stringTemplate.reduce((total, item, index) => total + item + (restVal[index] || ''), ''));
};

consoleString(['hello ', ', I am ', ', nice 2 meet U'], stu, tea);
// hello my students, I am luyi, nice 2 meet U

// 复杂的模板字符串语法
const consoleString2 = function (stringTemplate, ...restVal) {
  console.log(stringTemplate); // [ 'hello ', ', I am ', ', nice 2 meet U' ]
  console.log(restVal); // [ 'my students', 'luyi' ]
  console.log(stringTemplate.reduce((total, item, index) => total + item + (restVal[index] || ''), ''));
};
consoleString2`hello ${stu}, I am ${tea}, nice 2 meet U`;
// hello my students, I am luyi, nice 2 meet U

数值的扩展

Number.isFinite()、Number.isNaN()

  • Number.isFinite() 用来检查一个数值是否为有限的(finite),即不是 Infinity
    • 注意,如果参数类型不是数值,Number.isFinite 一律返回 false
  • Number.isNaN() 用来检查一个值是否为 NaN
    • 如果参数类型不是 NaNNumber.isNaN 一律返回 false

与传统的全局方法 isFinite()isNaN() 的区别在于,传统方法先调用 Number() 将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite() 对于非数值一律返回 falseNumber.isNaN() 只有对于 NaN 才返回 true,非 NaN 一律返回 false

Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false

Number.isNaN(NaN); // true
Number.isNaN(15); // false
Number.isNaN('15'); // false
Number.isNaN(true); // false
Number.isNaN(9 / NaN); // true
Number.isNaN('true' / 0); // true
Number.isNaN('true' / 'true'); // true

// 与传统全局上的方法对比
isFinite(25); // true
isFinite('25'); // true
Number.isFinite(25); // true
Number.isFinite('25'); // false

isNaN(NaN); // true
isNaN('NaN'); // true
Number.isNaN(NaN); // true
Number.isNaN('NaN'); // false
Number.isNaN(1); // false

Number.parseInt()、Number.parseFloat()

ES6 将全局方法 parseInt()parseFloat(),移植到 Number 对象上面,行为完全保持不变。

// ES5的写法
parseInt('12.34'); // 12
parseFloat('123.45#'); // 123.45

// ES6的写法
Number.parseInt('12.34'); // 12
Number.parseFloat('123.45#'); // 123.45

console.log(Number.parseInt === parseInt); // true
console.log(Number.parseFloat === parseFloat); // true

Number.isInteger()

Number.isInteger() 用来判断一个数值是否为整数;

整数和浮点数采用的是同样的储存方法,所以 2525.0 被视为同一个值;

如果参数不是数值,Number.isInteger 返回 false

Number.isInteger(25); // true
Number.isInteger(25.1); // false

Number.isInteger(25); // true
Number.isInteger(25.0); // true

Number.isInteger(); // false
Number.isInteger(null); // false
Number.isInteger('15'); // false
Number.isInteger(true); // false

函数的扩展

与解构赋值默认值结合使用

function foo({ x, y = 5 }) {
  console.log(x, y);
}

foo({}); // undefined 5
foo({ x: 1 }); // 1 5
foo({ x: 1, y: 2 }); // 1 2
// foo(); // TypeError: Cannot read property 'x' of undefined

name 属性

function foo() {}
console.log(foo.name); // "foo"

const bar = function baz() {};
console.log(bar.name); // "baz"

箭头函数

箭头函数有几个使用注意点:

  • 箭头函数没有自己的this对象;
  • 不可以当作构造函数,也就是说,不可以对箭头函数使用 new 命令,否则会抛出一个错误;
  • 不可以使用 arguments 对象,该对象在函数体内不存在,如果要用,可以用 rest 参数代替;
  • 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数;
var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

// 报错
// let getTempItem1 = id => { id: id, name: "Temp" };

// 不报错
let getTempItem2 = id => ({ id: id, name: 'Temp' });

数组的扩展

扩展运算符 ...

console.log(...[1, 2, 3]); // 1 2 3

// 注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
// (...[1, 2]) // Uncaught SyntaxError: Unexpected number
// console.log((...[1, 2])); // Uncaught SyntaxError: Unexpected number
console.log(...[1, 2]); // 1 2

/* 
  扩展运算符的应用 - 复制数组
*/
// ES5
const a1 = [1, 2, { name: 'tom' }];
const a2 = a1.concat(); // 第一层是深拷贝,第二层是浅拷贝

// ES6
const a3 = [1, 2, { name: 'tom' }];
const a4 = [...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' ]

// 这两种方法都是浅拷贝
const a5 = [{ foo: 1 }];
const a6 = [{ bar: 2 }];

const a7 = a5.concat(a6);
const a8 = [...a5, ...a6];
a7[0] === a5[0]; // true
a8[0] === a5[0]; // true

Array.from()

Array.from() 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 SetMap)。

// 类数组对象
let arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
};

// ES5 的写法
var arr1 = [].slice.call(arrayLike);
console.log(arr1); // ['a', 'b', 'c']

// ES6 的写法
let arr2 = Array.from(arrayLike);
console.log(arr2); // ['a', 'b', 'c']

find()、findIndex()

数组实例的 find 方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为 true 的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined

find 方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。

let res = [1, 4, -5, 10].find(n => n < 0);
console.log(res); // -5

let res2 = [1, 5, 10, 15].find(function (value, index, arr) {
  // console.log(value); // 当前的值
  // console.log(index); // 当前位置
  // console.log(arr); // 原数组 - [1, 5, 10, 15]
  return value > 9;
});
console.log(res2); // 10

数组实例的 findIndex 方法的用法与 find 方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回 -1

let res3 = [1, 5, 10, 15].findIndex(function (value, index, arr) {
  return value > 9;
});
console.log(res3); // 2

这两个方法都可以接受第二个参数,用来绑定回调函数的 this 对象。

function f(v) {
  return v > this.age;
}
let person = { name: 'John', age: 20 };
// find 和 findIndex 方法可以接收第二个参数 => 回调函数中 this 的指向
let res4 = [10, 12, 26, 15].find(f, person);
console.log(res4); // 26

let res5 = [10, 12, 26, 15].findIndex(f, person);
console.log(res5); // 2

这两个方法都可以发现 NaN,弥补了数组的 indexOf 方法的不足。

let resNan = [NaN].indexOf(NaN);
console.log(resNan); // -1

// Object.is() 方法判断两个值是否为同一个值
let resNan2 = [1, NaN].findIndex(y => Object.is(NaN, y));
console.log(resNan2); // 1

entries()、keys()、values()

ES6 提供三个新的方法—— entries()keys()values() ——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用 for...of 循环进行遍历,唯一的区别是 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"

includes()

let res1 = [1, 2, 3].includes(2);
console.log(res1); // true

let res2 = [(1, 2, 3)].includes(4);
console.log(res2); // false

let res3 = [(1, 2, NaN)].includes(NaN);
console.log(res3); // true

// 第二个参数表示搜索的起始位置
let res4 = [1, 2, 3].includes(3, 3);
console.log(res4); // false
let res5 = [1, 2, 3].includes(3, -1);
console.log(res5); // true

// 判断 NaN
let res6 = [NaN].indexOf(NaN);
console.log(res6); // -1

let res7 = [NaN].includes(NaN);
console.log(res7); // true

对象新增的方法

Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的 NaN 不等于自身,以及 +0 等于 -0

ES6 提出 “Same-value equality”(同值相等)算法,用来解决这个问题。

Object.is 就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('foo', 'foo'); // true
Object.is({}, {}); // false

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

Object.is(+0, -0); // false
Object.is(NaN, NaN); // true

数组和对象

数组和对象的细节

// 数组的细节
// 需要使用 Array.from 或者 .fill(0)
const funcGenerator = num => Array.from(new Array(num)).map(item => params => console.log(params, item));
const funcGenerator2 = num => new Array(num).fill(0).map(item => params => console.log(params, item));

funcGenerator(3).map((func, index) => func(index));
/* 
  0 undefined
  1 undefined
  2 undefined
*/

funcGenerator2(4).map((func, index) => func(index));
/* 
  0 0
  1 0
  2 0
  3 0
*/

// 对象的细节
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true

// ES next 采用了 SameValueZero() 的比较。是一个引擎内置的比较方式。

console.log([NaN].indexOf(NaN)); // -1
console.log([NaN].includes(NaN)); // true,includes 也是采用了 SameValueZero 的比较

Object.assign

// Object.assign 深拷贝还是浅拷贝?

let dist = { foo: 'foo' };
let bar = { bar: { bar: 'bar' } };
let baz = { baz: 'baz' };

const res = Object.assign(dist, bar, baz);
console.log(res); // { foo: 'foo', bar: { bar: 'bar' }, baz: 'baz' }
console.log(dist); // { foo: 'foo', bar: { bar: 'bar' }, baz: 'baz' }
console.log(res === dist); // true

bar.bar.bar = 'newBar';
baz.baz = 'newBaz';

// 第一层是深拷贝,第二层是浅拷贝
console.log(res); // { foo: 'foo', bar: { bar: 'newBar' }, baz: 'baz' };

get / set

class Person {
  constructor() {}

  _age = '';

  get age() {
    console.log(`actually I am ${this._age} years old~`);
    return '17';
  }

  set age(val) {
    console.log('It is useless to set my age, I am 17!');
    this._age = val;
  }
}

const luyi = new Person();
luyi.age = '35';
console.log('luyi is', luyi.age);

/* 
  It is useless to set my age, I am 17!
  actually I am 35 years old~
  luyi is 17
*/

Proxy

const luyi = {
  age: 35,
};

const luyiProxy = new Proxy(luyi, {
  get: function (target, propKey, receiver) {
    console.log('GET: ', target, propKey);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log('SET: ', target, propKey, value);
    // SET:  { age: 35 } age 40
    return Reflect.set(target, propKey, value, receiver);
  },
});

let res = (luyiProxy.age = 40);
console.log(res); // 40
console.log(luyiProxy.age); // 40
console.log(luyiProxy.age === res); // true

面试题:如何实现断言函数

const assert = new Proxy(
  {},
  {
    set(target, warning, value) {
      console.log(target, warning, value); // {} The teacher is Luyi!!! false

      if (!value) {
        console.error(warning);
      }
    },
  }
);

const teacher = 'luyi';

// 如果断言的内容是假的,我就打印
assert['The teacher is Luyi!!!'] = teacher === 'yunyin';

receiver

const luyi = {
  age: 35,
};

const luyiProxy = new Proxy(luyi, {
  get: function (target, propKey, receiver) {
    return receiver;
  },
  set: function (target, propKey, value, receiver) {
    console.log('SET:', target, propKey, value);
    return Reflect.set(target, propKey, value, receiver);
  },
});

// receiver 指向原始的读操作所在的那个对象, 一般情况下,就是 Proxy 的实例。
console.log(luyiProxy.age === luyiProxy); // true

Reflect

  1. Object 上一些明显属于语言内部的方法,放到 Reflect 对象上,现在 ObjectReflect 一同部署;
  2. 修改某些 Object 方法的返回结果,让其更合理;
const teacher = {
  age: 18,
  name: 'luyi',
};

Object.defineProperty(teacher, 'lessions', {
  writable: false,
  enumerable: false,
  configurable: false,
  value: 'vue',
});

const res = Object.defineProperty(teacher, 'lessions', {
  writable: true,
  enumerable: true,
  configurable: true,
  value: ['es6', 'esnext'],
});

console.log(res);
// Object.defineProperty 直接报错: Cannot redefine property: lessions

Reflect.defineProperty(teacher, 'lessions', {
  writable: false,
  enumerable: false,
  configurable: false,
  value: 'vue',
});

const res2 = Reflect.defineProperty(teacher, 'lessions', {
  writable: true,
  enumerable: true,
  configurable: true,
  value: ['es6', 'esnext'],
});

console.log(res2); // false
// Reflect.defineProperty 给 true or false

Map、Set、WeakMap、WeakSet

  • Map(对象):Map 对象保存键值对,并且能够记住键的原始插入顺序;

    • size:返回 Map 中条目数量;
    • set:方法为 Map 对象添加或更新一个指定了键(key)和值(value)的(新)键值对;
    • get:方法返回某个 Map 对象中的一个指定元素;
    • has:返回一个 boolean 值,用来表明 map 中是否存在指定元素;
    • delete:方法用于移除 Map 对象中指定的元素;
    • clear:方法会移除 Map 对象中的所有元素;
  • Set(数组):Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用;Set 中的元素只会 出现一次,即 Set 中的元素是唯一的;

    • size:返回 Set 中条目数量;
    • add 方法用来向一个 Set 对象的末尾添加一个指定的值;
    • has:方法返回一个布尔值来指示对应的值 value 是否存在 Set 对象中;
    • clear:方法用来清空一个 Set 对象中的所有元素;
    • delete:方法可以从一个 Set 对象中删除指定的元素;
  • Weak 表示作为唯一的部分,必须是一个对象;

  • Weak 是一个弱引用,不用考虑 GC

  • WeakMap 对象是一组键/值对的集合,其中的键是弱引用的,其键必须是对象,而值可以是任意的;

    • set:方法根据指定的 keyvalueWeakMap 对象中添加新/更新元素;
    • get:方法返回 WeakMap 指定的元素;
    • has:方法根据 WeakMap 对象的元素中是否存在 key 键返回一个 boolean 值;
    • delete:方法可以从一个 WeakMap 对象中删除指定的元素;
  • WeakSet:对象允许你将弱保持对象存储在一个集合中;

    • add:方法在 WeakSet 对象的最后一个元素后添加新的对象;
    • has:方法根据 WeakSet 是否存在相应对象返回布尔值;
    • delete:方法从 WeakSet 对象中移除指定的元素;

Map 的基本用法

let myMap = new Map();

let keyObj = {};
let keyFunc = function () {};
let keyString = 'a string';

// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, '和键keyObj关联的值');
myMap.set(keyFunc, '和键keyFunc关联的值');

console.log(myMap);
/* 
  Map(3) {
    'a string' => "和键'a string'关联的值",
    {} => '和键keyObj关联的值',
    [Function: keyFunc] => '和键keyFunc关联的值'
  }
*/
myMap.size; // 3

// 读取值
myMap.get(keyString); // "和键'a string'关联的值"
myMap.get(keyObj); // "和键keyObj关联的值"
myMap.get(keyFunc); // "和键keyFunc关联的值"

myMap.get('a string'); // "和键'a string'关联的值"
// 因为keyString === 'a string'

myMap.get({}); // undefined, 因为 keyObj !== {}
myMap.get(function () {}); // undefined, 因为 keyFunc !== function () {}

// 将 NaN 做为 Map 的键
myMap.set(NaN, 'not a number');
myMap.get(NaN); // "not a number"

let otherNaN = Number('foo');
console.log(otherNaN); // NaN
myMap.get(otherNaN); // "not a number"

console.log(myMap.delete(keyObj)); // true
console.log(myMap.has(NaN)); // true

myMap.clear();
console.log(myMap); // Map(0) {}

Set 的基本用法

let mySet = new Set();

mySet.add(1); // Set [ 1 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add('some text'); // Set [ 1, 5, "some text" ]

let o = { a: 1, b: 2 };
mySet.add(o);

mySet.add({ a: 1, b: 2 }); // o 指向的是不同的对象,所以没问题

console.log(mySet); // Set(5) { 1, 5, 'some text', { a: 1, b: 2 }, { a: 1, b: 2 } }

mySet.has(1); // true
mySet.has(3); // false
mySet.has(5); // true
mySet.has(Math.sqrt(25)); // true
mySet.has('Some Text'.toLowerCase()); // true
mySet.has(o); // true

mySet.size; // 5

mySet.delete(5); // true,  从 set 中移除5
mySet.has(5); // false, 5 已经被移除

mySet.size; // 4, 刚刚移除一个值

console.log(mySet); // Set(4) { 1, 'some text', { a: 1, b: 2 }, { a: 1, b: 2 } }

mySet.clear();
console.log(mySet); // Set(0) {}

WeakMap 的基本用法

const wm1 = new WeakMap(),
  wm2 = new WeakMap(),
  wm3 = new WeakMap();

const o1 = {},
  o2 = function () {},
  o3 = window;

wm1.set(o1, 37);
wm1.set(o2, 'azerty');

wm2.set(o1, o2); // value 可以是任意值,包括一个对象或一个函数
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个 WeakMap 对象

wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, wm2 中没有 o2 这个键
wm2.get(o3); // undefined, 值就是 undefined
wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使值是 undefined)
wm3.set(o1, 37);
wm3.get(o1); // 37
wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false

WeakSet 的基本用法

var ws = new WeakSet();

var foo = {};
var bar = {};

ws.add(foo);
ws.add(bar);

ws.has(foo); // true
ws.has(bar); // true

ws.delete(foo); // 从 set 中删除 foo 对象
ws.has(foo); // false, foo 对象已经被删除了
ws.has(bar); // true, bar 依然存在

小练习

const foos = new WeakSet();

class Foo {
  constructor() {
    foos.add(this);
  }

  method() {
    if (!foos.has(this)) {
      throw new TypeError(' Foo.prototype.method 只能在实例上调用');
    } else {
      console.log('using methods');
    }
  }
}

let f = new Foo();
let b = {};
Foo.prototype.method.call(f); // using methods
Foo.prototype.method.call(b); // TypeError:  Foo.prototype.method 只能在实例上调用

迭代器 - Iterator

  • 迭代器是一个接口,为各种不同的数据提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作:
    • 本质:指针;
    • 该接口主要供 for...of 消费;
let m = new Map();
m.set('a', 'foo');
m.set('b', 'bar');
m.set('c', 'baz');

let k = m.keys();

console.log(k.next()); // { value: 'a', done: false }
console.log(k.next()); // { value: 'b', done: false }
console.log(k.next()); // { value: 'c', done: false }
console.log(k.next()); // { value: undefined, done: true }

let arr = [1, 2, 3, 4, 5];
let a = arr[Symbol.iterator]();

console.log(a.next()); // { value: 1, done: false }
console.log(a.next()); // { value: 2, done: false }
console.log(a.next()); // { value: 3, done: false }
console.log(a.next()); // { value: 4, done: false }
console.log(a.next()); // { value: 5, done: false }
console.log(a.next()); // { value: undefined, done: true }
  • 原生具备 Iterator 的数据结构有:Array、Map、Set、String、TypedArray、arguments、NodeList

Object.entries

const obj = { a: 11, b: 22, c: 33 };

console.log(Object.entries(obj)); // [ [ 'a', 11 ], [ 'b', 22 ], [ 'c', 33 ] ]
console.log(Object.keys(obj)); // [ 'a', 'b', 'c' ]
console.log(Object.values(obj)); // [ 11, 22, 33 ]

// 实现 Object.entries
// 1、非 generator 的方法
function entries1(obj) {
  let arr = [];
  for (let key of Object.keys(obj)) {
    arr.push([key, obj[key]]);
  }
  return arr;
}

const k1 = entries1(obj);
console.log(k1); // [ [ 'a', 11 ], [ 'b', 22 ], [ 'c', 33 ] ]

// generator 的方法
function* entries2(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

const k2 = entries2(obj);
console.log(k2); // Object [Generator] {}

console.log(k2.next()); // { value: [ 'a', 11 ], done: false }
console.log(k2.next()); // { value: [ 'b', 22 ], done: false }
console.log(k2.next()); // { value: [ 'c', 33 ], done: false }
console.log(k2.next()); // { value: undefined, done: true }
// 或者
for (let item of k2) {
  console.log(item);
  /* 
    [ 'a', 11 ]
    [ 'b', 22 ]
    [ 'c', 33 ]
  */
}

Promise.allSettled

function allSettled(array) {
  return new Promise((resolve, reject) => {
    if (!(array instanceof Array)) return reject(new Error(' not Array!'));

    let res = [];
    let count = 0;

    array.forEach((func, index) => {
      Promise.resolve(func)
        .then(
          value => {
            res[index] = {
              status: 'fulfilled',
              value,
            };
          },
          reason => {
            res[index] = {
              status: 'rejected',
              reason,
            };
          }
        )
        .finally(() => {
          ++count === array.length && resolve(res);
        });
    });
  });
}

const p1 = new Promise((resolve, reject) => resolve(1));
const p2 = new Promise((resolve, reject) => reject(2));
const p3 = new Promise((resolve, reject) => resolve(3));

allSettled([p1, p2, p3]).then(value => {
  console.log(value);
  /* 
    [
      { status: 'fulfilled', value: 1 },
      { status: 'rejected', reason: 2 },
      { status: 'fulfilled', value: 3 }
    ]
  */
});

LRU 缓存

//  一个 Map 对象在迭代时会根据对象中元素的插入顺序来进行
// 新添加的元素会被插入到 map 的末尾,整个栈倒序查看
class LRUCache {
  constructor(capacity) {
    this.secretKey = new Map();
    this.capacity = capacity;
  }

  get(key) {
    if (this.secretKey.has(key)) {
      let tempValue = this.secretKey.get(key);
      this.secretKey.delete(key);
      this.secretKey.set(key, tempValue);
      return tempValue;
    } else {
      return -1;
    }
  }

  put(key, value) {
    // key存在,仅修改值
    if (this.secretKey.has(key)) {
      this.secretKey.delete(key);
      this.secretKey.set(key, value);
    }
    // key不存在,cache 未满
    else if (this.secretKey.size < this.capacity) {
      this.secretKey.set(key, value);
    }
    // 添加新 key,删除旧 key
    else {
      this.secretKey.set(key, value);
      // 删除 map 的第一个元素,即为最长未使用的
      this.secretKey.delete(this.secretKey.keys().next().value);
    }
  }
}

let cache = new LRUCache(2);
cache.put(1, 1);
cache.put(2, 2);
console.log(cache); // LRUCache { secretKey: Map(2) { 1 => 1, 2 => 2 }, capacity: 2 }

console.log('cache.get(1) ==== ', cache.get(1)); // 返回 1
console.log(cache); // LRUCache { secretKey: Map(2) { 2 => 2, 1 => 1 }, capacity: 2 }

cache.put(3, 3); // 该操作会使得密钥 2 作废
console.log(cache); // LRUCache { secretKey: Map(2) { 1 => 1, 3 => 3 }, capacity: 2 }

console.log('cache.get(2) ==== ', cache.get(2)); // 返回 -1 (未找到)
console.log(cache); // LRUCache { secretKey: Map(2) { 1 => 1, 3 => 3 }, capacity: 2 }

cache.put(4, 4); // 该操作会使得密钥 1 作废
console.log(cache); // LRUCache { secretKey: Map(2) { 3 => 3, 4 => 4 }, capacity: 2 }

console.log('cache.get(1)', cache.get(1)); // 返回 -1 (未找到)
console.log(cache); // LRUCache { secretKey: Map(2) { 3 => 3, 4 => 4 }, capacity: 2 }

console.log('cache.get(3)', cache.get(3)); // 返回  3
console.log(cache); // LRUCache { secretKey: Map(2) { 4 => 4, 3 => 3 }, capacity: 2 }

console.log('cache.get(4)', cache.get(4)); // 返回  4
console.log(cache); // LRUCache { secretKey: Map(2) { 3 => 3, 4 => 4 }, capacity: 2 }

红绿灯问题

红绿灯问题:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?

三个亮灯函数已经存在:

function red() {
  console.log('red');
}
function green() {
  console.log('green');
}
function yellow() {
  console.log('yellow');
}
// 解法1
const task = (timer, light, callback) => {
  setTimeout(() => {
    if (light === 'red') {
      red();
    } else if (light === 'green') {
      green();
    } else if (light === 'yellow') {
      yellow();
    }
    callback();
  }, timer);
};

const step = () => {
  task(3000, 'red', () => {
    task(1000, 'green', () => {
      task(2000, 'yellow', step);
    });
  });
};

step();
// 解法2
const task = (timer, light) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      if (light === 'red') {
        red();
      } else if (light === 'green') {
        green();
      } else if (light === 'yellow') {
        yellow();
      }
      resolve();
    }, timer);
  });

const step = () => {
  task(3000, 'red')
    .then(() => task(1000, 'green'))
    .then(() => task(2000, 'yellow'))
    .then(step);
};

step();
// 解法3
const task = (timer, light) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      if (light === 'red') {
        red();
      } else if (light === 'green') {
        green();
      } else if (light === 'yellow') {
        yellow();
      }
      resolve();
    }, timer);
  });

const taskRunner = async () => {
  await task(3000, 'red');
  await task(1000, 'green');
  await task(2000, 'yellow');
  taskRunner();
};

taskRunner();

参考网站

caniuse.com/ github.com/tc39/ecma26… es6.ruanyifeng.com/ [es6 & esnext] juejin.cn/post/695622… [babel]

推荐书籍

《你所不知道的 JS》