知識 分享 互助 懶人建站

    懶人建站專注于網頁素材下載,提供網站模板、網頁設計、ps素材、圖片素材等,服務于【個人站長】【網頁設計師】和【web開發從業者】的代碼素材與設計素材網站。

    懶人建站提供網頁素材下載、網站模板
    知識 分享 互助!

    javascript監聽數組變化

    作者:佳明媽 來源:oschina 2017-05-31 人氣:
    監聽javascript數組變化,從而改變數據是mvvm框架必備功能,下面嘗試使用不同的方法來實現js數組變化的監聽

    監聽javascript數組變化,從而改變數據是mvvm框架必備功能,下面嘗試使用不同的方法來實現js數組變化的監聽。

    javascript監聽數組變化思路

    1、定義變量arrayProto接收Array的prototype
    2、定義變量arrayMethods,通過Object.create()方法繼承arrayProto
    3、重新封裝數組中push,pop等常用方法。(這里我們只封裝我們需要監聽的數組的方法,并不做JavaScript原生Array中原型方法的重寫的這么一件暴力的事情)
    4、其他js數組變化監聽方法?

    js監聽數組變化實現方法

    這里我們首先需要確定的一件事情就是,我們只需要監聽我們需要監聽的數據數組的一個變更,而不是針對原生Array的一個重新封裝。

    其實代碼實現起來會比較簡短,這一部分代碼我會直接帶著注釋貼出來

    // 獲取Array原型
    const arrayProto = Array.prototype;
    const arrayMethods = Object.create(arrayProto);
    const newArrProto = [];
    [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ].forEach(method => {
      // 原生Array的原型方法
      let original = arrayMethods[method];
    
      // 將push,pop等方法重新封裝并定義在對象newArrProto的屬性上
      // 這里需要注意的是封裝好的方法是定義在newArrProto的屬性上而不是其原型屬性
      // newArrProto.__proto__ 沒有改變
      newArrProto[method] = function mutator() {
        console.log('監聽到數組的變化啦!');
    
        // 調用對應的原生方法并返回結果(新數組長度)
        return original.apply(this, arguments);
      }
    })
    
    let list = [1, 2];
    // 將我們要監聽的數組的原型指針指向上面定義的空數組對象
    // newArrProto的屬性上定義了我們封裝好的push,pop等方法
    list.__proto__ = newArrProto;
    list.push(3);  // 監聽到數組的變化啦! 3
    
    // 這里的list2沒有被重新定義原型指針,所以這里會正常執行原生Array上的原型方法
    let list2 = [1, 2];
    list2.push(3);  // 3

    目前為止我們已經實現了數組的監聽。從上面我們看出,當我們將需要監聽的數組的原型指針指向newArrProto對象上的時候(newArrProto的屬性上定義了我們封裝好的push,pop等方法)。這樣做的好處很明顯,不會污染到原生Array上的原型方法。

    其他js數組變化監聽方法

    1、分析實現的機制

    從上面我們看出,其實我們做了一件非常簡單的事情,首先我們將需要監聽的數組的原型指針指向newArrProto,然后它會執行原生Array中對應的原型方法,與此同時執行我們自己重新封裝的方法。

    那么問題來了,這種形式咋這么眼熟呢?這不就是我們見到的最多的繼承問題么?子類(newArrProto)和父類(Array)做的事情相似,卻又和父類做的事情不同。但是直接修改__proto__隱式原型指向總感覺心里怪怪的(因為我們可能看到的多的還是prototype),心里不(W)舒(T)服(F)。

    那么接下來的事情就是嘗試用繼承(常見的prototype)來實現數組的變更監聽

    2、利用ES6的extends實現

    首先這里我們會通過ES6的關鍵字extends實現繼承完成Array原型方法的重寫,咱總得先用另外一種方式來實現一下我們上面實現的功能,證明的確還有其他方法可以做到這件事。OK,廢話不多說,直接看代碼

    class NewArray extends Array {
      constructor(...args) {
        // 調用父類Array的constructor()
        super(...args)
      }
      push (...args) {
        console.log('監聽到數組的變化啦!');
    
        // 調用父類原型push方法
        return super.push(...args)
      }
      // ...
    }
    
    let list3 = [1, 2];
    
    let arr = new NewArray(...list3);
    console.log(arr)
    // (2) [1, 2]
    
    arr.push(3);
    // 監聽到數組的變化啦!
    console.log(arr)
    // (3) [1, 2, 3]

    3、ES5及以下的方法能實現js監聽數組變化嗎?

    OK,終于要回到我們常見的帶有prototype的繼承了,看看它究竟能不能也實現這件事情呢。這里我們直接上最優雅的繼承方式-寄生式組合繼承,看看能不能搞定這件事情。代碼如下

    /**
     * 寄生式繼承 繼承原型
     * 傳遞參數 subClass 子類
     * 傳遞參數 superClass 父類
     */
    function inheritObject(o){
      //聲明一個過渡函數
      function F(){}
      //過渡對象的原型繼承父對象
      F.prototype = o;
      return new F();
    }
    function inheritPrototype(subClass,superClass){
      //復制一份父類的原型副本保存在變量
      var p = inheritObject(superClass.prototype);
      //修正因為重寫子類原型導致子類的constructor指向父類
      p.constructor = subClass;
      //設置子類的原型
      subClass.prototype = p;
    }
    
    function ArrayOfMine (args) {
      Array.apply(this, args);
    }
    inheritPrototype(ArrayOfMine, Array);
    // 重寫父類Array的push,pop等方法
    ArrayOfMine.prototype.push = function () {
      console.log('監聽到數組的變化啦!');
      return Array.prototype.push.apply(this, arguments);
    }
    var list4 = [1, 2];
    var newList = new ArrayOfMine(list4);
    console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList));
    newList.push(3);
    console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList));

    目前我們這么看來,的的確確是利用寄生式組合繼承完成了一個類的繼承,那么console.log的結果又是如何的呢?是不是和我們預想的一樣呢,直接看圖說話吧
    ES5及以下的方法能實現js監聽數組變化嗎?

    我擦嘞,這特么什么鬼,教練,我們說好的,不是這個結果。這是典型的買家秀和賣家秀嗎?

    那么我們來追溯一下為什么會是這種情況,我們預想中的情況應該是這樣的

    newList => [1, 2]  newList.length => 2  Array.isArray(newList) => true

    push執行之后的理想結果

    newList => [1, 2, 3]  newList.length => 3  Array.isArray(newList) => true
    

    我們先拋棄Array的apply之后的結果,我們先用同樣的方式繼承我們自定義的父類Father,代碼如下

    function inheritObject(o){
      function F(){};
      F.prototype = o;
      return new F();
    }
    function inheritPrototype(subClass,superClass){
      var p = inheritObject(superClass.prototype);
      p.constructor = subClass;
      subClass.prototype = p;
    }
    
    function Father() {
      // 這里我們暫且就先假定參數只有一個
      this.args = arguments[0];
      return this.args;
    }
    Father.prototype.push = function () {
      this.args.push(arguments);
      console.log('我是父類方法');
    }
    function ArrayOfMine () {
      Father.apply(this, arguments);
    }
    inheritPrototype(ArrayOfMine, Father);
    // 重寫父類Array的push,pop等方法
    ArrayOfMine.prototype.push = function () {
      console.log('監聽到數組的變化啦!');
      return Father.prototype.push.apply(this, arguments);
    }
    var list4 = [1, 2];
    var newList = new ArrayOfMine(list4, 3);
    console.log(newList, newList instanceof Father);
    newList.push(3);
    console.log(newList, newList instanceof Father);

    結果如圖

    結果和我們之前預想的是一樣的,我們自己定義的類的話,這種做法是可以行的通的,那么問題就來了,為什么將父類改成Array就行不通了呢?

    為了搞清問題,查閱各種資料后。得出以下結論:
    因為Array構造函數執行時不會對傳進去的this做任何處理。不止Array,String,Number,Regexp,Object等等JS的內置類都不行。。這也是著名問題 ES5及以下的JS無法完美繼承數組 的來源,不清楚的小伙伴可以Google查查這個問題。那么,為什么不能完美繼承呢?

    1、數組有個響應式的length,一方面它會跟進你填入的元素的下表進行一個增長,另一方面如果你將它改小的話,它會直接將中間的元素也刪除掉

    var arr1 = [1];
    arr1[5] = 1;
    console.log(arr1.length === 6);  // true
    // 以及
    var arr2 = [1,2,3];
    arr2.length = 1
    console.log(arr2);
    // [1] 此時元素2,3被刪除了

    2、數組內部的[[class]] 屬性,這個屬性是我們用Array.isArray(someArray)和Object.prototype.String.call(someArray) 來判定someArray是否是數組的根源,而這又是內部引擎的實現,用任何JS方法都是無法改變的。而為啥要用這兩種方法進行數組的判定,相信大家從前面的代碼結果可以看出來,利用instanceof去判定是否為數組,結果是有問題的。

    因為數組其響應式的length屬性以及內部的[[class]]屬性我們無法再JS層面實現,這就導致我們無法去用任何一個對象來“模仿”一個數組,而我們想要創建一個ArrayOfMine繼承Array的話又必須直接用Array的構造函數,而上面我提到了Array構造函數執行時是不會對傳進去的this做任何處理,也就是說這樣你根本就不能繼承他。而利用__proto__隱式原型的指針變更卻能實現,因為他是一個非標準的屬性(已在ES6語言規范中標準化),詳請請點擊鏈接__proto__

    所以要實現最上面我們實現的功能,我們還是需要用到__proto__屬性。變更后代碼如下

    function inheritObject(o){
      function F(){}
      F.prototype = o;
      return new F();
    }
    function inheritPrototype(subClass,superClass){
      var p = inheritObject(superClass.prototype);
      p.constructor = subClass;
      subClass.prototype = p;
    }
    
    function ArrayOfMine () {
      var args = arguments
        , len = args.length
        , i = 0
        , args$1 = [];   // 保存所有arguments
      for (; i < len; i++) {
        // 判斷參數是否為數組,如果是則直接concat
        if (Array.isArray(args[i])) {
          args$1 = args$1.concat(args[i]);
        }
        // 如果不是數組,則直接push到
        else {
          args$1.push(args[i])
        }
      }
      // 接收Array.apply的返回值,剛接收的時候arr是一個Array
      var arr = Array.apply(null, args$1);
      // 將arr的__proto__屬性指向 ArrayOfMine的 prototype
      arr.__proto__ = ArrayOfMine.prototype;
      return arr;
    }
    inheritPrototype(ArrayOfMine, Array);
    // 重寫父類Array的push,pop等方法
    ArrayOfMine.prototype.push = function () {
      console.log('監聽到數組的變化啦!');
      return Array.prototype.push.apply(this, arguments);
    }
    var list4 = [1, 2];
    var newList = new ArrayOfMine(list4, 3);
    console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList));
    newList.push(4);
    console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList));

    結果如圖

    自此,我所知道幾種實現數組監聽的方法便得于實現了。

    js數組變化監聽總結

    總結以上幾點方案,完整的數組監聽代碼如下

    // Define Property  懶人建站 http://www.ifynpy.icu/
    function def (obj, key, val, enumerable) {
      Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        configurable: true,
        writable: true
      })
    }
    // observe array
    let arrayProto = Array.prototype;
    let arrayMethods = Object.create(arrayProto);
    [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ].forEach(method => {
      // 原始數組操作方法
      let original = arrayMethods[method];
      def(arrayMethods, method, function () {
        let arguments$1 = arguments;
        let i = arguments.length;
        let args = new Array(i);
    
        while (i--) {
          args[i] = arguments$1[i]
        }
        // 執行數組方法
        let result = original.apply(this, args);
        // 因 arrayMethods 是為了作為 Observer 中的 value 的原型或者直接作為屬性,所以此處的 this 一般就是指向 Observer 中的 value
        // 當然,還需要修改 Observer,使得其中的 value 有一個指向 Observer 自身的屬性,__ob__,以此將兩者關聯起來
        let ob = this.__ob__;
        // 存放新增數組元素
        let inserted;
        // 為add 進arry中的元素進行observe
        switch (method) {
          case 'push':
            inserted = args;
            break;
          case 'unshift':
            inserted = args;
            break;
          case 'splice':
            // 第三個參數開始才是新增元素
            inserted = args.slice(2);
            break;
        }
        if (inserted) {
          ob.observeArray(inserted);
        }
        // 通知數組變化
        ob.dep.notify();
        // 返回新數組長度
        return result;
      })
    
    })

    原文:https://my.oschina.net/qiangdada/blog/911252#comment-list
    github:https://github.com/xuqiang521/overwrite/tree/master/my-mvvm

    ↓ 查看全文

    javascript監聽數組變化由懶人建站收集整理,您可以自由傳播,請主動帶上本文鏈接

    懶人建站就是免費分享,覺得有用就多來支持一下,沒有能幫到您,懶人也只能表示遺憾,希望有一天能幫到您。

    javascript監聽數組變化-最新評論

    吉林省快三彩票开奖结果查询