next项目页面性能调优

news2024/11/15 15:55:46

next项目页面性能调优

一般来说性能优化可以分为加载时、运行时两部分的优化。

扩展参考链接:
前端性能优化 24 条建议
Webpack 4进阶–从前的日色变得慢 ,一下午只够打一次包
Webpack 分包优化首屏加载

参考指标

  1. FCP(First Contentful Paint):首次内容绘制时间,白屏时间
  2. LCP(Largest Contentful Paint):最大内容绘制时间,即网站渲染占比最多的元素绘制所花费的时间。
  3. TTL(Time to Interactive):首次可交互时间。
  4. FMP(First Meaningful Paint):首次有意义的渲染帧,从页面加载开始,到大部分或者主要内容已经在首屏上渲染的时间点。

性能测试方式

  1. 本地 / 测试环境 可直接使用google devtool自带的测试工具Lighthouse
  2. 正式环境:
    https://gtmetrix.com/
    https://pagespeed.web.dev/report
    https://www.webpagetest.org/

优化方法

一、开启webpack具体分析&Lighthouse treeMap,减少主包文件大小、代码分割分包

  1. 使用next提供的dynamic组件异步导入依赖,代码分割
    基本上非首屏内容皆可分割出去。是否在服务端渲染可以自行测试,不过经过我的尝试,在服务端渲染过一次、客户端直接拿数据优化效果更好。
import dynamic from 'next/dynamic';
const Component = dynamic(import('./component'));
// const Component = dynamic(import('./component'),{ssr:false});
  1. 代码分割后使用webpack-analyze查看包的大小,主包大小多大,页面打包后大小多大,是否tree-shaking,是否开启压缩,是否关闭source-map。
    以及最重要的是否把重复引入的库合并,不能每个包都打包进去了,那样就浪费资源了。
    可以参考之前的文章,webpack分类下的文章。

参考:
Next.js的Babel及拆包优化
前端小杂记 - 基于 NextJS 的网站构建、优化与发布
Webpack中 SplitChunks 插件用法详解

我的项目中,我发现主包build后有800多k
经过打印next自带的webpack.optimization信息,发现next拆包策略如下:

于是我想把主包里的800多k的包拆出来,仔细查看主包,发现里面有一些公共utils包,比如mobx / antd的内容,还有一些和其他包重复引用的部分,于是我想把这部分包提出来,怎么做呢,参考以前的webpack相关博客,可以使用cacheGroup,所以添加如下分包策略:

// 需判断是否是服务端打包,我们只修改非服务端打包的策略
// framework是next自带的分包策略,里面打包react相关

try {
  if (!isServer && !dev) {
    const cacheGroups = config.optimization.splitChunks.cacheGroups;
    // 1.mobx
    cacheGroups.antd = {
      name: 'antd',
      test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
      enforce: true,
      chunks: 'all',
    };
    // 2.工具类
    cacheGroups.vendors = {
      name: 'vendors',
      test: /[\\/]node_modules[\\/](mobx|axios|lodash|moment)[\\/]/,
      enforce: true,
      chunks: 'all',
      priority: 20,
    };
    // 3.utils&config
    cacheGroups.utils = {
      name: 'utils',
      test: /[\\/]src[\\/]utils[\\/]/,
      enforce: true,
      chunks: 'all',
      priority: 20,
    };
  }
} catch (e) {
  console.log(e);
}

经过本次分包后,主包肉眼可见地从800多k变为了500多k,里面杂乱重复引入的工具包减少了。而总体的打包大小从一开始的10多M变为9.7多M.

但是绝望滴事情发生鸟,当我自信满满地npm start想要看看优化效果时,报了node错误[nodemon] app crashed - waiting for file changes before starting...。。项目启动不了了

问题肯定是出在新加的这一段代码上,于是try / catch一下,提示:Cannot set property 'antd' of undefined,是因为本地dev环境自动配置的splitChunks为false,好吧,因此修改上述代码中增加判断条件:!isServer && !dev

  1. Tree-shaking+sideEffects
    treeshaking应该是自动在生产环境生效的,但是副作用标识我没有启用,虽然启用之后项目文件大小确实小了,但是担心对整个项目产生其他影响,测试不充分。
    参考:
    webpack之sideEffects

webpack4新增了一个sideEffects新特性,它允许我们通过配置的方式,去标识我们的代码是否有副作用,从而为Tree-shaking提供更大的压缩空间。
这里的副作用指的是模块执行时除了导出成员之外所做的事情。
sideEffects一般用于npm包标记是否有副作用。

