主讲:麓一
发展历史
- 浏览器脚本:
Javascript; - 服务器脚本:
PHP / ASP / JSP;
我们需要了解的内容/重点:
-
ECMAScript是一个标准,JavaScript是标准的实现; -
ECMAScript是JavaScript的规格; -
ECMAScript的方言,还有JScript,ActionScript; -
浏览器端对于语言特性的实现,有一些滞后;
-
浏览器在用户侧的升级,也有一些滞后;
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-2、babel-preset-2016等等;
- 使用的使用要注入不同的版本:
babel 7
preset-env:
-
targets : {} -
根据用户的一些浏览器的特性,在运行时动态的引入这些
polyfill(垫片);bundle size最小。 -
polyfill是一个概念:垫片。在一个只支持es5的浏览器中,去运行es6;
在没有任何配置选项的情况下,babel-preset-env 与 babel-preset-latest(或者 babel-preset-es2015,babel-preset-es2016 和 babel-preset-es2017 全部)的行为完全相同。
{
"presets": ["env"]
}
例如只包含了支持每个浏览器 最后两个版本 和 Safari 大于等于 7 版本所需的 polyfill 和代码转换。
{
"presets": [
[
"env",
{
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}
]
]
}
如果目标开发 Node.js 而不是浏览器应用的话,你可以配置 babel-preset-env 仅包含特定版本所需的 polyfill 和 transform:
{
"presets": [
[
"env",
{
"targets": {
"node": "6.10"
}
}
]
]
}
方便起见,你可以使用 "node": "current" 来包含用于运行 Babel 的 Node.js 最新版所必需的 polyfill 和 transform。
{
"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对象是所有(非箭头)函数中都可用的局部变量(类数组对象); -
callee是arguments对象的属性,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;- 如果参数类型不是
NaN,Number.isNaN一律返回false;
- 如果参数类型不是
与传统的全局方法 isFinite() 和 isNaN() 的区别在于,传统方法先调用 Number() 将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite() 对于非数值一律返回 false,Number.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() 用来判断一个数值是否为整数;
整数和浮点数采用的是同样的储存方法,所以 25 和 25.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 新增的数据结构 Set 和 Map)。
// 类数组对象
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
- 将
Object上一些明显属于语言内部的方法,放到Reflect对象上,现在Object和Reflect一同部署; - 修改某些
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:方法根据指定的key和value在WeakMap对象中添加新/更新元素;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 缓存
- leetCode 146 题 - leetcode-cn.com/problems/lr…
put的实现时,可以考虑使用this.cachrQueue.keys().next().value;
// 一个 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》