【Vue源码】变化侦测篇-Array的变化侦测

Array的变化侦测

Array所使用的侦测方法与Object不同,这是因为对于Object数据我们使用的时JS提供的对象原型上的方法Object.defineProperty,而这个方法是对象原型上的,所以Array无法使用这个方法,所以我们需要对Array型数据设计一套另外的变化侦测机制,当然设计思路是一样的,根本上还是在获取数据时收集依赖,数据变化时通知以来更新。

在哪里收集

Array型数据的依赖收集方式和Object数据的依赖收集方式相同,都是在getter中收集,虽然Array无法使用Object.defineproperty方法,但是平常在开发的时候,arr一般都是包含在data所返回的对象中的。

data(){
    return {
        arr:[1,2,3]
    }
}

我们说过,谁用到了数据,谁就是依赖,那么要用到arr这个数据,我们就要先从object数据对象中获取一下arr数据,而从object数据对象中获取arr数据自然就会触发arr的getter,所以我们就可以在getter中收集依赖

使Array型数据可观测

目前我们已经完成了一般可观测,即我们只知道Array型数据何时被读取了,而何时发生变化我们无法知道。

Object变化时通过setter来追踪的,只有某个数据发生了变化,就一定会触发这个数据上的setter,但是Array型数据没有setter怎么办?

我们知道,要想让Array型数据发生变化,那必然是操作了Array,而JS中提供的操作数组的方法就那么几种,我们可以重构这些方法,在不改变原有功能的前提下,我们为其新增一些其他功能

let arr = [1,2,3]
arr.push(4)
Array.prototype.newPush = function(val){
    console.log('arr被修改了')
    this.push(val)
}
arr.newPush(4)

在上面的例子中,我们针对数组的原生push方法定义一个新的newPUsh方法,这个newPush方法内部调用了原生push方法,这样就保证了新的newPush方法跟原生push方法具有相同的功能,而且我们还可以在newPush方法内部干一些别的事情,比如通知变化。

基于以上思路,在VUE中创建一个数组方法拦截器,他拦截在数组实例与Array.prototype之间,在拦截器内部重写操作数组的一些方法,当数组实例使用操作数组的方法时,其实使用的是拦截器中重写的方法,而不再使用Array.prototype上的原生方法。

Array原型中可以改变数组自身内容的方法有7个,分别是:push,pop,shift,unshift,splice,sort,reverse。那么拦截器如下:

//源码位置:/src/core/observer/array.js
const arrayProto = Array.prototyep 

export const arrayMethods = Object.create(arrayProto) //以数组原型为原型创建一个新对象,后续将新对象作为拦截器替换原Array原型

const methodsToPathc = {
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
}

/**
 * 定义新的方法
 */
methodsToPatch.forEach(function(method){
    const original = arrayProto(menthod) //缓存原生方法
    Object.property(arrayMethods,method,{ //Object.property的作用不只是可以检测到数据的读取与改变,还可以直接改变对象的值
        enumerable: false, //是否可以被枚举,因为是原型方法所以不可以在枚举的时候出现
        configurable: true, //是否可以被配置,因为要改变值所以是true
        writable: true,//是否可以赋值
        value: function mutator(...args){
            cosnt result = original.apply(this,args) //执行原生方法并将this指向当前所改变的方法
            return result
        }
    })
})

在上面代码中,首先创建了继承自Array原型的空对象arrayMethos,接着在arrayMethos上使用Object.defineproperty方法将那些可以改变自身的7个数组方法遍历逐个进行封装。最后,当我们使用push方法的时候,其实用的是函数mutator,而mutator函数内部执行了original函数,这个original函数就是Array.defineproperty上对应的原生方法。那么,接下来我们就可以在mutator函数中做一些其他的事,比如说发送通知。

接下来我们只需要把它挂载到数组实例与Array.prototype之间,这样拦截器才能够生效

//源码位置: /src/core/observer/index.js
export class Observer{
    constructor (value){
        this.value = value
        if(Array.isArray(value)){
            const augment = hasProto ? protoAugment : copyAugment
            augment(value, arrayMethods, arrayKeys)
        }else{
            this.walk(value)
        }
    }
}

//能力检测:判断__proto__是否可用,因为有的浏览器不支持该属性
export const hasProto = '__proto__' in {}

cosnt arrayKeys = Object.getOwnPropertyNames(arrayMethods) //枚举出对象内所有的key,包括不可枚举的属性

function protoAugment(target,src: Object,keys: any){
    target.__proto__ = src
}

function copyAugment(target: Object,src: Object,keys: array<string>){
    for(let i = 0, i < keys.length; i++){
        cosnt key = keys[i]
        def(target,key, src[key])
    }
}

上面代码首先判断了浏览器是否支持__proto__,如果支持,则调用protoAugment函数把value.proto = arrayMethods;如果不支持,则调用copyAugment函数把拦截器中重写的7个方法循环加入到value上。

拦截器生效后,当数组数据再发生变化时,我们就可以在拦截器中通知变化了,也就是我们就可以知道数组数据何时发生变化了

  • 27
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值