没有外部库的 JavaScript 自动完成

Posted

技术标签:

【中文标题】没有外部库的 JavaScript 自动完成【英文标题】:JavaScript autocomplete without external library 【发布时间】:2012-07-09 10:19:28 【问题描述】:

是否有不依赖于任何其他库的 javascript 自动完成库?

我没有使用 jQuery 或类似的东西,因为我正在制作一个需要保持额外光线的移动应用程序。

【问题讨论】:

webreference.com/programming/javascript/gr/column5/index.html @PeterAjtai 此链接已损坏。 【参考方案1】:

对于 2017 年以后需要简单解决方案的任何人,您可以使用 HTML5 的内置 <datalist> 标签,而不是依赖 JavaScript。

例子:

<datalist id="languages">
  <option value="HTML">
  <option value="CSS">
  <option value="JavaScript">
  <option value="Java">
  <option value="Ruby">
  <option value="php">
  <option value="Go">
  <option value="Erlang">
  <option value="Python">
  <option value="C">
  <option value="C#">
  <option value="C++">
</datalist>

<input type="text" list="languages">

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist

【讨论】:

想象这会比 JS 更快,因为它是一个浏览器 API。不过,我还没有做过任何基准测试,如果您有非常大的数据集,我建议您这样做。我只将它用于不需要任何花哨的小列表(200-300 项)(请注意,对于更复杂的设置,您需要使用 JavaScript 或两者的组合)。 截至 2021 年 4 月 20 日,上述示例也适用于 Safari。 谢谢,我已经通过删除有关 Safari 的行进行了相应的更新。【参考方案2】:

前天晚上我正在研究这个,我的解决方案原来是这里的ES6解决方案,像这样:

return this.data.filter((option) => 
    return option.first_name
        .toString()
        .toLowerCase()
        .indexOf(this.searchTerms.toLowerCase()) >= 0
)

但问题在于它不够健壮,无法处理过滤嵌套数据。你可以看到它正在过滤this.data,它的数据结构如下:

[
     first_name: 'Bob', ,
     first_name: 'Sally', ,
]

你可以看到它在搜索词和option.first_name都小写后基于this.searchTerms进行过滤,但是搜索option.user.first_name太死板了。我最初的尝试是通过字段进行过滤,例如:

this.field = 'user.first_name';

但这涉及到处理 this.field.split('.') 之类的野蛮自定义 JavaScript 并动态生成过滤器函数。

相反,我记得我之前使用过的一个旧库 fuse.js,它运行良好,因为它不仅可以处理我刚刚称为 this.field 的任意嵌套的情况,还可以处理基于定义的阈值。

在这里查看:https://fusejs.io/

[编辑说明]:我意识到这个问题是在寻找 no-external-library,但我想把这篇文章保留在这里,因为它提供了相邻的值。它并非旨在成为“解决方案”。

这是我目前的使用方式:

import Fuse from 'fuse.js';

const options = 
    threshold: 0.3,
    minMatchCharLength: 2,
    keys: [this.field],
;

const fuse = new Fuse(this.data, options);

this.filteredData = fuse.search(this.searchTerms);

您必须阅读 Fuse 文档才能更好地理解这一点,但从根本上讲,您可以看到使用要过滤的数据和选项创建了一个 new Fuse() 对象。

keys: [this.field] 部分很重要,因为您可以在此处传递要搜索的键,并且可以传递它们的数组。例如,您可以通过keys: ['user.first_name', 'user.friends.first_name'] 过滤this.data

我目前在 Vue JS 中使用它,所以我在实例 watch 函数中有上述逻辑,所以每次 this.searchTerms 更改时,该逻辑都会运行并更新 this.filteredData,它被放入我的下拉列表中我的自动完成组件。

另外我很抱歉,我刚刚意识到这个问题专门说没有外部库,但我还是会发布这个问题,因为每次我在 Vue JS 或 React JS 中进行 ES6 自动完成时都会发现这个问题。我认为有严格或松散的模糊匹配和支持任意嵌套的数据是非常有价值的。基于 Webpack 包分析器,fuse.js 压缩后为 4.1kb,因此它非常小,可以支持“所有”客户端过滤需求。

如果您使用外部库的能力有限,请考虑我的第一个示例代码。如果你的数据结构是静态的,它就可以工作,如果你想改变搜索的字段(即:如果你的对象总是扁平的),你可以轻松地将 option.first_name 更改为 option[this.field] 之类的东西。

您也可以更改要搜索的列表。试试这样的:

const radicalFilter = ( collection, field, searchTerms ) => 
    return collection.filter((option) => 
        return option[field]
            .toString()
            .toLowerCase()
            .indexOf(searchTerms.toLowerCase()) >= 0
    )


radicalFilter(
    collection: [ first_name: 'Bob' ,  first_name: 'Sally' ],
    field: 'first_name',
    searchTerms: 'bob',
)

