JavaScript 性能优化利器:全面解析防抖(Debounce)与节流(Throttle)技术、应用场景及 Lodash、RxJS、vueuse/core Hook 等高效第三方库实践攻略


目录

  1. 前言
  2. 原理与概念解析

    • 2.1 防抖(Debounce)概念
    • 2.2 节流(Throttle)概念
  3. 手写实现:Vanilla JS 版本

    • 3.1 手写防抖函数
    • 3.2 手写节流函数
  4. 应用场景剖析

    • 4.1 防抖常见场景
    • 4.2 节流常见场景
  5. Lodash 中的 Debounce 与 Throttle

    • 5.1 Lodash 安装与引入
    • 5.2 使用 _.debounce 示例
    • 5.3 使用 _.throttle 示例
    • 5.4 Lodash 参数详解与注意事项
  6. RxJS 中的 DebounceTime 与 ThrottleTime

    • 6.1 RxJS 安装与基础概念
    • 6.2 debounceTime 用法示例
    • 6.3 throttleTime 用法示例
    • 6.4 对比与转换:debounce vs auditTime vs sampleTime
  7. vueuse/core 中的 useDebounce 与 useThrottle

    • 7.1 vueuse 安装与引入
    • 7.2 useDebounce 示例
    • 7.3 useThrottle 示例
    • 7.4 与 Vue 响应式配合实战
  8. 性能对比与最佳实践

    • 8.1 原生 vs Lodash vs RxJS vs vueuse
    • 8.2 选择建议与组合使用
  9. 图解与数据流示意

    • 9.1 防抖流程图解
    • 9.2 节流流程图解
  10. 常见误区与调试技巧
  11. 总结

前言

在开发表现要求较高的 Web 应用时,我们经常会遇到 频繁触发事件 导致性能问题的情况,比如用户持续滚动触发 scroll、持续输入触发 input、窗口大小实时变动触发 resize 等。此时,若在回调中做较重的逻辑(如重新渲染、频繁 API 调用),就会造成卡顿、阻塞 UI 或请求过载。防抖(Debounce)与 节流(Throttle)技术为此提供了优雅的解决方案,通过对事件回调做“延迟”“限频”处理,确保在高频率触发时,能以可控的速率执行逻辑,从而极大地优化性能。

本文将从原理出发,向你详细讲解防抖与节流的概念、手写实现、典型应用场景,并系统介绍三类常用高效库的实践:

  • Lodash:经典实用,API 简洁;
  • RxJS:函数式响应式编程,适用于复杂事件流处理;
  • vueuse/core:在 Vue3 环境下的响应式 Hook 工具,集成度高、使用便捷。

通过代码示例ASCII 图解应用场景解析,帮助你迅速掌握并灵活运用于生产环境中。


原理与概念解析

2.1 防抖(Debounce)概念

定义:将多次同一函数调用合并为一次,只有在事件触发停止指定时长后,才执行该函数,若在等待期间再次触发,则重新计时。
  • 防抖用来 “抖开” 高频触发,只在最后一次触发后执行。
  • 典型:搜索输入联想,当用户停止输入 300ms 后再发起请求。

流程图示(最后触发后才执行):

用户输入:——|a|——|a|——|a|——|(停止300ms)|—— 执行 fn()

时间轴(ms):0   100   200      500
  • 若在 0ms 输入一次,100ms 又输入,则 0ms 的定时被清除;
  • 只有在输入停止 300ms(即比上次输入再过 300ms)后,才调用一次函数。

2.2 节流(Throttle)概念

定义:限制函数在指定时间段内只执行一次,若在等待期间再次触发,则忽略或延迟执行,确保执行频率不超过预设阈值。
  • 节流用来 “限制” 高频触发使得函数匀速执行
  • 典型:滚动监听时,若用户持续滚动,确保回调每 100ms 只执行一次。

流程图示(固定步伐执行):

|---100ms---|---100ms---|---100ms---|
触发频率:|a|a|a|a|a|a|a|a|a|a|...
执行时刻:|  fn  |  fn  |  fn  |  fn  |
  • 每隔 100ms,触发一次执行。若在这段时间内多次触发,均被忽略。

手写实现:Vanilla JS 版本

为了理解原理,先手写两个函数。

3.1 手写防抖函数

