第十三节: 数据劫持完整

101 阅读1分钟
入口文件 index.js
import { initMixin } from './init'
function Vue(options) {
  // 初始化
  this.__init(options);
}

initMixin(Vue);//初始化
export default Vue

初始化文件 init.js

import { initState } from './initState.js';
// 初始化业务
export function initMixin(Vue) {
  // 初始化
  Vue.prototype.__init = function(options) {
    let vm = this;// this -> vue实例
    vm.$options = options;
    // 初始化状态
    initState(vm);
  }
}

初始化状态 initState.js

import { observer } from './observer/index'
// 初始化状态
export function initState(vm) {
  let ops = vm.$options;
  // 1.判断是否有属性
  if(ops.data) {
    initData(vm);
  }
}

// 初始化data
function initData(vm) {
  // 1.获取data
  let data = vm.$options.data;
  // 2.判断是 对象 or 函数 用call来绑定this
  data = vm._data = typeof data === 'function' ? data.call(vm) : data;
  // 3. 将data上所有的属性代理到实例 vm上
  for(let key in data) {
    proxy(vm, '_data', key);
  }
  // 4.数据劫持
  observer(data);
}


// 代理给vm上
function proxy(vm, source, key) {
  Object.defineProperty(vm, key, {
    get() {
      return vm[source][key];
    },
    set(newValue) {
      vm[source][key] = newValue;
    }
  });
}

数据劫持 observer/index.js

import { ArrayMethods } from './arr';//给数组添加原型

export function observer(data) {
  // 1.判断data不是object不做劫持
  if(typeof data != 'object' || data == null ) {
    return data;
  }
  // 2.**对象 通过一个类劫持**
  return new Observer(data);
}

//  Object.defineProperty
class Observer {
  constructor(value) {
    // **精髓给data定义一个属性** @9527
    Object.defineProperty(value, '__obj__', {
      enumerable: false,//不能进行枚举
      value: this
    });

    // 判断是对象 还是 数组(观测数据)
    if(Array.isArray(value)) {//数组
      // => **给数组添加我们重写的方法**@@@@@@@@@@ 精髓
      value.__proto__ = ArrayMethods;
      // 如果数组里面是对象 [{a: 1}]
      // 处理数组对象劫持
      this.observerArr(value);
    }else {//对象
      this.walk(value);
    }
  }
  walk(data) {
    let keys = Object.keys(data);
    for(let i = 0; i < keys.length; i++) {
      // 对我们的每个属性进行劫持
      let key = keys[i];//属性
      let value = data[key];//属性值
      defineReactive(data, key, value);
    }
  }
  // 处理数组对象劫持
  observerArr(value) {
    for(let i = 0; i < value.length; i++) {
      observer(value[i]);
    }
  }
}

/**
 * 对对象中的属性进行劫持
 * @param {*} data
 * @param {*} key 
 * @param {*} value 
 */
function defineReactive(data, key, value) {
  // 可能是个对象就需要递归了: 深度劫持
  observer(value);
  Object.defineProperty(data, key, {
    get() {
      return value;
    },
    set(newValue) {
      // 设置的值相同
      if(newValue === value) return value;
      observer(value);//避免 对象 替换 地址失去劫持
      value = newValue;
    }
  })
}
// 对数组进行劫持
// 方法函数劫持,重写数组方法

数组的劫持

// 重写数组方法
// 1. 获取原来的数组方法
let oldArrayProtoMethods = Array.prototype;

// 2. 继承
export let ArrayMethods = Object.create(oldArrayProtoMethods);

// 3. 劫持
let methods = [
  'push',
  'pop',
  'unshift',
  'shift',
  'splice',
  'reverse',
  'sort'
]

methods.forEach(item => {
  ArrayMethods[item] = function(...args) {
    let result = oldArrayProtoMethods[item].apply(this, args);
    // 数组追加对象的情况
    let inserted;
    switch(item) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.splice(2);
        break;
    }
    let ob = this.__obj__;//this -> value
    if(inserted) {
      ob.observerArr(inserted);//对添加的对象进行劫持
    }
    return result;
  }
})

总结

    1. Vue原型上添加init方法
    1. vm赋值this(vue的实例)
    1. vm.$options = options方便以后去拿取数据
    1. initState(vm) =>初始化属性
    1. initData(vm) => 初始化数据
    • vm.$options.data获取值
    • 判断data是函数 or 对象
    • for/in遍历代理到 vm(vue实例上去)
    • observer(data) => 进行数据劫持
    1. 通过一个(Observer)类劫持
    • 判断是数组 or 对象
    1. 对象进行劫持
    • 循环对象对每个属性,通过Object.defineProperty进行数据劫持
    • 这里要考虑到对象的深度代理 get的时候 observer(value)
    • 对象修改成另一个对象 set的时候 observer(value)
    1. 对数组进行劫持
    • 这里需要重写数组的方法
    • 获取原来的数组方法
      • let oldArrayProtoMethods = Array.prototype;
    • 继承
      • export let ArrayMethods = Object.create(oldArrayProtoMethods);
    • 需要劫持的数组方法名遍历重写数组方法
      • 数组里面添加 对象的方式需要处理
      • let ob = this.obj;//this -> value value上有observerArr方法
      • ob.observerArr(inserted);//对添加的对象进行劫持
      • 上一步的精髓: 在(ctrl+f) @9527处加一个不能枚举的属性,代理的值上存observerArr方法,value给的是 Observer的实例