Vue2.0源码(一)响应式数据原理

Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。之前的jquery想要改变试图,是直接操作DOM来实现。现在我们只需要关心数据的改变会让代码的逻辑变的非常清晰。

Vue 内部就有一个机制能监听到数据变化然后触发更新 本篇主要介绍响应式数据的原理 

1、数据的初始化

new Vue({
  el: "#app",
  router,
  store,
  render: (h) => h(App),
});

这段代码 大家一定非常熟悉 这就是 Vue 实例化的过程 从 new 操作符 咱们可以看出 Vue 其实就是一个构造函数 没啥特别的 传入的参数就是一个对象 options,下面我们就来看下,实例的创建,我们构造函数都做了哪些操作

// src/core/instance/index.js

import { initMixin } from './init'

// Vue就是一个构造函数 通过new关键字进行实例化
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
//_init方法是挂载在Vue原型的方法 通过引入文件的方式进行原型挂载需要传入Vue
// 此做法有利于代码分割
initMixin(Vue)

export default Vue

这里创建了一个构造函数Vue,里面就是执行了一个_init函数

// src/core/instance/init.js
import { initState } from "./state";

//initMixin 把_init 方法挂载在 Vue 原型 供 Vue 实例调用
export function initMixin(Vue) {
  Vue.prototype._init = function (options) {
    const vm = this;
    // 这里的this代表调用_init方法的对象(实例对象)
    //  this.$options就是用户new Vue的时候传入的属性
    vm.$options = options;
    // 初始化状态
    initState(vm);
  };
}

Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等,这里只介绍初始化data数据,别的后面文章再详细介绍

// src/core/instance/state.js
import { observe } from '../observer/index'

export function initState (vm) {
  const opts = vm.$options
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {})
  }
}

function initData (vm: Component) {
  let data = vm.$options.data
  // 实例的_data属性就是传入的data
  // vue组件data推荐使用函数 防止数据在组件之间共享
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}

  const keys = Object.keys(data)
  let i = keys.length
  while (i--) {
    const key = keys[i]
    // 把data数据绑定到vm上,在页面中可以通过this.来获取和赋值
    // 修改_data的值
    proxy(vm, `_data`, key)
  }
  observe(data)
}

// 数据代理
function proxy(object, sourceKey, key) {
  Object.defineProperty(object, key, {
    get() {
      return object[sourceKey][key];
    },
    set(newValue) {
      object[sourceKey][key] = newValue;
    },
  });
}

这里主要是把data的数据代理到this._data,后面用于this.$data,我们主要关注 initData 里面的 observe 是响应式数据核心

2.对象的数据劫持

// src/core/observer/index.js

export function observe (value) {
  // 如果传过来的是对象或者数组 进行属性劫持
  if (
    Object.prototype.toString.call(value) === "[object Object]" ||
    Array.isArray(value)
  ) {
    return new Observer(value);
  }
}

export class Observer {
  constructor (value) {
    // 如果是数组的话,重新定义
    if (Array.isArray(value)) {
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
 
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

export function defineReactive (obj, key) {
  observe(value); // 递归关键
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 获取值
      return value
    },
    set (newVal) {
      //设置值
      val = newVal
    }
  })
}

数据劫持核心是 defineReactive 函数 主要使用 Object.defineProperty 来对数据 get 和 set 进行劫持 这里就解决了之前的问题 为啥数据变动了会自动更新视图 我们可以在 set 里面去通知视图更新

上面Observer函数中对数组,我们进行了额外的处理,为什么数组,我们不进行直接的劫持呢?

如果说我们对数组的下标get,set劫持,我们也能够相应变化,但是如果数据里面有成千上万个数呢,每一个元素下标都添加 get 和 set 方法 这样对于性能来说是承担不起的 所以此方法只用来劫持对象。

3.数组的观测

// src/core/observer/index.js

import { arrayMethods } from "./array";

class Observer {
  constructor(value) {
     def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 这里对数组做了额外判断
      // 通过重写数组原型方法来对数组的七种方法进行拦截
      value.__proto__ = arrayMethods;
      // 如果数组里面还包含数组 需要递归判断
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }
  observeArray(items) {
    for (let i = 0; i < items.length; i++) {
      observe(items[i]);
    }
  }
}

export function def (obj, key, val) {
  Object.defineProperty(obj, key, {
    // val值指代的就是Observer的实例
    value: val,
    // 不可枚举
    enumerable: false,
    writable: true,
    configurable: true
  })
}


因为对数组下标的拦截太浪费性能 对 Observer 构造函数传入的数据参数增加了数组的判断

添加 __ob__ 这段代码的意思就是给每个响应式数据增加了一个不可枚举的__ob__属性 并且指向了 Observer 实例 那么我们首先可以根据这个属性来防止已经被响应式观察的数据反复被观测 其次 响应式数据可以使用__ob__来获取 Observer 实例的相关方法 这对数组很关键