/**
 * debounce(fn, wait, immediate = false)
 * @param {Function} fn - 需要防抖包装的函数
 * @param {Number} wait - 延迟时长(ms)
 * @param {Boolean} immediate - 是否立即执行一次(leading)
 * @return {Function} debounced 函数
 */
function debounce(fn, wait, immediate = false) {
  let timer = null;
  return function (...args) {
    const context = this;
    if (timer) clearTimeout(timer);

    if (immediate) {
      const callNow = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, wait);
      if (callNow) fn.apply(context, args);
    } else {
      timer = setTimeout(() => {
        fn.apply(context, args);
      }, wait);
    }
  };
}

// 用法示例:
const onResize = debounce(() => {
  console.log('窗口大小改变,执行回调');
}, 300);

window.addEventListener('resize', onResize);
  • timer:保留上一次定时器引用,若再次触发则清除。
  • immediate(可选):若为 true,则在第一次触发时立即调用一次,然后在等待期间不再触发;等待期结束后再次触发时会重复上述流程。

3.2 手写节流函数

/**
 * throttle(fn, wait, options = { leading: true, trailing: true })
 * @param {Function} fn - 需要节流包装的函数
 * @param {Number} wait - 最小时间间隔(ms)
 * @param {Object} options - { leading, trailing }
 * @return {Function} throttled 函数
 */
function throttle(fn, wait, options = {}) {
  let timer = null;
  let lastArgs, lastThis;
  let lastInvokeTime = 0;
  const { leading = true, trailing = true } = options;

  const invoke = (time) => {
    lastInvokeTime = time;
    fn.apply(lastThis, lastArgs);
    lastThis = lastArgs = null;
  };

  return function (...args) {
    const now = Date.now();
    if (!lastInvokeTime && !leading) {
      lastInvokeTime = now;
    }
    const remaining = wait - (now - lastInvokeTime);
    lastThis = this;
    lastArgs = args;

    if (remaining <= 0 || remaining > wait) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      invoke(now);
    } else if (!timer && trailing) {
      timer = setTimeout(() => {
        timer = null;
        invoke(Date.now());
      }, remaining);
    }
  };
}

// 用法示例:
const onScroll = throttle(() => {
  console.log('滚动事件处理,间隔至少 200ms');
}, 200);

window.addEventListener('scroll', onScroll);
  • lastInvokeTime:记录上次执行的时间戳,用于计算剩余冷却时间;
  • leading/trailing:控制是否在最开始最后一次触发时各执行一次;
  • 若触发频繁,则只在间隔结束时执行一次;若在等待期间再次触发符合 trailing,则在剩余时间后执行。

应用场景剖析

4.1 防抖常见场景

  1. 输入搜索联想

    const onInput = debounce((e) => {
      fetch(`/api/search?q=${e.target.value}`).then(/* ... */);
    }, 300);
    input.addEventListener('input', onInput);
    • 用户停止输入 300ms 后才发请求,避免每个字符都触发请求。
  2. 表单校验

    const validate = debounce((value) => {
      // 假设请求服务端校验用户名是否已存在
      fetch(`/api/check?username=${value}`).then(/* ... */);
    }, 500);
    usernameInput.addEventListener('input', (e) => validate(e.target.value));
  3. 窗口大小调整

    window.addEventListener('resize', debounce(() => {
      console.log('重新计算布局');
    }, 200));
  4. 按钮防重复点击(立即执行模式):

    const onClick = debounce(() => {
      submitForm();
    }, 1000, true); // 立即执行,后续 1s 内无效
    button.addEventListener('click', onClick);

4.2 节流常见场景

  1. 滚动监听

    window.addEventListener('scroll', throttle(() => {
      // 更新下拉加载或固定导航等逻辑
      updateHeader();
    }, 100));
  2. 鼠标移动追踪

    document.addEventListener('mousemove', throttle((e) => {
      console.log(`坐标:${e.clientX}, ${e.clientY}`);
    }, 50));
  3. 动画帧渲染(非 requestAnimationFrame):

    window.addEventListener('scroll', throttle(() => {
      window.requestAnimationFrame(() => {
        // 渲染 DOM 变化
      });
    }, 16)); // 接近 60FPS
  4. 表单快闪保存(每 2 秒保存一次表单内容):

    const onFormChange = throttle((data) => {
      saveDraft(data);
    }, 2000);
    form.addEventListener('input', (e) => onFormChange(getFormData()));

