【面试】不定期更新

151 阅读19分钟

JavaScript

1、数组和对象的操作方法

老是用,但是突然问。一下子有点懵...就是打你一个措手不及

数组

  • 添加
    • push:在数组末尾添加一个或多个元素
    • unshift:在数组开始添加一个或多个元素
  • 删除
    • shift:移除数组的第一个元素
    • pop:移除数组的最后一个元素
    • splice:可以删除、插入或替换数组中的元素
  • 数组遍历
    • forEach
    • for
  • 数组转换
    • map
    • filter
    • reduce
  • findIndex
  • find
  • fill
  • concat
  • indexOf
  • lastIndexOf
  • at
  • every
  • some
  • join
  • reverse
slice 与 splice 的区别
  • slice:提取数组的某些部分,不会改变原来的数组
slice()
slice(start)
slice(start, end)


// 定义一个数组
const fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry'];

// 使用 slice() 方法
const slicedFruits1 = fruits.slice(1, 3); // ['banana', 'cherry']
const slicedFruits2 = fruits.slice(-2); // ['date','elderberry'] Interview 1

  • splice:删除、插入,会改变原来的数组
splice(start, deleteCount, item1, item2, ...);

const fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry'];
const removedFruits = fruits.splice(1, 2, 'grape', 'fig');

console.log('splice() 移除的元素:', removedFruits);   // ['apple', 'banana']
console.log('splice() 操作后的原数组:', fruits);  // [ 'apple', 'grape', 'fig', 'date', 'elderberry' ]
  • 用 splice 写个 react 版本的表格删除
import React, { useState } from 'react';

const TableDeleteRow = () => {
    const [rows, setRows] = useState([
        { id: 1, name: '张三', age: 20 },
        { id: 2, name: '李四', age: 25 },
        { id: 3, name: '王五', age: 30 }
    ]);

    const handleDelete = (index) => {
       // Interview 4 
       // 为啥需要拷贝?
       // React 数据不可变原则,直接修改会导致React 无法检测数据的改变,因为引用并没有变 -> Interview 5
        const newRows = [...rows];
        newRows.splice(index, 1);
        setRows(newRows);
    };

    return (
        <div>
            <table>
                <thead>
                    <tr>
                        <th>姓名</th>
                        <th>年龄</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {rows.map((row, index) => (
                        <tr key={row.id}>
                            <td>{row.name}</td>
                            <td>{row.age}</td>
                            <td>
                                <button onClick={() => handleDelete(index)}>删除</button>
                            </td>
                        </tr>
                    ))}
                </tbody>
            </table>
        </div>
    );
};

export default TableDeleteRow;
    

对象

  • 遍历属性
    • Object.keys()Object.values() 和 Object.entries()
    • for...in
  • 删除属性 :delete
  • 创建: Object.create

2、引用类型与基本数据类型

基本类型

基本类型:null、undefined、number、boolean、string、symbol、bigint

存储方式:存在栈内存里面。每个变量都有自己独立的存储空间,存储的是实际的值

引用类型

Object、Array、Function、Date

类型:引用类型的值存在堆内存中,而变量栈内存里面存放的是指向堆内存中对象的引用地址。这意味着多个变量可以指向同一个对象

举例:

let m = { a: 10, b: 20 }; let n = m; n.a = 15;

image.png

复制的是引用地址,他们指向的是同一个对象,所以上面的例子当中 m.a = 15

类型判断

  • 基本类型: 可以用 typeof(Null、Array、Date会返回 object)
  • 引用类型: instanceof 可以判断(基本类型无效)引用类型
  • Object.prototype.toString.call()
  • Array.isArray 专门用来判断数组

3、深拷贝与浅拷贝

浅拷贝

拷贝对象,只会拷贝对象的引用,因此他们引用的是同一个对象

实现方法:

1 Object.assign()

const original = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, original);

2、扩展运算符...

const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };

深拷贝

1、 JSON.parse()JSON.stringify()

这种方式不能拷贝 undefind、Symbol

2、手写一个简易版的deepClone 方法

function deepClone(obj) {
  if (typeof obj !== "object" || obj == null) {
    return obj;
  }
  let clone;
  if (Array.isArray(obj)) {
    for (let i = 0; i < obj.length; i++) {
      clone[i] = deepClone(obj[i]);
    }
  } else {
    clone = {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        clone[key] = obj[key];
      }
    }
  }
  return clone;
}

4、const、let、var 的区别

var

