手写vue2源码系列之初始化Vue及数据劫持和代理(二)

我们知道在使用vue的时候,要new vue({}),因此可以得出vue就是一个构造函数,只是我们要传一个options作为参数即可,因此我们先把初始结构搭建起来。

初始化导出Vue

搭建好之后,我们就开始准备来自己动手写vue2源码啦。

Vue基本结构搭建

我们知道,Vue在初始化的时候要做很多事,比如watch,methods,computed以及各种生命周期钩子,而这些方法肯定都是挂载在vue的原型上,因此我们先分类把结构处理好。

js 复制代码
//src/index.js
import {initMixin} from './init'
function Vue(options){
    this._init(options);//在Vue的原型身上要增加一个_init方法,把配置对象挂载在实例身上
}
initMixin(Vue)
export default Vue
js 复制代码
//src/init.js
import {initState} from './initState'
function initMixin(Vue) {
  Vue.prototype._init = function (options) {
    const vm = this; //声明一个vm=this,这样方便后续拿值,且由于this是实例对象,根据地址引用,操作vm就相当于操作this
    vm.$options = options; //把new Vue传进来的options配置对象挂载在vue的实例身上
     initState(vm);
  };
}
export { initMixin };
js 复制代码
//src/initState.js
import { observer } from "./observer/object.js";
export function initState(vm) {
  let opts = vm.$options;
  if (opts.props) {
    //如果配置对象里传入了props
    initProps(vm);
  }
  if (opts.data) {
    //如果配置对象里传入了data
    initData(vm);
  }
  if (opts.watch) {
    //如果配置对象里传入了watch
    initWatch(vm);
  }
  if (opts.computed) {
    //如果配置对象里传入了computed
    initComputed(vm);
  }
  if (opts.methods) {
    //如果配置对象里传入了methods
    initMethods(vm);
  }
}

// 初始化data,注意这里要把data先放在_data中,方便后边做数据代理
function initData(vm){
    let data = vm.$options.data;
    // 判断这里data是对象形式还是函数形式
    vm._data=data=typeof data=='function'? data.call(vm):data
    // 这里定义好data之后,就需要对这个data进行数据劫持,在src文件夹下新建observe文件夹
    observer(data)

}
function initProps(vm){

}
function initWatch(vm){

}
function initComputed(vm){

}
function initMethods(vm){

}

数据劫持

数据劫持的本质其实就是要监测对象的变化,然后告诉视图,我更新了,然后让视图更新即可。 数据劫持和代理的相关文章Vue原理学习 - 实现数据代理和数据劫持 - 掘金 (juejin.cn)

而Vue2的官方文档里说,Object.defineProperty只能监听对象的变化,而不能监听数组的变化,这其实是不对的,因为是可以监听数组的变化的,但是由于性能原因, Object.defineProperty只能劫持已有属性,要监听数组变化,必须预设数组长度,遍历劫持,但数组长度在实际引用中是不可预料的,因此Vue2中没有采用 Object.defineProperty对数组进行劫持,这也是为什么在Vue2中,且由于Object.defineProperty有一个缺点,就是对象新增或者删除的属性无法被 set 监听到 只有对象本身存在的属性修改才会被劫持 ,所以在Vue2中才会有$set这个方法来增加属性

参考文献: vue2为什么不用Object.defineProperty劫持数组 - 掘金 (juejin.cn)

第一层数据的劫持

我们知道,data是一个对象,其数据可能是这样: { name:'张三' } 也可能是这样 { person:{ name:'张三'} } 即数据分为一层数据或者嵌套数据,当然我们实际工作中肯定是嵌套数据用的多啦,这里分为两个模块,先对一层数据进行劫持,再用递归对多层数据进行劫持。

js 复制代码
// src/observer/object.js
// observer函数就是对data进行数据劫持
export function observer(data) {
  //1判断data是否是对象或者存在,因为Object.defineProperty只对对象有用
  if (typeof data != "object" || data == null) {
    return data;
  }

  Observer(data);
}

function Observer(data) {
  walk(data); //对data进行遍历
}
function walk(data) {
  //1. 首先获取data对象中的所有keys
  const keys = Object.keys(data);
  for (let key of keys) {
    // 2. 然后对data进行数据劫持,数据劫持的目的是进行数据视图的响应式更新
    // 比如,当数据发生改变,我们可以监听到,然后去更新视图,这样就做到了数据驱动视图
    defineReactive(data, key, data[key]);
  }
}
function defineReactive(data, key, data_key) {
  if (typeof data !== "object" || data == null) {
    return;
  }
  Object.defineProperty(data, key, {
    get() {
      console.log("我被读取了");
      return data_key; //当想读取data中的值时,就return对应的值即可
    },
    set(newValue) {
      if (newValue === data_key) return; //如果设置的新值和旧值相同,就跳出去
      console.log("我被设置新值了");
      data_key = newValue;
    },
  });
}

如上,我们完成了一层数据的劫持。

递归劫持所有数据

我们把上面的walk函数中的代码修改为如下,即可进行一个简单的递归

js 复制代码
function walk(data) {
  //1. 首先获取data对象中的所有keys
  const keys = Object.keys(data);
  for (let key of keys) {
    // 2. 然后对data进行数据劫持,数据劫持的目的是进行数据视图的响应式更新
    // 比如,当数据发生改变,我们可以监听到,然后去更新视图,这样就做到了数据驱动视图
    if(typeof data[key]=='object'){
      walk(data[key])//在这里进行递归
    }else{
      defineReactive(data, key, data[key]);
    }
   
  }
}
js 复制代码
//index.html
 <div id="app">hello Vue</div>
    <script src="/dist/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          name: "张三",
          person: { name: "李四" },
          arr: [1, 2, 3, 4],
        },
      });
      console.log(vm._data);
    </script>

对数组进行劫持

在上面的操作中,我们对数组和对象都进行了劫持,但是对数组进行劫持是非常消耗性能的,因此这里我们要重新数组的原型方法,以至于不让数组被劫持

数据代理

数据代理就比较好理解了,我们在options传入的data,Vue首先帮我们放在了_data上,然后通过数据代理把_data上的数据又放在了this身上,这也是为什么我们可以通过this和vm访问到时数据,如this.a,vm.a

相关推荐
研☆香6 分钟前
jQuery特殊属性操作方法
前端·javascript·jquery
ShiJiuD66688899910 分钟前
外卖项目总结下 (前端板块)
前端
liming49521 分钟前
Maven中央库迁移
服务器·前端·maven
超哥--7 小时前
B站视频内容智能分析系统(九):React 前端与管理面板
前端·react.js·前端框架
Cutecat_10 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
qq_4221525710 小时前
PDF 加水印工具怎么选?2026 年文档版权保护方案对比
前端·pdf·github
kyriewen11 小时前
手写 Promise.all、race、any:不到 30 行代码,解决并发异步的所有姿势
前端·javascript·面试
brucelee18611 小时前
OpenClaw 浏览器控制(Chrome MCP)完整教程
前端·chrome
ct97812 小时前
React 状态管理方案深度对比
开发语言·前端·react
胡志辉的博客12 小时前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·chrome·chromium·event loop