Lodash 中的 Debounce 与 Throttle

5.1 Lodash 安装与引入

npm install lodash --save
# 或者按需加载:
npm install lodash.debounce lodash.throttle --save

在代码中引入:

import debounce from 'lodash.debounce';
import throttle from 'lodash.throttle';

5.2 使用 _.debounce 示例

<template>
  <input v-model="query" placeholder="请输入关键字" />
</template>

<script>
import { ref, watch } from 'vue';
import debounce from 'lodash.debounce';

export default {
  setup() {
    const query = ref('');

    // 1. 手动包装一个防抖函数
    const fetchData = debounce((val) => {
      console.log('发送请求:', val);
      // 调用 API
    }, 300);

    // 2. 监听 query 变化并调用防抖函数
    watch(query, (newVal) => {
      fetchData(newVal);
    });

    return { query };
  }
};
</script>
  • Lodash 的 _.debounce 默认为 不立即执行,可传入第三个参数 { leading: true } 使其立即执行一次

    const fn = debounce(doSomething, 300, { leading: true, trailing: false });
  • 参数详解

    • leading:是否在开始时立即执行一次;
    • trailing:是否在延迟结束后再执行一次;
    • maxWait:指定最长等待时间,防止长时间不触发。

5.3 使用 _.throttle 示例

<template>
  <div @scroll="handleScroll" class="scroll-container">
    <!-- 滚动内容 -->
  </div>
</template>

<script>
import throttle from 'lodash.throttle';
import { onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    const handleScroll = throttle((event) => {
      console.log('滚动位置:', event.target.scrollTop);
    }, 100);

    onMounted(() => {
      const container = document.querySelector('.scroll-container');
      container.addEventListener('scroll', handleScroll);
    });
    onUnmounted(() => {
      const container = document.querySelector('.scroll-container');
      container.removeEventListener('scroll', handleScroll);
    });

    return {};
  }
};
</script>
  • 默认 Lodash 的 _.throttle立即执行一次(leading),并在等待结束后执行最后一次(trailing)。
  • 可通过第三个参数控制:

    const fn = throttle(fn, 100, { leading: false, trailing: true });

5.4 Lodash 参数详解与注意事项

  • wait:至少等待时间,单位毫秒。
  • options.leading:是否在最前面先执行一次(第一触发立即执行)。
  • options.trailing:是否在最后面再执行一次(等待期间最后一次触发会在结束时调用)。
  • options.maxWait(仅限 debounce):最长等待时间,确保在该时间后必定触发一次。

注意_.debounce_.throttle 返回的都是“可取消”的函数实例,带有 .cancel().flush() 方法,如:

const debouncedFn = debounce(fn, 300);
// 取消剩余等待
debouncedFn.cancel();
// 立即执行剩余等待
debouncedFn.flush();

RxJS 中的 DebounceTime 与 ThrottleTime

6.1 RxJS 安装与基础概念

RxJS(Reactive Extensions for JavaScript)是一套基于 Observable 可观察流的数据处理库,擅长处理异步事件流。其核心概念:

  • Observable:可观察对象,表示一串随时间推移的事件序列。
  • Operator:操作符,用于对 Observable 进行转换、过滤、节流、防抖等处理。
  • Subscription:订阅,允许你获取 Observable 数据并取消订阅。

安装 RxJS:

npm install rxjs --save

6.2 debounceTime 用法示例

<template>
  <input ref="searchInput" placeholder="输入后搜索" />
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue';
import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';

export default {
  setup() {
    const searchInput = ref(null);
    let subscription;

    onMounted(() => {
      // 1. 创建可观察流:input 的 keyup 事件
      const keyup$ = fromEvent(searchInput.value, 'keyup').pipe(
        // 2. 防抖:只在 500ms 内不再触发时发出最后一次值
        debounceTime(500),
        // 3. 获取输入值
        map((event) => event.target.value)
      );

      // 4. 订阅并处理搜索
      subscription = keyup$.subscribe((value) => {
        console.log('搜索:', value);
        // 调用 API ...
      });
    });

    onUnmounted(() => {
      subscription && subscription.unsubscribe();
    });

    return { searchInput };
  }
};
</script>
  • debounceTime(500):表示如果 500ms 内没有新的值到来,则将最后一个值发出;等同于防抖。
  • RxJS 的 mapfilter 等操作符可组合使用,适用复杂事件流场景。