各种不好,变量提升,可重新赋值、重复声明,作用域是函数作用域

// var 的函数作用域
function testVar() {
    if (true) {
        var x = 10;
    }
    console.log(x); // 输出 10
}
testVar();

let

块级作用域,块级作用域由 {} 界定,var 的那些问题,let 通通没有

const

const 声明的常量,一旦赋值就不能改变,但是 const 如果声明的是对象或者数组, 内部的属性可以改变(这是因为 const 声明的是变量的引用不变,而不是变量的指向的值不变) InterView 5

  • 怎么让 const 声明的对象或者数组也不可变呢

使用 Object.freeze(), 但也只是浅浅的冻结,如果是引用类型,也是可以更改

const obj = {
   a: 1,
   b: {
       c: 2,
       d: 3,
   },
}

const objFreeze = Object.freeze(obj)

obj.a = 10;
obj.b.c = 20;

console.log('obj:', obj);

// 输出:obj: { a: 1, b: { c: 20, d: 3 } }
  • 解决办法:可以递归冻结和使用三方库

5、防抖节流

  • 防抖 是在延迟一段后时间执行,如果这段时间内重新触发事件,则重新计时,适用于输入框、窗口改变
function debounce(fn,delay){
  let timer = null;
  return function(...args){
     if(timer) clearTimeout(timer);
     timer = setTimeout(()=>{
       fn.apply(this,args);
     },delay)
  }
}

input.addEventListener('keyup',(e)=>debouce(search,500))
  • 节流 是每隔一定的时间执行,无论触发多少次,目标函数只会在间隔内执行一次,适用于滚动监听
     funtion throttle(fn,interval){
       let lastTime = 0;
       return function(...args){
         const now = Date.now();
         if(now -lastTime >= interval){
           fn.apply(this,args);
           lastTime = now;
         }
       }
     }
     
     window.addEventListener('scroll', throttle(logScroll,1000))
    

6、 0.1 + 0.2 != 0.3 的原理

浮点数两种格式: 单精度浮点数(32 位)和 双精度浮点数(64 位)

浮点数组成部分:

  • 符号位:0 表示正数,1 表示负数
  • 指数位:表示浮点数的指数部分(单精度指数位占 8 位,双精度占 11 位),
  • 尾数位:用于表示浮点数的小数部分,在单精度占 23 位,双精度占 52 位

原理: 在计算机当中,用二进制来表示数字。0.1和 0.2 无法被精确表示,而是以无限循环的二进制小数形式存储。因为存储的位数有限,他们被近似存储

解决办法:

  • 使用 toFixed
  • 先扩大后缩小
  • 使用 BigInt
  • 使用第三方库(如 decimal.js,big.js)

6、事件循环

js 本身是单线程语言,在同一时间只能只能一个任务,其目的是为了防止多线程修改共享数据。但是在实际工作中,会出现一些耗时比较久的任务,如果这些都同步执行,那将会是灾难,所以 js 引入异步机制来解决这个问题

  • 执行栈与任务队列

执行栈:也称为调用栈,是一种存储函数调用关系的数据结构。当函数被调用时,它会被压入执行栈的顶部,当函数执行完毕后,会从执行栈中弹出。JavaScript 引擎会按照执行栈中函数的顺序依次执行它们

任务队列:用于存储异步任务。当一个异步任务被触发(如定时器到期、网络请求完成),它不会立即执行,而是会被放入任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列

  • 宏任务和微任务

    • 宏任务:也称为 Task,常见的宏任务有 setTimeoutsetIntervalsetImmediate(仅在 Node.js 环境中)、I/O 操作、UI 渲染等。
  • 微任务:也称为 Jobs,常见的微任务有 Promise 的回调函数、MutationObserverprocess.nextTick(仅在 Node.js 环境中)等

  • 事件循环:js 在执行任务的时候,会优先执行同步任务,会推入调用栈并且理解执行,执行完毕,调用栈会被弹出。当碰到异步任务的时候,js 不会立即执行,而是将这些任务交给运行时的环境(如浏览器的 Web API 货 Node.js 的 Libuv)。任务完成后,回调函数放入任务队列,任务队列分为宏任务和微任务。当调用栈被清空的时候,会从任务队列去拿,优先执行微任务

7、 ### 浏览器的两种路由

哈希路由

通过浏览器的hashChange 检测 # 后面的变化,兼容性好,对 SEO 不怎么优化,服务器不需要做什么配置

历史路由