二、 视频、图片加载优化&懒加载

参考以前的文章怎么优化一个多图多视频的页面

1. next/image做了很多图片加载优化的工作,以及最好使用相关懒加载组件,等图片出现时再加载请求;

从NextJS的封装的Image组件看如何优化图片加载

  • import LazyLoad from ‘react-lazyload’;
  • import Image from ‘next/image’; next提供的图片加载优化组件,也可以实现懒加载的功能。
2. 减少图片大小以及减少请求消耗带宽资源。
  • 可以使用next/image压缩,或者使用webp格式,fontIcon代替svg。
  • 使用CDN缓存图片静态资源,雪碧图合并图片等。
  • 图片可以渐进式加载
    // 我们可以使用webP格式的图片或者分辨率较低的压缩图,再叠加一层清晰的 png 图片,实现快速显示的效果。
    // 叠加 png 图片的目的是防止某些浏览器不支持 webP 格式。
    background-image: url("img/beijing.webp"),url("img/beijing.png");
    
    // 使用picture标签,保底img为webp格式
    <picture>
      <source media="(min-width:465px)" srcset="/i/photo/tulip.jpg">
      <img src="/i/photo/flower.gif" alt="Flowers" style="width:auto;">
    </picture>
    

压缩工具:

  • https://squoosh.app/
  • https://tinypng.com/
  • https://imagecompressor.com/
3. 页面视频 懒加载监听

使用intersectionObserver来监听是否进入页面视野。
提供一份监听全页面视频懒加载工具函数。

/**
 * @description 视频懒加载
 * - <video data-src={src} data-poster={poster} autoPlay playsInline loop muted/>
 */

function VideoLazyLoad(threshold = 300) {
  function videoLoaded(video) {
    return video.getAttribute('src');
  }

  const lazyVideos = [].slice.call(document.querySelectorAll('video[data-src]'));

  if ('IntersectionObserver' in window) {
    let lazyVideoObserver = new IntersectionObserver(
      function (entries, observer) {
        entries.forEach(function (_video, _index) {
          const video = _video.target;
          const videoPlaying =
            video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
          if (_video.isIntersecting) {
            if (videoLoaded(video)) {
              video.play();
            } else {
              video.poster = video.getAttribute('data-poster');
              video.src = video.getAttribute('data-src');
              video.load();
              video.play();
            }
          } else {
            if (videoLoaded(video) && videoPlaying) {
              video.pause();
            }
          }
        });
      },
      {
        root: document.getElementById('_next'),
        rootMargin: `${threshold}px 0px`,
      }
    );

    lazyVideos.forEach(function (lazyVideo) {
      lazyVideoObserver.observe(lazyVideo);
    });
  } else {
    lazyVideos.forEach(function (video) {
      video.src = video.getAttribute('data-src');
      video.poster = video.getAttribute('data-poster');
      video.load();
    });
  }
}

/** 视频懒加载,但是在首次进入视野时加载一次,之后是否暂停、播放由用户手动控制。 */
export function VideoLazyLoadFirst(threshold = 300) {
  const lazyVideos = [].slice.call(document.querySelectorAll('video[data-src]'));

  if ('IntersectionObserver' in window) {
    let lazyVideoObserver = new IntersectionObserver(
      function (entries, observer) {
        entries.forEach((_video, _index) => {
          const video = _video.target;
          if (_video.isIntersecting && !video.src) {
            video.poster = video.getAttribute('data-poster');
            video.src = video.getAttribute('data-src');
            video.load();
            video.play();
          }
        });
      },
      {
        root: document.getElementById('_next'),
        rootMargin: `${threshold}px 0px`,
      }
    );

    lazyVideos.forEach(function (lazyVideo) {
      lazyVideoObserver.observe(lazyVideo);
    });
  } else {
    lazyVideos.forEach(function (video) {
      video.src = video.getAttribute('data-src');
      video.poster = video.getAttribute('data-poster');
      video.load();
    });
  }
}

组件按需加载

如果一个包不合常理地比较大,可以看源代码里是否引入了其他不需要的代码,需注意lodash / @ant-design/Icon。
比如@ant-design/icons ,最好改成:@ant-design/icons/lib

了解IntersectionObserver&优化懒加载和业务方案思路

Intersection Observer API MDN
IntersectionObserver MDN
IntersectionObserver API可以帮助我们实现“检测一个元素是否可见或者两个元素是否相交”,具体应用于实现图片视频资源懒加载、内容无限滚动、广告曝光埋点等功能。