6.3 throttleTime 用法示例

<template>
  <div ref="scrollContainer" class="scrollable">
    <!-- 滚动内容 -->
  </div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue';
import { fromEvent } from 'rxjs';
import { throttleTime, map } from 'rxjs/operators';

export default {
  setup() {
    const scrollContainer = ref(null);
    let subscription;

    onMounted(() => {
      const scroll$ = fromEvent(scrollContainer.value, 'scroll').pipe(
        // 每 200ms 最多发出一次滚动事件
        throttleTime(200),
        map((event) => event.target.scrollTop)
      );

      subscription = scroll$.subscribe((pos) => {
        console.log('滚动位置:', pos);
        // 更新虚拟列表、图表等
      });
    });

    onUnmounted(() => {
      subscription && subscription.unsubscribe();
    });

    return { scrollContainer };
  }
};
</script>

<style>
.scrollable {
  height: 300px;
  overflow-y: auto;
}
</style>
  • throttleTime(200):表示节流,每 200ms 最多发出一个值。
  • RxJS 中,还有 auditTimesampleTimedebounce 等多种相关操作符,可根据需求灵活选用。

6.4 对比与转换:debounce vs auditTime vs sampleTime

操作符特点场景示例
debounceTime只在事件停止指定时长后发出最后一次搜索防抖
throttleTime在指定时间窗口内只发出第一次或最后一次(取决于 config滚动节流
auditTime在窗口期结束后发出最新一次值等待窗口结束后再处理(如中断时)
sampleTime定时发出上一次值(即定时取样)定时抓取最新状态
debounce接收一个函数,只有当该函数返回的 Observable 发出值时,才发出源 Observable 上的值复杂场景链式防抖

示意图(以每次事件流到来时戳记发射点,| 表示事件到来):

事件流:|---|---|-----|---|----|
debounceTime(200ms):      ━━>X (只有最后一个发射)
throttleTime(200ms): |-->|-->|-->|...
auditTime(200ms):   |------>|------>|
sampleTime(200ms):  |----X----X----X|

vueuse/core 中的 useDebounce 与 useThrottle

7.1 vueuse 安装与引入

vueuse 是一套基于 Vue 3 Composition API 的工具函数集合,包含大量方便的 Hook。

npm install @vueuse/core --save

在组件中引入:

import { ref, watch } from 'vue';
import { useDebounce, useThrottle } from '@vueuse/core';

7.2 useDebounce 示例

<template>
  <input v-model="query" placeholder="输入后搜索" />
</template>

<script setup>
import { ref, watch } from 'vue';
import { useDebounce } from '@vueuse/core';

const query = ref('');

// 1. 创建一个防抖的响应式值
const debouncedQuery = useDebounce(query, 500);

// 2. 监听防抖后的值
watch(debouncedQuery, (val) => {
  console.log('防抖后搜索:', val);
  // 调用 API
});
</script>
  • useDebounce(source, delay):接收一个响应式引用或计算属性,返回一个“防抖后”的响应式引用(ref)。
  • query 在 500ms 内不再变化时,debouncedQuery 才更新。

7.3 useThrottle 示例

<template>
  <div ref="scrollContainer" class="scrollable">
    <!-- 滚动内容 -->
  </div>
</template>

<script setup>
import { ref, watch } from 'vue';
import { useThrottle } from '@vueuse/core';

const scrollTop = ref(0);
const scrollContainer = ref(null);

// 监听原始滚动
scrollContainer.value?.addEventListener('scroll', (e) => {
  scrollTop.value = e.target.scrollTop;
});

// 1. 节流后的响应式值
const throttledScroll = useThrottle(scrollTop, 200);

// 2. 监听节流后的值
watch(throttledScroll, (pos) => {
  console.log('节流后滚动位置:', pos);
  // 更新虚拟列表…
});
</script>

<style>
.scrollable {
  height: 300px;
  overflow-y: auto;
}
</style>
  • useThrottle(source, delay):将 scrollTop 节流后生成 throttledScroll
  • 监听 throttledScroll,确保回调每 200ms 最多执行一次。

7.4 与 Vue 响应式配合实战

<template>
  <textarea v-model="text" placeholder="大文本变化时防抖保存"></textarea>
</template>

<script setup>
import { ref, watch } from 'vue';
import { useDebounceFn } from '@vueuse/core'; // 直接防抖函数

const text = ref('');

// 1. 创建一个防抖保存函数
const saveDraft = useDebounceFn(() => {
  console.log('保存草稿:', text.value);
  // 本地存储或 API 调用
}, 1000);

// 2. 监听 text 每次变化,调用防抖保存
watch(text, () => {
  saveDraft();
});
</script>
  • useDebounceFn(fn, delay):创建一个防抖后的函数,与 watch 配合使用极其便捷。

性能对比与最佳实践

8.1 原生 vs Lodash vs RxJS vs vueuse

方式代码长度灵活性依赖大小场景适用性
原生手写 (Vanilla JS)最小,需自行管理细节完全可控无依赖简单场景,学习理解时使用
Lodash代码量少,API 直观灵活可配置\~70KB(全量),按需 \~4KB绝大多数场景,兼容旧项目
RxJS需学习 Observable 概念极高,可处理复杂流\~200KB(全部)复杂异步/事件流处理,如实时图表
vueuse/core代码极简,集成 Vue与 Vue 响应式天然结合\~20KBVue3 环境下推荐,简化代码量
  • 原生手写:适合想深入理解原理或无额外依赖需求,但需关注边界情况(如立即执行、取消、节流参数)。
  • Lodash:最常用、兼容性好,大多数 Web 项目适用;按需加载可避免打包臃肿。
  • RxJS:当事件流之间存在复杂依赖与转换(如“滚动时抛弃前一次节流结果”),RxJS 的组合操作符无可比拟。
  • vueuse/core:在 Vue3 项目中,useDebounceuseThrottleFnuseDebounceFn 等 Hook 封装简洁,与响应式系统天然兼容。

8.2 选择建议与组合使用

  • 简单场景(如输入防抖、滚动节流):首选 Lodash 或 vueuse。
  • 复杂事件流(多种事件链式处理、状态共享):考虑 RxJS。
  • Vue3 项目:推荐 vueuse,代码量少、易维护。
  • 需支持 IE 或旧项目:用 Lodash(兼容更好)。

图解与数据流示意

9.1 防抖流程图解

事件触发 (User input)
   │
   ├─ 立即清除前一定时器
   │
   ├─ 设置新定时器 (delay = 300ms)
   │
   └─ 延迟结束后执行回调
       ↓
    fn()

ASCII 图示:

|--t0--|--t1--|--t2--|====(no event for delay)====| fn()
  • t0, t1, t2 分别为多次触发时间点,中间间隔小于 delay,只有最后一次停止后才会 fn()

9.2 节流流程图解

事件触发流:|--A--B--C--D--E--F--...
interval = 100ms
执行点:  |_____A_____|_____B_____|_____C_____
  • 在 A 触发后立即执行(如果 leading: true),接下来的 B、C、D 触发在 100ms 内均被忽略;
  • 直到时间窗结束,若 trailing: true,则执行最后一次触发(如 F)。
时间轴:
0ms: A → fn()
30ms: B (忽略)
60ms: C (忽略)
100ms: (结束此窗) → 若 B/C 中有触发,则 fn() 再执行(取最后一次)

常见误区与调试技巧

  1. “防抖”与“节流”混用场景

    • 误区:把防抖当成节流使用,例如滚动事件用防抖会导致滚动结束后才触发回调,体验差。
    • 建议:滚动、鼠标移动等持续事件用节流;输入、搜索请求等用防抖。
  2. 立即执行陷阱

    • Lodash 默认 debounce 不会立即执行,但手写版本有 immediate 选项。使用不当会导致业务逻辑在第一次触发时就先行执行。
    • 调试技巧:在浏览器控制台加 console.time() / console.timeEnd() 查看实际调用时机。
  3. 定时器未清除导致内存泄漏

    • 在组件卸载时,若没有 .cancel()clearTimeout(),定时器仍旧存在,可能误触。
    • 建议:在 onBeforeUnmount 生命周期里手动清理。
  4. RxJS 组合过度使用

    • 误区:遇到一点点防抖需求就引入 RxJS,导致打包过大、学习成本高。
    • 建议:只在业务流程复杂(需多操作符组合)时才使用 RxJS,否则 Lodash 或 vueuse 更轻量。
  5. 节流丢失最新值

    • 若只用 leading: true, trailing: false,在一段高频触发期间,只有第一触发会执行,后续直到下一窗才可执行,但最终状态可能并非最新。
    • 建议:根据业务选择合适的 leading/trailing 选项。如果要执行最后一次,请设置 trailing: true

总结

本文从概念原理手写实现应用场景,到三大主流库(Lodash、RxJS、vueuse/core)的实践教程,全面拆解了 JavaScript 中的**防抖(Debounce)节流(Throttle)**技术。核心要点回顾:

  1. 防抖:将多次高频触发合并,最后一次停止后才执行,适用于搜索输入、校验、按钮防连点等场景。
  2. 节流:限定函数执行频率,使其在指定时间窗内匀速执行一次,适用于滚动、鼠标移动、窗口大小变化等。
  3. 手写实现:通过 setTimeout/clearTimeoutDate.now() 实现基本防抖与节流逻辑;
  4. Lodash:提供 _.debounce_.throttle,API 简洁易用,可选 leading/trailing,带有 .cancel().flush() 方法;
  5. RxJS:通过 debounceTimethrottleTime 等操作符,适合复杂事件流处理,需学习 Observable 概念;
  6. vueuse/core:Vue3 专用 Hook,如 useDebounceuseThrottleuseDebounceFn,与响应式系统天然兼容,一行代码解决常见场景;
  7. 最佳实践:根据场景选择最轻量方案,避免过度依赖,注意在组件卸载或业务切换时及时清理定时器/订阅,确保性能与稳定性。

掌握这些技术后,你可以有效避免页面卡顿、请求泛滥,提高前端性能与用户体验,为大型项目的稳定运行保驾护航。希望本文能帮助你系统梳理防抖与节流的方方面面,迅速融会贯通并在实际项目中灵活运用。

2024-09-06



// 安装 Rust 语言
// 打开命令行工具,运行以下命令:
// 如果你已经安装了 rustup,你可以直接安装 Rust 编译器:
rustup-init
 
// 安装成功后,验证安装:
rustc --version
 
// 创建一个新的 Rust 项目
// 在命令行中,导航到你希望创建项目的目录,然后运行:
cargo new hello_world
cd hello_world
 
// 编辑项目源代码
// 打开 `src/main.rs` 文件并替换内容为:
// 文件 `src/main.rs`
fn main() {
    println!("Hello, world!");
}
 
// 运行你的 Rust 程序
// 在项目目录中,运行:
cargo run
 
// 如果你想要构建发布版本的程序,运行:
cargo build --release
 
// 这将在 `target/release/` 目录下生成一个优化过的可执行文件。

这个例子展示了如何安装 Rust 语言,创建一个新的 Rust 项目,编辑和运行一个简单的 "Hello, world!" 程序。这个过程对于学习 Rust 语言和 Cargo 包管理器都是有帮助的。

2024-09-04

要在Linux环境下部署Wiki.js并配置公网地址远程访问,请按照以下步骤操作:

  1. 安装Node.js和MongoDB:



sudo apt update
sudo apt install -y nodejs
sudo apt install -y npm
sudo systemctl start mongodb
sudo systemctl enable mongodb
  1. 确保Node.js和npm的版本满足Wiki.js的要求。
  2. 创建一个新的Wiki.js项目:



npm install -g wiki.js
wiki.js init /path/to/wikijs
cd /path/to/wikijs
npm install
  1. 配置Wiki.js的配置文件config/local.yml,确保数据库连接正确。
  2. 启动Wiki.js服务:



npm start
  1. 配置服务器防火墙允许外部访问。如果你使用的是云服务,通常有一个安全组或防火墙设置,你需要添加规则来允许80(HTTP)或443(HTTPS,如果配置TLS)端口的流量。
  2. 如果你有一个域名,你可以配置一个域名DNS解析到你的服务器公网IP,并在Wiki.js的配置文件中设置HTTPS支持。
  3. 如果你没有域名,你可以使用服务器的公网IP直接访问Wiki.js。

以上步骤提供了一个基本的Wiki.js部署流程。根据你的具体需求和环境配置,可能需要额外的步骤,例如配置反向代理、设置HTTPS等。

2024-09-04

以下是一个简单的todoList清单的实现,使用了HTML、CSS和JavaScript。




<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List</title>
<style>
    #todos {
        width: 300px;
        margin: 0 auto;
    }
    #todos input {
        width: 100%;
        padding: 10px;
        margin-bottom: 10px;
    }
    #todos ul {
        list-style-type: square;
        padding-left: 20px;
    }
