Skip to content

模拟题五

1.CSS 选择器有哪些

id 选择器、class 选择器、标签选择器、通用选择器、属性选择器、相连选择器、子选择器、后代选择器、伪类选择器

2. 手写 Object.create

javascript
function create(proto) {
    function F() {}
    F.prototype = proto
    return new F()
}

相关文章:Object.create

3. 作用域

JavaScript 的作用域是词法作用域,其特点是在那里定义它就作用在那里,与定义位置有关而与调用位置无关

作用域分为全局作用域、函数作用域、块级作用域

全局作用域顾名思义,全局中只有一个

函数作用域只作用于函数中,函数内的变量函数外不能调用。

块级作用域仅在 let、const 声明时才会形成

因为函数作用域的特点使得作用域有链状,即作用域链

4.ES Module 和 CommonJs

类似问题:前端模块化机制有哪些

顺序:commonJS-ADM/CMD-ES6

ES6 import 模块引入 ESModule

node commonJS

AMD 使用 requireJS 来编写模块化,依赖必须提前申明好;推崇返回值的方式对外输出

CMD 玉伯提出,使用 seajs 来编写模块化,支持动态引入依赖,推崇通过给 module.exports 赋值的方式对外输出

CommonJS 与 ES6 模块的区别?

CommonJS 输出的是一个值的拷贝,ES6 模块输出的是值的引用

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口

5.React: useMemo、useCallback是什么

useCallback:缓存函数,结合 memo 能让子组件不重复渲染

  • useCallback(() => {}, [desp])

useMemo:缓存值,结合 memo 能让子组件不重复渲染

  • useMemo(() => value, [desp])

衍生问题:除了useCallback和 useMemo,React 还有那些性能优化的点

除了useCallback和 useMemo,React 还有那些性能优化的点

React Fiber 的工作流简单来说分为两个阶段:render 和 commit

render 阶段会进行多次调用(会被打断)

commit 阶段是将虚拟 DOM 渲染到真实DOM上

所以优化可以分为三类

  • render阶段优化。即跳过不必要的组件的渲染更新

  • commit 阶段优化。即减少 commit 阶段耗时

  • 非渲染方面的优化

render阶段优化

React.memo

useMemo、useCallback 实现未定的 props 值

列表项使用 key 属性

commit 阶段优化

避免在 didMount、didUpdate 中更新组件 State

也就是说无不必要不要使用 useLayoutEffect,它和 didMount 一样,会阻塞页面渲染

非渲染方面的优化

组件按需挂载

懒加载:webpack 的动态导入+ React.lazy

懒渲染:即当组件进入或即将进入可视区域时才渲染组件,如Modal、Drawer

虚拟列表:懒渲染中的一种特殊场景,不渲染所有数据,只渲染可视区域中的数据。当用户滑动时,通过监听 scroll 来判断是上滑还是下拉,从而更新数据。同理 IntersectionObserver(交叉观察者)和 getBoundingClientRect 也能实现

6. React:组件库按需加载原理

通过 babel-plugin-import 插件可以快速配置好自动按需加载组件

在项目中的 config 文件中,修改 babel 插件配置,如下所示:

javascript
module.exports = {
    plugins: [
        ['import', {
          libraryName: 'vant',
          libraryDirectory: 'es',
          style: true
        }, 'vant']
    ]
    
	plugins: [
        ["import", {
            libraryName: "antd", 
            libraryDirectory: "es",
            style: "css" }
        ]
  	]
};

这样,babel-plugin-import 就能自动实现按需加载

ElementUI使用的是babel-plugin-component

babel-plugin-import 插件的原理

![image-20240919122206758](D:\Documents\PicGo Files\image-20240919122206758.png)

简单来说,按需加载不过是将 import {Button} from 'antd' 的形式转换为 import Button from 'antd/es/button' 的形式的过程

对于组件本身来说,要支持按需加载功能,就要在打包时支持以 ES 模块化方式导出

比如说 rollup,就可以在 rollup.config.js 中配置