过去这类边界检测的功能通过​getBoundingClientRect()或者scroll事件监听实现,它们运行在主线程上,频繁的触发会让性能变差,而IntersectionObserver提供了一种更加优雅的检测方式,它在渲染进程上监听交集事件,主线程上异步接受通知并调用回调函数,因此有部分人认为:IntersectionObserver的回调方法里可以随意写高消耗代码,不会影响主线程,这种观点是错误的,JS代码执行是单线程的,因此JS代码的解释执行都在主线程上,因此我们在回调函数中也要注意尽量写的简单一些。

MDN————
请留意,你注册的回调函数将会在主线程中被执行。所以该函数执行速度要尽可能的快。如果有一些耗时的操作需要执行,建议使用 Window.requestIdleCallback() 方法。

API

// 创建实例,创建一个新的 IntersectionObserver(观察器) 对象,当其监听到目标元素的可见部分(的比例)超过了一个或多个阈值(threshold)时,会执行指定的回调函数。
const observer = new IntersectionObserver(callback, option);
 
// 开始观察element1 / 2,如果需要观察多个对象,可以使用observe方法多次,而每个dom节点的交点变动都会触发callback
observer.observe(element1);
observer.observe(element2);
[domsArray].forEach(d=>observer.observe(d));

// 返回所有观察目标的 IntersectionObserverEntry 对象数组
IntersectionObserver.takeRecords()
 
// 停止观察
observer.unobserve(element);
 
// 关闭观察器
observer.disconnect();

new IntersectionObserver(callback, options)

可以将这个交集观察构造函数理解到两个对象,一个是根对象(窗口对象),一个是被观察对象(我们要观察的目标),callback的触发时间就是当 我们要观察的目标窗口对象产生交集的时候。

options 对象可用于控制窗口对象的参数。

root
指定根 (root) 元素,用于检查目标的可见性。必须是目标元素的父级元素。如果未指定或者为null,则默认为浏览器视窗。

rootMargin
根 (root) 元素的外边距。类似于 CSS 中的 margin 属性,比如 “10px 20px 30px 40px” (top、right、bottom、left)。如果有指定 root 参数,则 rootMargin 也可以使用百分比来取值。该属性值是用作 root 元素和 target 发生交集时候的计算交集的区域范围,使用该属性可以控制 root 元素每一边的收缩或者扩张。默认值为四个边距全是 0。

threshold
触发的阈值设定。可以是单一的 number 也可以是 number 数组,target 元素和 root 元素相交程度达到该值的时候 IntersectionObserver 注册的回调函数将会被执行。
如果你只是想要探测当 target 元素的在 root 元素中的可见性超过 50% 的时候,你可以指定该属性值为 0.5。如果你想要 target 元素在 root 元素的可见程度每多 25% 就执行一次回调,那么你可以指定一个数组 [0, 0.25, 0.5, 0.75, 1]。默认值是 0 (意味着只要有一个 target 像素出现在 root 元素中,回调函数将会被执行)。该值为 1.0 含义是当 target 完全出现在 root 元素中时候 回调才会被执行。
如果 isIntersecting 为真,target 元素的至少已经达到 thresholds 属性值当中规定的其中一个阈值,如果为假,则 target 元素不在给定的阈值范围内可见。

现在假如我的观察目标对象设置为某个视频,想要让它懒加载,通过这个懒加载方案来理解上述属性吧。

Q1:想要实现每次视频的可见程度75%的时候播放,可见程度25%的时候暂停,这种效果怎么办呢

可见程度75%,则 不可见程度25%,可以将threshold设定在0.25这个点,即可见程度25%或者不可见程度25%的时候它都会触发一次回调函数,具体的判断可以使用观察器对象的intersectionRatio属性,这个属性是当前目标在窗口中的可见比例。
PS:0.25这个属性对左右方向的进入视窗也同样成立。

Q2:我不想在视频进入视窗内才开始加载,能不能提前200px加载呢?

可以。修改rootMargin为200px 0即可,即将视窗口 上下 各加长200px,这时滚轮上下滑动,视频提前了200px触及视窗,也就提前了200px加载。

Q3:threshold和rootMargin是什么关系呢?

