vue源码浅析(对象和数组依赖处理)

//先看一例子

import Vue from './instance/vue'
let v = new Vue({
  data:{
    a:1,
    b:{
      c:3
    }
  }
})
console.log(v.b.c);//3
  
v.$watch("b.c",(newVal,oldVal)=>console.log('newVal',newVal,'oldVal',oldVal,'\n')); //1秒后 newVal { d: [Getter/Setter] } oldVal 3 
 
v.$watch("b.c.d",(newVal,oldVal)=>console.log('newVal',newVal,'oldVal',oldVal,'\n')) //2秒后 newVal 5 oldVal 4
 
 
setTimeout(()=>{
  v.b.c = {d:4};
},1000)
 
setTimeout(()=>{
  v.b.c.d = 5
},2000)

click here 可运行的 example

(以下都是简化的代码,实际的源码复杂许多)首先是让vue本身对data引用,以及添加$watch方法:

import Watcher from '../watcher'
import {observe} from "../observer"
export default class Vue {
  constructor (options={}) {
    this.$options=options
    let data = this._data=this.$options.data
 
//shallow iterate through keys to give vue direct reference
    Object.keys(data).forEach(key=>this._proxy(key))
 
//deep iterate through all keys to proxy all keys
    observe(data,this)
  }
 
  $watch(expOrFn, cb, options){
    new Watcher(this, expOrFn, cb)
  }
  _proxy(key) {
    var self = this
     
    Object.defineProperty(self, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter () {
        return self._data[key]
      },
      set: function proxySetter (val) {
        self._data[key] = val
      }
    })
  }
 
}

接着实现深度代理函数observe,递归代理所有属性,从而监测所有属性的变化:

import {def} from "../util"
import Dep from "./dep"
export default class  Observer{
  constructor(value) {
    this.value = value
    this.walk(value)
  }
  //deep observe each key
  walk(value){
    Object.keys(value).forEach(key=>this.convert(key,value[key]))
  }
  convert(key, val){
    defineReactive(this.value, key, val)
  }
}
export function defineReactive (obj, key, val) {
  var dep = new Dep()
 
//recursive observe all keys
  var childOb = observe(val)
 
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: ()=>{
      // check dependency to subscribe
      if(Dep.target){
        dep.addSub(Dep.target)
      }
      return val
    },
    set:newVal=> {
      var value =  val
      if (newVal === value) {
        return
      }
      val = newVal
//check change to reobserve and publish all dependencies
      childOb = observe(newVal)
      dep.notify()
    }
  })
}
 
export function observe (value, vm) {
  if (!value || typeof value !== 'object') {
    return
  }
  return new Observer(value)
}

实现依赖处理:

// import { toArray } from '../util/index'
let uid = 0
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 *
 * @constructor
 */
export default function Dep () {
  this.id = uid++
  this.subs = []
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
/**
 * Add a directive subscriber.
 *
 * @param {Directive} sub
 */
Dep.prototype.addSub = function (sub) {
  this.subs.push(sub)
}
/**
 * Remove a directive subscriber.
 *
 * @param {Directive} sub
 */
Dep.prototype.removeSub = function (sub) {
  // this.subs.$remove(sub)
}
/**
 * Add self as a dependency to the target watcher.
 */
Dep.prototype.depend = function () {
  Dep.target.addDep(this)
}
/**
 * Notify all subscribers of a new value.
 */
Dep.prototype.notify = function () {
  // stablize the subscriber list first
  var subs = (this.subs)
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

实现watch:

import Dep from './observer/dep'
export default class Watcher {
  constructor(vm, expOrFn, cb) {
    this.cb = cb
    this.vm = vm
    this.expOrFn = expOrFn
    this.value = this.get()
  }
  update(){
    this.run()
  }
  run(){
    const  value = this.get()
    if(value !==this.value){ //check isEqual if not then callback
      this.cb.call(this.vm,value,this.value)
      this.value = value
    }
  }
  addDep(dep){
    dep.addSub(this)
  }
  get(){
 
    this.beforeGet();
    console.log('\n','watch get');
    //fn or expr
     var res = this.vm._data,
            key = [];
      console.log('expOrFn',this.expOrFn)
 
//to watch instance like a.b.c
    if(typeof this.expOrFn == 'string'){
      this.expOrFn.split('.').forEach(key=> {
        res = res[key] // each will invoke getter, since Dep.target is true ,this will surely add this into dep
      })
    }
 
    this.afterGet();
    return res
  }
}
 
 
/**
 * Prepare for dependency collection.
 */
Watcher.prototype.beforeGet = function () {
  Dep.target = this;
};
/**
 * Clean up for dependency collection.
 */
Watcher.prototype.afterGet = function () {
  Dep.target = null;
};

以上示例是可以无依赖直接运行的。

接下来是vue的源码片段;

一般对象的处理可以直接代理defineProperty就可以了,不过对于Array的各种操作就不管用了,所以vue进行了基本数组方法代理:

function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}
 
function indexOf (arr, obj) {
  var i = arr.length
  while (i--) {
    if (arr[i] === obj) return i
  }
  return -1
}
 
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
 
;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  var original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    var i = arguments.length
    var args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    var result = original.apply(this, args)
    var ob = this.__ob__
    var inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
//same as object change
//check change to reobserve and publish all dependencies
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})
 
 
//es5 defineProperty对于数组的某些操作和属性(如:length)变化代理有问题,所以需要使用定义的方法操作
具体原因看http://www.cnblogs.com/ziyunfei/archive/2012/11/30/2795744.html和
http://wiki.jikexueyuan.com/project/vue-js/practices.html
 
def(
  arrayProto,
  '$set',
  function $set (index, val) {
    if (index >= this.length) {
      this.length = Number(index) + 1
    }
    return this.splice(index, 1, val)[0] // 在里面还是通过代理方法splice实现
  }
)
 
def(
  arrayProto,
  '$remove',
  function $remove (item) {
    /* istanbul ignore if */
    if (!this.length) return
    var index = indexOf(this, item)
    if (index > -1) {
      return this.splice(index, 1)
    }
  }
)


看实际的observer干了什么:

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
 
 
 
export function Observer (value) {
  this.value = value
  this.dep = new Dep()
  def(value, '__ob__', this)
  if (isArray(value)) {
    var augment = hasProto  //hasProo = '__prop__' in {};  __prop__只在某些浏览器才暴漏出来
      ? protoAugment  // 直接将 value.__proto__ = src
      : copyAugment //直接改变本身方法
    augment(value, arrayMethods, arrayKeys)
    this.observeArray(value)
  } else {
    this.walk(value)
  }
}
 
 
 
function copyAugment (target, src, keys) {
  for (var i = 0, l = keys.length; i < l; i++) {
    var key = keys[i]
    def(target, key, src[key])
  }
}
function protoAugment (target, src) {
  target.__proto__ = src
}

总的来说,vue是代理了原始数据的增删改查,从而进行事件订阅和发布等操作,从而控制数据流。

heavily inspired by:https://segmentfault.com/a/1190000004384515

转载于:https://my.oschina.net/wizardpisces/blog/624652

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值