在这里插入图片描述

前端老铁必看:搞定回流重绘性能翻倍,DocumentFragment加Vue keep-alive真香实战

开篇先唠两句

兄弟们,有没有遇到过这种尴尬时刻?页面卡成PPT,鼠标点一下半天没反应,用户在那疯狂点击,结果直接给你整出个"页面无响应"。你打开任务管理器一看,好家伙,CPU占用直接飙到90%,风扇转得跟直升机似的。这时候你心里肯定在骂:我代码明明没写错啊,逻辑都对,怎么就跑成这德行了?

我跟你说,这种情况十之八九是**回流(Reflow)重绘(Repaint)**这俩兄弟在搞事情。今天咱不整那些虚头巴脑的理论,就聊怎么让页面丝滑如德芙。很多干了三四年的前端,你问他回流重绘到底啥关系,他照样给你懵圈。没关系,看完这篇,保证你出去跟人吹牛逼都有底气。

回流重绘这俩兄弟到底咋回事

浏览器渲染流水线你得先整明白

要搞清楚这俩货,得先知道浏览器是怎么把代码变成你屏幕上看到的画面的。这个过程大概分这么几步:

  1. 解析HTML → 构建DOM树
  2. 解析CSS → 构建CSSOM树
  3. DOM + CSSOM → 合成渲染树(Render Tree)
  4. 布局(Layout) → 计算每个节点的大小和位置
  5. 绘制(Paint) → 把像素画到屏幕上
  6. 合成(Composite) → 图层合并,最终呈现

这里面第4步就是回流,第5步就是重绘。简单说,回流就是浏览器重新计算元素的位置和大小,重绘就是重新填充像素颜色。

DOM一变浏览器就得重新算布局,这就是回流

回流这玩意儿特别烦人。你想啊,页面里一个元素的位置变了,它后面的元素是不是也得跟着动?就像排队,前面的人突然往后退一步,后面所有人都要挪位置。所以回流的成本是几何级增长的,牵一发而动全身。

比如你这么写:

// 糟糕的做法 - 触发三次回流
const box = document.getElementById('box');
box.style.width = '100px';      // 第一次回流
box.style.height = '100px';     // 第二次回流  
box.style.margin = '10px';      // 第三次回流

浏览器每次读到一行样式修改,都得重新算一遍布局。三次修改,三次回流,性能直接炸裂。

样式一变但布局不变,那叫重绘,开销小点但也不能瞎搞

重绘就温柔多了。比如你把背景色从红色改成蓝色,元素的位置大小都没变,浏览器只需要重新填充颜色就行,不需要重新计算布局。

// 这只会触发重绘,不会触发回流
box.style.backgroundColor = 'red';
box.style.color = 'blue';

但是!重绘虽然比重流便宜,也不是不要钱。如果你每秒重绘几百次,照样卡死你。而且重绘一定发生在回流之后,如果你触发了回流,必然跟着触发重绘,这就是双重打击。

为啥说回流比重绘更费性能,给你算笔账就懂了

咱们来算笔账。假设页面有1000个DOM节点:

  • 重绘一次:浏览器需要遍历渲染树,重新绘制受影响的区域,大概需要5ms
  • 回流一次:浏览器不仅要遍历,还要重新计算几何信息,然后还要重绘,大概需要50ms

看起来差10倍?太天真了!回流还有个副作用——它会强制刷新渲染队列。啥意思呢?现代浏览器为了优化性能,会把一堆样式修改攒在一起,等到关键时刻(比如读取布局信息)再统一处理。但如果你在修改样式的过程中,突然去读取了某个元素的offsetHeight,浏览器为了保证数据准确,必须立即执行之前积压的所有回流操作。

// 致命代码 - 强制同步回流
function killPerformance() {
    const boxes = document.querySelectorAll('.box');
    
    for (let i = 0; i < boxes.length; i++) {
        const box = boxes[i];
        // 修改样式
        box.style.width = '100px';
        // 立即读取布局信息 - 强制回流!
        console.log(box.offsetHeight);  // 这里触发回流
    }
}
// 1000个元素就是1000次回流,页面直接卡死

哪些操作会触发回流,踩过的坑都给你们列出来

我整理了个清单,这些操作都会触发回流,平时能躲就躲:

