数据结构 Set 和 Map 整体概述: Set是一种类似数组的数据结构,区别在于其存储的成员都是不重复的,由此带来了它的一个应用就是:去重。Set通过new关键字实例化,入参可以是数组 or 类数组的对象。
值得注意的是:在Set中,只能存储一个NaN,这说明在 Set 数据结构中,NaN等于NaN。
Set实例的方法: 操作方法 add()、delete()、has()和 clear(); 遍历方法:keys()、values()、entries()和 forEach();扩展运算符…、 数组方法 map()、filter()方法也可以用于 Set 结构。 由此它可以很方便的实现数组的交、并、差集。
WeakSet类似于 Set,主要区别在于 1.成员只能是对象类型; 2.对象都是弱引用(如果其他对象都不再引用该对象,垃圾回收机制会自动回收该对象所占的内存,不可预测何时会发生,故 WeakSet 不可被遍历)
JavaScript 对象 Object 都是键值 K-V 对的集合,但 K 取值只能是字符串和 Symbol,Map 也是 K-V 的集合,然而其 K 可以取任意类型。 如果需要键值对的集合,Map 比 Object 更适合。Map 通过 new 关键字实例化。
Map实例的方法:set()、get()、has()、delete()和 clear(); 遍历方法同 Set。
Map与其它数据结构的互相转换:Map <—> 数组| Map <—> 对象| Map <—> JSON。
WeakMap类似于Map,主要区别在于: 1.只接受对象作为键名; 2.键名所指向的对象不计入垃圾回收机制。
Set Set 是一个类似数组,但是与数组不同,它具有唯一性.里面的元素都是不重复的,本身是一个构造函数.
1 2 3 4 5 6 7 8 9 10 11 const  SetArr  = new  Set ();[1 ,1 ,2 ,2 ,3 ,3 ].forEach (item =>SetArr .add (item)); console .log (SetArr );=> 0 : 1 1 : 2 2 : 3 size : 3 __proto__ : Set 
 
实例属性 
constructor: 构造函数,就是 Set 函数 
size: 返回的是 Set 实例的长度 
 
实例方法 
add(): 往 Set 里添加值,返回 Set 本身 
delete(): 删除某个值,返回布尔值判断是否成功 
has(value): 判断是否有 value,返回布尔值 
clear(): 清除 Set 中所有值,无返回值 
 
遍历方法 
forEach(): 使用回调函数遍历元素 
entries(): 返回键值对的遍历器,用于遍历[键名,键值]组成的数组 
values(): 返回键值遍历器,用于遍历所有键值 
keys(): 返回键名遍历器,用于遍历所有键名
由于 Set 结构是只有键值的结构,所有 keys 方法与 values 方法返回一致。
 
 
 
forEach 1 2 3 let  s = new  Set ([1 , 2 , 3 , 4 , 5 ]);s.forEach ((item ) =>  console .log (item + 1 )); 
 
entries 1 2 3 4 5 6 7 let  s = new  Set (["a" , "b" , "c" ]);for  (let  item of  s.entries ()) {  console .log (item); } 
 
values 1 2 3 4 5 let  s = new  Set (["a" , "b" , "c" ]);for  (let  item of  s.values ()) {  console .log (item); } 
 
keys 1 2 3 4 5 let  s = new  Set (["a" , "b" , "c" ]);for  (let  item of  s.keys ()) {  console .log (item); } 
 
使用 set 实现交并差 1 2 3 4 5 6 7 8 9 let  a = new  Set ([1 , 2 , 3 , 4 ]);let  b = new  Set ([3 , 4 , 5 , 6 ]);let  value = new  Set ([...a].filter ((item ) =>  b.has (item)));let  value2 = new  Set ([...a], [...b]);let  value3 = new  Set ([...a].filter ((item ) =>  !b.has (item)));
 
WeakSet 一个弱的 Set 结构,弱的体现在WeakSet的成员只能是对象.WeakSet的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用.WeakSet没有 size 属性,无法遍历.只有实例方法:
add(): 向WeakSet添加新成员 
delete(): 删除WeakSet的成员 
has(): 判断WeakSet是否包含某个元素 
 
Map Set 结构是没有键只有值,而 Map 结构是键值的组合.也就可以使用各种数据类型作为键(包括对象).
1 2 3 4 5 let  map = new  Map ();let  obj = { name : "alan" , age : 20  };map.set (obj, "这是一些描述" ); map.get (obj); console .log (map);
 
