JavaScript知识简记(二)

40 阅读6分钟

事件循环机制

事件循环是JS的事件异步执行机制,先执行同步代码,然后检查微任务队列并执行微任务,所有微任务执行完后再执行宏任务,每次执行完宏任务后都必须先清空微任务队列中的任务才能继续执行下一个宏任务。

1.宏任务:setTimeout,setInterval,IO事件,UI渲染(requestAiminationFrame)。

2.微任务:promise.then,process.nextTick,Mutation Observer

node的事件循环区别:先执行同步代码,再执行nextTick,再执行微任务,最后按照6个阶段(定时器阶段,******待定回调阶段,****闲置准备阶段,****轮询阶段,****检查阶段,****关闭回调阶段)**执行宏任务。

捕获/冒泡

捕获阶段:事件从父元素到子元素依次执行。

目标阶段:事件传递到事件源元素为目标阶段,可以通过event.target获取触发事件的具体元素。

冒泡阶段:事件从子元素到父元素依次执行。

addEventListener默认为冒泡,第三个值为true则为捕获。event.stopPropgation()停止事件传播,event.preventDefault()阻止默认行为。

事件委托:在父元素上直接绑定事件后可以通过利用事件的冒泡来为所有子元素直接绑定事件。

typeof/instanceof

typeof:主要用于基础类型object,undefined,string,number,function,symbol,boolean。但无法用于null(返回'object‘),数组,此时可以用Array.isArray(),insatanceOf,Object.ptototype.toString.call实现。

instanceof:主要用于原型链判断。

for of/in区别

for in:主要遍历对象的可枚举的属性,不可枚举的属性可以通过Object.getOwnpropertyNames().

for of:主要遍历数组,set,map等可迭代对象,内部实现基于迭代器,比起forEach好处是可以使用break/return等中断循环。

深拷贝/浅拷贝

1.浅拷贝: 浅拷贝只拷贝一层,对于对象只拷贝对象的指针值,拓展运算符和Object.asign都是浅拷贝。

2.**深拷贝:**深拷贝是完全拷贝,包括对象的所有内容全部拷贝一份,JSON.stringfy()是深拷贝,但会忽略undefined,symbol,函数。以下是手写实现深拷贝,关键点在于使用weakMap防止循环引用,区分数组跟对象,使用hasOwnProperty防止拷贝原型对象上的属性,使用Object.getOwnPropertySymbols拷贝symbols值:

// 深拷贝
function deepCopy(target, hashMap = new WeakMap()) {  
if (typeof target !== "object" || target == null) 
return target;  
if (target instanceof RegExp) 
return new RegExp(target);  
if (target instanceof Date) 
return new Date(target);  
if (hashMap.has(target))
return hashMap.get(target);  
let res = Array.isArray(target) ? [] : {};  
hashMap.set(target,res);  
for (let i in target) {    
if (target.getOwnProperty(i)) res[i] = deepCopy(target[i], hashMap);  }  
for (let i of Object.getOwnPropertySymbols(target)) {    
res[i] = deepCopy(target[i], hashMap);  
}  
return res;}

Set/Map

1.Set的方法有:add,delete,has,clear,size()

// Set
const s=new Set();
[1,2,3,4,5].forEach(x=>s.add(x));
for(let i of s){  console.log(i);}
const arr=[1,2,1];[...new Set(arr)]//去除数组中重复值.const array=Array.from(s);//Set转为数组

2.Map的方法有,Map会维护插入元素的顺序:

// Map
const hashMap=new Map();
const o={a:1};
hashMap.set(o,'ccc');//各种类型包括对象都能作为键hashMap.get(o);hashMap.has(o);hashMap.delete(o);
for(let i of hashMap){  
console.log(i);//结果为数组,i[0]为key,i[1]为value}
for(const [key,value] of hashMap.entries()){
  console.log(key,value);
}

Map的应用,比如LRU缓存:

```class LRUCache{  constructor(size){    this.capicity=size;    this.cache=new Map();  }  get(key){    if(!this.cache.has(key)) return -1;    const value=this.cache.get(key);    this.cache.delete(key);    this.cache.set(key,value);    return value;  }  put(key,value){    if(this.cache.has(key)){      this.cache.delete(key);    }else if(this.cache.size()>=this.capicity){      this.cache.delete(this.cache.keys().next().value);    }    this.cache.set(key,value);  }  clear(){    this.cache.clear();  }  delete(key){    this.cache.delete(key);  }}```

JS隐式转换

1.*,/,-会隐式转换为Number

2.对于+(在前的优先):

  • 一侧为String,另一侧转为字符串相加。
  • 一侧为Number时,另一侧为非Number的原始类型时,转为Number后计算。
  • 一侧为Number时,另一侧为引用类型,转为字符串相加。

3.逻辑判断语句(if,while,for)优先转为boolean,其中null,'',0,undefined,NaN为false,其它如{},[]为true.

4.对于'=='号使用:

  • 任何和NaN比较结果都为false,包括NaN自身.
  • undefined==null为true,其他与undefined/null比较都为false.
  • String与Number比较,String会转为Number.
  • Boolean与Number比较,Boolean转为Number.

Web Worker

web worker是一个独立的js文件,拥有独立的上下文,即无法读取网页dom,也无法读取本地文件,必须和主线程通过消息进行通信,即互相通过postMessage和onmessage实现通信。

JS垃圾回收机制

引用计数:一个对象是否被需要由是否还有其它对象引用它决定。如果没有引用,则引用计数为0,将会被垃圾回收,也就是根据对象的可达性来决定是否保留该对象(被弃用的实验品,存在循环引用无法释放问题)。

标记清除:标记阶段标记所有活动对象,清除阶段清除所有没有标记的对象。

分代回收:

  • 对于创建销毁快速,存活时间较短的对象,使用新生代进行存放管理,通过频繁扫描确保失去作用后快速清理。新生代分为Fromto区域,新创建的对象先是在From区域,从根对象开始递归遍历,将被根引用的对象复制到to区域,然后将to区域作为新的From区域,并清除From区域的对象。
  • 对于在新生代中存活时间较长的对象会晋升到使用老生代进行存放管理,降低清理频率(通过标记清除法进行清理)。

JS调用栈

函数实际上是调用栈,而函数的定义比函数的执行先进行。

AMD/CMD/ES6 Module规范

AMD:依赖前置,提前加载出来,是异步加载模块风格。

CMD:依赖就近,按需加载出来,是同步加载模块风格。

define(['a','b'],function(res){})//回调函数风格,AMD
define(function(require){
    const a=require('a');
    const b=require('b');
}//同步风格,CMD

ES6 Module:import/export,编译时分析,支持treeShaking,且输出的为引用类型,其他模块方式都是值拷贝方式。

export const a={};
impot a from './file.js';

Common.js:运行时加载,同步执行。

const a={}
module.exports={a}

const x=require('./file.js')
x.a

类数组

有length以及数字索引,但没有数组哪些方法的对象为类数组,如arguments,nodeList,类数组转为真数组方式:

  • Array.from()
  • 拓展运算符...
  • Array.prototype.slice.call()

WeakMap/WeakSet

键只能是对象,同时对这个键是弱引用,也就是当只有WeakMap/WeakSet引用时,可被垃圾回收,不可进行遍历.

  • 应用场景1:存储类的私有属性。

    const privateData=new WeakMap(); class user{ constructor(){ privateData.set(this,{ a:1, b:2 })
    } }

  • 应用场景2:存储dom元素相关属性。

    const domData=new WeakMap(); domData.set(document.getElementById('a'),{x:1,y:2});

实现类的私有属性

  • new WeakMap.set(this, { key:value } )

  • 使用Symbol作为属性名

  • 使用#定义

    class user{
        #a;
        #b;
    }
    
  • 使用Proxy的get或者set拦截器,即包装成Proxy后使用get/set阻止对私有变量的修改。