元素几何属性相关的读写:

// 读取这些属性会强制回流
elem.offsetLeft/offsetTop/offsetWidth/offsetHeight
elem.clientLeft/clientTop/clientWidth/clientHeight  
elem.scrollLeft/scrollTop/scrollWidth/scrollHeight
elem.getBoundingClientRect()
elem.getComputedStyle()

修改这些样式会触发回流:

width, height, padding, margin, border-width
position, top, left, right, bottom
display, float, clear, overflow
font-size, line-height, vertical-align

DOM操作:

appendChild, removeChild, insertBefore
innerHTML, outerHTML
cloneNode

窗口操作:

window.resize
window.scroll

实战避坑指南:

// ❌ 错误示范 - 循环中读取offsetHeight
function badExample() {
    const container = document.getElementById('list');
    for (let i = 0; i < 1000; i++) {
        const item = document.createElement('div');
        item.className = 'item';
        container.appendChild(item);  // 每次都可能触发回流
        // 致命一击!
        console.log(container.offsetHeight);  
    }
}

// ✅ 正确做法 - 先批量操作,最后统一读取
function goodExample() {
    const container = document.getElementById('list');
    const fragment = document.createDocumentFragment();  // 等会讲这神器
    
    for (let i = 0; i < 1000; i++) {
        const item = document.createElement('div');
        item.className = 'item';
        fragment.appendChild(item);  // 操作fragment不触发回流
    }
    
    container.appendChild(fragment);  // 只触发一次回流
    
    // 所有操作完再读取
    console.log(container.offsetHeight);
}

DocumentFragment这玩意儿有多香

一次性插入一堆DOM节点,别傻傻地循环append

兄弟们,我见过太多人写这种代码了:

// 菜鸟写法 - 卡到怀疑人生
function renderList(data) {
    const list = document.getElementById('list');
    list.innerHTML = '';  // 清空,触发回流
    
    data.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item.name;
        li.className = 'list-item';
        // 每次appendChild都触发回流!
        list.appendChild(li);
    });
}
// 如果data有1000条,就是1000次回流,页面直接假死

这种写法在数据量小的时候看不出来,一旦超过500条,用户就能明显感觉到卡顿。为啥?因为每次appendChild都会改变DOM结构,浏览器就要重新计算布局。

这货就是个临时容器,不会触发多次回流

DocumentFragment是个啥?简单说就是个轻量级的文档对象,它存在于内存中,不属于主DOM树。你对它进行任何操作,都不会触发浏览器的回流或重绘,直到你把它塞进真正的DOM里。

// 使用DocumentFragment优化
function renderListOptimized(data) {
    const list = document.getElementById('list');
    list.innerHTML = '';  // 还是清空,但只触发一次
    
    // 创建一个文档片段
    const fragment = document.createDocumentFragment();
    
    data.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item.name;
        li.className = 'list-item';
        // 操作fragment,零回流!
        fragment.appendChild(li);
    });
    
    // 一次性插入,只触发一次回流
    list.appendChild(fragment);
}

看到没?同样是1000条数据,第一种写法1000次回流,第二种只有2次(清空+插入),性能差距能有几百倍

实际代码对比给你看,性能差距能吓你一跳

咱们来个真实的性能测试:

// 测试代码
function performanceTest() {
    const data = Array.from({length: 5000}, (_, i) => ({name: `Item ${i}`}));
    
    // 测试1:直接操作DOM
    console.time('直接操作DOM');
    const list1 = document.createElement('div');
    data.forEach(item => {
        const div = document.createElement('div');
        div.textContent = item.name;
        list1.appendChild(div);  // 每次触发回流
    });
    document.body.appendChild(list1);
    console.timeEnd('直接操作DOM');  // 大概 150-300ms
    
    // 测试2:使用DocumentFragment
    console.time('使用Fragment');
    const list2 = document.createElement('div');
    const fragment = document.createDocumentFragment();
    
    data.forEach(item => {
        const div = document.createElement('div');
        div.textContent = item.name;
        fragment.appendChild(div);  // 零回流
    });
    list2.appendChild(fragment);  // 只触发一次
    document.body.appendChild(list2);
    console.timeEnd('使用Fragment');  // 大概 10-20ms
    
    // 测试结果:性能提升 10-30倍!
}