</style>
</head>
<body>
 
<div id="todos">
    <input type="text" id="todo-input" placeholder="Add a task">
    <button onclick="addTodo()">Add</button>
    <ul id="todo-list"></ul>
</div>
 
<script>
    function addTodo() {
        var input = document.getElementById('todo-input');
        var todoList = document.getElementById('todo-list');
        if (input.value) {
            var newItem = document.createElement('li');
            newItem.textContent = input.value;
            todoList.appendChild(newItem);
            input.value = '';
        }
    }
</script>
 
</body>
</html>

这个实现包括了一个文本输入框和一个按钮,用户可以在输入框中输入任务,点击按钮后将任务添加到清单中。每个任务是一个列表项(li),它们被添加到无序列表(ul)中。使用JavaScript函数addTodo处理添加任务的逻辑。

2024-09-04



<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>
 
<script>
import { ref, reactive } from 'vue';
 
export default {
  setup() {
    // 使用ref创建响应式的基本类型数据
    const count = ref(0);
 
    // 使用reactive创建响应式的对象
    const state = reactive({
      message: 'Hello Vue 3!'
    });
 
    // 定义一个方法用于增加count的值
    function increment() {
      count.value++;
    }
 
    // 暴露到模板,返回一个对象,这样模板就可以访问这些变量和函数
    return {
      count,
      state,
      increment
    };
  }
};
</script>