通过 History API 来实现的,后退与前进都是通过 pushState、popState、replaceState 来实现的,对 SEO 好,需要服务器配置

server { 
  listen 80; 
  server_name your_domain.com; 
  root /var/www/html; index index.html index.htm; 
  location / { 
    try_files $uri $uri/ /index.html; 
  } 
 }

7、手写 flattenArray

 function flatteryArray(arr) {
  return arr.reduce((acc, val) => {
    if (Array.isArray(val)) {
      acc.push(...flatteryArray(val));
    } else {
      acc.push(val);
    }
    return acc;
  }, []);
}

8、普通函数与箭头函数的区别

  • 箭头函数: this 指向在定义的时候就确定了,指向外层作用域的 this,全局模式下,this 指向不能被改变,严格模式 this 是 undefined。没有 arguments,但是有剩余参数
  • 普通函数:this 指向能够被改变(通过 apply、bind、call),this 指向取决于调用方式,比如通过对象调用,this 指向该对象;通过构造函数,this 指向创建的对象实例等等

一个经典问题:普通函数 setTimeout

const outerObj = {
   outerProp: 'I am outer property',
   middleObj: {
       innerObj: {
           // 普通函数
           printOuterProp: function() {
               // 使用闭包来保存最外层的this
               const self = this; 
               return function() {
                   // 通过闭包变量self来访问最外层对象的属性
                   console.log(self.outerObj.outerProp); 
               };
           }()
       }
   }
};

// 调用最内层方法
outerObj.middleObj.innerObj.printOuterProp; 
const outerObj2 = {
    outerProp: 'I am another outer property',
    middleObj: {
        innerObj: {
            // 箭头函数
            printOuterProp: () => {
                console.log(outerObj2.outerProp); 
            }
        }
    }
};

// 调用最内层方法
outerObj2.middleObj.innerObj.printOuterProp(); 

9、如何判断对象是不是空对象

谁会这么判断

const obj = {};

// 方法1:使用 Object.keys()
if (Object.keys(obj).length === 0) {
    console.log("对象为空");
}

// 方法2:使用 JSON.stringify()
if (JSON.stringify(obj) === '{}') {
    console.log("对象为空");
}

直接用 lodash 的 isEmpty 可以判断,因为它可以

  • 空对象 {}  → true
  • 空数组 []  → true
  • 空字符串 ""  → true
  • null 或 undefined → true
  • Map 或 Set 为空 → true
  • 类数组对象(如 arguments)为空 → true
  • 数字、布尔值、函数等非集合类型 → true(因为它们没有可枚举的属性)

源码分析(简化版)

function isEmpty(value) {
  // 处理 null 和 undefined
  if (value == null) {
    return true;
  }

  // 处理数组、字符串、类数组对象(如 arguments)
  if (Array.isArray(value) || typeof value === 'string' || isArrayLike(value)) {
    return value.length === 0;
  }

  // 处理 Map 和 Set
  if (value instanceof Map || value instanceof Set) {
    return value.size === 0;
  }

  // 处理普通对象(检查可枚举的自身属性)
  if (typeof value === 'object') {
    return Object.keys(value).length === 0;
  }

  // 其他情况(如数字、布尔值、函数等)
  return true;
}

10、 浏览器页面渲染

11、如何避免js 阻塞渲染

  • 按需加载js脚本(Code Splitting)
  • 使用 defer 和 async 属性
    • defer 属性在 HTML DOM 完成后再执行,适用于依赖 DOM 或有执行顺序有要求的脚本
    • 添加 async 属性,脚本会在下载后立即执行,不会阻塞 HTML 解析,适用于独立脚本,如统计代码
  • 将脚本放在 标签之前,这样可以让 DOM 先渲染
  • 使用 requestIdleCallback 或者 requestAnimationFrame
    • requestIdleCallback: 在浏览器空闲的时候执行低优先级任务
    • requestAnimationFrame:在下一帧渲染前执行动画相关任务

12、最近使用过的大模型

13、如何保证ws在什么情况下不会断联,客户端如何保证高可用,如何保证消息不丢失

  • 服务器或网络问题
  • 客户端页面关闭、
  • 握手失败
  • 协议违规
  • 连接超时
  • 并发连接数的限制

14、ws支持那些类型的消息,大消息如何优化

支持的类型: 文本、二进制(音频、图片、视频等)

如何优化: 消息分帧、压缩、异步处理、缓存策略

15、 APP选型如何对 flutter、rn进行考量,各自能解决什么样的业务场景和技术场景