实例属性 
constructor: Map 构造函数 
size: 返回的是 Map 元素的长度 
 
实例方法 
set(key, value): 设置 key 所对应的 value 值 
get(key): 获取 key 所对应的 value 值,若无则返回 undefined 
delete(): 删除某个值,返回布尔值判断是否成功 
has(value): 判断是否有 value,返回布尔值 
clear(): 清除 Set 中所有值,无返回值 
 
遍历方法 
forEach(): 使用回调函数遍历元素 
entries(): 返回键值对的遍历器,用于遍历[键名,键值]组成的数组 
values(): 返回键值遍历器,用于遍历所有键值 
keys(): 返回键名遍历器,用于遍历所有键名 
 
keys 1 2 3 4 5 6 7 8 9 let  person = new  Map ([  ["name" , "alan" ],   ["age" , "20" ], ]); for  (let  item of  person.keys ()) {  console .log (item); } 
 
values 1 2 3 4 5 6 7 8 9 let  person = new  Map ([  ["name" , "alan" ],   ["age" , "20" ], ]); for  (let  item of  person.values ()) {  console .log (item); } 
 
与其他数据结构转换 
1 2 3 4 5 let  person = new  Map ([  ["name" , "alan" ],   ["age" , "20" ], ]); console .log (...person);
 
1 2 3 4 5 let  arr = [  ["name" , "alan" ],   ["age" , "20" ], ]; let  m = new  Map (arr);
 
1 2 3 4 5 6 7 8 9 10 11 12 13 let  person = new  Map ([  ["name" , "alan" ],   ["age" , "20" ], ]); function  swap (map ) {  let  obj = Object .create (null );   for  (let  [key, value] of  map) {     obj[key] = value;   }   return  obj; } swap (person);
 
1 2 3 4 5 6 7 8 let  person = { name : "allen" , age : "20"  };function  swap (obj ) {  for  (let  key of  Object .keys (obj)) {     map.set (key, obj[key]);   }   return  map; } swap (person);
 
WeakMap WeakMap 与 Map 一样可以生成键值对的集合,但是也有不同的地方,主要是有以下的两点:
WeakMap 只接受对象作为键名(不包括 null) 
ct 
 
与 WeakSet 相似,WeakMap 也是没有遍历的操作,也没有 size 属性,没有办法列出所有键名(由于垃圾回收机制的运行),也不能清空。
实例方法 
get(): 
set(): 
delete(): 删除 WeakMap 的成员 
has(): 判断 WeakMap 是否包含某个元素 
 
Array.from 将类数组对象转化为数组. 第二个参数可以对传入值进行处理.
模块化 方式有立即执行函数,AMD,CMD,CommonJS,ES6 模块化. 其中最常用的是 CommonJS 和 ES6 模块化
区别 
CommonJS 即使用require(),module.exports,通常在 Node 中使用.支持动态导入.属于同步导入. 
ES6 模块化即使用import,export default.通常用于浏览器,属于异步导入. 
CommonJS 在导出时是值拷贝,就算导出的值变了,导入的也不会变.如果想更新,必须重新导入.ES6 模块化采用实时绑定,导出导入都指向同一个地址,所以导入变化导出也会变化. 
ES6 模块会编译成require/exports来执行. 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 define (["./a" , "./b" ], function  (a, b ) {     a.do ();   b.do (); }); define (function  (require , exports , module  ) {        var  a = require ("./a" );   a.doSomething (); }); 
 
Commonjs 1 2 3 4 5 6 7 8 9 10 module .exports  = {  a : 1 , }; exports .a  = 1 ;var  module  = require ("./a.js" );module .a ; 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var  module  = require ("./a.js" );module .a ;module .exports  = {  a : 1 , }; var  module  = {  id : "xxxx" ,    exports : {},  }; var  exports  = module .exports ;var  load = function  (module  ) {     var  a = 1 ;   module .exports  = a;   return  module .exports ; }; 
 
另外虽然 exports 和 module.exports 用法相似,但是不能对 exports 直接赋值。因为 var exports = module.exports 这句代码表明了 exports 和 module.exports 享有相同地址,通过改变对象的属性值会对两者都起效,但是如果直接对 exports 赋值就会导致两者不再指向同一个内存地址,修改并不会对 module.exports 起效。
箭头函数 
箭头函数继承而来的 this 指向永远不变. 
 
