改變input的值不會觸發change事件的解決思路

一般來講,若是咱們本身經過 value 改變了 input 元素的值,咱們確定是知道的,可是在某些場景下,頁面上有別的邏輯在改變 input 的 value 值,咱們可能但願能在這個值發生變化的時候收到通知。因而咱們想到了 onchange 事件,然而咱們遺憾的發現,onchange 事件卻並不會被觸發,由於onchange事件觸發是有條件的。數組

onchange 事件的觸發條件

onchange 觸發須要三個步驟:函數

  1. input 元素得到焦點
  2. input 元素的值發生變化
  3. input 元素失去焦點

並且必須是點擊觸發的,這句話的意思是,儘管咱們能夠經過 input.focus() 使 input 元素得到焦點,能夠經過 input.value 改變值,能夠經過 input.blur() 使元素失去焦點,可是這並不會觸發 onchange 事件,能夠看下面的 demo 一探究竟:this

如何在改變 value 時得到通知

一種方法是使用 timer。經過 setInterval 的方式來不斷查看 value 值是否發生變化。這種方法雖然能夠 work,可是實時性不是很好,也比較浪費資源。因此有沒有第二種方法呢,答案是本文接下來要說的 -- 重寫 value 屬性 spa

其實這種操做盡管不推薦,可是仍是比較常見的。好比 Vue,經過重寫 Array 的 push,pop,concat 等方法,從而實現了只要對數組進行上述操做,就能觸發界面更新。那麼接下來,咱們來嘗試重寫 input 元素的這個 value 屬性,實現改變 value 值時,咱們能夠獲得通知。prototype

能夠判斷的是,value 絕對不是一個簡單的值,因此咱們先看看 value 是如何定義的:code

let input = document.querySelector(input);
console.log(Object.getOwnPropertyDescriptor(input, 'value'))

能夠看到打印出來是 undefined,因此 value 這個屬性是 input 元素繼承過來的,也就是位於 HTMLInputElement 的 prototype 上 -- input.constructor.prototype 或者 input.__proto__。因而將上面的代碼改一下:對象

console.log(Object.getOwnPropertyDescriptor(input.__proto__, 'value'))

打印結果以下:
uploading-image-256926.pngblog

因而咱們知道了 value 是掛在 input 元素原型對象上的一個 getter 和 setter 的屬性。那麼接下來,咱們只要改寫 setter,在 setter 中加入通知代碼,而後同時調用原來的 setter,就能夠檢測 value 的變化。代碼以下:繼承

let descriper = Object.getOwnPropertyDescriptor(input.__proto__, 'value');
// 取出原先的 get 和 set 函數
let getValue = descriper.get;
let setValue = descriper.set;

Object.defineProperty(
  input.__proto__, 
  'value', 
  {
    configurable: true,
    enumerable: true,
    get: function (){
      return getValue.call(this);
    },
    // 重寫 set 方法
    set: function (){
      console.log(arguments, this);
      // 加入通知代碼
      $(this).trigger('valChange');
      setValue.call(this, ...arguments);
    }
  })

下面是一個 demo,能夠看到,點擊 button 設置 value 時,能夠被看到控制檯打印出 value 發生變化。事件

總結

在經過 js 設置 value 時,沒法觸發 onchange 事件,這裏這個問題提供了另一種解決思路,基本思想上寫一個新的函數替換原有 value 屬性的 setter,在新函數中加入本身的邏輯後調用原有的 setter。(本文完)

相關文章
相關標籤/搜索