我能不能达到:视频在视窗口提前200px加载,而在到达视窗被我们看到的时候播放呢?
答案是:不能。
我经过尝试,发现,当使用rootMargin让视窗扩大的同时,它的交集区域也在扩大,曾经我们rootMargin默认是窗口大小的时候,threshold设置成0,就可以在用户看到的同时调用回调函数。但是rootMargin设置成200px 0时,threshold设置仍然为0,此时触发回调函数的时候,视频还在视窗的上200px呢。所以“视频什么时候达到视窗”的这个阶段也就无法估计了。(无法得知200px和视频大小和视窗长度之间的关系,因此无法算出比例)。
不过想想,就算达到200px时提前加载和播放,实际上也能达到我们想要的效果,大差不差吧。

callback回调函数与使用

其实搞懂了option基本上就搞懂了这个观察API。但是callback中传入给我们的“目标对象”还经过了一些观察者的包装,有很多我们可以用的上的属性。

// 简单使用
const callback = (entries, observer) => {
  // entries被观察的目标对象数组。
  entries.forEach(entry => {
    // Each entry describes an intersection change for one observed target element:
    // entry.boundingClientRect // 目标对象在视窗内的top / bottom / left / right等距离。
    // entry.intersectionRect // 同上类似,用的不多没有仔细研究。
    // entry.intersectionRatio // 目标对象在视窗内的交集比例。(用的比较多)
    // entry.isIntersecting // 当前目标是否视窗内可见(达到设定的阈值)
    // entry.rootBounds // 边界检测
    // entry.target // 当前目标的DOM节点实例。(底下当时传入观察的element1)
    // entry.time
  });
};

const observer = new IntersectionObserver(callback, option);
observer.observe(element1);

最后,实际使用。

我们重新修改了上面的lazyloadVideo方法,目的在于希望视频在25%这个节点再去播放,不可见75%的时候暂停,这样节省一些内存消耗。
当然,这个函数还有改进的空间:比如一个多视频的页面,当用户快速滑下,上一批视频还未加载完的情况,将阻塞后续的视频加载,这时候需要取消上一批不可见视频的加载,腾出空间给下一批,这个后续再安排吧。

/** 
 * - <video data-src={src} data-poster={poster} autoPlay playsInline loop muted/>
 * @param {number} rootMarginY 上下方向 视口窗的扩张/收缩距离
 * @param {number} rootMarginX 左右方向 视口窗的扩张/收缩距离
 * @param {number|string} threshold 视频的可见程度提醒断点
 */

function VideoLazyLoad(rootMarginY = 150, rootMarginX = 0, threshold) {
  function videoLoaded(video) {
    return video.getAttribute('src');
  }

  const lazyVideos = [].slice.call(document.querySelectorAll('video[data-src]'));

  if ('IntersectionObserver' in window) {
    let lazyVideoObserver = new IntersectionObserver(
      function (entries, observer) {
        entries.forEach(function (_video, _index) {
          const video = _video.target;
          const videoPlaying =
            video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
          if (!_video.isIntersecting && videoLoaded(video) && videoPlaying) {
            video.pause();
          }

          // 可见度0-2 先配置视频封面,视频不允许播放。
          if (_video.intersectionRatio > 0 && _video.intersectionRatio < 0.25) {
            if (!video.getAttribute('poster')) {
              if (video.getAttribute('data-poster')) {
                video.poster = video.getAttribute('data-poster');
              } else {
                video.src = video.getAttribute('data-src');
              }
            }

            if (videoLoaded(video) && videoPlaying) {
              video.pause();
            }
          }

          if (_video.intersectionRatio >= 0.25) {
            if (videoLoaded(video)) {
              video.play();
            } else {
              if (!video.getAttribute('poster')) {
                video.poster = video.getAttribute('data-poster');
              }
              video.src = video.getAttribute('data-src');
              video.load();
              video.play();
            }
          }
        });
      },
      {
        root: document.getElementById('_next'),
        rootMargin: `${rootMarginY}px ${rootMarginX}px`,
        threshold: threshold ?? [0, 0.25],
      }
    );

    lazyVideos.forEach(function (lazyVideo) {
      lazyVideoObserver.observe(lazyVideo);
    });
  } else {
    lazyVideos.forEach(function (video) {
      video.src = video.getAttribute('data-src');
      video.poster = video.getAttribute('data-poster');
      video.load();
    });
  }
}

——————————————

下方内容新更新于2024-02-07

——————————————

针对性能分析指标进行项目优化

通过Chrome提供的lightHouse / performace等工具,以及第三方性能检测工具比如gtmetrix等,都可以给到我们一些建议指标。

