Advanced-Frontend/Daily-Interview-Question

第 79 题:input 搜索如何防抖,如何处理中文输入

Opened this issue · 19 comments

防抖就不说了,主要是这里提到的中文输入问题,其实看过elementui框架源码的童鞋都应该知道,elementui是通过compositionstart & compositionend做的中文输入处理:
相关代码:
<input
ref="input"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
>
这3个方法是原生的方法,这里简单介绍下,官方定义如下compositionstart 事件触发于一段文字的输入之前(类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词)
简单来说就是切换中文输入法时在打拼音时(此时input内还没有填入真正的内容),会首先触发compositionstart,然后每打一个拼音字母,触发compositionupdate,最后将输入好的中文填入input中时触发compositionend。触发compositionstart时,文本框会填入 “虚拟文本”(待确认文本),同时触发input事件;在触发compositionend时,就是填入实际内容后(已确认文本),所以这里如果不想触发input事件的话就得设置一个bool变量来控制。
image
根据上图可以看到

输入到input框触发input事件
失去焦点后内容有改变触发change事件
识别到你开始使用中文输入法触发**compositionstart 事件
未输入结束但还在输入中触发
compositionupdate **事件
输入完成(也就是我们回车或者选择了对应的文字插入到输入框的时刻)触发compositionend事件。

那么问题来了 使用这几个事件能做什么?
因为input组件常常跟form表单一起出现,需要做表单验证
image
为了解决中文输入法输入内容时还没将中文插入到输入框就验证的问题

我们希望中文输入完成以后才验证

165b363b21f8b414

这个在复制粘贴中文内容的时候不会触发事件

这个在复制粘贴中文内容的时候不会触发事件

有一个onpaste 事件

简易防抖

<div>
    <input type="text" id="ipt">
  </div>

  <script>
    let ipt = document.getElementById('ipt');
    let dbFun = debounce()
    ipt.addEventListener('keyup', function (e) {
      dbFun(e.target.value);
    })

    function debounce() {
      let timer;
      return function (value) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          console.log(value)
        }, 500);
      }
    }
  </script>

参考vue源码对v-model的实现中,对输入中文的处理

<input id='myinput'>
    function jeiliu(timeout){
        var timer;
        function input(e){
        if(e.target.composing){
            return ;
        }
        if(timer){
           clearTimeout(timer);
        }
        timer = setTimeout(() => {
               console.log(e.target.value);
               timer = null;
           }, timeout); 
        }
        return input;
    }

    function onCompositionStart(e){
        e.target.composing = true;
    }
    function onCompositionEnd(e){
        //console.log(e.target)
        e.target.composing = false;
        var event = document.createEvent('HTMLEvents');
        event.initEvent('input');
        e.target.dispatchEvent(event);
    }
    var input_dom = document.getElementById('myinput');
    input_dom.addEventListener('input',jeiliu(1000));
    input_dom.addEventListener('compositionstart',onCompositionStart);
    input_dom.addEventListener('compositionend',onCompositionEnd);

参考vue源码对v-model的实现中,对输入中文的处理

<input id='myinput'>
    function jeiliu(timeout){
        var timer;
        function input(e){
        if(e.target.composing){
            return ;
        }
        if(timer){
           clearTimeout(timer);
        }
        timer = setTimeout(() => {
               console.log(e.target.value);
               timer = null;
           }, timeout); 
        }
        return input;
    }

    function onCompositionStart(e){
        e.target.composing = true;
    }
    function onCompositionEnd(e){
        //console.log(e.target)
        e.target.composing = false;
        var event = document.createEvent('HTMLEvents');
        event.initEvent('input');
        e.target.dispatchEvent(event);
    }
    var input_dom = document.getElementById('myinput');
    input_dom.addEventListener('input',jeiliu(1000));
    input_dom.addEventListener('compositionstart',onCompositionStart);
    input_dom.addEventListener('compositionend',onCompositionEnd);

'jieliu',拼音没有那么棒棒hhh

简易防抖

<div>
    <input type="text" id="ipt">
  </div>

  <script>
    let ipt = document.getElementById('ipt');
    let dbFun = debounce()
    ipt.addEventListener('keyup', function (e) {
      dbFun(e.target.value);
    })

    function debounce() {
      let timer;
      return function (value) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          console.log(value)
        }, 500);
      }
    }
  </script>

我看怎么像节流

简易防抖

<div>
    <input type="text" id="ipt">
  </div>

  <script>
    let ipt = document.getElementById('ipt');
    let dbFun = debounce()
    ipt.addEventListener('keyup', function (e) {
      dbFun(e.target.value);
    })

    function debounce() {
      let timer;
      return function (value) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          console.log(value)
        }, 500);
      }
    }
  </script>