这个简单的Vue 3组件示例展示了如何使用setup函数、ref函数和reactive函数来创建响应式数据和方法。setup函数是Vue 3组件中一个新的组成部分,它在组件实例被创建时执行,允许我们使用Composition API。ref用于基本类型数据,而reactive用于复杂对象类型。通过setup函数返回的对象,我们可以在模板中访问这些响应式数据和方法。

2024-08-27

在JavaScript中,没有内置的直接调用打印功能的API。但是,你可以使用一些技巧来模拟打印功能。

一种方法是使用window.print(),这会打开浏览器的打印预览界面,允许用户直接打印当前页面的内容。




window.print();

如果你想打印页面上特定的部分,你可以创建一个新窗口或iframe,将需要打印的内容写入该窗口或iframe,然后调用print方法。




// 创建一个新的iframe
var printFrame = document.createElement('iframe');
printFrame.style.visibility = 'hidden';
document.body.appendChild(printFrame);
 
// 需要打印的内容
var content = document.getElementById('printSection').innerHTML;
 
// 写入内容到iframe
var frameDocument = printFrame.contentDocument || printFrame.contentWindow.document;
frameDocument.open();
frameDocument.write('<html><head><title>Print</title>');
frameDocument.write('</head><body>');
frameDocument.write(content);
frameDocument.write('</body></html>');
frameDocument.close();
 