对于项目的进一步优化,除了之前已经提过很多次的懒加载、媒体资源压缩、webpack分包优化等等,其实我们也可以根据报告对某个指标进行优化。

TTL

一些第三方资源请求太多,阻碍了主要网页资源请求,造成TTL很高,打开列表一看全是第三方资源。
于是有以下几个措施:

  1. 对于第三方资源进行延迟加载,需要用户对页面进行交互才开始下载资源,新增对用户交互行为的监听以及创建变量,一些非首屏的懒加载方案同样可以使用。
  2. 合理分包减少请求大小、合并小资源请求减少请求数量。
  3. 做好缓存处理。
CLS
  1. 查看是什么DOM节点造成的页面重排,CLS指标偏高。
  2. 图片设置好宽高。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1439019.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《MySQL 简易速速上手小册》第3章:性能优化策略(2024 最新版)

文章目录 3.1 查询优化技巧3.1.1 基础知识3.1.2 重点案例3.1.3 拓展案例 3.2 索引和查询性能3.2.1 基础知识3.2.2 重点案例3.2.3 拓展案例 3.3 优化数据库结构和存储引擎3.3.1 基础知识3.3.2 重点案例3.3.3 拓展案例 3.1 查询优化技巧 让我们来聊聊如何让你的 MySQL 查询跑得像…

【Linux】vim的基本操作与配置(上)

Hello everybody!今天我们要进入vim的讲解了。学会了vim,咱们就可以在Linux系统上做一些简单的编程啦&#xff01; 那么废话不多说&#xff0c;咱们直接进入正题&#xff01; 1.初识vim vim是一款多模式的文本编辑器&#xff0c;可以对一个文件进行编辑操作。 它一共有三个模…

【射影几何13 】梅氏定理和塞瓦定理探讨

梅氏定理和塞瓦定理 目录 一、说明二、梅涅劳斯&#xff08;Menelaus&#xff09;定理三、塞瓦(Giovanni Ceva&#xff09;定理四、塞瓦点的推广 一、说明 在射影几何中&#xff0c;梅涅劳斯&#xff08;Menelaus&#xff09;定理和塞瓦定理是非常重要的基本定理。通过这两个定…

09 AB 10串口通信发送原理

通用异步收发传输器&#xff08; Universal Asynchronous Receiver/Transmitter&#xff0c; UART&#xff09;是一种异步收发传输器&#xff0c;其在数据发送时将并行数据转换成串行数据来传输&#xff0c; 在数据接收时将接收到的串行数据转换成并行数据&#xff0c; 可以实现…