使用call,apply,bind都无法改变箭头函数 this 指向.
箭头函数不能作为构造函数使用.因为它没有自己的 this 
箭头函数没有自己的arguments对象. 
 
在箭头函数中访问 arguments 实际上获得的是外层局部(函数)执行环境中的值. 如果想访问箭头函数中的参数,可以使用rest参数.
箭头函数没有原型prototype 
箭头函数不能用作Generator函数,不能使用 yeild 关键字 
 
类和继承 ES6 的类,完全可以看作构造函数的另一种写法。 类的数据类型就是函数,类本身就指向构造函数。
1 2 3 4 5 6 class  Point  {   } typeof  Point ; Point  === Point .prototype  .constructor ; 
 
类的所有方法都定义在类的 prototype 属性上面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class  Point  {  constructor ( ) {        }   toString ( ) {        }   toValue ( ) {        } } Point .prototype   = {  constructor ( ) {},   toString ( ) {},   toValue ( ) {}, }; 
 
constructor 关键字 constructor()方法就是构造方法,而this关键字则代表实例对象。constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有 constructor()方法,如果没有显式定义,一个空的constructor()`方法会被默认添加。
extends 关键字 作用:类继承 底层原理: 替换原型继承(不会覆盖子构造函数的原型) extends 关键字替换原型的原型:s1.__proto__.__proto__ = Person.prototype
super 关键字 用于调用父类的方法. 子类中如果想使用constructor,必须使用super关键字调用父类的方法.super这个关键字,既可以当作函数使用,也可以当作对象使用
在子类的构造函数中,只有调用 super()之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有 super()方法才能让子类实例继承父类。 如果子类没有定义 constructor()方法,这个方法会默认添加,并且里面会调用 super()。也就是说,不管有没有显式定义,任何一个子类都有 constructor()方法。
 
当做函数 1 2 3 4 5 6 7 class  A  {}class  B  extends  A  {  constructor ( ) {     super ();   } } 
 
注意,super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B 的实例,因此 super()在这里相当于 A.prototype.constructor.call(this)。
 
当做对象 super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 class  A  {  p ( ) {     return  2 ;   } } class  B  extends  A  {  constructor ( ) {     super ();     console .log (super .p ());    } } let  b = new  B ();
 
上面代码中,子类 B 当中的 super.p(),就是将 super 当作一个对象使用。这时,super 在普通方法之中,指向 A.prototype,所以 super.p()就相当于 A.prototype.p()。
注意,由于 super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class  Person  {     private name :string = 'Tom' ;      public age : number = 10 ;      protected sex : string;   getName ( ) {     return  'Name'    }   constructor (name: string ) {     this .name  = name   } } const  person = new  Person ('dell' );console .log (person.name );class  Teacher  extends  Person  {  getTeacherName ( ) {     return  'teacher'    }   getName ( ){         return  super .getName ()   }   constructor (age: number ){               super ('dell' );   } } const  person = new  Teacher (123 );console .log (person.name ); console .log (person.age ); 
 
类中的 getter 和 setter 私有属性无法在外部调用,可以利用getter间接调用私有属性.使用setter间接设置私有属性.
1 2 3 4 5 6 7 8 9 10 11 class  Person  {  constructor (private  _name: string  ) {}   get  name () {     return  this ._name ;   }   set  name (name: string  ) {     this ._name  = name;   } } const  person = new  Person ("dell" );person.name  = "dell" ; 
 
单例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class  Demo  {  private  static  instance : Demo ;   private  constructor ( ) {}   static  getInstance ( ) {     if  (!instance) {       this .instance  = new  Demo ();     }     return  this .instance ;   } } const  demo1 = Demo .getInstance ();const  demo2 = Demo .getInstance ();
 
抽象类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 abstruct class  Geom  {   width : number ;   getType ( ) {     return  'Geom'    }      abstract  getArea (): number ; } class  Circle  extends  Geom  {  getArea ( ) {     return  '123'  } 
 
Proxy Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。 Proxy 内置一系列陷阱,用于创建一个对象的代理,从而实现基本操作的拦截和自定义.
简单来说, 通过Proxy创建对于原始对象的代理对象,从而在代理对象中使用Reflect达到对JS操作的拦截.
1 let  p = new  Proxy (target, handler)
target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
1 2 3 4 5 6 7 8 9 10 11 const  obj = {  name : 'Tom'  } const  proxy = new  Proxy (obj, {  get (target, key, receiver ) {     console .log ('劫持的对象名' , target)     console .log ('劫持的属性名' , key)     return  target[key]   } }) proxy.name   
上面就是通过Proxy创建了一个代理对象,同时在Proxy内部声明了一个get陷阱.
当我们访问`proxy.name`时实际触发了对应的get陷阱.执行陷阱中的逻辑.
`target`就是劫持的源对象`obj`,
`key`就是劫持的获取的name值,注意getter函数取值时才会触发.
`receiver`表示代理的对象proxy或者继承Proxy的对象.也就是谁调用了函数,就是指的谁.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 const  parent = {  name : 'parent' ,   get  value () {     return  this .name ;   }, }; const  handler = {get (target, key, receiver ) {return  Reflect .get (target, key);}, }; const  proxy = new  Proxy (parent, handler);const  obj = {name : 'obj' ,}; Object .setPrototypeOf (obj, proxy);console .log (obj.value ); const  handler = {get (target, key, receiver ) {return  Reflect .get (target, key, receiver);}, }; `` `` - Proxy 中接受的Receiver 形参表示代理对象本身或者继承自代理对象的对象 - Reflect 中传递的Receiver 实参表示修改执行原始操作时的this 指向.(实际上就是指向调用者). 接下来我们通过 Proxy  来实现一个数据响应式 `` `javascript let onWatch = (obj, setBind, getLogger) => {   let handler = {     get(target, property, receiver) {       getLogger(target, property)       return Reflect.get(target, property, receiver)     },     set(target, property, value, receiver) {       setBind(value, property)       return Reflect.set(target, property, value, receiver)     }   }   return new Proxy(obj, handler) } let obj = { a: 1 } let p = onWatch(   obj,   (v, property) => {     console.log(` 监听到属性${property}改变为${v}`)   },   (target, property) => {     console.log(` '${property}'  = ${target[property]}`)   } ) p.a = 2 // 监听到属性a改变 p.a // 'a' = 2 ` `` ` 在上述代码中,我们通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。 当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。 # Reflect Reflect 提供拦截 JS 操作的方法.并非一个构造函数,不能用 new 进行调用. 它的所有方法和属性都是静态的. ` `` javascriptReflect .get (target, key, receiver);
 