// 更狠的对比 - 带样式计算
function heavyPerformanceTest() {
    const data = Array.from({length: 1000}, (_, i) => i);
    
    // 菜鸟版 - 强制同步回流
    console.time('菜鸟版');
    const container = document.createElement('div');
    document.body.appendChild(container);
    
    data.forEach(i => {
        const div = document.createElement('div');
        div.style.width = `${i}px`;
        container.appendChild(div);
        // 致命错误!读取导致强制回流
        const height = container.offsetHeight;
    });
    console.timeEnd('菜鸟版');  // 可能超过 1000ms
    
    // 大神版 - 批量操作
    console.time('大神版');
    const container2 = document.createElement('div');
    const fragment = document.createDocumentFragment();
    
    data.forEach(i => {
        const div = document.createElement('div');
        div.style.width = `${i}px`;
        fragment.appendChild(div);
    });
    
    container2.appendChild(fragment);
    document.body.appendChild(container2);
    // 最后才读取
    const height = container2.offsetHeight;
    console.timeEnd('大神版');  // 大概 20-30ms
    
    // 差距:50倍以上!
}

创建 fragment 的几种写法,哪种更顺手你自己挑

除了标准的createDocumentFragment,还有几种变体写法:

// 写法1:标准写法
const fragment = document.createDocumentFragment();

// 写法2:使用模板元素(HTML5)
const template = document.createElement('template');
template.innerHTML = `
    <div class="item">Item 1</div>
    <div class="item">Item 2</div>
`;
const fragment2 = template.content;  // 这也是个DocumentFragment

// 写法3:cloneNode大法(适合静态结构)
const templateNode = document.createElement('div');
templateNode.innerHTML = '<span class="tag"></span>';
const fragment3 = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {
    // cloneNode比createElement快一点
    const clone = templateNode.firstChild.cloneNode(true);
    clone.textContent = `Tag ${i}`;
    fragment3.appendChild(clone);
}

// 写法4:Range对象(高级玩法)
const range = document.createRange();
const fragment4 = range.createContextualFragment(`
    <div>HTML字符串直接转fragment</div>
    <p>适合从接口返回的HTML</p>
`);

性能对比:

function compareCreationMethods() {
    const count = 10000;
    
    // 方法1:createDocumentFragment + createElement
    console.time('createElement');
    const f1 = document.createDocumentFragment();
    for (let i = 0; i < count; i++) {
        const div = document.createElement('div');
        div.textContent = 'test';
        f1.appendChild(div);
    }
    console.timeEnd('createElement');
    
    // 方法2:cloneNode
    console.time('cloneNode');
    const template = document.createElement('div');
    template.innerHTML = '<div>test</div>';
    const f2 = document.createDocumentFragment();
    for (let i = 0; i < count; i++) {
        const clone = template.firstChild.cloneNode(true);
        f2.appendChild(clone);
    }
    console.timeEnd('cloneNode');
    
    // 方法3:innerHTML(最慢,但写起来爽)
    console.time('innerHTML');
    const f3 = document.createDocumentFragment();
    let html = '';
    for (let i = 0; i < count; i++) {
        html += '<div>test</div>';
    }
    const temp = document.createElement('div');
    temp.innerHTML = html;
    while (temp.firstChild) {
        f3.appendChild(temp.firstChild);
    }
    console.timeEnd('innerHTML');
    
    // 通常结果:cloneNode > createElement >> innerHTML
}

别以为这技术过时了,2026年照样能打的优化手段

有人说:“现在都2026年了,还用DocumentFragment?直接上虚拟DOM啊!” 兄弟,虚拟DOM是框架层面的事,原生DOM操作该优化还得优化。而且有些场景框架帮不了你,比如:

// 场景1:富文本编辑器的光标定位
function insertNodesAtCursor(nodes) {
    const selection = window.getSelection();
    const range = selection.getRangeAt(0);
    
    // 必须原生操作,框架插不上手
    const fragment = document.createDocumentFragment();
    nodes.forEach(node => fragment.appendChild(node));
    
    range.deleteContents();
    range.insertNode(fragment);
    
    // 重新定位光标
    range.collapse(false);
    selection.removeAllRanges();
    selection.addRange(range);
}

