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

相关推荐
江城开朗的豌豆4 分钟前
v-if和v-for一起用?小心踩坑!小杨教你正确写法
前端·javascript·vue.js
代码羊羊9 分钟前
Web3 常用前端库介绍
前端·web3
于慨32 分钟前
uniapp云托管前端网页
前端·uni-app
前端呆猿2 小时前
小程序性能优化全攻略:提升用户体验的关键策略
前端·性能优化·小程序
y东施效颦3 小时前
uni-app 配置华为离线推送流程
前端·vue.js·uni-app
老神在在0016 小时前
SpringMVC1
java·前端·学习·spring
薛定谔的算法9 小时前
# 从0到1构建React项目:一个仓库展示应用的架构实践
前端·react.js
Tina学编程9 小时前
HTML基础P1 | HTML基本元素
服务器·前端·html
一只小风华~10 小时前
Web前端:JavaScript和CSS实现的基础登录验证功能
前端
90后的晨仔10 小时前
Vue Router 入门指南:从零开始实现前端路由管理
前端·vue.js