2周刷完100道前端优质面试——深度篇

深度篇——原理

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
* 还没有开始渲染DOM
mounted
* 完成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. onpopstate
MemoryHistor( V4 之前叫做 abstract history)
* 页面渲染正常,没有路由映射(就是IP+端口号),没有前进和后退
扩展:React-router 也有相同的 3 种模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值