你这不对哦,不是防抖

function debounce(fn, wait) {
  var timer = null;
  return function () {
      var context = this
      var args = arguments
      if (timer) {
          clearTimeout(timer);
          timer = null;
      }
      timer = setTimeout(function () {
          fn.apply(context, args)
      }, wait)
  }
}

var fn = function () {
  console.log('boom')
}

setInterval(debounce(fn,500),1000) // 第一次在1500ms后触发,之后每1000ms触发一次

setInterval(debounce(fn,2000),1000) // 不会触发一次(我把函数防抖看出技能读条,如果读条没完成就用技能,便会失败而且重新读条)

简易防抖

<div>
    <input type="text" id="ipt">
  </div>

  <script>
    let ipt = document.getElementById('ipt');
    let dbFun = debounce()
    ipt.addEventListener('keyup', function (e) {
      dbFun(e.target.value);
    })

    function debounce() {
      let timer;
      return function (value) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          console.log(value)
        }, 500);
      }
    }
  </script>

你这不对哦,不是防抖

function debounce(fn, wait) {
  var timer = null;
  return function () {
      var context = this
      var args = arguments
      if (timer) {
          clearTimeout(timer);
          timer = null;
      }
      timer = setTimeout(function () {
          fn.apply(context, args)
      }, wait)
  }
}

var fn = function () {
  console.log('boom')
}

setInterval(debounce(fn,500),1000) // 第一次在1500ms后触发,之后每1000ms触发一次

setInterval(debounce(fn,2000),1000) // 不会触发一次(我把函数防抖看出技能读条,如果读条没完成就用技能,便会失败而且重新读条)

我怎么感觉你们两个没什么区别?

<!DOCTYPE html>
<html>
  <meta charset="utf-8" />

  <body>
    <div class="control">
      <label for="name">Input:</label>
      <input type="text" id="example" name="example" />
    </div>

    <div class="event-log">
      <label>Log:</label>
      <textarea
        readonly
        class="event-log-contents"
        rows="8"
        cols="25"
      ></textarea>
      <button class="clear-log">Clear</button>
    </div>
  </body>

  <script>
    const search = document.querySelector('input[type="text"]');
    const log = document.querySelector(".event-log-contents");
    const clearLog = document.querySelector(".clear-log");
    var isInputZh = false;

    clearLog.addEventListener("click", () => {
      log.textContent = "";
    });

    search.addEventListener(
      "compositionstart",
      function(e) {
        isInputZh = true;
      },
      false
    );
    search.addEventListener(
      "compositionend",
      function(e) {
        isInputZh = false;
        log.textContent += e.data;
      },
      false
    );
    search.addEventListener(
      "keyup",
      function(e) {
        if (!isInputZh) {
          log.textContent = e.target.value;
        }
        console.log("input: "+log.textContent);
      },
      false
    );
  </script>
</html>

我发现需要注意的是input事件是会在compositionend之前被触发:
Screen Shot 2020-03-06 at 2 27 51 AM
Screen Shot 2020-03-06 at 2 28 22 AM

有问题欢迎指正!

vue版本:
防抖就不说了

<template>
  <div id="app">
    <input v-model="value" @input="input" @compositionstart="compositionstart" @compositionend="compositionend" />
  </div>
</template>

<script>
```

export default {
  name: "App",
  components: {
  },
  data() {
    return {
      value: "",
      isInputZh: ""
    };
  },
  methods: {
    input() {
      if (this.isInputZh) return;
      console.log(this.value)
    },
    compositionstart() {
      this.isInputZh = true;
    },
    compositionend() {
      this.isInputZh = false;
    }
  }
};
</script>

<style>

</style>
`

简易防抖

<div>
    <input type="text" id="ipt">
  </div>

  <script>
    let ipt = document.getElementById('ipt');
    let dbFun = debounce()
    ipt.addEventListener('keyup', function (e) {
      dbFun(e.target.value);
    })

    function debounce() {
      let timer;
      return function (value) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          console.log(value)
        }, 500);
      }
    }
  </script>

你这不对哦,不是防抖

function debounce(fn, wait) {
  var timer = null;
  return function () {
      var context = this
      var args = arguments
      if (timer) {
          clearTimeout(timer);
          timer = null;
      }
      timer = setTimeout(function () {
          fn.apply(context, args)
      }, wait)
  }
}

var fn = function () {
  console.log('boom')
}

setInterval(debounce(fn,500),1000) // 第一次在1500ms后触发,之后每1000ms触发一次