// 场景2:Web Component内部优化
class MyList extends HTMLElement {
    connectedCallback() {
        const data = JSON.parse(this.getAttribute('data') || '[]');
        const shadow = this.attachShadow({mode: 'open'});
        
        const fragment = document.createDocumentFragment();
        const ul = document.createElement('ul');
        
        data.forEach(item => {
            const li = document.createElement('li');
            li.innerHTML = `
                <span class="title">${item.title}</span>
                <span class="desc">${item.desc}</span>
            `;
            ul.appendChild(li);
        });
        
        fragment.appendChild(ul);
        
        // 一次性添加样式+DOM
        const style = document.createElement('style');
        style.textContent = `
            ul { list-style: none; padding: 0; }
            li { padding: 10px; border-bottom: 1px solid #eee; }
            .title { font-weight: bold; color: #333; }
            .desc { color: #666; font-size: 14px; }
        `;
        fragment.appendChild(style);
        
        shadow.appendChild(fragment);  // 只触发一次渲染
    }
}

// 场景3:Canvas/WebGL叠加层
function createOverlayElements(count) {
    const container = document.getElementById('canvas-overlay');
    const fragment = document.createDocumentFragment();
    
    for (let i = 0; i < count; i++) {
        const marker = document.createElement('div');
        marker.className = 'map-marker';
        marker.style.cssText = `
            position: absolute;
            left: ${Math.random() * 100}%;
            top: ${Math.random() * 100}%;
            transform: translate(-50%, -50%);
        `;
        marker.dataset.id = i;
        fragment.appendChild(marker);
    }
    
    container.appendChild(fragment);
}

Vue keep-alive 缓存组件的正确打开方式

列表跳详情页再回来,数据没了是不是很崩溃

做电商或者后台管理系统的兄弟肯定遇到过这种场景:用户在一个很长的商品列表页,滚到第50条,点进去看详情,看完返回,卧槽,列表回到顶部了,之前筛选的条件也全没了,用户当场崩溃,产品经理提着刀来找你。

传统的解决方案是啥?用Vuex或者Pinia把列表状态存起来,返回时再恢复。但这玩意儿写起来麻烦,还要处理各种边界情况。其实Vue早就给你准备好了解决方案——keep-alive

keep-alive 就是给组件上个存档点,状态给你保住

keep-alive是Vue内置的一个抽象组件,它能把包裹的动态组件缓存起来,而不是销毁重建。简单说就是给组件拍了个快照,下次再显示时直接恢复,而不是重新创建。

<!-- 基础用法 -->
<template>
  <div>
    <button @click="currentTab = 'List'">列表</button>
    <button @click="currentTab = 'Detail'">详情</button>
    
    <!-- 包裹动态组件 -->
    <keep-alive>
      <component :is="currentTab"></component>
    </keep-alive>
  </div>
</template>

<script>
import List from './List.vue'
import Detail from './Detail.vue'

export default {
  components: { List, Detail },
  data() {
    return {
      currentTab: 'List'
    }
  }
}
</script>

在这个例子中,切换到Detail再切回List,List组件不会被销毁,它的所有状态(滚动位置、筛选条件、表单数据)都还在。

include 和 exclude 咋用,别整个项目全缓存那要炸

如果你无脑给所有组件加keep-alive,内存会爆炸。特别是那种大数据量的表格组件,缓存十个八个内存就扛不住了。所以得用includeexclude精确控制。

<template>
  <div>
    <!-- 只缓存List组件,Detail不缓存 -->
    <keep-alive include="List">
      <component :is="currentTab"></component>
    </keep-alive>
    
    <!-- 或者缓存多个,用逗号分隔 -->
    <keep-alive include="List,UserProfile,Settings">
      <router-view></router-view>
    </keep-alive>
    
    <!-- 用数组更灵活 -->
    <keep-alive :include="cachedViews">
      <router-view></router-view>
    </keep-alive>
    
    <!-- 排除特定组件 -->
    <keep-alive exclude="RealTimeChart,HeavyMap">
      <component :is="dynamicComp"></component>
    </keep-alive>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentTab: 'List',
      // 动态控制缓存列表
      cachedViews: ['List', 'UserProfile']
    }
  },
  methods: {
    // 根据用户权限动态调整缓存
    updateCache() {
      if (this.isAdmin) {
        this.cachedViews.push('AdminPanel');
      } else {
        this.cachedViews = this.cachedViews.filter(v => v !== 'AdminPanel');
      }
    }
  }
}
</script>

