30天JS挑战(第六天)

124 阅读2分钟

第六天挑战(搜索框)

地址:javascript30.com/

所有内容均上传至gitee,答案不唯一,仅代表本人思路

中文详解:github.com/soyaine/Jav…

该详解是Soyaine及其团队整理编撰的,是对源代码的详解强烈推荐大家观看学习!!!!!!!

本人gitee:gitee.com/thats-all-r…

效果

image.png

  • 样式分析

    • 组件整体居中,分为两个部分,输入框和展示框,展示框有类3d效果,使用transform实现
  • 逻辑分析

    • 在输入框输入关键字后,实时检索符合关键字的城市
    • 检索出的城市高亮关键字
    • 检索后的城市根据后方数字大小进行降序排序

本人代码及思路分析

仅提供布局及逻辑代码

结构:

<div class="main">
  <input type="text">
  <p></p>
</div>

逻辑:

<script src="message.js">
  </script>
<script>
const input = document.querySelector('input')
const main = document.querySelector('.main')
let p = document.querySelectorAll('p')
console.log(p)
input.addEventListener('input', (e) => {
  p.forEach(item => main.removeChild(item))
  const city = list.filter((item) => {
    console.log(e.target.value)
    return item.city.toLowerCase().includes(e.target.value) || item.state.includes(e.target.value)
  }).sort((a,b) => {
    return b.population - a.population
  })
  console.log(city)
  for (let i = 0; i < city.length; i++) {
    const p = document.createElement('p')
    p.innerText = `${city[i].city} | ${city[i].state}  ${city[i].population}`
    main.appendChild(p)
  }
  p = document.querySelectorAll('p')
})
</script>

分析:

  • 整体思路: 获取城市数据,获取input元素监听value事件,根据input输入框内的值,通过filter函数进行检索,通过sort进行排序,并且创建p标签添加到main盒子中,在每次添加时,先删除上一次添加的p标签

  • 具体实现:

    • 首先通过querySelector选定需要监听和修改的元素
    • 这里在html结构中写了一个p标签是因为,当添加子元素后,前一次的子元素不会清空,会继续进行累加,所以在添加新的子元素时清空掉上一次添加的子元素,又因为这一步的操作优先级较高,当这一步进行的时候,并没有子元素被生成,所以这里先添加一个p标签作为前置条件进行启动
    • 其次这里我将数据放置在另一个js文件中,所以引用了script标签获取数据
    • 创建一个数组city,使用filter方法includes方法将数组内的元素根据输入框内的值进行过滤
    • 通过sort方法将数据进行排序
    • 循环为数组内的每一个对象都创建一个p标签作为子元素添加到页面中去
    • 最后抓取所有的p标签,确保在下次进行输入操作的时候可以先被清除掉
  • 弊端分析(与官方方法对比):

    • 由于在大规模数据(峰值数据1000条) 中使用了for循环,所以导致该功能性能开销极大(内存占用244MB)
    • 频繁的对元素进行删除和添加操作
    • 需要在结构上单独使用一个p标签作为前置条件
  • 这种写法没有优化,也仅仅是一个快速完成需求的写法,建议仅当个乐子看看,极其不建议应用在实际中

官方代码

官方代码仅代表该案例原作者思路,不唯一

结构

<form class="search-form">
  <input type="text" class="search" placeholder="City or State">
  <ul class="suggestions">
    <li>Filter for a city</li>
    <li>or a state</li>
  </ul>
</form>

逻辑

const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json';
​
const cities = [];
fetch(endpoint)
  .then(blob => blob.json())
  .then(data => cities.push(...data));
​
function findMatches(wordToMatch, cities) {
  return cities.filter(place => {
    // here we need to figure out if the city or state matches what was searched
    console.log(1)
    const regex = new RegExp(wordToMatch, 'gi');
    return place.city.match(regex) || place.state.match(regex)
  });
}
​
function numberWithCommas(x) {
  return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
​
function displayMatches() {
  const matchArray = findMatches(this.value, cities);
  const html = matchArray.map(place => {
    const regex = new RegExp(this.value, 'gi');
    const cityName = place.city.replace(regex, `<span class="hl">${this.value}</span>`);
    const stateName = place.state.replace(regex, `<span class="hl">${this.value}</span>`);
    return `
      <li>
        <span class="name">${cityName}, ${stateName}</span>
        <span class="population">${numberWithCommas(place.population)}</span>
      </li>
    `;
  }).join('');
  suggestions.innerHTML = html;
}
​
const searchInput = document.querySelector('.search');
const suggestions = document.querySelector('.suggestions');
​
searchInput.addEventListener('change', displayMatches);
searchInput.addEventListener('keyup', displayMatches);

分析

仅代表本人对该代码的分析

建议直接去看Soyaine的中文详解 建议直接去看Soyaine的中文详解 建议直接去看Soyaine的中文详解

  • 整体思路: 十分巧妙的通过正则表达式对数组内的元素进行过滤,并且通过字符串模板+innerHTML结构的方式的覆盖法解决了多次搜索元素累加的问题

  • 具体实现:

    • fetch:fetch方法获取从链接地址获取城市信息,通过 .then操作,在确保信息获取成功后再执行赋值操作

    • tfindMatches:数据筛选函数,对实际操作的数组cities使用filter过滤正则的方法,对输入条件wordToMatch进行全局且不区分大小写的过滤

    • numberWithCommas:将数字转换成字符串,并且每三位数加一个分隔符

    • displayMatches:渲染函数,首先通过调用findMatches获取到符合条件的数组,并且将这个数组进行二次正则过滤,创建regex正则条件,条件为输入框内的值,并且根据过滤条件,将原来的城市名字中所包含的符合条件的值添加span标签,进行高亮处理

      //假设第一次过滤后符合条件的值为   abc
      //经过第二次匹配过滤,正则条件为 b
      //经过第二次过滤后,原来的值变为   a<span class='h1'>b</span>c
      const cityName = place.city.replace(regex, `<span class="hl">${this.value}</span>`);
      const stateName = place.state.replace(regex, `<span class="hl">${this.value}</span>`);
      
    • 返回字符串模板,将其赋值给其父元素

    • 获取元素,监听事件

  • 优点:

    • fetch函数避免将数据抓取到本地
    • 正则表达式避免了对数组大量进行操作
    • map+字符串模板避免了循环的使用,并且避免了多次搜索造成的元素累加
    • 节省了性能开销