手写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

相关推荐
海石11 分钟前
去到比北方更北的地方—2025年终总结
前端·ai编程·年终总结
一个懒人懒人18 分钟前
Promise async/await与fetch的概念
前端·javascript·html
Mintopia24 分钟前
Web 安全与反编译源码下的权限设计:构筑前后端一致的防护体系
前端·安全
输出输入26 分钟前
前端核心技术
开发语言·前端
Mintopia31 分钟前
Web 安全与反编译源码下的权限设计:构建前后端一体的信任防线
前端·安全·编译原理
林深现海1 小时前
Jetson Orin nano/nx刷机后无法打开chrome/firefox浏览器
前端·chrome·firefox
黄诂多1 小时前
APP原生与H5互调Bridge技术原理及基础使用
前端
前端市界1 小时前
用 React 手搓一个 3D 翻页书籍组件,呼吸海浪式翻页,交互体验带感!
前端·架构·github
文艺理科生1 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling1 小时前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试