javascript
export default {
  input: "./src/index.js",
  output: [
    {
      file: './dist/my-lib-umd.js',
      format: 'umd',
      name: 'myLib'   
      //当入口文件有export时,'umd'格式必须指定name
      //这样,在通过<script>标签引入时,才能通过name访问到export的内容。
    },
    {
      file: './dist/my-lib-es.js',
      format: 'es'
    },
    {
      file: './dist/my-lib-cjs.js',
      format: 'cjs'
    }
  ]
}

衍生问题:按需加载css

按需加载css

CSS in JS

  1. 使用方不存在接入成本;
    1. 配合 sideEffects 可进行 Tree Shaking

7.html缓存了怎么更新,js和css缓存是怎么更新的

一般来说,html 不缓存,js、css 缓存,html 中会加载 js 和 css,后两者会以哈希名的方式引入到 html 中

html 被缓存了就是要让缓存失效,一般有两种解法,一如果资源都在 OSS 或者 CDN 上,那么在 OSS 或者 CDN 上设置过期时间;二在 nginx 中设置过期时间

衍生问题:HTTP强缓存和协商缓存

HTTP强缓存和协商缓存

HTTP先走强缓存,Cache-Control失效,走协商缓存ETag,ETag未变,返回 304,变化返回新资源并打上 ETag 标签

8.埋点 SDK 设计思路

设计到日制和埋点

  • 自动化上报页面PV、UV,记录用户点击路径行为
  • 自动上报页面异常
  • 发送埋点信息时,不影响性能,不阻碍页面主流程加载和请求发送

数据上报

使用 navigator.sendBeacon ,它的优势在于它能异步操作,即使在页面卸载后,该方法也可以继续发送数据,并且这个方法可以实现跨域发送数据

还有以下几种上报

  • ajax、fetch 上报
  • image 上报
  • jsonp 上报

上报时机

上报时机有三种:

  • requestIdleCallback/setTimeout 延时上报
  • beforeUnload 回调函数里上报
  • 缓存上报数据,达到一定数量后再上报

先缓存上报数据,缓存到一定数量后,利用 requestIdleCallback/setTimeout 延时上报,在页面离开时统一将未上报数据进行上报

性能监控

  • FP、FCP、LCP、onload、DOMContentLoaded
  • 资源加载时间
  • 接口请求耗时

错误监控

  • 资源加载错误:addEventListener('error')

  • JS 错误:window.onerror 或者 window.addEventListener('error', callback)

    • window.onerror 无法捕获资源加载错误
  • Promise 错误 error 无法捕获, window.addEventListener('unhandledrejection', callback)

window.addEventListener('error', event => {
  this.error(error);
})
window.addEventListener('unhandledrejection', event => {
  	this.error(new Error(event.reason), { type: 'unhandledrejection' })
})

行为监控

  • UV、PV
  • 页面停留时长
  • 用户点击

9.进程和线程的区别

进程是一个应用起来的实例,线程是运行在进程中的最小单位

浏览器是是多进程架构,其中有一个浏览器主进程,多个渲染进程(一个 Tab 就是一个进程)

渲染进程中包括多个线程:GUI 渲染线程、JS 引擎线程、事件触发线程、异步线程、定时器线程

GUI 渲染线程和 JS 引擎线程是互斥的。GUI 负责渲染页面,JS引擎负责执行 JS 脚本,但是 JS 能操作 DOM,所以两者不能同时进行

10. 算法题:无重复字符的最长子串

javascript
var lengthOfLongestSubstring = function(s) {
    const memo = new Set();
    let longestLen = 0;
    let len = s.length;
    let slow = 0;
    for (let fast = 0; fast < len; fast++) {
        let char = s[fast]

        while(memo.has(char) && slow < len) {
            // 删掉重复的字母
            memo.delete(s[slow])
            slow++
        }
        longestLen = Math.max(longestLen, fast - slow + 1)
        memo.add(char)
    }  
    return longestLen;
};