面试问题(深拷贝与浅拷贝,useRef,强缓存与协商缓存,类组件与函数组件)

介绍项目

深拷贝与浅拷贝(针对于数组,对象这种引用数据类型)

浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针。

浅拷贝就是拷贝指向原来对象的指针,使原对象的引用计数+1,可以理解为创建了一个指向原对象的新指针而已,并没有创建一个全新的对象。

浅拷贝的方法有:

1)展开运算符...

2)Object.assign()es6

3)arr.slice()     数组slice()方法可以从已有数组中返回选定的元素:用法:array.slice(start, end),该方法不会改变原始数组。该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。

4)arr.concat()方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。

5)手动实现浅拷贝

function shallowCopy(object) {
  // 只拷贝对象
  if (!object || typeof object !== "object") return;

  // 根据 object 的类型判断是新建一个数组还是对象
  let newObject = Array.isArray(object) ? [] : {};

  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] = object[key];
    }
  }

  return newObject;
}

深拷贝的方法有:

1)使用 JSON 序列化和反序列化(适用于数据为简单对象、数组)

JSON.parse(JSON.stringify(originalObject))    拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。

2)lodash库的_.cloneDeep方法

3)递归手动实现深拷贝

// 深拷贝的实现
function deepCopy(object) {
  if (!object || typeof object !== "object") return;

  let newObject = Array.isArray(object) ? [] : {};

  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }

  return newObject;
}

深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。

深拷贝就是拷贝出和原来仅仅是值一样,但是内存地址完全不一样的新的对象,创建后和原对象没有任何关系。

本质区别在于:

  • 是否开启新的内存地址
  • 是否影响内存地址的引用计数

赋值情况:

浅拷贝情况:

深拷贝情况:

useRef

1、什么是useRef

  • 返回一个可变的 ref 对象,该对象只有个 .current 属性,初始值为传入的参数( initialValue )。
  • 返回的 ref 对象在组件的整个生命周期内保持不变。
  • 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方。所以说如果有render内需要渲染的数据肯定不能使用useRef ,而得用 useState 。
  • useRef 类似于类组件的 this。

通俗点说useRef就像是可以在 .current 属性中保存一个可变值的“盒子”。

2、使用

与类组件中的 this.groupBodyRef = React.createRef(),他们大部分用法基本一致,都是可以存变量或者Dom节点

useRef与createRef的区别

在一个组件的正常的生命周期中可以大致分为3个阶段:

1、从创建组件到挂载到DOM阶段。初始化props以及state, 根据state与props来构建DOM
2、组件依赖的props以及state状态发生变更,触发更新
3、销毁阶段
第一个阶段,useRef与createRef没有差别

第二个阶段,createRef每次都会返回个新的引用;而useRef不会随着组件的更新而重新创建

第三个阶段,两者都会销毁

2.1 、我的项目中使用到useRef的场景:

1、用来存储当前操作改动的是哪一行数据

具体实现:

const currentSelectedRowIndex = useRef<number>();
<a
   style={{marginLeft: '20px'}}
   onClick={() => {
       selectService();
       currentSelectedRowIndex.current = name;
   }}
>
   选择服务
</a>

2、用于初始化echarts图表

import React, {useEffect, useRef} from 'react';
import * as echarts from 'echarts';

interface IProps {
    title?: string;
    dateTrend: any;
    trendData: any;
}
const Chart = (props: IProps) => {
    const {title, dateTrend, trendData} = props;
    const minData = trendData.reduce((min: number, item: any) => {
        const itemMin = Math.min(...item.data.filter((val: number) => val !== null));
        return itemMin < min ? itemMin : min;
    }, Infinity);
    let max = Number.MIN_SAFE_INTEGER;
    for (const item of trendData) {
        const itemMax = Math.max(...item.data.filter((item: number) => item !== null));
        max = Math.max(max, itemMax);
    }
    const chartRef: any = useRef();
    let options: any = {};
    if (dateTrend?.length === 0) {
        options = {
            title: {
                text: '暂无数据',
                x: 'center',
                y: 'center',
                textStyle: {
                    fontSize: 14,
                    fontWeight: 'normal'
                }
            }
        };
    } else {
        options = {
            title: {
                text: title,
                left: '50px',
                top: '4px'
            },
            tooltip: {
                trigger: 'axis'
            },
            legend: {
                data: trendData?.map((item: any) => item.name),
                type: 'scroll'
            },
            xAxis: {
                type: 'category',
                data: dateTrend
            },
            yAxis: {
                type: 'value',
                min: minData,
                max: max,
                splitLine: {
                    lineStyle: {
                        type: 'dashed'
                    },
                    show: true
                }
            },
            series: trendData
        };
    }
    useEffect(() => {
        const chart = echarts.init(chartRef.current);
        // 设置图表实例的配置项和数据
        chart.setOption(options);
        return () => {
            echarts.dispose(chart);
        };
    }, [trendData, dateTrend]);
    return (
        <>
            <div style={{height: '400px'}} ref={chartRef}></div>
        </>
    );
};
export default Chart;

2.2、还知道的可以这样使用

import React, { useState, useRef, useCallback } from 'react'