特性FlutterReactive Native
开发语言DartJS/TS
性能接近原生(无 Brigde)可能有 Bridge 瓶颈
UI 渲染自带 Skia 引擎依赖原生组件(通过Bridge 通信)
特性跨平台、声明式 widget 编程熟悉 ts/js

16、h5 离线包的实现机制,需要注意哪些问题

分模块打包

控制业务模块的大小,按需加载

差异化更新

精准控制离线的资源,对于变动频繁的单独管理

监控体系

降级策略

离线包加载失败,有没有完善的网络回退机制

测试覆盖

关注弱网环境

虚拟 DOM 的优势

  • 跨平台
  • 提高性能

CSS

1、flex:1 的含义

经典面试题了,代表着 flex: 1 1 0%, 是 flex-grow、flex-shrink、flex-basis 的 缩写。 埋个彩蛋: flex:2 代表啥意思

  • flex-grow:定义了剩余空间中扩展的能力,为 1 表示均分空间。假设有个项目为 2 表示,其他项目为 1,表示前者的空间比其他的多一倍。 为 0 表示不扩展(默认值)

image.png

<style>
 .container {
        display: flex;
        border: 2px dashed crimson;
        width: 300px;
      }

      .container .item {
        border: 2px solid deepskyblue;
      }

      .container .grow {
        flex-grow: 2;
        width: 400px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="item grow">南蓝</div>
      <div class="item">南蓝</div>
      <div class="item">南蓝</div>
      <div class="item">南蓝</div>
      <div class="item">南蓝</div>
    </div>
  </body>
  • flex-shrink:定义了空间不足的时候如何收缩默认值为 1 。为 1 表示等比例收缩,为 0 表示不收缩

image.png

image.png

image.png

<style>
      .container {
        width: 300px;
        display: flex;
        border: 2px dashed crimson;
      }
      .container .item {
        flex-shrink: 1;
        width: 100px;
        border: 2px solid deepskyblue;
      }
      .container .shrink {
        flex-shrink: 0;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="item shrink">南蓝</div>
      <div class="item">南蓝</div>
      <div class="item">南蓝</div>
      <div class="item">南蓝</div>
      <div class="item">南蓝</div>
    </div>
  </body>

flex-basis: 0,定义了项目占据的主轴空间大小,默认值是 auto

flex-none

flex:0 0 auto

表示元素尺寸不会收缩也不会扩展,flex-basis: auto 表示固定尺寸由内容决定,元素的内容不会换行

image.png

适用场景: 不换行,小范围的内容上面 Interview 2

image.png

<style>
      .container {
        display: flex;
        padding: 0.5rem;
        border: 1px solid lightgray;
        background-color: #fff;
      }
      img {
        width: 3rem;
        height: 3rem;
        margin-right: 0.5rem;
      }
      button {
        align-self: center;
        padding: 5px;
        margin-left: 0.5rem;
      }
    </style>
    <title>Flex Auto Navbar</title>
  </head>

  <body>

    <div class="container">
      <img src="1.jpg" />
      <p>南兰兰兰纳啦啦啦电饭锅黄金季节好过分的</p>
      <button>按钮</button>
    </div>
  </body>

哎呀,妈呀,以前遇到这种问题,直接按钮写死宽度,但是如果文字一旦过多,比如说英文比中文多,那么按钮文字会换行,其实一般不能换。 设置 flex:none 就好了

 <button class="flex-none">按钮</button>

flex-0

flex: 0 1 0%

flex-shrink 为 1,表示会收缩,但是不会扩展,元素内容会换行。

image.png

flex-initial

0 1 auto

image.png

image.png

flex-auto

flex: 1 1 auto

这个属性和 flex:1 非常相似,请看图片的区别

image.png

适用的场景:基于内容动态布局。这个时候用 flex:1 就很丑了 Interview 3

image.png

总结

简写详细适用场景
flex:noneflex: 0 0 auto;适用于不换行的内容固定或者较少的小控件元素上,如按钮
flex:0flex: 0 1 0%;很少用
flex:initial(默认,相当于 dispaly: flex)flex: 0 1 auto;默认行为
flex:1flex: 1 1 0%;等分布局
flex:autoflex: 1 1 auto;基于内容动态适配的布局

参考:

2、BFC

如何触发 BFC

1、 根元素:<html> 天然就是一个 BFC 2、 浮动元素: float 设置为leftright 3、 绝对定位: position 设置为absolutefixed 4、 display 属性: - display:flow-root - display: inline-blocktableflexgrid 5、 overflow 属性: - overflow:hiddenscrollauto

BFC 特性

1、外边距折叠问题: 在同一个 BFC 中,上下相邻的块级元素外边距会折叠;不同的BFC 不会折叠

<div style="margin-bottom: 20px; background: lightgreen;">块1</div>
<div style="margin-top: 30px; background: lightblue;">块2</div>

他们属于同一个 BFC,所以内边距会折叠。解决方式,创建一个 BFC,还记得创建的条件吗 ,overflow: hiddenfloatdisplay: flow-root

2、内容元素垂直排列

3、BFC 容器会自动包含其内部浮动元素

4、BFC 内部的布局不会影响外部元素,外部的布局也不会干扰 BFC 内部

应用场景

性能优化

清除浮动、防止外边距折叠、布局隔离(防止浮动或者定位元素干扰其他)、多列布局

1、性能优化指标

FCP(First Contentful Paint): 首次内容绘制

  • 定义: 首次看到内容的加载时间
  • 优化手段:减少服务器资源加载时间、比如通过分包、压缩资源等方式

LCP(Largest Contentful Paint):最大内容绘制

  • 定义: 看到最大文本或者图片的加载时间
  • 优化手段:图片懒加载,使用 Webp 的图片加载方式等,加载时间<=2.5s

FID(First Input Delay): 首次输入延迟

  • 定义: 用户首次与页面交互,浏览器响应的时间(比如点击、输入等)
  • 优化手段: 减少 js 主线程的交互时间,避免长任务,加载时间<=100ms

累计布局偏移 (Cumulative Layout Shift, CLS)

  • 定义: 页面加载过程中因为偏移导致视觉稳定的得分
  • 优化手段: 为图片设定尺寸,避免动态插入,加载时间<=0.1

时间到交互 (Time to Interactive, TTI)

  • 定义:页面加载到完全可以交互(主线程空闲且能响应用户输入的时间)
  • 优化建议: 减少三方脚本、异步加载非关键资源

总阻塞时间 (Total Blocking Time, TBT)

  • 定义:页面加载过程中,主线程被长任务阻塞的时间
  • 优化手段:拆分长任务、使用 web worker 处理复杂计算

速度指数 (Speed Index)

  • 定义:页面内容在可视区域内逐渐显示的平均时间
  • 优化建议: 优先加载视口内资源、优化渲染路径

2、 React 性能优化

代码层面

  1. 组件拆分:把大型组件拆分成多个小的、可复用的组件。这样不但能提升代码的可读性与可维护性,还可以借助 React 的按需渲染特性来减少不必要的渲染。
  2. 使用 React.memo:对于纯函数组件,可使用 React.memo 对其进行包裹,以此来阻止组件在 props 未发生变化时进行重新渲染。

说是这么说,但在实际工作中也不是每次都在纯函数组件中包裹了 React.memo

 import React from 'react';

const ChildComponent = React.memo(({ text }) => {
    console.log('ChildComponent rendered');
    return <div>{text}</div>;
});

export default ChildComponent;
    
  1. 避免内联函数:在 JSX 里使用内联函数会在每次渲染时都创建新的函数实例,这可能会致使子组件不必要的重新渲染。可以把内联函数提取到组件外部或者使用 useCallback 来对其进行优化。
  2. 使用 useCallback 和 useMemouseCallback 用于缓存函数,而 useMemo 用于缓存计算结果。在需要传递函数给子组件或者进行复杂计算时,使用它们能够避免不必要的计算和渲染。
  • useCallback
// ChildComponent
import React from 'react';

const ChildComponent = ({ onClick }) => {
    console.log('ChildComponent rendered');
    return <button onClick={onClick}>Increment</button>;
};

export default ChildComponent;
// AppWithUseCallback
import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';

const AppWithUseCallback = () => {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        setCount(count + 1);
    }, [count]);

    return (
        <div>
            <h1>With useCallback</h1>
            <h2>Count: {count}</h2>
            <ChildComponent onClick={handleClick} />
        </div>
    );
};

