zoukankan      html  css  js  c++  java
  • 监听数组变化,实现响应

    举个例子,来说明下为什么监听不到数组变化

    var target ={
        val: 1
    }
    
    let _value = target.val
    Object.defineProperty(target,"val",{
        get:function(){
            return _value
        },
        set:function(newVal){
            _value = newVal
            console.log("setted")
        }
    })
    
    console.log(target.val)  // 1
    console.log(target.val = [])  // setted []
    console.log(target.val = [1,2,3])  // setted [1,2,3]
    console.log(target.val[1]=10)    // 10
    console.log(target.val.push(8))   // 4
    console.log(target.val.length=5)  // 5
    console.log(target.val)  // [1, 10, 3, 8, empty]

    从本例中可以看到,当taget.val被设置为数组后,想要对数组内部进行修改,通过数组索引去赋值 target.val[1]=10 ,不会触发set方法执行。

    那么该如何实现呢?

    我们先来了解下 Array.prototype.push.call() 相关知识,便于监听数组,实现响应做铺垫。

    Array.prototype.push.apply() 

    var a = [1,2,3];
    var b = [4,5,6];
    
    Array.prototype.push.apply(a, b);
    console.log(a) //[1,2,3,4,5,6]

    原生push方法接受的参数是一个参数列表,它不会自动把数组扩展成参数列表,使用apply的写法可以将数组型参数扩展成参数列表,这样合并两个数组就可以直接传数组参数了。

    注:合并数组为什么不直接使用Array.prototype.concat()呢?

    因为concat不会改变原数组,concat会返回新数组,而上面apply这种写法直接改变数组a。

    简单实现监听数组变化

    let arr = [1,2,3]
    console.log(arr)

    打印结果看来,数组的隐式原型上挂载了一些方法,如push()、pop()、shift()、unshift()、splice()、sort()、reverse()等。

    我们重新改写下方法

    let arr = [1,2,3]
    arr.__proto__ = {
        push: function() {
            // 这里的this指arr,即[1,2,3]
            return Array.prototype.push.apply(this, arguments)
        }
    }
    arr.push(6)
    console.log('修改后数组:',arr)

    在官方文档,所需监视的只有 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 7 种方法。我们可以遍历一下:

    只需要监听我们需要监听的数据数组的一个变更,而不是针对原生Array的一个重新封装。 

    会重写Array.prototype.push方法,并生成一个新的数组赋值给数据,这样数据双向绑定就会触发。

    首先让这个对象继承 Array 本身的所有属性,这样就不会影响到数组本身其他属性的使用,后面对相应的函数进行改写,也就是在原方法调用后去通知其它相关依赖这个属性发生了变化,这点和 Object.defineProperty 中 setter所做的事情几乎完全一样,唯一的区别是可以细化到用户到底做的是哪一种操作,以及数组的长度是否变化

    不会污染到原生Array上的原型方法。

    首先我们将需要监听的数组的原型指针指向newArrProto,然后它会执行原生Array中对应的原型方法,与此同时执行我们自己重新封装的方法。

    那么问题来了,这种形式咋这么眼熟呢?这不就是我们见到的最多的继承问题么?子类(newArrProto)和父类(Array)做的事情相似,却又和父类做的事情不同。但是直接修改__proto__隐式原型指向总感觉心里怪怪的(因为我们可能看到的多的还是prototype),心里不(W)舒(T)服(F)。

    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('监听到数组的变化啦!');
       // 更新视图,dep.notify()
    // 调用对应的原生方法并返回结果(新数组长度) return original.apply(this, arguments); } }) let list1 = [1, 2]; // 将我们要监听的数组的原型指针指向上面定义的空数组对象 // newArrProto的属性上定义了我们封装好的push,pop等方法 list1.__proto__ = newArrProto; list1.push(3); // 监听到数组的变化啦! 3 // list2没有被重新定义原型指针,所以这里会正常执行原生Array上的原型方法 let list2 = [1, 2]; list2.push(3); // 3

    Array.prototype.push.call() 

    var obj = {}
    console.log(Array.prototype.push.call(obj, 'a','b','c')) // 3
    console.log(obj) // {0: "a", 1: "b", 2: "c", length: 3}
    
    var obj1 = {
        length: 5
    }
    console.log(Array.prototype.push.call(obj1, 'a','b','c')) // 8
    console.log(obj1) // {5: "a", 6: "b", 7: "c", length: 8}
    
    var obj2 = {
        0: 'e',
        1: 'f',
        length: 7
    }
    console.log(Array.prototype.push.call(obj2, 'a','b','c')) // 10
    console.log(obj2) // {0: "e", 1: "f", 7: "a", 8: "b", 9: "c", length: 10}

    通过上面对比结果,我们可以看出:

    1)当对象中不含有length属性时,调用数组原型方法push,将对象转为类数组对象,新增属性的索引从0开始,且lengt指是新增属性的个数

    2)当对象中含有length属性时,新增属性的索引命名从length长度开始计算。 

    eg: obj1中length为5,新增加属性的索引分别为5、6、7;obj2中length为7,新增加属性的索引分别为7、8、9 

    Array.prototype.slice.call()

    Array.prototype.slice.call()方法是只能在类数组上起作用的,并不能同push()方法一样可以可以使对象转换为带有length属性的类数组对象。

    结论,当对象中没有length属性时,默认添加的新属性索引应为0,因为a中已经有为0的key了,于是将原来的banana覆盖了,便有了现在的结果。

  • 相关阅读:
    对称的二元变量和不对称的二元变量之间的区别是什么?
    数据挖掘中ID3算法实现zz
    深入浅出谈数据挖掘zz
    JS动态生成表格后 合并单元格
    JS 点击元素发ajax请求 打开一个新窗口
    JS实现拖动div层移动
    简单的表格json控件
    JS添加标签效果
    JS模板引擎
    如何使用seajs+jQuery构建中型项目
  • 原文地址:https://www.cnblogs.com/renzm0318/p/12301928.html
Copyright © 2011-2022 走看看