export default function StopWatch() {
    const [now, setNow] = useState(Date.now())
    const ref = useRef()

    const handleStart = useCallback(() => {
        ref.current = setInterval(() => {
            setNow(Date.now())
        }, 1000)
    }, [])

     const handleStop = useCallback(() => {
         clearInterval(ref.current)
     }, [])

    return (
        <>
            <h1>Now Time : {now}</h1>
            <button onClick={handleStart}>Start</button>
            <button onClick={handleStop}>Stop</button>
        </>
    )
}

使用 ref 存储 setInterval 返回的ID,需要清除时,我们只需要clearInterval(ref.current)就可以了

webpack的配置和相关内容

强缓存和协商缓存

强缓存是指浏览器在请求资源时,直接从本地缓存中获取资源,不发送请求到服务器。这样可以减少网络延迟并提高页面加载速度。

实现强缓存的方式:
  1. Expires 头部(HTTP1.0使用):服务器返回的响应头中包含一个 "Expires" 字段,表示资源的过期时间,浏览器会根据这个时间判断是否使用缓存。

  2. Cache-Control 头部(HTTP1.1及之后使用):使用 "Cache-Control" 头部来控制缓存行为,常用的指令包括 "max-age"、"no-cache"、"no-store" 等。

max-age:单位是秒,用于指定资源在缓存中的最大存活时间。当值设为max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。

no-cache:不使用本地缓存。跳过设置强缓存,但是不妨碍设置协商缓存;一般如果你做了强缓存,只有在强缓存失效了才走协商缓存的

no-store:禁止浏览器缓存数据,也禁止保存至临时文件中,每次都重新请求。没有所谓的强缓存、协商缓存了。

public:任何情况下都缓存。

private:只能被终端用户的浏览器缓存。

协商缓存是指浏览器在发起请求时,通过与服务器进行协商来确定是否使用缓存。如果缓存未过期,服务器会返回 304 状态码,告知浏览器继续使用缓存;如果缓存过期或者需要验证,服务器会返回新的资源。

整体步骤:

1、将所缓存的资源信息发送给服务器

2、让服务器判断资源是否已经更新

  • 若已更新,则返回更新后的资源
  • 若没有更新,返回304状态,告诉浏览器可直接使用本地缓存的资源

整个过程至少与服务器通信一次。

实现协商缓存的方式:
  1. Last-Modified (响应头)和 If-Modified-Since(请求头):服务器会在响应头中返回资源的最后修改时间,当浏览器再次请求资源时,会发送一个 "If-Modified-Since" 头部字段,服务器会根据资源的修改时间判断是否需要返回新的资源。

1. 浏览器第一次发请求,服务器在返回的 respone 的 header 加上 Last-Modified,表示资源的最后修改时间

2. 再次请求资源,在 requset 的 header 加上 If-Modified-Since ,值就是上一次请求返回的 Last-Modified 值

3. 服务器根据请求传过来的值判断资源是否有变化,没有则返回 304,有变化就正常返回资源内容,更新 Last-Modified 的值

4. 304 从缓存加载资源,否则直接从服务器加载资源

     2.ETag (响应头)和 If-None-Match(请求头):服务器会给资源生成一个唯一的标识符(ETag),浏览器再次请求资源时会带上 "If-None-Match" 头部字段,服务器会根据 ETag 判断资源是否更新。一个标识符字符串,表示文件唯一标识,只要文件内容改动,ETag就会重新计算。缓存流程和 Last-Modified 一样。

Last-Modified 与 Etag 的对比:
  • 如果我们打开文件但并没有修改其内容,Last-Modified 也会改变,而Etag则不会改变。
  • Last-Modified 的时间单位为秒,如果一秒内对文件进行了多次修改,那么由于其时间单位是秒,所以Last-Modified不会改变,最终浏览器还是会去读取缓存资源,而此时缓存的资源已经过时了。
  • Etag的优先级高于Last-Modified。

浏览器缓存过程(强缓存转协商缓存的过程)

  • 1.浏览器第一次加载资源,服务器返回200,浏览器将资源文件从服务器上请求下载下来,并把文件、文件的返回时间、response header一并缓存

  • 2.下一次加载资源时,查看cache-control的设置

    • 如果值是no-cache,则表示不缓存,则直接去请求数据;
    • 如果值是max-age,则比较当前时间和上一次返回200时的时间差是否大于max-age,若小于max-age,则表示没有过期,命中强缓存,不发请求直接从本地缓存读取该文件;如果时间差大于max-age,则表示资源过期了,则向服务器发送请求,并且header中携带有If-None-Match、If-Modified-Since、Etag值等
  • 3.如果服务器收到的请求头中,有Etag值优先根据Etag的值判断被请求的文件有没有做修改,
    如果Etag值一致则没有修改,命中协商缓存返回304,则读取缓存资源;
    如果Etag值不一致则有改动,则直接返回新的资源,并带上新的Etag值;

  • 4.如果服务器收到的请求头中,没有Etag值,则将If-Modified-Since被请求文件的最后修改时间做比对,
    如果一致则没有修改,命中协商缓存,返回304;
    如果不一致则有修改,则返回新的文件和并在响应头中携带last-modified ,下次请求时通过If-Modified-Since携带上last-modified的值;

自定义hooks

类组件和函数组件的区别

类组件:

class定义

有this

有生命周期函数

函数组件:

function定义

hooks:useState   useEffect,   useCallback,   useMemo,   useRef

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值