Vue2 数据响应式原理

数据响应式原理

  • 把一个普通的javascript对象传给Vue实例的data选项,

  • Vue将遍历此对象所有的属性,并用Object.defineProperty把这些属性全部转换为getter/setter

  • Vue内部会对数据进行劫持操作,进而追踪依赖,在属性被访问和修改时通知变化

定义defineReactive函数,调用该函数可以用Object.defineProperty把传入对象的属性全部转换为getter/setter

import observe from './observe.js'
import Dep from './Dep.js'

export default function defineReactive(data, key, val) {
  const dep = new Dep();
  if (arguments.length == 2) {
    val = data[key];
  }

  // 子元素要进行observe形成递归
  let childOb = observe(val)

  Object.defineProperty(data, key, {
    // 可枚举
    enumerable: true,
    // 可配置
    configurable: true,
    // 数据劫持
    // getter
    get() {
      // 如果处于依赖收集
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend()
        }
      }
      return val;
    },
    // setter
    set(newValue) {
      if (val = newValue) {
        return
      }
      val = newValue;
      childOb = observe(newValue);
      // 发布订阅模式,通知dep
      dep.notify();
    }
  })
}

定义Obserser类 目的是:将一个正常的object转换成每个层级都是响应式(可以被侦测的)的object

import { def } from "./utils.js"
import defineReactive from './defineReactive.js'
import { arrayMethods } from './array.js'
import observe from "./observe.js";
import Dep from "./Dep.js";

export default class Observer {
  constructor(value) {
    this.dep = new Dep();
    // 给实例
    def(value, '__ob__', this, false)
    console.log('我是Observer的构造器', value);
    // 检查是数组还是对象
    if (Array.isArray(value)) {
      // 改变数组原型的指向
      Object.setPrototypeOf(value, arrayMethods);
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }
  // 遍历变成响应式
  walk(value) {
    for (let k in value) {
      defineReactive(value, k);
    }
  };
  // 数组特殊遍历
  observeArray(arr) {
    for (let i = 0; i < arr.length; i++) {
      observe(arr[i]);
    }
  };
}

定义obserse函数检测对象上是否有Observer,如果没有则添加Observer

import Observer from './Observer.js'

export default function observe(value) {
  // 如果value不是对象直接return
  if (typeof value !== 'object') return;
  // define ob
  let ob;
  if (typeof value.__ob__ !== 'undefined') {
    ob = value.__ob__;
  } else {
    ob = new Observer(value);
  }
  return ob;
}

定义array,若对象上属性是数组,依次递归为其添加Observer

/**
 * 改写Array.prototype上的七个方法
 * push pop shift unshift splice sort reverse
 */
import { def } from './utils.js'
const arrayPrototype = Array.prototype;
export const arrayMethods = Object.create(arrayPrototype)

// 要被改写的7个方法
const methodsNeedChange = [
  'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
];

methodsNeedChange.forEach(methodName => {
  // 备份原来的方法
  const original = arrayPrototype[methodName];
  // 定义新的方法
  def(arrayMethods, methodName, function () {
    // 恢复原来的功能
    const result = original.apply(this, arguments);
    // 把类数组对象变成数组
    const args = [...arguments];
    console.log(arguments);
    // 把数组的__ob__取出;
    const ob = this.__ob__;
    // 有三种方法push/unshift/splice能够插入新项,现在需要把新项也变成observe
    let inserted = [];
    switch (methodName) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        // splice格式是splice(下标,数量,插入的新项)
        inserted = args.slice(2);
        break;
    }
    // 判断是否有新项
    if (inserted) {
      ob.observeArray(inserted);
    }
    console.log('hahahha~~~');
    ob.dep.notify();
    return result
  }, false)
})

定义Dep类进行依赖收集

let uid = 0;
export default class Dep {
  constructor() {
    this.id = uid++;
    // 用数组存储自己的订阅者
    this.subs = [];

  }

  // 添加订阅
  addSub(sub) {
    this.subs.push(sub);
  }
  // 添加依赖
  depend() {
    // Dep.target 就是制定的全局位置
    if (Dep.target) {
      this.addSub(Dep.target);

    }
  }
  // 通知更新
  notify() {
    console.log('我是notify');
    // 浅克隆一份
    const subs = this.subs.slice();
    // 遍历
    for (let i = 0; i < subs.length; i++) {
      subs[i].update();
    }
  }
}

定义Watcher类

import Dep from './Dep.js'

let uid = 0;
export default class Watcher {
  constructor(target, expression, callback) {
    this.id = uid++
    this.target = target;
    this.getter = parsePath(expression);
    this.callback = callback;
    this.value = this.get();
  }

  update() {
    this.run()
  }

  get() {
    Dep.target = this;
    const obj = this.target;
    let value;
    try {
      value = this.getter(obj);
    } finally {
      Dep.target = null;
    }
    return value;
  }


  run() {
    this.getAndInvoke(this.callback);
  }

  getAndInvoke(cb) {
    const value = this.get();
    if (value !== this.value || typeof value === 'object') {
      const oldValue = this.value;
      this.value = value;
      cb.call(this.target, value, oldValue);
    }
  }

}

function parsePath(str) {
  let segments = str.split('.');

  return (obj) => {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return;
      obj = obj[segments[i]];
    }
    return obj;
  }
}
posted @ 2021-04-29 11:55  王杰11  阅读(328)  评论(0)    收藏  举报