虽然也可以直接使用target[key],但是 Reflect 可以用 receiver,更强大.
案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let  array = [1 , 2 , 3 ];array[-1 ];  array[-2 ];  array[-3 ];  array = new  Proxy (array, {   get (target, key ) {          key = Number (key);     if  (Number (key) < 0 ) {       key = key + target.length ;     }     return  Reflect .get (target, key);   }, }); 
 
深层遍历 1 2 3 4 5 6 7 8 9 const  handler = {  get (target, key ){     let  res = Reflect .get (target.key )     retrun typeof  res === 'object'  ? new  Proxy (res,handler) : res   }, 	set (target, key, val ){     return  Reflect .set (target, key, val)   } } 
 
Generator Generator 即生成器,它会返回一个迭代器.Generator 最大的特点就是可以控制函数的执行。可以作为异步解决方案.
1 2 3 4 5 6 7 8 9 function * foo (x ) {  let  y = 2  * (yield  x + 1 );   let  z = yield  y / 3 ;   return  x + y + z; } let  it = foo (5 );console .log (it.next ()); console .log (it.next (12 )); console .log (it.next (13 )); 
 
Generator 函数需要手动调用next执行,在遇到yield会暂停,返回一个对象,包括执行结果和是否执行完毕.继续调用next会从之前暂停的地方开始,如果传入参数,会覆盖之前的结果.到下一个yield结束. 分析上面的代码:
第一次执行next,参数是 5,遇到yield暂停,返回值为 5+1 => 6. 
第二次执行next,next有参数 12, 覆盖上一次yield处的值,就是y = 2*12,要在这一次的yield处暂停就要 24/3,得到 8 
最后一次执行next,z 就被赋值为 13,结合第二次 y 是 24,第一次 x 是 5,最后返回 5+13+24=42. 
 