配合路由元信息使用(最佳实践):

// router/index.js
const routes = [
  {
    path: '/list',
    component: () => import('@/views/List.vue'),
    meta: {
      keepAlive: true,  // 需要缓存
      title: '商品列表'
    }
  },
  {
    path: '/detail/:id',
    component: () => import('@/views/Detail.vue'),
    meta: {
      keepAlive: false,  // 不需要缓存
      title: '商品详情'
    }
  },
  {
    path: '/realtime',
    component: () => import('@/views/Realtime.vue'),
    meta: {
      keepAlive: false,  // 实时数据不需要缓存
      title: '实时监控'
    }
  }
]

// App.vue
<template>
  <keep-alive :include="cachedViews">
    <router-view v-if="$route.meta.keepAlive"></router-view>
  </keep-alive>
  <router-view v-if="!$route.meta.keepAlive"></router-view>
</template>

<script>
export default {
  computed: {
    cachedViews() {
      // 从路由配置中提取需要缓存的组件名
      return this.$router.options.routes
        .filter(route => route.meta?.keepAlive)
        .map(route => route.component?.name)
        .filter(Boolean);
    }
  }
}
</script>

配合 activated 和 deactivated 生命周期玩出花来

被keep-alive缓存的组件,会多出两个生命周期钩子:activated(激活时)和deactivated(停用时)。这俩钩子特别有用,比如:

<script>
export default {
  name: 'ProductList',
  data() {
    return {
      list: [],
      scrollTop: 0,
      isLoading: false
    }
  },
  
  // 第一次创建时调用
  created() {
    console.log('created - 组件创建');
    this.fetchData();
  },
  
  // 每次激活时调用(包括第一次)
  activated() {
    console.log('activated - 组件激活');
    
    // 恢复滚动位置
    this.$refs.scrollContainer.scrollTop = this.scrollTop;
    
    // 检查数据是否需要刷新(比如离开超过5分钟)
    if (Date.now() - this.lastLeaveTime > 5 * 60 * 1000) {
      this.refreshData();
    }
    
    // 开启定时刷新
    this.startAutoRefresh();
    
    // 上报埋点
    this.trackEvent('page_view', { page: 'product_list' });
  },
  
  // 每次停用时调用
  deactivated() {
    console.log('deactivated - 组件停用');
    
    // 保存滚动位置
    this.scrollTop = this.$refs.scrollContainer.scrollTop;
    this.lastLeaveTime = Date.now();
    
    // 清理定时器,防止内存泄漏
    this.stopAutoRefresh();
    
    // 关闭WebSocket连接
    this.disconnectWebSocket();
    
    // 暂停视频播放
    if (this.$refs.videoPlayer) {
      this.$refs.videoPlayer.pause();
    }
  },
  
  // 组件销毁时调用(keep-alive组件很少走到这里)
  beforeDestroy() {
    console.log('beforeDestroy - 组件销毁');
    this.stopAutoRefresh();
  },
  
  methods: {
    fetchData() {
      // 初始加载数据
      this.isLoading = true;
      api.getProductList().then(res => {
        this.list = res.data;
        this.isLoading = false;
      });
    },
    
    refreshData() {
      // 局部刷新,不重置整个列表
      api.getProductList({ refresh: true }).then(res => {
        // 智能合并数据,保留用户已选中的项
        this.mergeList(res.data);
      });
    },
    
    startAutoRefresh() {
      // 每30秒刷新一次数据
      this.refreshTimer = setInterval(() => {
        this.refreshData();
      }, 30000);
    },
    
    stopAutoRefresh() {
      if (this.refreshTimer) {
        clearInterval(this.refreshTimer);
        this.refreshTimer = null;
      }
    },
    
    disconnectWebSocket() {
      if (this.ws) {
        this.ws.close();
        this.ws = null;
      }
    }
  }
}
</script>

动态组件加 keep-alive 的组合拳怎么打

实际项目中,经常遇到这种需求:Tab切换,每个Tab里都有复杂的表格或图表,要求切换时不重新加载。这时候就得动态组件+keep-alive组合拳。