// 调用打印功能
printFrame.contentWindow.print();
 
// 清理,移除iframe
document.body.removeChild(printFrame);

请注意,这种方法可能会受到浏览器安全设置和用户的打印设置的影响。

2024-08-27



import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
 
public class C919PhotoGather implements PageProcessor {
 
    private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);
 
    @Override
    public Site getSite() {
        return site;
    }
 
    @Override
    public void process(Page page) {
        // 假设页面上有用于下载的图片链接列表,我们通过jQuery选择器提取这些链接
        List<String> imageUrls = page.getHtml().$("div.g-content img").each(new Function<Element, String>() {
            @Override
            public String apply(Element element) {
                return element.attr("data-original");
            }
        });
 
        // 将提取的图片链接保存到页面对象中,供之后处理
        page.putField("imageUrls", imageUrls);
 
        // 提取下一页链接并加入爬虫的爬取队列
        String nextLink = page.getHtml().$("a.next").links().get();
        page.addTargetRequest(nextLink);
    }
 
    public static void main(String[] args) {
        Spider.create(new C919PhotoGather())
                .addUrl("http://photo.c-star.org/C919/")
                .thread(5)
                .run();
    }
}

这个代码实例展示了如何使用XxlCrawler库来实现一个简单的网页爬虫,该爬虫会从一个模拟的商飞C919相册页面开始,提取该页面上的图片链接,并且跟踪分页,爬取整个相册的所有图片。这个例子教会开发者如何使用XxlCrawler进行基本的网页爬取工作。