setInterval(debounce(fn,2000),1000) // 不会触发一次(我把函数防抖看出技能读条,如果读条没完成就用技能,便会失败而且重新读条)

我怎么感觉你们两个没什么区别?

哈哈哈 就是没区别

function debounce(func,wait,immediate){
// wait 延迟执行毫秒数, immediate true 表立即执行,false 表非立即执行
let timeout;
return function () {
let context = this;
let args = arguments;

if (timeout) clearTimeout(timeout);
if (immediate) {
    let callNow = !timeout;
    timeout = setTimeout(() => {
        timeout = null;
    }, wait)
    if (callNow) func.apply(context, args)
}
else {
    timeout = setTimeout(() => {
        func.apply(context, args)
    }, wait);
}

}
}

// input 搜索如何防抖,如何处理中文输入
      // 事件触发后,n秒后再执行回调
      const ipt = document.querySelector(".input");

      /**
       * @param {fn} 需要防抖的函数
       * @param {time} 防抖的时间
       * */
      function debounce(fn, time) {
        let timer = 0;
        return function (args) {
          clearInterval(timer);
          timer = setTimeout(function () {
            fn.call(this, args);
          }, time);
        };
      }

      function ajax(val) {
        console.log("val", val);
      }

      const test = debounce(ajax, 1500);

      ipt.addEventListener("keyup", (e) => {
        test(e.target.value);
      });

贴个完整的

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="text" id="spell_input" />
    <script>
      function throttle(fn, delay) {
        let timer;
        let isFirstCall = true;
        return function (...args) {
          const ctx = this;
          if (isFirstCall) {
            isFirstCall = false;
            fn.apply(ctx, args);
            return;
          }
          if (timer) return;
          timer = setTimeout(() => {
            timer = null;
            fn.apply(ctx, args);
          }, delay);
        };
      }
      function input(e) {
        if (e.target.composing) {
          return;
        }
        console.log(e.target.value);
      }

      function onCompositionStart(e) {
        e.target.composing = true;
      }
      function onCompositionEnd(e) {
        const event = new CustomEvent("input");
        e.target.composing = false;
        e.target.dispatchEvent(event);
      }
      function onCompositionUpdate(e) {
        console.log(e.target.value);
      }
      const inputEle = document.getElementById("spell_input");
      inputEle.addEventListener("input", throttle(input, 1000));
      inputEle.addEventListener("compositionstart", onCompositionStart);
      inputEle.addEventListener("compositionend", onCompositionEnd);
      inputEle.addEventListener("compositionupdate", onCompositionUpdate);
    </script>
  </body>
</html>

贴个完整的

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="text" id="spell_input" />
    <script>
      function throttle(fn, delay) {
        let timer;
        let isFirstCall = true;
        return function (...args) {
          const ctx = this;
          if (isFirstCall) {
            isFirstCall = false;
            fn.apply(ctx, args);
            return;
          }
          if (timer) return;
          timer = setTimeout(() => {
            timer = null;
            fn.apply(ctx, args);
          }, delay);
        };
      }
      function input(e) {
        if (e.target.composing) {
          return;
        }
        console.log(e.target.value);
      }

      function onCompositionStart(e) {
        e.target.composing = true;
      }
      function onCompositionEnd(e) {
        const event = new CustomEvent("input");
        e.target.composing = false;
        e.target.dispatchEvent(event);
      }
      function onCompositionUpdate(e) {
        console.log(e.target.value);
      }
      const inputEle = document.getElementById("spell_input");
      inputEle.addEventListener("input", throttle(input, 1000));
      inputEle.addEventListener("compositionstart", onCompositionStart);
      inputEle.addEventListener("compositionend", onCompositionEnd);
      inputEle.addEventListener("compositionupdate", onCompositionUpdate);
    </script>
  </body>
</html>

throttle这个是节流

防抖

function debounce(fn, delay) {
  let timer
  return function() {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arguments)
    }, delay)
  }
}
  1. 防抖
function debounce(fn, delay) {
    let timer = null;
    return function(...args) {
        const ctx = this;
        if (timer) {
            clearTimeout(timer);
        } else {
            timer = setTimeout(()=>{
                fn.bind(ctx, ...args);
            }
            , delay);
        }
    }
}

const handlerChange=(ev)=>{console.log(ev.target.value)}
const handlerDebChange=debounce(handlerChange,1e3);

<input  onChange={handlerDebChange} />
  1. 通过 合成事件 来判断区分 非英文 输入法
    可以通过一个 flag isInCompos 来判断 字符真正输入到 input 或 textarea 中的时机
    compositionstart isInCompos=true
    compositionupdate isInCompos=true
    compositionend isInCompos=false