Fuse.js:打造极致模糊搜索体验
Fuse.js 完全学习指南:JavaScript模糊搜索库
🎯 什么是 Fuse.js?
Fuse.js 是一个轻量、强大且无依赖的JavaScript模糊搜索库。它提供了简单而强大的模糊搜索功能,可以在任何 JavaScript 环境中使用,包括浏览器和 Node.js。
🌟 核心特点
- 轻量级:压缩后仅 ~12KB,无外部依赖
- 模糊搜索:支持拼写错误、部分匹配等容错搜索
- 高度可配置:提供丰富的配置选项控制搜索行为
- 多字段搜索:支持在对象的多个字段中搜索
- 权重系统:不同字段可以设置不同的搜索权重
- 高亮显示:支持搜索结果高亮显示
- 跨平台:支持浏览器、Node.js、Deno等环境
- TypeScript支持:提供完整的TypeScript类型定义
📦 安装与引入
NPM 安装
# 使用 npm
npm install fuse.js# 使用 yarn
yarn add fuse.js
引入方式
ES6 模块语法
import Fuse from 'fuse.js'
CommonJS
const Fuse = require('fuse.js')
直接 <script>
引入
<!-- 开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js"></script><!-- 生产版本(推荐) -->
<script src="https://cdn.jsdelivr.net/npm/fuse.js@7.1.0/dist/fuse.min.js"></script><!-- ES 模块版本 -->
<script type="module">import Fuse from 'https://cdn.jsdelivr.net/npm/fuse.js@7.1.0/dist/fuse.mjs'
</script>
Deno
// @deno-types="https://deno.land/x/fuse@v7.1.0/dist/fuse.d.ts"
import Fuse from 'https://deno.land/x/fuse@v7.1.0/dist/fuse.min.mjs'
🚀 基础使用
1. 简单数组搜索
// 创建简单的字符串数组
const books = ["老人与海","百年孤独", "哈利·波特","三体","1984","了不起的盖茨比"
]// 创建 Fuse 实例
const fuse = new Fuse(books)// 执行搜索
const result = fuse.search('盖茨比')
console.log(result)
/*
输出:
[{item: "了不起的盖茨比",refIndex: 5}
]
*/
2. 对象数组搜索
// 创建对象数组
const books = [{title: "老人与海",author: "海明威",year: 1952,genre: "小说"},{title: "百年孤独",author: "马尔克斯", year: 1967,genre: "魔幻现实主义"},{title: "三体",author: "刘慈欣",year: 2006,genre: "科幻"}
]// 指定搜索字段
const options = {keys: ['title', 'author']
}const fuse = new Fuse(books, options)// 搜索示例
const result = fuse.search('刘慈')
console.log(result)
/*
输出:
[{item: {title: "三体",author: "刘慈欣",year: 2006,genre: "科幻"},refIndex: 2}
]
*/
⚙️ 配置选项详解
基础搜索配置
const options = {// 是否按分数排序结果shouldSort: true,// 包含匹配结果的元数据includeMatches: true,// 包含分数信息includeScore: true,// 分数阈值(0.0 = 完全匹配,1.0 = 匹配任何内容)threshold: 0.3,// 搜索位置location: 0,// 搜索距离distance: 100,// 最小匹配字符长度minMatchCharLength: 1,// 是否查找所有匹配findAllMatches: false
}
搜索字段配置
const options = {keys: [// 简单字段'title','author',// 带权重的字段(权重范围 0-1){name: 'title',weight: 0.8 // 标题权重更高},{name: 'author', weight: 0.2 // 作者权重较低},// 嵌套字段'author.firstName','author.lastName',// 带权重的嵌套字段{name: 'tags',weight: 0.5}]
}
高级配置选项
const options = {// 忽略大小写isCaseSensitive: false,// 忽略变音符号ignoreLocation: false,// 忽略字段长度规范ignoreFieldNorm: false,// 字段长度规范影响因子fieldNormWeight: 1,// 搜索算法选择useExtendedSearch: false
}
🔍 搜索功能详解
1. 基础模糊搜索
const books = [{ title: "JavaScript高级程序设计" },{ title: "Vue.js实战" },{ title: "React技术栈开发" },{ title: "Node.js权威指南" }
]const fuse = new Fuse(books, {keys: ['title'],threshold: 0.4
})// 模糊搜索(允许拼写错误)
console.log(fuse.search('javscript')) // 找到 "JavaScript高级程序设计"
console.log(fuse.search('vue')) // 找到 "Vue.js实战"
console.log(fuse.search('react')) // 找到 "React技术栈开发"
2. 扩展搜索语法
const options = {keys: ['title', 'author'],useExtendedSearch: true
}const fuse = new Fuse(books, options)// 精确匹配
console.log(fuse.search('="Node.js"'))// 包含搜索
console.log(fuse.search("'js"))// 排除搜索
console.log(fuse.search('!vue'))// 前缀搜索
console.log(fuse.search('^java'))// 后缀搜索
console.log(fuse.search('.js$'))// 逻辑运算符
console.log(fuse.search('javascript | vue')) // OR
console.log(fuse.search('node !express')) // AND NOT
3. 获取搜索匹配详情
const options = {keys: ['title'],includeMatches: true,includeScore: true,threshold: 0.3
}const fuse = new Fuse(books, options)
const result = fuse.search('javascript')console.log(result)
/*
输出:
[{item: { title: "JavaScript高级程序设计" },refIndex: 0,score: 0.001,matches: [{indices: [[0, 9]],value: "JavaScript高级程序设计",key: "title"}]}
]
*/
🎨 实际应用案例
案例1:书籍搜索系统
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>图书搜索系统</title><script src="https://cdn.jsdelivr.net/npm/fuse.js@7.1.0/dist/fuse.min.js"></script><style>.search-container {max-width: 800px;margin: 50px auto;padding: 20px;}.search-box {width: 100%;padding: 12px;font-size: 16px;border: 2px solid #ddd;border-radius: 8px;margin-bottom: 20px;}.book-item {border: 1px solid #eee;border-radius: 8px;padding: 15px;margin-bottom: 10px;background: white;box-shadow: 0 2px 4px rgba(0,0,0,0.1);}.book-title {font-size: 18px;font-weight: bold;color: #333;margin-bottom: 5px;}.book-meta {color: #666;font-size: 14px;}.highlight {background-color: yellow;font-weight: bold;}.no-results {text-align: center;color: #999;padding: 50px;}</style>
</head>
<body><div class="search-container"><h1>📚 图书搜索系统</h1><input type="text" class="search-box" placeholder="搜索书名、作者或分类..." id="searchInput"><div id="results"></div></div><script>// 书籍数据const books = [{title: "JavaScript高级程序设计",author: "Nicholas C. Zakas",year: 2020,category: "编程",rating: 4.8,description: "JavaScript开发者必读经典,深入解析语言特性"},{title: "Vue.js实战",author: "梁灏",year: 2019,category: "前端",rating: 4.6,description: "Vue.js框架实战指南,从入门到精通"},{title: "三体",author: "刘慈欣",year: 2006,category: "科幻",rating: 4.9,description: "获得雨果奖的中国科幻小说代表作"},{title: "百年孤独",author: "加西亚·马尔克斯",year: 1967,category: "文学",rating: 4.7,description: "魔幻现实主义文学的巅峰之作"},{title: "设计模式",author: "Gang of Four",year: 1994,category: "编程",rating: 4.5,description: "软件工程中的设计模式经典教材"}]// 配置 Fuse.jsconst options = {keys: [{ name: 'title', weight: 0.4 },{ name: 'author', weight: 0.3 },{ name: 'category', weight: 0.2 },{ name: 'description', weight: 0.1 }],threshold: 0.3,includeMatches: true,includeScore: true}const fuse = new Fuse(books, options)// 高亮显示匹配文本function highlightMatches(text, matches) {if (!matches || matches.length === 0) return textlet highlighted = textconst indices = matches[0].indices// 从后往前替换,避免索引偏移for (let i = indices.length - 1; i >= 0; i--) {const [start, end] = indices[i]highlighted = highlighted.slice(0, start) + '<span class="highlight">' + highlighted.slice(start, end + 1) + '</span>' + highlighted.slice(end + 1)}return highlighted}// 渲染搜索结果function renderResults(results) {const resultsContainer = document.getElementById('results')if (results.length === 0) {resultsContainer.innerHTML = '<div class="no-results">没有找到相关书籍</div>'return}const html = results.map(result => {const book = result.itemconst matches = result.matches || []// 获取标题和作者的匹配高亮const titleMatch = matches.find(m => m.key === 'title')const authorMatch = matches.find(m => m.key === 'author')const highlightedTitle = titleMatch ? highlightMatches(book.title, [titleMatch]) : book.titleconst highlightedAuthor = authorMatch ? highlightMatches(book.author, [authorMatch]) : book.authorreturn `<div class="book-item"><div class="book-title">${highlightedTitle}</div><div class="book-meta">作者:${highlightedAuthor} | 年份:${book.year} | 分类:${book.category} | 评分:${book.rating}⭐</div><div style="margin-top: 8px; color: #666; font-size: 14px;">${book.description}</div></div>`}).join('')resultsContainer.innerHTML = html}// 搜索处理function handleSearch() {const query = document.getElementById('searchInput').value.trim()if (query === '') {renderResults(books.map((book, index) => ({ item: book, refIndex: index })))return}const results = fuse.search(query)renderResults(results)}// 绑定事件document.getElementById('searchInput').addEventListener('input', handleSearch)// 初始显示所有书籍renderResults(books.map((book, index) => ({ item: book, refIndex: index })))</script>
</body>
</html>
案例2:Vue.js 集成搜索组件
<template><div class="search-component"><div class="search-header"><inputv-model="searchQuery"@input="handleSearch"class="search-input"placeholder="搜索员工姓名、部门或技能..."autocomplete="off"><div class="search-stats" v-if="searchQuery">找到 {{ filteredEmployees.length }} 个结果</div></div><div class="filters"><button v-for="dept in departments" :key="dept"@click="filterByDepartment(dept)":class="['filter-btn', { active: selectedDepartment === dept }]">{{ dept }}</button></div><div class="results-container"><div v-for="employee in filteredEmployees" :key="employee.item.id"class="employee-card"><div class="employee-avatar">{{ employee.item.name.charAt(0) }}</div><div class="employee-info"><h3 v-html="highlightText(employee.item.name, employee.matches, 'name')"></h3><p class="department">{{ employee.item.department }}</p><div class="skills"><span v-for="skill in employee.item.skills" :key="skill"class="skill-tag"v-html="highlightText(skill, employee.matches, 'skills')"></span></div><div class="contact">📧 {{ employee.item.email }} | 📞 {{ employee.item.phone }}</div></div><div class="score" v-if="employee.score">匹配度: {{ Math.round((1 - employee.score) * 100) }}%</div></div></div></div>
</template><script>
import Fuse from 'fuse.js'export default {name: 'EmployeeSearch',data() {return {searchQuery: '',selectedDepartment: 'all',employees: [{id: 1,name: '张三',department: '技术部',email: 'zhangsan@company.com',phone: '138****1234',skills: ['JavaScript', 'Vue.js', 'Node.js', 'Python']},{id: 2,name: '李四',department: '设计部',email: 'lisi@company.com',phone: '139****5678',skills: ['Photoshop', 'Figma', 'UI设计', '用户体验']},{id: 3,name: '王五',department: '产品部',email: 'wangwu@company.com',phone: '137****9012',skills: ['产品规划', '数据分析', 'Axure', 'SQL']},{id: 4,name: '赵六',department: '技术部',email: 'zhaoliu@company.com',phone: '136****3456',skills: ['React', 'TypeScript', 'GraphQL', 'MongoDB']}],filteredEmployees: [],fuse: null}},computed: {departments() {const depts = ['all', ...new Set(this.employees.map(emp => emp.department))]return depts}},mounted() {this.initializeFuse()this.filteredEmployees = this.employees.map((emp, index) => ({item: emp,refIndex: index}))},methods: {initializeFuse() {const options = {keys: [{ name: 'name', weight: 0.4 },{ name: 'department', weight: 0.2 },{ name: 'skills', weight: 0.3 },{ name: 'email', weight: 0.1 }],threshold: 0.3,includeMatches: true,includeScore: true,minMatchCharLength: 1}this.fuse = new Fuse(this.employees, options)},handleSearch() {if (!this.searchQuery.trim()) {this.filteredEmployees = this.employees.map((emp, index) => ({item: emp,refIndex: index}))this.applyDepartmentFilter()return}const results = this.fuse.search(this.searchQuery)this.filteredEmployees = resultsthis.applyDepartmentFilter()},filterByDepartment(department) {this.selectedDepartment = departmentthis.applyDepartmentFilter()},applyDepartmentFilter() {if (this.selectedDepartment === 'all') returnthis.filteredEmployees = this.filteredEmployees.filter(emp => emp.item.department === this.selectedDepartment)},highlightText(text, matches, key) {if (!matches || !Array.isArray(text)) {const match = matches?.find(m => m.key === key)if (!match) return textlet highlighted = textconst indices = match.indicesfor (let i = indices.length - 1; i >= 0; i--) {const [start, end] = indices[i]highlighted = highlighted.slice(0, start) + '<mark>' + highlighted.slice(start, end + 1) + '</mark>' + highlighted.slice(end + 1)}return highlighted}return text}}
}
</script><style scoped>
.search-component {max-width: 1000px;margin: 0 auto;padding: 20px;
}.search-header {margin-bottom: 20px;
}.search-input {width: 100%;padding: 12px 16px;font-size: 16px;border: 2px solid #e1e5e9;border-radius: 8px;outline: none;transition: border-color 0.3s;
}.search-input:focus {border-color: #007bff;
}.search-stats {margin-top: 8px;color: #666;font-size: 14px;
}.filters {display: flex;gap: 10px;margin-bottom: 20px;
}.filter-btn {padding: 8px 16px;border: 1px solid #ddd;background: white;border-radius: 20px;cursor: pointer;transition: all 0.3s;
}.filter-btn:hover,
.filter-btn.active {background: #007bff;color: white;border-color: #007bff;
}.results-container {display: grid;gap: 15px;
}.employee-card {display: flex;align-items: center;padding: 20px;border: 1px solid #e1e5e9;border-radius: 8px;background: white;transition: box-shadow 0.3s;
}.employee-card:hover {box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}.employee-avatar {width: 50px;height: 50px;border-radius: 50%;background: #007bff;color: white;display: flex;align-items: center;justify-content: center;font-size: 18px;font-weight: bold;margin-right: 15px;
}.employee-info {flex: 1;
}.employee-info h3 {margin: 0 0 5px 0;font-size: 18px;
}.department {color: #666;margin: 0 0 10px 0;
}.skills {display: flex;flex-wrap: wrap;gap: 5px;margin-bottom: 10px;
}.skill-tag {background: #f8f9fa;padding: 4px 8px;border-radius: 12px;font-size: 12px;color: #495057;
}.contact {font-size: 14px;color: #666;
}.score {text-align: right;color: #28a745;font-weight: bold;
}/* 高亮样式 */
:deep(mark) {background-color: #ffeb3b;color: #333;padding: 1px 2px;border-radius: 2px;
}
</style>
案例3:React Hooks 集成
import React, { useState, useMemo, useCallback } from 'react'
import Fuse from 'fuse.js'// 自定义 Hook:useFuseSearch
function useFuseSearch(data, options) {const [query, setQuery] = useState('')const fuse = useMemo(() => {return new Fuse(data, options)}, [data, options])const results = useMemo(() => {if (!query.trim()) {return data.map((item, index) => ({ item, refIndex: index }))}return fuse.search(query)}, [fuse, query, data])return {query,setQuery,results,search: useCallback((searchQuery) => {return fuse.search(searchQuery)}, [fuse])}
}// 主搜索组件
function ProductSearch() {const products = [{id: 1,name: 'MacBook Pro 16寸',brand: 'Apple',category: '笔记本电脑',price: 16999,tags: ['高性能', '创作', '专业']},{id: 2,name: 'iPhone 14 Pro',brand: 'Apple', category: '智能手机',price: 7999,tags: ['摄影', '5G', '高端']},{id: 3,name: 'Surface Laptop 5',brand: 'Microsoft',category: '笔记本电脑',price: 8888,tags: ['轻薄', '办公', '便携']},{id: 4,name: 'Galaxy S23 Ultra',brand: 'Samsung',category: '智能手机',price: 9999,tags: ['大屏', 'S Pen', '摄影']}]const searchOptions = {keys: [{ name: 'name', weight: 0.4 },{ name: 'brand', weight: 0.3 },{ name: 'category', weight: 0.2 },{ name: 'tags', weight: 0.1 }],threshold: 0.3,includeMatches: true,includeScore: true}const { query, setQuery, results } = useFuseSearch(products, searchOptions)const [sortBy, setSortBy] = useState('relevance')// 排序结果const sortedResults = useMemo(() => {const sorted = [...results]switch (sortBy) {case 'price-asc':return sorted.sort((a, b) => a.item.price - b.item.price)case 'price-desc':return sorted.sort((a, b) => b.item.price - a.item.price)case 'name':return sorted.sort((a, b) => a.item.name.localeCompare(b.item.name))default:return sorted // 保持相关性排序}}, [results, sortBy])// 高亮匹配文本const highlightMatches = (text, matches) => {if (!matches || matches.length === 0) return textconst match = matches[0]if (!match) return textlet highlighted = textconst indices = match.indicesfor (let i = indices.length - 1; i >= 0; i--) {const [start, end] = indices[i]highlighted = highlighted.slice(0, start) + '<mark style="background: #ffeb3b; padding: 1px 2px; border-radius: 2px;">' + highlighted.slice(start, end + 1) + '</mark>' + highlighted.slice(end + 1)}return highlighted}return (<div style={{ maxWidth: '800px', margin: '50px auto', padding: '20px' }}><h1>🛍️ 产品搜索</h1>{/* 搜索栏 */}<div style={{ marginBottom: '20px' }}><inputtype="text"value={query}onChange={(e) => setQuery(e.target.value)}placeholder="搜索产品名称、品牌或分类..."style={{width: '100%',padding: '12px',fontSize: '16px',border: '2px solid #ddd',borderRadius: '8px',outline: 'none'}}/></div>{/* 排序选项 */}<div style={{ marginBottom: '20px', display: 'flex', gap: '10px', alignItems: 'center' }}><span>排序:</span><select value={sortBy} onChange={(e) => setSortBy(e.target.value)}style={{ padding: '5px 10px', borderRadius: '4px', border: '1px solid #ddd' }}><option value="relevance">相关性</option><option value="price-asc">价格从低到高</option><option value="price-desc">价格从高到低</option><option value="name">名称</option></select><span style={{ marginLeft: 'auto', color: '#666' }}>找到 {sortedResults.length} 个结果</span></div>{/* 搜索结果 */}<div style={{ display: 'grid', gap: '15px' }}>{sortedResults.map((result) => {const product = result.itemconst matches = result.matches || []const nameMatch = matches.find(m => m.key === 'name')const brandMatch = matches.find(m => m.key === 'brand')return (<divkey={product.id}style={{border: '1px solid #eee',borderRadius: '8px',padding: '20px',background: 'white',boxShadow: '0 2px 4px rgba(0,0,0,0.1)'}}><div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start' }}><div style={{ flex: 1 }}><h3 style={{ margin: '0 0 5px 0', fontSize: '18px' }}dangerouslySetInnerHTML={{__html: nameMatch ? highlightMatches(product.name, [nameMatch]) : product.name}}/><p style={{ margin: '0 0 10px 0', color: '#666' }}>品牌:<span dangerouslySetInnerHTML={{__html: brandMatch ? highlightMatches(product.brand, [brandMatch]) : product.brand}} />{' | '}分类:{product.category}</p><div style={{ display: 'flex', gap: '5px', marginBottom: '10px' }}>{product.tags.map(tag => (<spankey={tag}style={{background: '#f0f0f0',padding: '2px 8px',borderRadius: '12px',fontSize: '12px',color: '#666'}}>{tag}</span>))}</div></div><div style={{ textAlign: 'right' }}><div style={{ fontSize: '20px', fontWeight: 'bold', color: '#e74c3c' }}>¥{product.price.toLocaleString()}</div>{result.score && (<div style={{ fontSize: '12px', color: '#999', marginTop: '5px' }}>匹配度: {Math.round((1 - result.score) * 100)}%</div>)}</div></div></div>)})}</div>{sortedResults.length === 0 && query && (<div style={{ textAlign: 'center', padding: '50px', color: '#999' }}>没有找到匹配的产品</div>)}</div>)
}export default ProductSearch
🔧 性能优化建议
1. 大数据集处理
// 对于大数据集,考虑使用 Web Workers
class FuseWorker {constructor(data, options) {this.worker = new Worker('/fuse-worker.js')this.worker.postMessage({ type: 'init', data, options })}search(query) {return new Promise((resolve) => {this.worker.onmessage = (e) => {if (e.data.type === 'search-result') {resolve(e.data.results)}}this.worker.postMessage({ type: 'search', query })})}
}// fuse-worker.js
let fuseself.onmessage = function(e) {const { type, data, options, query } = e.dataif (type === 'init') {importScripts('https://cdn.jsdelivr.net/npm/fuse.js@7.1.0/dist/fuse.min.js')fuse = new Fuse(data, options)}if (type === 'search' && fuse) {const results = fuse.search(query)self.postMessage({ type: 'search-result', results })}
}
2. 防抖搜索
// 使用防抖避免频繁搜索
function useDebounce(value, delay) {const [debouncedValue, setDebouncedValue] = useState(value)useEffect(() => {const handler = setTimeout(() => {setDebouncedValue(value)}, delay)return () => {clearTimeout(handler)}}, [value, delay])return debouncedValue
}// 在组件中使用
function SearchComponent() {const [query, setQuery] = useState('')const debouncedQuery = useDebounce(query, 300)const results = useMemo(() => {if (!debouncedQuery) return []return fuse.search(debouncedQuery)}, [debouncedQuery])// ...
}
3. 结果缓存
// 简单的 LRU 缓存实现
class LRUCache {constructor(capacity) {this.capacity = capacitythis.cache = new Map()}get(key) {if (this.cache.has(key)) {const value = this.cache.get(key)this.cache.delete(key)this.cache.set(key, value)return value}return null}set(key, value) {if (this.cache.has(key)) {this.cache.delete(key)} else if (this.cache.size >= this.capacity) {const firstKey = this.cache.keys().next().valuethis.cache.delete(firstKey)}this.cache.set(key, value)}
}// 带缓存的搜索函数
const searchCache = new LRUCache(100)function cachedSearch(fuse, query) {const cached = searchCache.get(query)if (cached) return cachedconst results = fuse.search(query)searchCache.set(query, results)return results
}
🛠️ 高级功能
1. 自定义评分函数
const options = {// 自定义字段权重getFn: (obj, path) => {// 自定义字段获取逻辑if (path === 'fullName') {return `${obj.firstName} ${obj.lastName}`}return obj[path]},// 自定义排序函数sortFn: (a, b) => {// 优先显示完全匹配if (a.score === 0 && b.score !== 0) return -1if (a.score !== 0 && b.score === 0) return 1// 按分数排序return a.score - b.score}
}
2. 动态更新索引
class DynamicFuse {constructor(initialData, options) {this.options = optionsthis.data = [...initialData]this.fuse = new Fuse(this.data, options)}add(item) {this.data.push(item)this.rebuildIndex()}remove(predicate) {this.data = this.data.filter(item => !predicate(item))this.rebuildIndex()}update(predicate, updater) {this.data = this.data.map(item => predicate(item) ? updater(item) : item)this.rebuildIndex()}rebuildIndex() {this.fuse = new Fuse(this.data, this.options)}search(query) {return this.fuse.search(query)}
}
3. 多语言支持
// 多语言搜索配置
const multiLanguageOptions = {keys: ['title.zh','title.en', 'description.zh','description.en'],threshold: 0.3,// 自定义获取函数支持多语言getFn: (obj, path) => {const locale = getCurrentLocale() // 获取当前语言if (path.includes('.')) {const [field, lang] = path.split('.')return obj[field] && obj[field][lang]}return obj[path]}
}// 多语言数据示例
const multiLanguageData = [{id: 1,title: {zh: '苹果手机',en: 'Apple iPhone'},description: {zh: '高端智能手机',en: 'Premium smartphone'}}
]
📝 最佳实践
1. 合理设置阈值
// 不同场景的阈值建议
const thresholds = {exact: 0.0, // 精确匹配strict: 0.2, // 严格搜索moderate: 0.4, // 中等容错loose: 0.6, // 宽松搜索veryLoose: 0.8 // 非常宽松
}// 根据数据类型选择合适的阈值
const getThreshold = (dataType) => {switch (dataType) {case 'email':case 'id':return thresholds.exactcase 'name':case 'title':return thresholds.moderatecase 'description':case 'content':return thresholds.loosedefault:return thresholds.moderate}
}
2. 优化搜索键配置
// 智能权重分配
const getSearchKeys = (dataFields) => {return dataFields.map(field => {let weight = 0.1 // 默认权重// 根据字段类型分配权重if (field.includes('title') || field.includes('name')) {weight = 0.4} else if (field.includes('tag') || field.includes('category')) {weight = 0.3} else if (field.includes('description') || field.includes('content')) {weight = 0.2}return { name: field, weight }})
}
3. 错误处理
class SafeFuse {constructor(data, options) {try {this.fuse = new Fuse(data, options)this.isReady = true} catch (error) {console.error('Fuse.js 初始化失败:', error)this.isReady = false}}search(query) {if (!this.isReady) {console.warn('Fuse.js 未就绪,返回原始数据')return []}try {return this.fuse.search(query)} catch (error) {console.error('搜索出错:', error)return []}}
}
🎯 总结
Fuse.js 是一个功能强大且易用的模糊搜索库,适用于各种 JavaScript 应用场景:
✅ 简单易用:API设计简洁,上手快速
✅ 功能丰富:支持模糊搜索、权重配置、高亮显示等
✅ 高度可配置:提供30+个配置选项满足不同需求
✅ 性能优秀:轻量级设计,适合大数据集处理
✅ 跨平台支持:浏览器、Node.js、Deno 全平台兼容
✅ TypeScript友好:完整的类型定义支持
通过合理配置和优化,Fuse.js 可以为您的应用提供专业级的搜索体验,大大提升用户满意度。
开始您的智能搜索之旅吧! 🔍
💡 开发建议:在实际项目中,建议结合防抖、缓存、虚拟滚动等技术,构建高性能的搜索系统。
相关文章:
Fuse.js:打造极致模糊搜索体验
Fuse.js 完全学习指南:JavaScript模糊搜索库 🎯 什么是 Fuse.js? Fuse.js 是一个轻量、强大且无依赖的JavaScript模糊搜索库。它提供了简单而强大的模糊搜索功能,可以在任何 JavaScript 环境中使用,包括浏览器和 Nod…...
MyBatis分页插件(以PageHelper为例)与MySQL分页语法的关系
MyBatis分页插件(以PageHelper为例)与MySQL分页语法关系总结 MyBatis的分页插件(如PageHelper)底层实现依赖于数据库的分页语法。对于MySQL数据库来说,其分页逻辑最终会转化为LIMIT语句,下面展开详细说明&…...
CentOS 7.9 安装 宝塔面板
在 CentOS 7.9 上安装 宝塔面板(BT Panel) 的完整步骤如下: 1. 准备工作 系统要求: CentOS 7.x(推荐 7.9)内存 ≥ 1GB(建议 2GB)硬盘 ≥ 20GBroot 权限(需使用 root 用户…...
使用Redis作为缓存优化ElasticSearch读写性能
在现代数据密集型应用中,ElasticSearch凭借其强大的全文搜索能力成为许多系统的首选搜索引擎。然而,随着数据量和查询量的增长,ElasticSearch的读写性能可能会成为瓶颈。本文将详细介绍如何使用Redis作为缓存层来显著提升ElasticSearch的读写…...

项目交付后缺乏回顾和改进,如何持续优化
项目交付后缺乏回顾和改进可通过建立定期回顾机制、实施反馈闭环流程、开展持续学习和培训、运用数据驱动分析、培养持续改进文化来持续优化。 其中,实施反馈闭环流程尤其重要,它能够确保反馈信息得到有效传递、处理与追踪,形成良好的改进生态…...

从0开始学习R语言--Day15--非参数检验
非参数检验 如果在进行T检验去比较两组数据差异时,假如数据里存在异常值,会把数据之间的差异拉的很大,影响正常的判断。那么这个时候,我们可以尝试用非参数检验的方式来比较数据。 假设我们有A,B两筐苹果,…...
Linux或者Windows下PHP版本查看方法总结
确定当前服务器或本地环境中 PHP 的版本,可以通过以下几种方法进行操作: 1. 通过命令行检查 这是最直接且常用的方法,适用于本地开发环境或有 SSH 访问权限的服务器。 方法一:php -v 命令 php -v输出示例:PHP 8.1.12 (cli) (built: Oct 12 2023 12:34:56) (NTS) Copyri…...

EC2 实例详解:AWS 的云服务器怎么玩?☁️
弹性计算、灵活计费、全球可用,AWS EC2 全攻略 在 AWS 生态中,有两个核心服务是非常关键的,一个是 S3(对象存储),另一个就是我们今天的主角 —— Amazon EC2(Elastic Compute Cloud)…...

第三发 DSP 点击控制系统
背景 在第三方 DSP 上投放广告,需要根据 DP Link 的点击次数进行控制。比如当 DP Link 达到 5000 后,后续的点击将不能带来收益,但是后续的广告却要付出成本。因此需要建立一个 DP Link 池,当 DP Link 到达限制后,…...
saveOrUpdate 有个缺点,不会把值赋值为null,解决办法
针对 MyBatis-Plus 的 saveOrUpdate 方法无法将字段更新为 null 的问题,这是因为 MyBatis-Plus 默认会忽略 null 值字段。以下是几种解决方案: 方案 1:使用 update(entity, wrapper) 手动指定更新条件 原理:通过 UpdateWrapper …...
Java面试:企业协同SaaS中的技术挑战与解决方案
Java面试:企业协同SaaS中的技术挑战与解决方案 面试场景 在一家知名互联网大厂,面试官老王正在对一位应聘企业协同SaaS开发职位的程序员谢飞机进行技术面试。 第一轮提问:基础技术 老王:谢飞机,你好。首先…...

【笔记】在 MSYS2 MINGW64 环境中降级 NumPy 2.2.6 到 2.2.4
📝 在 MSYS2 MINGW64 环境中降级 NumPy 到 2.2.4 ✅ 目标说明 在 MSYS2 的 MINGW64 工具链环境中,将 NumPy 从 2.2.6 成功降级到 2.2.4。 🧰 环境信息 项目内容操作系统Windows 11MSYS2 终端类型MINGW64(默认终端)Py…...
前端限流如何实现,如何防止服务器过载
前端限流是一种控制请求频率的技术,旨在防止过多的请求在同一时间段内发送到服务器,避免造成服务器过载或触发反爬虫机制。实现前端限流的方法有很多,下面介绍几种常见的策略和技术: 1. 时间窗口算法 时间窗口算法是最简单的限流…...
基于大模型的慢性硬脑膜下血肿预测与诊疗系统技术方案
目录 一、术前阶段二、并发症风险预测三、手术方案制定四、麻醉方案生成五、术后护理与康复六、系统集成方案七、实验验证与统计分析八、健康教育与随访一、术前阶段 1. 数据预处理与特征提取 伪代码: # 输入:患者多模态影像数据(CT/MRI)、病史、生理指标 def preproce…...

vue入门环境搭建及demo运行
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 vue简介:第一步:安装node.jsnode简介第二步:安装vue.js第三步:安装vue-cli工具第四步 :安装webpack第五步…...
git checkout C1解释
git checkout C1 的意思是: 让 Git 切换到某个提交(commit)ID 为 C1 的状态。 🔍 更具体地说: C1 通常是一个 commit 的哈希值(可以是前几位,比如 6a3f9d2) git checkout C1 会让你…...

原始数据去哪找?分享15个免费官方网站
目录 一、找数据的免费官方网站 (一)国家级数据宝库:权威且全面 1.中国国家统计局 2.香港政府数据中心 3.OECD数据库 (二)企业情报中心:洞察商业本质 4.巨潮资讯 5.EDGAR数据库 6.天眼查/企查查&a…...

宝塔部署 Vue + NestJS 全栈项目
宝塔部署 Vue NestJS 全栈项目 前言一、Node.js版本管理器1、安装2、配置 二、NestJS项目管理(等同Node项目)1、Git安装2、拉取项目代码3、无法自动认证4、添加Node项目5、配置防火墙(两道) 三、Vue项目管理1、项目上传2、Nginx安…...

# [特殊字符] Unity UI 性能优化终极指南 — LayoutGroup篇
🎯 Unity UI 性能优化终极指南 — LayoutGroup篇 🧩 什么是 LayoutGroup? LayoutGroup 是一类用于 自动排列子节点 的UI组件。 代表组件: HorizontalLayoutGroupVerticalLayoutGroupGridLayoutGroup 可以搭配: Conte…...
Apache Iceberg 如何实现分布式 ACID 事务:深度解析大数据时代的可靠数据管理
引言:大数据时代的事务挑战 在大数据时代,传统数据库的 ACID 事务模型面临前所未有的挑战: 海量数据:PB 级数据难以使用传统事务机制管理多并发写入:数十甚至上百个作业同时写入同一数据集复杂分析:长时间运行的查询需要一致性视图混合负载:批处理和流处理同时访问相同…...
计算A图片所有颜色占B图片红色区域的百分比
import cv2 import numpy as npdef calculate_overlap_percentage(a_image_path, b_image_path):# 读取A组和B组图像a_image cv2.imread(a_image_path)b_image cv2.imread(b_image_path)# 将图像从BGR转为HSV色彩空间,便于颜色筛选a_hsv cv2.cvtColor(a_image, c…...

2024-2025-2-《移动机器人设计与实践》-复习资料-8……
2024-2025-2-《移动机器人设计与实践》-复习资料-1-7-CSDN博客 08 移动机器人基础编程 单选题(6题) 在ROS中,用于移动机器人速度控制的消息类型通常是? A. std_msgs/StringB. geometry_msgs/TwistC. sensor_msgs/ImageD. nav_ms…...

如何监测光伏系统中的电能质量问题?分布式光伏电能质量解决方案
根据光伏相关技术规范要求,通过10(6)kV~35kV电压等级并网的变流器类型分布式电源应在公共连接点装设满足GB/T 19862要求的A级电能质量监测装置。用于监测分布式光伏发出的电能的质量,指标包括谐波、电压偏差、电压不平衡度、电压波动和闪变等。 CET中电…...
电子电路:全面深入了解晶振的定义、作用及应用
本次了解重点: 1.压电效应的数学描述 2.生产工艺以及关键工序 3.电路设计部分如负阻原理和匹配电容计算 4.失效案例比如冷启动问题 5.新形态晶振技术引入5G和量子计算 6.温补晶振的补偿机制 7故障案例讲解-更换负载电池或增加预热电路 蓝牙音频断续-频偏导致 工控机死机-起振电…...
Day-15【选择与循环】选择结构-if语句
目录 一、if语句 (1)单分支选择结构 (2)双分支选择结构 (3)多分支选择结构 (4)if-else的嵌套使用 二、开关分支语句(switch) (1)…...
定时器时钟来源可以从输入捕获引脚输入
外部时钟模式 和 输入捕获。 核心结论: 外部时钟模式的输入引脚 ≠ 输入捕获功能的输入引脚(通常情况): 外部时钟模式有专用的输入引脚 (ETR) 和可选的替代输入通道(如TI1, TI2)。 输入捕获功能有自己的专…...

SPL 轻量级多源混算实践 4 - 查询 MongoDB
除了以上常见数据源,还有 NoSQL、MQ 等数据源,其中以 MongoDB 最为常用。我们用 SPL 连接 MongoDB 做计算。 导入 MongoDB 数据。 外部库 SPL 支持的多种数据源大概分两类,一类是像 RDB 有 JDBC 直接使用,或者文件等直接读取&a…...
星敏感器:卫星姿态测量的“星空导航仪”
星敏感器:卫星姿态测量的“星空导航仪” 1. 引言 在卫星、航天器和深空探测器的姿态控制系统中,星敏感器(Star Tracker) 是最精确的姿态测量设备之一。它通过识别恒星的位置,计算出航天器在惯性空间中的三轴姿态&…...
Cat.1与Cat.4区别及应用场景
Cat.1 和 Cat.4 都是 LTE(4G)网络中的终端设备类别,主要区别在于 数据传输速率、复杂度和功耗,这直接影响了它们的应用场景和成本。 以下是它们的主要区别: 数据传输速率 (核心区别): Cat.1 (Category 1)&…...
大宽带怎么做
我有10个G的宽带资源,怎样运行P2P才能将收益巨大化,主要有以下几种方式: 1.多设备汇聚模式:使用多台支持千兆网络的服务器或专用PCDN设备(如N1盒子),将10条宽带分别接入不同设备,通过…...