2024-08-27

字体反爬通常是指网站通过设置自定义字体(Web Fonts)来保护数据,使得爬虫难以从网页中直接解析出文本内容。这里提供一个基本的方法来处理这种情况:

  1. 分析网站的字体文件:网站通常会在其服务器上提供.woff.woff2等格式的字体文件。你需要下载这些字体文件。
  2. 使用fontTools库来转换字体文件:fontTools是Python中处理字体的库。
  3. 使用pyftsubset工具来提取特定字符的子集字体。
  4. 使用ReportLab库来创建一个简单的PDF,并使用提取出的字体。
  5. 使用OCR技术(如tesseract)来识别图片中的文本。

以下是使用fontToolspyftsubset的示例代码:




from fontTools.ttLib import TTFont
import pyftsubset
 
# 下载的字体文件路径
font_path = 'path_to_font.woff'
 
# 加载字体
font = TTFont(font_path)
 
# 提取子集字体
subset_font = pyftsubset.Subset(font_path, options=['--text=0123456789abcdefghijklmnopqrstuvwxyz'])
with open('subset_font.woff', 'wb') as out:
    out.write(subset_font.subset())
 
# 接下来,你可以使用OCR工具来处理网页截图,并尝试识别文本。
# 例如,使用tesseract识别图片中的文本:
# !tesseract subset_font.woff output -l eng --oem 3 --psm 6 nohup

请注意,这个方法可能不总是有效,因为有些网站采用了更高级的防爬策略,例如动态渲染、JavaScript渲染的内容等。此外,这种方法对于处理复杂的布局和样式可能会有挑战。对于复杂的网站,可能需要更高级的图像处理和机器学习技术来解决。

2024-08-27



<template>
  <div>
    <input v-model="publicKey" placeholder="请输入RSA公钥" />
    <button @click="encryptData">加密数据</button>
  </div>
</template>
 
<script>
import JSEncrypt from 'jsencrypt/bin/jsencrypt'
 
export default {
  data() {
    return {
      publicKey: '',
    }
  },
  methods: {
    encryptData() {
      const encrypt = new JSEncrypt()
      encrypt.setPublicKey(this.publicKey)
      const encrypted = encrypt.encrypt('需要加密的数据')
      console.log('加密后的数据:', encrypted)
      // 这里可以将加密后的数据发送到后端
    }
  }
}
</script>

这段代码展示了如何在Vue 3应用中使用jsencrypt库进行数据加密。用户在输入框中输入RSA公钥,点击按钮后,会触发encryptData方法,使用公钥对默认的字符串"需要加密的数据"进行加密,并在控制台输出加密后的结果。在实际应用中,你可以替换默认的数据,并将加密后的数据发送到后端进行处理。

2024-08-27



import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}
 
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('数据卡和数据表'),
      ),
      body: ListView(
        children: <Widget>[
          Card(
            child: Column(
              children: <Widget>[
                ListTile(
                  title: Text('张三'),
                  subtitle: Text('工程师'),
                ),
                ListTile(
                  title: Text('联系方式'),
                  subtitle: Text('电话:1234567890'),
                ),
                ListTile(
                  title: Text('地址'),
                  subtitle: Text('北京市朝阳区'),
                ),
              ],
            ),
          ),
          // 添加更多的Card...
        ],
      ),
    );
  }
}

这段代码创建了一个简单的Flutter应用,其中包含一个HomePage页面,该页面使用ListView来展示多个Card。每个Card包含了一个Column,其中包含了几个ListTile,用于展示不同类型的数据。这是学习Flutter布局和数据展示的一个很好的起点。