【数据分享】1929-2023年全球站点的逐年平均降水量(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;说到常用的降水数据&#xff0c;最详细的降水数据是具体到气象监测站点的降水数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2023年全…

训练集,验证集,测试集比例

三者的区别 训练集&#xff08;train set&#xff09; —— 用于模型拟合的数据样本。验证集&#xff08;validation set&#xff09;—— 是模型训练过程中单独留出的样本集&#xff0c;它可以用于调整模型的超参数和用于对模型的能力进行初步评估。 通常用来在模型迭代训练时…

DevOps落地笔记-17|度量指标:寻找真正的好指标?

前面几个课时端到端地介绍了软件开发全生命周期中涉及的最佳实践&#xff0c;经过上面几个步骤&#xff0c;企业在进行 DevOps 转型时技术方面的问题解决了&#xff0c;这个时候我们还缺些什么呢&#xff1f;事实上很多团队和组织在实施 DevOps 时都专注于技术&#xff0c;而忽…

【力扣】查找总价格为目标值的两个商品,双指针法

查找总价格为目标值的两个商品原题地址 方法一&#xff1a;双指针 这道题和力扣第一题“两数之和”非常像&#xff0c;区别是这道题已经把数组排好序了&#xff0c;所以不考虑暴力枚举和哈希集合的方法&#xff0c;而是利用单调性&#xff0c;使用双指针求解。 考虑数组pric…

零代码3D可视化快速开发平台

老子云平台 老子云3D可视化快速开发平台&#xff0c;集云压缩、云烘焙、云存储云展示于一体&#xff0c;使3D模型资源自动输出至移动端PC端、Web端&#xff0c;能在多设备、全平台进行展示和交互&#xff0c;是全球领先、自主可控的自动化3D云引擎。此技术已经在全球申请了专利…

力扣优选算法100道——【模板】前缀和(一维)

【模板】前缀和_牛客题霸_牛客网 (nowcoder.com) 目录 &#x1f6a9;了解题意 &#x1f6a9;算法原理 &#x1f388;设定下标为1开始 &#x1f388;取值的范围 &#x1f6a9;实现代码 &#x1f6a9;了解题意 第一行的3和2&#xff0c;3代表行数&#xff0c;2代表q次查询(…

【Java数据结构】ArrayList和LinkedList的遍历

一&#xff1a;ArrayList的遍历 import java.util.ArrayList; import java.util.Iterator; import java.util.List;/*** ArrayList的遍历*/ public class Test {public static void main(String[] args) {List<Integer> list new ArrayList<>();list.add(5);list…

MATLAB环境下生成对抗网络系列(11种)

为了构建有效的图像深度学习模型&#xff0c;数据增强是一个非常行之有效的方法。图像的数据增强是一套使用有限数据来提高训练数据集质量和规模的数据空间解决方案。广义的图像数据增强算法包括&#xff1a;几何变换、颜色空间增强、核滤波器、混合图像、随机擦除、特征空间增…

寒假作业2024.2.6

1.现有无序序列数组为23,24,12,5,33,5347&#xff0c;请使用以下排序实现编程 函数1:请使用冒泡排序实现升序排序 函数2:请使用简单选择排序实现升序排序 函数3:请使用直接插入排序实现升序排序 函数4:请使用插入排序实现升序排序 #include <stdio.h> #include <stdl…

一个坐标系查询网站python获取所有坐标系

技术路线选择 我是使用的vue 3开发的网页界面&#xff0c;element-plus构建网页组件&#xff0c;openlayer展示地图&#xff0c;express提供后端API&#xff0c;vercel进行在线部署。 python获取所有坐标系 想要展示所有坐标系&#xff0c;那需要先获取坐标系&#xff0c;怎么…

【开源】基于JAVA+Vue+SpringBoot的贫困地区人口信息管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 人口信息管理模块2.2 精准扶贫管理模块2.3 特殊群体管理模块2.4 案件信息管理模块2.5 物资补助模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 人口表3.2.2 扶贫表3.2.3 特殊群体表3.2.4 案件表3.2.5 物资补助表 四…

机器人学、机器视觉与控制 上机笔记(2.1章节)

机器人学、机器视觉与控制 上机笔记&#xff08;2.1章节&#xff09; 1、前言2、本篇内容3、代码记录3.1、新建se23.2、生成坐标系3.3、将T1表示的变换绘制3.4、完整绘制代码3.5、获取点*在坐标系1下的表示3.6、相对坐标获取完整代码 4、结语 1、前言 工作需要&#xff0c;想同…

HTTP协议笔记

HTTP协议笔记 参考&#xff1a; &#xff08;建议精读&#xff09;HTTP灵魂之问&#xff0c;巩固你的 HTTP 知识体系 《透视 HTTP 协议》——chrono 目录&#xff1a; 1、说说你对HTTP的了解吧。  1. HTTP状态码。  2. HTTP请求头和响应头&#xff0c;其中包括cookie、跨域响…

AcWing 1238 日志统计(双指针算法)

题目概述 小明维护着一个程序员论坛。现在他收集了一份”点赞”日志&#xff0c;日志共有 N 行。 其中每一行的格式是&#xff1a; ts id表示在 ts 时刻编号 id 的帖子收到一个”赞”。 现在小明想统计有哪些帖子曾经是”热帖”。 如果一个帖子曾在任意一个长度为 D 的时间段…

《MySQL 简易速速上手小册》第1章:MySQL 基础和安装(2024 最新版)

文章目录 1.1 MySQL 概览&#xff1a;版本、特性和生态系统1.1.1 基础知识1.1.2 重点案例1.1.3 拓展案例 1.2 安装和配置 MySQL1.2.1 基础知识1.2.2 安装步骤1.2.3 重点案例1.2.4 拓展案例 1.3 基础命令和操作1.3.1 基础知识1.3.2 重点案例1.3.3 拓展案例 1.1 MySQL 概览&#…

JUC ThreadLocal

文章目录 ThreadLocal ^1.2^ 的作用使用场景示例1ThreadLocal 变量初始化ThreadLocal 源码分析源码分析总结 内存泄漏问题示例说明new Thread 方式 执行结果pool 方式执行结果原因解析总结 ThreadLocal 1.2 的作用 ThreadLocal 为每个线程提供单独的变量副本。每个变量副本都是…