深度篇——原理
1.JS内存垃圾回收用什么算法
垃圾回收:函数已经执行完了 再也用不到的一些对象和数据
引用计数(之前) 循环引用出现一些缺陷, 引用次数为0则被清除
标记清除(现代)从js的根(window)下逐步遍历,只要能找到的就保留,没有找到就清除
I.【连环问】JS闭包是内存泄漏吗
闭包不是内存泄漏,但是闭包的数据不会被垃圾回收
II.如何检测JS内存泄漏
泄漏的情况就是一直上升,正常的情况是锯齿上升下降高低形状
可使用 Chrome devTools 的Performance 和 Memory 工具来检测 js 内存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GZFthd0x-1651834815722)(image/检测内存泄漏.jpg)]
III.JS内存泄漏的场景有哪些(Vue为例)
被全局变量、函数引用,组件销毁时未清除 (window定义的变量和函数 要去销毁 赋值为 null)
被全局事件、定时器引用,组件销毁时未清除 (setInterval ,clearInterval,window.addEventListen,window.removeEventListener)
被自定义事件引用,组件销毁时未清除 (event)
IV.WeakMap WeakSet
弱引用 ,weakMap的key 只能是引用类型
没有length,size ,也不能用foreach 只能用get去控制;
不会影响垃圾回收,也不会带来内存泄漏的风险
2.浏览器和nodejs事件循环(EventLoop)有什么
js 是单线程的(无论在浏览器还是nodejs)
浏览器中js执行和DOM渲染共用一个线程
异步
浏览器异步——宏任务和微任务
- 宏任务,如 setTimeout setInterval 网络请求
- 微任务,如 promise async/await
- 微任务在下一轮
DOM渲染
之前执行,宏任务在之后执行
console.log('start')
setTimeout(()=>{
console.log('timeout')
})
Promise.resolve().then(()=>{
console.log('Promise then')
})
console.log('end')
//start end Promise then timeout
//队列先进先出,宏任务队列(MarcoTask Queue) 微任务队列(MicroTask Queue)
nodejs异步——宏任务和微任务
- Nodejs同样使用ES语法,也是单线程,也需要异步
- 异步任务也分:宏任务 + 微任务
- 但是,它的宏任务和微任务,分不同
类型
,有`不同优先级
nodejs
宏任务
类型和优先级(高到低)Timers - setTimeout setInterval
V/O callbacks - 处理网络、流、TCP 的错误回调
Idle, prepare- 闲置状态(nodejs 内部使用)
Poll 轮询 -执行 poll 中的I/0队列
Check 检查 - 存储 setimmediate 回调
Close callbacks - 关闭回调,如 socket.on(‘close’ )
nodejs
微任务
类型和优先级包括:promise, async/await, process.nextTick
注意,process.nextTick 优先级最高
nodejs event loop
• 执行同步代码
• 执行微任务(process.nextTick 优先级更高)
• 按顺序执行 6 个类型的宏任务( 每个结束时都执行当前的微任务)
注意事项
• 推荐使用 setImmediate 代替 process.nextTick*
• 本文基于nodejs 最新版本。nodejs 低版本可能会有不同
console.info('start')
setImmediate(() => {
console.info("setImmediate')
})
setTimeout(()=>
console.info("timeout)
})
Promise.resolve().then(()=>
console.info("promise then')
})
process.nextTick(()=>{
console.info(nextTick)
})
console.info("end" )
//start end nextTick promise then timeout setImmediate
3.vdom真的很快?
- Virtual DOM ,虚拟DOM
- 用JS对象模拟DOM节点数据
- 由React最先推广的
- Vue React 数据驱动视图这是比较核心的;只关注业务数据,而不用再关心DOM的变化
vdom 并不快,js直接操作DOM才是最快的
但“数据驱动视图”要有合适的技术方案,不能全部DOM重建
vdom就是目前最合适的技术方案(并不是因为它快,而是合适)
svelte 组件编译更准确,像外科手术式操作DOM,像JQuery那样操作Dom,但没有Vue、React稳定
4.遍历数组,for 和 forEach 哪个快?
时间复杂度都是O(n)
但是for比forEach更快一些
forEach 每次都要创建一个函数来调用,而for不会创建函数
函数需要独立的作用域,会有额外的开销
越
低级
的代码,性能往往越好
日常开发别只考虑性能,forEach 代码可读性更好
5.nodejs如何开启多进程,进程如何通讯-进程和线程的,使用child_process或者cluster(集群)
进程
process vs线程
thread
• 进程,OS(操作系统) 进行资源分配和调度的最小单位,有独立内存空间;线程,OS 进行运算调度的最小单位,共享进程内存空间
• 一个进程包含多个线程,进程之间是独立的
• JS是单线程的,但可以开启多进程执行,如 WebWorker
为何需要多进程?
• 多核CPU,更适合处理多进程,内存较大,多个进程才能更好的利用(单进程有内存上限,内存利用不高,资源浪费),总之,“压榨〞机器资源,更快,更节省!
nodejs开启多进程可以使用开启子进程 child_process.fork 和 cluster.fork,使用send和on传递消息
实际工作中可以使用PM2插件帮助我们做进程守护
使用 child_process 的方式
//主进程-process-fork
const http = require('http')
const fork = require('child_process').fork
const server = http.createServer((req,res)=>{
if(req.url === 'get_sum'){
console.info('主进程 id',process.pid)
//开启子进程
const computeProcess = fork('./compute.js')
computeProcess.send('开始计算')
computeProcess.on('message',data=>{
console.info('主进程接受到的信息:',data)
res.send('sum is '+data)
})
computeProcess.on('close',data=>{
console.info('子进程因报错而退出')
computeProcess.kill()
res.send('error ')
})
}
})
server.listen(3000,()=>{
console.info('localhost:3000')
})
//子进程-compute
function getSum(){
let sum = 0
for(let i = 0;i < 10000;i++){
sum += i
}
return sum
}
process.on('message',data=>{
console.info('子进程 id',process.pid)
console.info('子进程接受到的信息:',data)
const sum = getSum()
//发送消息给主进程
process.send(sum)
})
打印的结果
localhost:3000主进程 id 80780
子进程 id 80781
子进程接受到的信息:开始计算
主进程接受到的信息:49995000
使用 cluster 的方式
const http = require('http')
const cpuCoreLength = require('os').cpus().length
const cluster = require('cluster')
if(cluster.isMaster){
for(let i=0;i<cpuCoreLength;i++){
cluster.fork() //开启子进程
}
cluster.on('exit',worker=>{
console.log('子进程退出')
cluster.fork() //进程守护
})
}else{
//多个进程会共享一个TCP连接,提供一份网络服务
const server = http.createServer((req,res)=>{
res.writeHead(200)
res.end('done')
})
server.listen(3000)
}
6.js-bridge的实现原理
小游戏: chrome://dino/
JS Bridge常用的实现方式
- 注册全局API (简单的封装)
- URL Scheme (推荐)
//封装JS-bridge
const sdk = {
invoke(url, data = {}, onSuccess, onError) {
const iframe = document.createElement("iframe")
iframe.style.visibility ='hidden'
document.body.appendChild(iframe)
iframe.onload = () => {
const content = iframel.contentWindow.document.body.innerHTML
onSuccess(JSON.parse(content))
iframe.remove()
}
iframe.onerror = () => {
onError()
iframe.remove()
iframe.src= `my-app-name://$(url}?data=${JSON.stringify(data)}`
}
}
fn1(data, onSuccess, onError) {this. invoke('api/fn1', data, onSuccess, onError)}
fn2(data, onSuccess, onError) {this.invoke('api/fn2', data,onSuccess, onError)}
}
sdk.fn1()
7.requestIdleCallback 和 requestAnimationFrame 区别
requestAnimationFrame 每次渲染完都会执行,高优
requestIdleCallback 空闲时才会执行,低优
都是宏任务
<html>
<body>
<button id='btn1'>change</button>
<div id="box"></div>
<script>
const box = document.getElementById( 'box' )
document.getElementById('btn1').addEventListener('click',()=>{
let curWidth = 100
const maxwidth= 400
function addwidth(){
curWidth = curWidth + 3
box.style.width =
`${curwidth}px`
if (curwidth < maxWidth) {
//requestIdleCallback实现效果一样
window.requestAnimationFrame(addwidth)//时间不用自己控制
}
}
addwidth()
})
</script>
</body>
</html>
执行先后顺序
//执行顺序:start end timeout requestAnimationFrame requestIdleCallback
window.onload = () => {
console.info("start')
setTimeout(() => {
console.info('timeout )
})
window.requestIdleCallback(()=>
{
console.info('requestIdleCallback")
})
window.requestAnimationFrame(()=>
{
console.info('requestAnimationFrame")
})
console.info("end')
}
8.vue生命周期
beforeCreate
* 创建一个空白的Vue实例
* data method 尚未被初始化,不可使用create
* Vue实例初始化完成,完成响应式绑定
* data method 都已经初始化完成,可调用
* 尚未开始渲染模板beforeMount
* 编译模板,调用render 生成vdom
* 还没有开始渲染DOMmounted
* 完成DOM渲染
* 组件创建完成
* 开始由“创建阶段”进入“运行阶段”beforeUpdate
* data发生变化之后
* 准备更新DOM(尚未更新DOM)
updated
* data发生变化,且DOM更新完成
* (不要在updated中修改data,可能会导致死循环)
beforeUnmount
* 组件进入销毁阶段(尚未销毁,可正常使用)
* 可移除、解绑一些全局事件、自定义事件
unmounted
* 组件被销毁了
* 所有子组件也都被销毁了
补充说明——keep-alive组件
* onActivated缓存组件被激活
* onDeactivated缓存组件被隐藏
Vue什么时候操作DOM比较合适
- mounted和updated都不能保证子组件全部挂载完成
- 使用$nextTick渲染DOM
mounted(){
this.$nextTick(function(){
//仅在整个试图都被渲染之后才会运行的代码
})
}
Ajax应该在哪个生命周期?
- created和mounted
- 推荐mounted
9.Vue2 Vue3 React 三者diff算法有什么区别?
Vue2-双端比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaCg1qcP-1651834815724)(image/vue2Diff.jpg)]
Vue3-最长递增子序列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hKzOCT1-1651834815725)(image/vue3Diff.jpg)]
React-仅右移
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UwPKatA3-1651834815726)(image/ReactDiff.jpg)]
Vue React 为何循环时必须使用 key?
- vdom diff 算法会根据 key 判断元素是否要删除
- 匹配了 key ,则只移动元素 -性能较好
- 未匹配key ,则删除重建-性能较差
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WzCGMWOK-1651834815726)(image/ReactKey.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YFKHhau-1651834815727)(image/keyTemplate.jpg)]
介绍diff 算法
- diff 算法很早就有
- diff 算法应用广泛,例如 github 的Pull Request 中的代码 diff
- 如果要严格 diff 两棵树 ,时间复杂度 O(n^3),不可用
Tree diff 的优化——时间复杂度降到O(n)
- 只比较同一层级,不跨级比较tag 不同则删掉重建(不再去比较内部的细节)
- 子节点通过 key 区分(key 的重要性)
10.Vue-router-MemoryHistory(abstract’)?
Vue-router 三种模式
Hash
* location.hash->带 #WebHistory
* history. pushState
* window. onpopstateMemoryHistor
( V4 之前叫做 abstract history)
* 页面渲染正常,没有路由映射(就是IP+端口号),没有前进和后退扩展:React-router 也有相同的 3 种模式