export default AppWithUseCallback;
    
// AppWithoutUseCallback
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

const AppWithoutUseCallback = () => {
    const [count, setCount] = useState(0);

    const handleClick = () => {
        setCount(count + 1);
    };

    return (
        <div>
            <h1>Without useCallback</h1>
            <h2>Count: {count}</h2>
            <ChildComponent onClick={handleClick} />
        </div>
    );
};

export default AppWithoutUseCallback;
    

没加 useCallback 之后,handleClick 会重新创建会被重新渲染,所以 ChildComponent 接收到变化,会重新渲染。加了 useCallback之后,缓存了 handleClick 函数,count 变化,handleClick 才会变化

  • useMemo
import React, { useState, useMemo } from 'react';

const App = () => {
    const [count, setCount] = useState(0);

    // 使用 useMemo 缓存计算结果
    const doubleCount = useMemo(() => {
        console.log('Calculating double count...');
        return count * 2;
    }, [count]);

    return (
        <div>
            <h1>Count: {count}</h1>
            <h2>Double Count: {doubleCount}</h2>
            <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
    );
};

export default App;

在 React 19 中,你不可以使用 React.memo、 useCallback、useMemo 来做优化,因为 React compiler 已经帮你做了这些事情。具体可以参考 React Compiler

  1. 减少不必要的状态:仅在状态确实需要改变 UI 时才使用状态。对于一些不需要影响 UI 的数据,可以使用普通的变量来进行存储。
  2. 使用 React.lazy 和 Suspense:针对大型组件(echarts、富文本等)或者路由组件,可使用 React.lazy 进行懒加载,并且使用 Suspense 来处理加载状态。这样能够提升应用的初始加载速度。