Promise Promise 是异步问题同步化解决方案. Promise 本身不是异步,它是个构造函数. Promise 中的函数是同步的. Promise 有一个参数,即excutor执行器,它有两个参数,resolve和reject.excutor是同步执行,而then是异步调用的. 如果想连续then需要上一个promise中 return 一个新的promise. Promise 对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止.
状态 promise 内部在resolve之前的代码处于pending状态,之后的是fulfilled. promise 在catch中如果没有抛出错误,则会返回fufilled
链式调用 状态固化后,就不再捕获错误了.比如 resolve()返回后面有报错的内容,但是 catch 就不再捕获了. 案例: 当 promise 作为参数传递到另一个 promise 中,这个第二个 promise 中的状态就会失效.依赖于第一个 promise.
1 2 3 4 5 6 7 8 const  p1 = new  Promise ((reslove,reject )=> {  setTimeout (()=> {reject (new  Error ('fail' )},3000 ) }) const  p2 = new  Promise ((resolve,reject )=> {  setTimeout (()=> {resolve (p1)},1000 ) }) p2.then (result =>console .log (result)) 	.catch (err =>console .log (err)) 
 
Promise.all()如果三个内容都失败,只返回第一个失败的信息.如果只有一个出错,也只会返回一个.而且不返回正常的. Promise.race()是谁先完成返回谁,无论是成功还是失败.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 promise   .then ((value ) =>  {     return  new  Promise ((resolve, reject ) =>  {       setTimeout (() =>  {         reject ("Error" );       }, 300 );     });   })   .then (     (value ) =>  {       console .log (value);     },     (reason ) =>  {       console .log (reason);     }         )   .then (               (value ) =>  {       console .log (value);     },      (reason ) =>  {       console .log (reason);     }   ); 
 
1 2 3 4 5 6 7 8 9 10 .then (   (value )=> {throw  new  Error ('Error' ) ) .then (    	(value )=> {console .log (value)},   (reason )=> {console .log (reason)}    ) 
 
缺点 其实它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。
如何中断 promise 插入一个 pending 状态的 promise.
catch catch 在 promise 的源码层面就是一个 then,如果 catch 中 return 的有值,可以在 catch 后跟 then. 既有 then 中的失败回调,又有 catch 的情况,会走最近的那个.
1 2 3 4 5 6 7 8 9 10 11 12 13 const  p1 = Promise .resolve ();p1.then (() =>  {   console .log (1 );   throw  new  Error (); })   .catch (() =>  {     console .log (2 );   })   .then (() =>  {     console .log (3 );   }); 
 
案例 使用 promise 加载图片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function  loadIMg (src ) {  return  new  Promise ((resolve, reject ) =>  {     const  img = document .createElement ("img" );     img.onload  = function  ( ) {       resolve (img);     };     img.onerror  = function  ( ) {       reject (new  Error ("加载失败" ));     };     img.src  = src;   }); } const  url1 =  "https://i1.hdslb.com/bfs/face/f4d60f852eb1a85696447838c90a94acad31b7ae.jpg@160w_160h_1c_1s.webp" ; const  url2 =  "https://i0.hdslb.com/bfs/face/a8ef30d6688d0b532bd20baa160417deae8f386d.jpg@240w_240h_1c_1s.webp" ; loadImg (url1)  .then ((img ) =>  {     console .log (img);     return  loadImg (url2);   })   .then ((img2 ) =>  {     console .log (img2);   }); 
 
语法糖 Promise.resolve() Promise.reject()
Promise.all 如果所有都正确执行,会按顺序返回.有一个失败,就失败.
Async/await 一个函数前加了async,它就会返回 Promise.它的返回值会被Promise.resolve()包裹.
await只能配套async使用
缺点 因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。 如果代码没有依赖性的话,完全可以使用 Promise.all 的方式
async/await 和 promise 的关系 await 是等待 promise 的返回,await 做不了 promise 返回状态的工作. await 对应Promise.then成功的情况. await 后如果是函数,则会把函数返回的结果用Promise.then()返回. async 中的Promise.reject()需要使用try/catch进行捕获错误. async 中的函数内部属于同步代码,在遇到 await 之前都可以先执行.
1 2 3 4 5 6 7 8 9 let  a = 0 ;let  b  = async  ( ) => {  a = a + (await  10 );   console .log ("2" , a);  }; b ();a++; console .log ("1" , a); 
 
首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来 
因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码 
同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10 
 
上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator。