Vue源码解析—new Vue()响应性数据劫持

1

响应性数据劫持

a

本章主要讲解红框中重要部分——数据劫持,其他先不看

本章完整代码:https://github.com/gitboyzcf/vue2-mini

阅读完将会了解:

  • vue2的data选项中对象是怎样劫持
  • vue2的data选项中数组是怎样劫持

话不多说直接代码讲解!!!

代码讲解

注意:根据数字序号引导进行阅读,仔细阅读注释

index.html

<body>
  <div id="app">vue2</div>
  <script type="module">
    import Vue from './dist/vue.js'
    let vm = new Vue({
      data() {
        return {
          name: 'zhagsan',
          age: 123,
          address:{
            test:'123321'
          },
          list:[1,2,3],
          list2:[{a:123}]
        }
      }
    });
    vm.name = 'lise'
    vm.address = {
      test2:444
    }
    console.log(vm);
  </script>
</body>

index.js

import { initMixin } from "./init";

// vue构造函数 
function Vue(options) {
  this._init(options) // ==== 1
}

initMixin(Vue); //_init 在此进入 -》 init.js
// ...

export default Vue;

init.js

import { initState } from "./state";

export function initMixin(Vue) {
  Vue.prototype._init = function (options) {
    const vm = this; // this 当前vue实例对象

    //将传入的选项放到vue示例上 ==== 2
    vm.$options = options;

    // 初始化状态 ==== 3 -》 state.js
    initState(vm);
  };
}

state.js


import { observe } from "./observe/index";

export function initState(vm) {
  // 得到选项(data、methods、watch ...) ==== 4
  const opts = vm.$options;
  // 是否有data选项 
  if (opts.data) {
    initData(vm); // ==== 5
  }
  // ...
}

function proxy(vm, target,key){
  // 将data中的属性代理一份放到vm下
  Object.defineProperty(vm,key,{
    get(){
      return vm[target][key];
    },
    set(newValue){
      vm[target][key] = newValue;
    }
  })
}

function initData(vm) {  // 处理data数据 ==== 6
  // 得到data选项
  let data = vm.$options.data;
  // 判断data是否为方法(如果是data选项是个方法 就调用此方法并改变this指向为 当前vue实例) ==== 7
  data = typeof data === "function" ? data.call(vm) : data;
  // console.log(data);

  vm._data = data;

  // 数据劫持 ==== 8  -》 observe/index.js
  observe(data);

  
  
  //============== 此部分先不看 根据序号引导来看 
  // 为了方便是vm直接获取_data中的属性 再次代理
  /**
   * data(){
   *   return {
   *      test:1
   *    }
   * }
   * this.test 或者  vm.test
   */
  for (let key in data) {
    proxy(vm, "_data", key);
  }
  //=====
}

observe/index.js

import { ArrayMethods } from "./array"; // ==== 15 -》 observe/array.js

class Observe {
  constructor(data) {

    // ========!!! 根据引导去看
    // 对每个对象添加Observe实例
    Object.defineProperty(data,'__ob__',{
      enumerable: false, // 不允许枚举(遍历)
      value:this
    })
    // =======


    // 判断是否为数组对数组进行操作  ==== 11
    if (Array.isArray(data)) {
      // 将数组对象的原型指向新创建的对象 // ==== 20
      data.__proto__ = ArrayMethods;
      // 如果是数组对象 再次给对象进行响应拦截处理[{a:1}]
      this.observeArray(data)

    } else {
      // 循环对象中的属性进行defineProperty劫持 ==== 12
      this.walk(data);
    }
  }
  // 定义循环对属性依次进行劫持
  walk(data) {
    // “重新定义”属性 ——性能消耗 ==== 13
    Object.keys(data).forEach((key) => defineReactive(data, key, data[key]));
  }

  // 处理数组对象 [{a:123}] (遍历数组中的每一个对象进行数据劫持) ==== 21 (15 - 21 是对数组方法以及数组对象的劫持)去往-》array.js 看感叹号处
  observeArray(data){
    for(let i = 0; i < data.length; i++){
      observe(data[i]);
    }
  }
}

// 公共响应式拦截  
export function defineReactive(target, key, value) {
  // 如果data中的属性还是对象递归代理
  /**
   * 为何在此递归?(当默认data中属性值为对象时进行深层劫持变为响应式)
   * data(){
   *  return{
   *    test:{
   *      a:123
   *    }
   *  }
   * }
   * 
   */
  observe(value);

  Object.defineProperty(target, key, {  // ==== 14 (1 - 14  为data数据对象劫持,数组的劫持在上面👆从15开始)
    // 属性劫持
    get() {
      // 取值走get
      return value;
    },
    set(newValue) {
      // 修改值走set
      // 判断值是否一致
      if (newValue === value) return;
      /**
       * 为何在此递归?(当赋值为对象时,再次将对象中的属性进行劫持变为响应式)
       * data(){
       *  return{
       *    test:1
       *  }
       * }
       * 
       * this.test = {a:123}
       * 
       */
      observe(newValue);
      value = newValue;
    },
  });
}

export function observe(data) {
  // 判断data是否为对象 ==== 9
  if (typeof data !== "object" || data == null) {
    return; //只对对象进行劫持
  }

  // 创建劫持对象
  return new Observe(data); // ==== 10
}

observe/array.js

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

// 创建新对象并用原型链方式将新对象继承Array原型 ==== 17
let ArrayMethods = Object.create(oldArrayProtoMethods);

// 定义劫持的方法 ——当data中的数组调用下面任意方法时 可拦截操作
let methods = ["push", "pop", "unshift", "shift", "splice"];

// 在新对象中添加劫持的方法 ==== 18
methods.forEach((item) => {
  ArrayMethods[item] = function (...args) {
    // 做劫持操作...
    console.log("数组劫持",this);
    
    /**
     * 当前的this指向调用着(例如下面代码 this指向 test对应的值)
     * data(){
     *  return {
     *    test:[{a:123}]
     *  }
     * }
     * test.push({b:456})
     * 
     * args 输出[{b:456}]
     */

    // 使用原生数组方法
    let result = oldArrayProtoMethods[item].apply(this, args);

    // ====== 此处是解决当使用push添加为对象时为对象进行劫持 先不看 根据序号引导走!!!  ======
    
    let ob = this.__ob__ //Observer实例对象  __ob__此属性的添加请看 -》observe/index.js 的感叹号标记(细品this指向)
    let inserted; // 参数
    switch (item) {//判断是否为调用添加数组方法
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
        break;
    }
    if(inserted){
      ob.observeArray(inserted) // 对数组添加的对象进行数据劫持
    }
    //======
    return result;
  };
});

export { ArrayMethods }; // ==== 19 -》 observe/index.js






到这里就结束了,后续还会跟进,还请持续关注!
感谢阅读,若有错误可以在下方评论区留言哦!!!

111

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值