// (router 使用的是 V6, React 使用的 18)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { Suspense } from 'react';

// 懒加载组件
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  </Router>
);
    

性能层面

  1. 优化图片资源:对图片进行压缩和优化,使用合适的图片格式(如 WebP),并且使用懒加载技术来减少初始加载时的资源消耗。
  2. 使用 CDN:对于第三方库和静态资源,可使用 CDN 来加速加载。

网络层面

  1. 减少 API 请求:合并多个 API 请求,避免不必要的重复请求。可以使用缓存策略来减少对相同数据的重复请求。
  2. 优化 API 响应:在服务器端对数据进行分页、排序和过滤,减少返回的数据量。

打包层面

  1. 代码分割:使用 Webpack 等打包工具进行代码分割,将应用拆分成多个小的 bundle,实现按需加载

Webpack使用代码分割主要是两个思路,一个 es 6 的 dynamic import 和使用 splitChunks

// dynamic import 的 webpack 配置
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      chunkFilename: '[name].[contenthash].chunk.js'
  }
};
  

optimization.splitChunks.chunks: 'all' 表示对所有类型的块(同步和异步)进行代码分割,将公共模块提取到单独的文件中

// splitChunks
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].[contenthash].js',
        path: path.resolve(__dirname, 'dist')
    },
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    }
};
    
  1. 压缩代码:在打包时对代码进行压缩,去除不必要的空格和注释,减少文件大小。

样式层面

  1. 避免内联样式:内联样式会增加 DOM 的大小,并且不利于样式的复用和维护。尽量使用 CSS 类来定义样式。
  2. 使用 CSS 模块化:使用 CSS Modules 或者 styled-components 等技术来避免全局样式冲突。

其他层面

  1. 优化事件处理:避免在事件处理函数中进行复杂的计算或者阻塞操作,可以使用异步操作来处理耗时任务。
  2. 使用 React 上下文(Context) :当多个组件需要共享数据时,使用 React 上下文可以避免通过 props 层层传递数据。

框架

Vue 2.0 与 Vue 3.0 的区别对比

Vue 2 与 Vue 3 的区别

  • Vue2:使用 Object.defineProperty() 来实现响应式、选项式 API
    • 选项式 API: 以“组件实例”的概念为中心 (即上述例子中的 this),对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致
  • Vue3: 采用 Proxy 对象来实现响应式,解决了 Object.defineProperty() 的局限性、组合式 API
    • 组合式 API:函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题
      • 使用 ref() 和 reactive(),使我们可以直接创建响应式状态、计算属性和侦听器。
      • 组合式 API 并不是函数式编程。组合式 API 是以 Vue 中数据可变的、细粒度的响应性系统为基础的,而函数式编程通常强调数据不可变
      • 与 React hooks 相比,有相同的逻辑组织能力
    • 声明周期
      • Vue 2:有 beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed 等生命周期钩子
      • Vue 3:保留了部分生命周期钩子,但名称有所改变,如 beforeDestroy 改为 beforeUnmountdestroyed 改为 unmounted

网络

1、 如何取消请求

1、比如用户用户突然切换页面, AbortController