// src/core/observer/array.js
// 先保留数组原型
const arrayProto = Array.prototype;
// 然后将arrayMethods继承自数组原型
// 这里是面向切片编程思想(AOP)--不破坏封装的前提下,动态的扩展功能
export const arrayMethods = Object.create(arrayProto);
let methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "reverse",
  "sort",
];
methodsToPatch.forEach((method) => {
  arrayMethods[method] = function (...args) {
    //   这里保留原型方法的执行结果
    const result = arrayProto[method].apply(this, args);
    // 这句话是关键
    // this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4)  this就是a  ob就是a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例
    const ob = this.__ob__;

    // 这里的标志就是代表数组有新增操作
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
      default:
        break;
    }
    // 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测
    if (inserted) ob.observeArray(inserted);
    // 之后咱们还可以在这里检测到数组改变了之后从而触发视图更新的操作--后续源码会揭晓
    return result;
  };
});

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Vue2.0和Three.js是两个非常流行的前端技术,分别用于构建交互式的Web应用和创建动态的3D场景。结合使用Vue2.0和Three.js,可以开发出一个功能强大的3D粮仓管理系统。 这个系统源码使用Vue2.0作为前端框架,通过Vue的组件化开发模式,将界面划分为多个可复用的组件,方便管理和维护。同时,借助Vue响应式数据绑定机制,实现了与后端数据的实时同步。 Three.js作为渲染引擎,实现了3D环境中的粮仓模型的创建、渲染和交互功能。通过Three.js的API,可以创建粮仓模型的几何形状、表面材质和光影效果,并在场景中展示粮仓的实时状态。 该系统源码具有以下功能: 1. 用户登录和权限管理:使用Vue的路由和组件间通信机制,实现了用户登录和权限验证的功能,确保只有具备管理权限的用户可以访问系统。 2. 粮仓管理:通过Three.js的渲染引擎,将粮仓模型以3D图形的形式展示给用户,并实现了对粮仓的实时监控和管理功能。用户可以查看粮仓的存储容量、温度、湿度等指标,并可以进行添加、删除和修改粮仓的操作。 3. 数据统计与分析:系统支持对粮仓数据进行统计和分析,包括存储容量趋势、温湿度变化等。通过可视化的图表展示,用户可以直观地了解粮仓的运营状态,并进行粮食管理的决策。 4. 报警与通知:系统实时监测粮仓的状态,如果出现异常情况(如温度过高、湿度过低等),系统将自动生成报警信息,并通过邮件或消息通知管理员,以便及时处理问题。 总之,该3D粮仓管理系统源码结合了Vue2.0和Three.js的优势,旨在提供一个直观、便捷的粮食管理工具,提高粮仓管理的效率和准确性。它不仅可以在Web端浏览器中使用,还可以借助Vue的移动端适配能力,在移动设备上进行访问和管理。 ### 回答2: Vue2.0 和 Three.js 是两个非常流行的前端技术库,Vue2.0 是一个用于构建用户界面的前端框架,而 Three.js 是一个用于创建和渲染3D图形的JavaScript库。一个3D粮仓管理系统的源码,结合了这两个技术,将会非常强大和吸引人。 这个系统的主要功能是管理和监控一个3D的粮仓。它使用 Three.js 来创建和渲染3D模型,可以展示粮仓的实际布局、粮食的存储情况以及其他相关的信息。用户可以通过这个系统来了解粮仓的容量、存储的粮食种类和数量等。同时,系统还可以提供一些管理功能,比如添加、删除和修改粮食的信息。 该系统的前端界面使用 Vue2.0 构建,利用 Vue 的组件化开发方式,将系统的不同模块拆分成独立的组件,提高了代码的可维护性和可复用性。利用 Vue数据双向绑定特性,可以方便地更新和显示粮仓的实时状态和数据。 在这个系统的后端,通常会使用一个服务器来处理前端发送的请求,并与数据库进行交互,用于存储和获取粮食的相关信息。服务器端可以选择合适的后端技术,比如 Node.js、Python、PHP等,根据实际需求来选择。 综上所述,基于 Vue2.0 和 Three.js,编写一个3D粮仓管理系统的源码能够有效地展示粮仓的实际情况和管理粮食的相关信息。这个系统不仅功能实用,而且还具有良好的用户体验和可扩展性。对于粮仓管理人员来说,这个系统将大大提高工作效率和粮食管理的准确性。 ### 回答3: Vue 2.0是一种用于构建用户界面的JavaScript框架,而Three.js是一个用于创建各种3D图形的JavaScript库。通过使用Vue 2.0和Three.js,可以开发出一个功能丰富的3D粮仓管理系统。 这个系统的源码包括以下几个方面的内容: 1. 页面结构:通过Vue 2.0的组件化开发,可以将系统拆分为多个可重用的组件,例如粮仓列表、粮仓详情等。使用Vue Router实现路由管理,以实现不同页面间的切换。 2. 数据交互:通过Vue数据绑定功能,可以将前端的数据与后端API进行交互。使用axios库来发送HTTP请求,从后端获取粮仓数据,并将其显示在前端页面上。同时,可以将用户对粮仓的操作(如增加、删除或更新)发送给后端。 3. Three.js集成:通过在Vue组件中使用Three.js库,可以实现3D视图的渲染和交互。可以使用Three.js创建一个粮仓的3D模型,并在前端页面中展示出来。可以通过鼠标或手势交互来旋转、缩放或平移视图。 4. 功能实现:通过Vue的事件机制,可以实现系统中的各种功能,如搜索粮仓、排序粮仓列表、添加新的粮仓等。可以在前端页面上提供用户友好的界面,让用户方便地管理粮仓。 总结来说,这个源码是一个综合运用了Vue 2.0和Three.js的3D粮仓管理系统。它实现了前端与后端的数据交互,并通过Three.js库在前端展示了3D粮仓模型。同时,它还具备了一系列功能,如搜索、排序和添加粮仓等,提供了用户友好的交互界面。通过研究源码,可以学习到Vue 2.0和Three.js的使用技巧,为开发其他类似的系统提供参考。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值