根据我过去几年的经验,上面的示例非常有效。我已经用它过滤了react-table 组件中的 10,000 条记录,并且毫不费力。它不会创建任何额外的中间数据结构。它只是 Array.prototype.filter() 获取您的数组并返回一个包含匹配项的新数组。

【讨论】:

【参考方案3】:

Here 是一个基本的 JavaScript 示例,可以修改为自动完成控件:

var people = ['Steven', 'Sean', 'Stefan', 'Sam', 'Nathan'];

function matchPeople(input) 
  var reg = new RegExp(input.split('').join('\\w*').replace(/\W/, ""), 'i');
  return people.filter(function(person) 
    if (person.match(reg)) 
      return person;
    
  );


function changeInput(val) 
  var autoCompleteResult = matchPeople(val);
  document.getElementById("result").innerHTML = autoCompleteResult;
<input type="text" onkeyup="changeInput(this.value)">
<div id="result"></div>

【讨论】:

为了未来读者的利益,我为此实现了一个库 (github.com/uohzxela/fuzzy-autocomplete)。它确实依赖于 jQuery。然而,一个香草 JS 版本正在开发中。 你好!真的很喜欢你的回答! 2个问题。 1 - 缓存已编译的表达式是否重要或有必要?我问是因为这篇文章:dustindiaz.com/autocomplete-fuzzy-matching 2- 如何限制它从数组中返回 x 数量? .join('\\w*') 替换为.join('\\w*\\s*\\w*') 可以匹配来自不同单词的字母 遗憾的是,这是一个自动建议而不是自动完成。您无法选择建议。 这是一个与此代码示例松散相关的codepen。它使用 jQuery 过滤大约 500 个元素。表现不错。使用 vanilla js 时应该会表现得更好。【参考方案4】:

ES2016 特性:Array.prototype.includes 没有外部库。

function autoComplete(Arr, Input) 
    return Arr.filter(e =>e.toLowerCase().includes(Input.toLowerCase()));

Codepen Demo

【讨论】:

您好@ronaldtgi,IE 11 及更早版本不支持包含功能,根据“w3schools.com/jsref/jsref_includes.asp”中的信息,那么可以使用什么来代替包含? 嗨@AshishShah,或者,您可以使用 indexOf() 。例如function autoComplete(Arr, Input) return Arr.filter(e=&gt;e.toLowerCase().indexOf(Input.toLowerCase()) !== -1); 【参考方案5】:

我通过将 JSON 请求发送回服务器并使用 Python 代码执行自动完成来完成此操作。它有点慢,但它节省了发送大量数据。

【讨论】:

【参考方案6】:

自动完成脚本的核心是对术语字典的 ajax 调用。

我假设您的移动应用程序已经包含一个 ajax 功能,所以您最好还是从头开始编写自动完成功能?基本上你只需要一个输入标签、一个触发 ajax 调用的 keyup 事件处理程序和一个用于收集响应的 div。

[更新] 基于 cmets,来自 John Resig 博客的一些参考资料:

http://ejohn.org/blog/revised-javascript-dictionary-search/

http://ejohn.org/blog/jquery-livesearch/

【讨论】:

我打算从本地字典或远程 AJAX 调用或两者的混合中进行自动完成工作(因此已使用的单词会从远程调用添加到本地字典中)或。 .. 使用移动设备上的现有字典。我不认为数据源是自动完成脚本的责任,而是一个单独的问题。对我来说,自己制作最复杂的部分是一种搜索可能匹配数组的有效方法。我想象某种 b-tree 算法,但我更愿意使用经过深思熟虑的代码来完成这项任务,而不是自己编写。 好的,我明白了。如果使用远程调用,查询将被移交给数据源,而不是在 JavaScript 代码中处理。如果字典是本地的,那么我不知道(我想这取决于您的规则:匹配开头,匹配字符串的任何部分?)。我一直在使用 John Resig 博客中的几篇文章作为参考来做这些事情,例如 ejohn.org/blog/revised-javascript-dictionary-search 我已编辑我的答案以添加参考。我正在使用第二个进行本地过滤。 我之前正在阅读这些链接。第一个是依赖于 jQuery 的解决方案,所以我跳过了它。第二个非常有趣(以及导致它的两篇文章),但不幸的是不太适合我的需要,因为我必须匹配单词的开头,而不是整个单词。我可能会将它用作家庭烘焙解决方案的起点,因为他几乎完成了所有基准测试工作以找出什么是有效的,而且我可能会以最少的编辑而侥幸逃脱。感谢您的研究。 我最终使用第二个链接作为我自己的解决方案的起点,该解决方案非常有效 - 但目前可能缺乏功能。非常感谢!

以上是关于没有外部库的 JavaScript 自动完成的主要内容,如果未能解决你的问题,请参考以下文章

Telerik 使用 Javascript 的自动完成框 textchanged 事件

Atom JavaScript 自动完成

MySQL半同步的配置

没有 jQuery UI 的自动完成

使用 Javascript/KendoUI 自动完成渲染数据时出错 - 对象 #<Object> 没有方法“切片” - 如何解决?

如何判断何时未使用谷歌位置自动完成建议?