<template>
  <div class="dashboard">
    <div class="tabs">
      <button 
        v-for="tab in tabs" 
        :key="tab.name"
        :class="{ active: currentTab === tab.name }"
        @click="currentTab = tab.name"
      >
        {{ tab.label }}
      </button>
    </div>
    
    <!-- 高级用法:max限制缓存数量,LRU淘汰 -->
    <keep-alive 
      :include="cachedComponents" 
      :max="5"  <!-- 最多缓存5个,超出时销毁最久未使用的 -->
    >
      <component 
        :is="currentTabComponent" 
        :key="currentTab"
        v-bind="currentTabProps"
      ></component>
    </keep-alive>
  </div>
</template>

<script>
import SalesChart from './components/SalesChart.vue'
import UserTable from './components/UserTable.vue'
import OrderList from './components/OrderList.vue'
import RealtimeMap from './components/RealtimeMap.vue'

export default {
  components: { SalesChart, UserTable, OrderList, RealtimeMap },
  
  data() {
    return {
      currentTab: 'SalesChart',
      tabs: [
        { name: 'SalesChart', label: '销售趋势', cache: true },
        { name: 'UserTable', label: '用户列表', cache: true },
        { name: 'OrderList', label: '订单管理', cache: true },
        { name: 'RealtimeMap', label: '实时地图', cache: false }  // 实时数据不缓存
      ],
      // 每个Tab的专属配置
      tabConfigs: {
        SalesChart: { dateRange: '7d', type: 'line' },
        UserTable: { pageSize: 50, filters: {} },
        OrderList: { status: 'all', sortBy: 'date' }
      }
    }
  },
  
  computed: {
    currentTabComponent() {
      return this.currentTab
    },
    
    currentTabProps() {
      return this.tabConfigs[this.currentTab] || {}
    },
    
    // 动态计算需要缓存的组件
    cachedComponents() {
      return this.tabs
        .filter(tab => tab.cache)
        .map(tab => tab.name)
    }
  },
  
  watch: {
    currentTab(newVal, oldVal) {
      // 记录切换行为,用于分析用户习惯
      this.$analytics.track('tab_switch', {
        from: oldVal,
        to: newVal,
        timestamp: Date.now()
      });
    }
  }
}
</script>

这俩技术放一起能擦出啥火花

大批量渲染列表时用 fragment 减少回流

想象一个场景:后台管理系统,用户点击"加载更多",一次塞进来500条数据。如果用普通写法,浏览器直接卡死。这时候fragment+分页加载组合拳:

<template>
  <div class="infinite-list" ref="listContainer" @scroll="handleScroll">
    <div v-for="item in displayList" :key="item.id" class="item">
      <img :src="item.avatar" loading="lazy">
      <div class="content">
        <h3>{{ item.title }}</h3>
        <p>{{ item.desc }}</p>
      </div>
    </div>
    
    <div v-if="loading" class="loading">加载中...</div>
    <div v-if="noMore" class="no-more">没有更多了</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      allData: [],        // 所有数据
      displayCount: 50,   // 当前显示数量
      loading: false,
      noMore: false,
      batchSize: 50       // 每批加载数量
    }
  },
  
  computed: {
    displayList() {
      return this.allData.slice(0, this.displayCount);
    }
  },
  
  methods: {
    // 初始加载
    async loadInitialData() {
      const res = await fetch('/api/large-list');
      this.allData = res.data;  // 假设有2000条
      
      // 先渲染前50条,让用户立刻看到内容
      this.displayCount = this.batchSize;
      
      // 剩余数据用requestIdleCallback延迟渲染,不阻塞主线程
      if ('requestIdleCallback' in window) {
        requestIdleCallback(this.renderRemainingData, { timeout: 2000 });
      } else {
        setTimeout(this.renderRemainingData, 0);
      }
    },
    
    // 空闲时渲染剩余数据(分批插入,避免单次回流过大)
    renderRemainingData(deadline) {
      const total = this.allData.length;
      const container = this.$refs.listContainer;
      
      // 使用fragment批量插入
      const batchInsert = (count) => {
        const fragment = document.createDocumentFragment();
        const start = this.displayCount;
        const end = Math.min(start + count, total);
        
       



>欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

  >推荐:[DTcode7](https://blog.csdn.net/black_cat7?spm=1010.2135.3001.5343)的博客首页。  
   一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,<font color='red'>一边打入敌人内部一边持续提升自己</font>,为我们广大开发同胞谋福祉,<font color='red'>坚决抵制睿智产品折磨我们码农兄弟!</font>
***
<table><tr><th><b><font color='red'>专栏系列(点击解锁)</font></b><th><b><font color='red'>学习路线(点击解锁)</font></b><th><b><font color='red'>知识定位</font></b><tr><td><a href=https://blog.csdn.net/black_cat7/category_11327978.html target=_blank rel='noopener noreferrer '>《微信小程序相关博客》</a>
   <td>持续更新中~<td align=left>结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等<tr><td><a href=https://blog.csdn.net/black_cat7/category_12588918.html target=_blank rel='noopener noreferrer '>《AIGC相关博客》</a>
   <td>持续更新中~<td align=left>AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结<tr><td rowspan=4><a href=https://blog.csdn.net/black_cat7/category_12663459.html target=_blank rel='noopener noreferrer '>《HTML网站开发相关》</a>
   <td><a href=https://blog.csdn.net/black_cat7/category_12674047.html target=_blank rel='noopener noreferrer '>《前端基础入门三大核心之html相关博客》</a><td align=left>前端基础入门三大核心之html板块的内容,<mark>入坑前端或者辅助学习的必看知识</mark><tr>
   <td><a href=https://blog.csdn.net/black_cat7/category_11352548.html target=_blank rel='noopener noreferrer '>《前端基础入门三大核心之JS相关博客》</a><td align=left>前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。<br>通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心
   <tr><td><a href=https://blog.csdn.net/black_cat7/category_12674041.html target=_blank rel='noopener noreferrer '>《前端基础入门三大核心之CSS相关博客》</a><td align=left>介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页<tr>
   <td><a href=https://blog.csdn.net/black_cat7/category_12674050.html target=_blank rel='noopener noreferrer '>《canvas绘图相关博客》</a><td align=left>Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化
   <tr><td><a href=https://blog.csdn.net/black_cat7/category_11343005.html target=_blank rel='noopener noreferrer'>《Vue实战相关博客》</a><td>持续更新中~<td align=left>详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅<tr>
   <td><a href=https://blog.csdn.net/black_cat7/category_12674092.html target=_blank rel='noopener noreferrer '>《python相关博客》</a><td>持续更新中~<td align=left>Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具
   <tr><td><a href=https://blog.csdn.net/black_cat7/category_11463502.html target=_blank rel='noopener noreferrer '>《sql数据库相关博客》</a><td>持续更新中~<td align=left>SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能
   <tr><td><a href=https://blog.csdn.net/black_cat7/category_12671914.html target=_blank rel='noopener noreferrer '>《算法系列相关博客》</a><td>持续更新中~<td align=left>算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维
   <tr><td rowspan=3><a href=https://blog.csdn.net/black_cat7/category_12463107.html target=_blank rel='noopener noreferrer '>《IT信息技术相关博客》</a><td>持续更新中~<td align=left>作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识
   <tr><td><a href=https://blog.csdn.net/black_cat7/category_12652565.html target=_blank rel='noopener noreferrer '>《信息化人员基础技能知识相关博客》</a><td align=left>无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方
   <tr><td><a href=https://blog.csdn.net/black_cat7/category_12674074.html target=_blank rel='noopener noreferrer '>《信息化技能面试宝典相关博客》</a><td align=left>涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面
   <tr><td><a href=https://blog.csdn.net/black_cat7/category_11376833.html target=_blank rel='noopener noreferrer'>《前端开发习惯与小技巧相关博客》</a><td>持续更新中~<td align=left>罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等<tr><td><a href=https://blog.csdn.net/black_cat7/category_12271105.html target=_blank rel='noopener noreferrer '>《photoshop相关博客》</a>
   <td>持续更新中~<td align=left>基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结<tr><td><a href=https://blog.csdn.net/black_cat7/category_11412314.html target=_blank rel='noopener noreferrer '>日常开发&办公&生产【实用工具】分享相关博客》</a><td>持续更新中~<td align=left>分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具</table> <br>


   > 吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤 <br>
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/390a9fd6846d45319add4341b304011d.jpeg)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