vue2.x核心源码深入浅出,我还是去看源码了( 二 )


arrayMethods定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/array.ts,这里写了一个拦截器methodsToPatch用来拦截数组原有的7个方法并进行重写,这就是为什么vue只能通过变异方法来改变data里的数组,而不能使用array[0]=newValue的原因 。官网文档说是由于 JavaScript 的限制 , Vue 不能检测数组和对象的变化 。其实就是因为defineProperty方法只能监控对象,不能监控数组 。
const arrayProto = Array.prototypeexport const arrayMethods = Object.create(arrayProto)const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']/** * Intercept mutating methods and emit events */methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method]def(arrayMethods, method, function mutator(...args) {const result = original.apply(this, args)const ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}if (inserted) ob.observeArray(inserted)// notify changeif (__DEV__) {ob.dep.notify({type: TriggerOpTypes.ARRAY_MUTATION,target: this,key: method})} else {ob.dep.notify()}return result})})【vue2.x核心源码深入浅出,我还是去看源码了】继续接上上面的observe类源码说 , 如果是属性是对象的话,则会对对象的每一个属性调用defineReactive() 。源码定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/index.ts#L131 。其重点是下面这些代码(L157~L213) 。
Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = https://www.huyubaike.com/biancheng/getter ? getter.call(obj) : valif (Dep.target) {if (__DEV__) {dep.depend({target: obj,type: TrackOpTypes.GET,key})} else {dep.depend()}if (childOb) {childOb.dep.depend()if (isArray(value)) {dependArray(value)}}}return isRef(value) && !shallow ? value.value : value},set: function reactiveSetter(newVal) {const value = getter ? getter.call(obj) : valif (!hasChanged(value, newVal)) {return}if (__DEV__ && customSetter) {customSetter()}if (setter) {setter.call(obj, newVal)} else if (getter) {// #7981: for accessor properties without setterreturn} else if (!shallow && isRef(value) && !isRef(newVal)) {value.value = newValreturn} else {val = newVal}childOb = !shallow && observe(newVal, false, mock)if (__DEV__) {dep.notify({type: TriggerOpTypes.SET,target: obj,key,newValue: newVal,oldValue: value})} else {dep.notify()}}})这就是双向绑定最核心的部分了,利用object.defineProperty()给每个属性添加getter和setter 。getter里主要是调用了Dep类的depend(),Dep类的源码定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/dep.ts#L21,depend()主要是调用了Dep.target.addDep(),可以看到Dep类下有个静态类型target,它就是一个DepTarget,这个DepTarget接口是定义在#L10 , 而Watcher类则是对DepTarget接口的实现,所以addDep()的定义需要在Watcher类中去寻找,源码定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/watcher.ts#L160,它又调用回dep.addSub(),其作用是将与当前属性相关的watcher实例之间的依赖关系存进一个叫subs的数组里,这个过程就是依赖收集 。那么问题来了:为什么这里要调过来调过去,直接调用不行么,这也是考点之一 , vue的双向绑定采用的是什么设计模式?看了这段代码,你就知道了,它采用的是发布者-订阅者模式,而不是观察者模式,因为Dep类就充当了发布者订阅者中的一个消息中转站,就是所谓的调度中心,这样发布者和订阅者就不受对方干扰 , 实现解耦 。
然后setter里主要是调用了dep.notify(),notify()源码定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/dep.ts#L51,其作用是遍历subs数组,然后通知到与当前属性相关的每个watcher实例,调用watcher.update()触发视图更新,这个过程叫做派发更新 。
export default class Dep {static target?: DepTarget | nullid: numbersubs: Array<DepTarget>constructor() {this.id = uid++this.subs = []}addSub(sub: DepTarget) {this.subs.push(sub)}removeSub(sub: DepTarget) {remove(this.subs, sub)}depend(info?: DebuggerEventExtraInfo) {if (Dep.target) {Dep.target.addDep(this)if (__DEV__ && info && Dep.target.onTrack) {Dep.target.onTrack({effect: Dep.target,...info})}}}notify(info?: DebuggerEventExtraInfo) {// stabilize the subscriber list firstconst subs = this.subs.slice()if (__DEV__ && !config.async) {// subs aren't sorted in scheduler if not running async// we need to sort them now to make sure they fire in correct// ordersubs.sort((a, b) => a.id - b.id)}for (let i = 0, l = subs.length; i < l; i++) {if (__DEV__ && info) {const sub = subs[i]sub.onTrigger &&sub.onTrigger({effect: subs[i],...info})}subs[i].update()}}}

推荐阅读