Vue2源码梳理:关于数据驱动,与new Vue时的初始化操作

vue面试考察知识点全梳理 数据驱动组件化虚拟dom、diff局部最优更新Vue.js 的源码在 src 目录下,其目录结构如下。src├── compiler # 编译相关├── core # 核心代码├── platforms # 不同平台的支持├── server # 服务端渲染├── sfc # .vue 文件解析├── shared # 共享代码compiler:编译相关的代码。它包括把模板解析成 ast 语法树,ast 语法树优化,代码生成等功能。core。 阅读详情

数据驱动


1 )概述

  • vue的一个核心思想,就是数据驱动

  • 所谓数据驱动,就是指视图是由数据驱动生成的

  • 对视图的修改并不会直接操作dom,而是通过修改数据

  • 它相比我们传统的前端开发,如使用 jQuery 的前端库直接去修改 dom 的的话

  • 它大大简化了代码量,特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变得非常清晰

  • 因为dom变成了数据的映射,我们所有的逻辑都是对数据的修改,而并不关注dom,这样的代码非常有利于维护

  • 在vue.js中,可以采用简洁的模板语法来声明式的将数据渲染为 dom,示例

    // main.js
    import Vue from 'vue'
    
    var app = new Vue({
        el: '#app',
        /*
        data: {
          message: 'Hello'
        },
        */
        data() {
          return {
            message: 'Hello'
          }
        }
    })
    
    // App.vue
    <div id="app">
      {{ message }}
    </div>
    
    • 这实例的配置有两个参数
      • el: 是它的一个挂载的dom对象
      • data: 是相关的数据
    • 在模板中就声明是起了这样的一个差值message
    • 最终映射到浏览器上,可以看到这个文本节点
    • 它实际上就生成了一个字符串文本
  • 这个例子是为了说明这个数据在js中定义的这个数据 最终是怎么渲染到dom上的

  • 这就是 new Vue 的时候帮我们做的这些事情

2 )数据驱动的关注

  • 第一,分析数据是怎么映射到dom的
    • 传入了这样一个javascript对象,最终怎么生成到dom上的
  • 第二,数据的变化驱动视图的变化
    • 对message的修改,视图是怎么跟着变化的

new Vue时,做了哪些处理

  • 在 src/core/instance/index.js 中
    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)
    }
    initMixin(Vue)
    stateMixin(Vue)
    eventsMixin(Vue)
    lifecycleMixin(Vue)
    renderMixin(Vue)
    
    export default Vue
    
  • 它实际上就是一个class,也就是一个function实现的这个class
  • 它实际上就是执行了这个原型上的 _init 方法
  • 这个 _init 方法在 src/core/instance/init.js 中定义
  • 注意,这个 _init 方法是执行下面的 initMixin(Vue) 时,才被挂载的
  • 就在这个 initMixin 方法的内部,挂载了 _init, 如下
    Vue.prototype._init = function (options?: Object) {
      const vm: Component = this
      // a uid
      vm._uid = uid++
    
      let startTag, endTag
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        startTag = `vue-perf-start:${vm._uid}`
        endTag = `vue-perf-end:${vm._uid}`
        mark(startTag)
      }
    
      // a flag to avoid this being observed
      vm._isVue = true
      // merge options
      if (options && options._isComponent) {
        // optimize internal component instantiation
        // since dynamic options merging is pretty slow, and none of the
        // internal component options needs special treatment.
        initInternalComponent(vm, options)
      } else {
        vm.$options = mergeOptions(
          resolveConstructorOptions(vm.constructor),
          options || {},
          vm
        )
      }
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        initProxy(vm)
      } else {
        vm._renderProxy = vm
      }
      // expose real self
      vm._self = vm
      initLifecycle(vm)
      initEvents(vm)
      initRender(vm)
      callHook(vm, 'beforeCreate')
      initInjections(vm) // resolve injections before data/props
      initState(vm)
      initProvide(vm) // resolve provide after data/props
      callHook(vm, 'created')
    
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        vm._name = formatComponentName(vm, false)
        mark(endTag)
        measure(`vue ${vm._name} init`, startTag, endTag)
      }
    
      if (vm.$options.el) {
        vm.$mount(vm.$options.el)
      }
    }
    
  • 在这个 _init 方法中,做了一堆初始化的工作
    • 比如定义 uid
    • 合并options
      • 它会把传入的options,最终都merge到 $options 上
      • 可以通过 $options.el 访问到我们初始化时的 el 对象
      • 可以通过 $options.data 访问到我们定义的 data
    • 后面就是一堆初始化函数
      • 比如,初始化生命周期,事件中心,render,injections, state, provide, state 这些
      • 注意,中间的两次 callHooks 的调用
    • 最后,挂载这个 el 对象
  • 在上面的demo里面, 定义了这个data,需要看下这个data怎么初始化的?
    var app = new Vue({
      el: '#app',
      /*
      data: {
        message: 'Hello'
      },
      */
      data() {
        return {
          message: 'Hello'
        }
      }
      mounted() {
        console.log(this.message); // Hello
        console.log(this._data.message); // Hello
      }
    })
    
  • 现在看下,为何能够通过 this.message 访问到数据的
  • 进入上述的 initState, 定义在 src/core/instance/state.js
    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    
    export function proxy (target: Object, sourceKey: string, key: string) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      }
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    
    export function initState (vm: Component) {
      vm._watchers = []
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true /* asRootData */)
      }
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    
    function initData (vm: Component) {
      let data = vm.$options.data
      data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}
      if (!isPlainObject(data)) {
        data = {}
        process.env.NODE_ENV !== 'production' && warn(
          'data functions should return an object:\n' +
          'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
          vm
        )
      }
      // proxy data on instance
      const keys = Object.keys(data)
      const props = vm.$options.props
      const methods = vm.$options.methods
      let i = keys.length
      while (i--) {
        const key = keys[i]
        if (process.env.NODE_ENV !== 'production') {
          if (methods && hasOwn(methods, key)) {
            warn(
              `Method "${key}" has already been defined as a data property.`,
              vm
            )
          }
        }
        if (props && hasOwn(props, key)) {
          process.env.NODE_ENV !== 'production' && warn(
            `The data property "${key}" is already declared as a prop. ` +
            `Use prop default value instead.`,
            vm
          )
        } else if (!isReserved(key)) {
          proxy(vm, `_data`, key)
        }
      }
      // observe data
      observe(data, true /* asRootData */)
    }
    
    export function getData (data: Function, vm: Component): any {
      // #7573 disable dep collection when invoking data getters
      pushTarget()
      try {
        return data.call(vm, vm)
      } catch (e) {
        handleError(e, vm, `data()`)
        return {}
      } finally {
        popTarget()
      }
    }
    
    • 在 initState 中
      • 如果有 props, 则初始化 props
      • 如果有 methods, 则初始化 methods
      • 如果有 data, 则初始化 data,看 initData
    • 在 initData 中
      • 从 $options 中拿到 data
      • 判断 data 是否是一个 function, 通常我们推荐写一个function, 而非直接使用一个对象
      • 如果是函数,则调用 getData, 否则直接使用 data
      • 后面处理好的 data 不是对象,在 dev 环境中进行输出警告
      • 再后面 拿到 keys, props, methods,它们之间就做了一个循环对比
        • 比如说在这个 data 里面定义了这个message
        • 那就不能在 props 下也用 message
        • 或者在 methods 里面用这个 message
        • 为什么不能用,为什么会冲突,是因为它们最终都会挂载到 vm 上
        • 也就是说, 这个message,它最终会挂到这个this
        • 这样,this.message 就可以访问到当前数据了
      • 那这个是怎么实现的呢?实际上就通过这个 proxy 函数实现的
      • proxy 顾名思义就是代理
        • 它实际上通过这个 sharedPropertyDefinition 对象定义了一个get 和一个set 两个函数
        • 然后通过 Object.defineProperty这个方法,代理了这个 target 的 key
        • 就是对 target 的 key 的访问做了一层 get 和 set
        • 这个 target 实际上就是 vm,也就是说访问 vm[key], 这个 getter 函数就会执行,返回 this[sourceKey][key]
        • 这个 sourceKey 就是 _data, 也就是说,当我们访问 this.message 实际上是从 this._data.message 中获取的
      • 因为它是通过这个 proxy 做层代理,在调用 proxy 的时候,实际上就是把这个 _data 作为 sourceKey 传入
      • 这是通过 this.message 能狗访问到 message 定义到的数据的原因,但要注意,_data在后续开发中不要使用,这个_表示私有,不对外提供
      • 类似的,props的访问也类似,这里暂时跳过
      • 之后通过调用 observe 对 data 做一个响应式处理, 这块也跳过
  • 总结
    • 当执行 new Vue 的时候,它实际上是执行了 _init 的方法,这个方法做一堆初始化的工作
    • 先是对 options做合并,接下来就执行一系列的方法
    • 在其中的 initState 过程中,对 data 做一层 proxy 的处理,最后对data做响应式处理
    • 最后会调用 vm.$mount 将 el 进行挂载

扩展


调试Vue2源码小技巧

  • 一般而言,我们都在 dev 环境下调试源码的,没有说在生产环境下调试
  • 我们如果需要调试哪个API或相关流程,需要先建一个小demo项目,之后在当前项目的 node_modules 中
  • 找到 vue 目录,查看 package.json 找到 module 配置,dist/vue.runtime.esm.js
  • 其实这里,并不是真实的入口 !!!
  • 这个demo实际上是 vue-cli 生成的,这个模板工程是由webpack构建出来的,在build/webpack.base.conf.js 中的
  • resolve 属性中,配置了 alias
    {
      'vue': resolve('node_modules/vue/dist/vue.esm.js'),
      '@': resolve('src'),
    }
    
  • 所以,真实的源码是在 node_modules/vue/dist/vue.esm.js 中
  • 这个在 vue-cli 初始化工程的时候,如果选择了 Runtime + Compiler 版本时,会添加这个 alias 的 vue 这行代码
  • 所以,真正使用的是这个文件,这个是一个大的打包后的代码
  • 可以在这个文件中搜索方法,并进行 debugger
  • 后续如何调试,就忽略不再赘述了
人工蜂群优化算法(ABC)详解:原理、实现与应用 人工蜂群算法(Artificial Bee Colony, ABC)是Karaboga于2005年提出的一种群体智能优化算法,其灵感来源于蜜蜂群体在自然界中高效的采蜜行为。该算法通过模拟雇佣蜂、观察蜂和侦察蜂三种角色的分工协作来寻找最优解。在ABC算法中,每个潜在的解决方案被视为一个“食物源”,其质量由适应度函数评估。雇佣蜂负责在已知优质食物源附近进行局部开采;观察蜂根据雇佣蜂分享的信息,以一定概率选择并跟进优质食物源;侦察蜂则随机探索新区域,防止种群陷入局部最优。 阅读详情

相关推荐

VisionPro动态坐标系构建:CogFixtureTool与CogFixtureNPointToNPointTool实战解析

本文深入解析了VisionPro中动态坐标系构建的核心技术,重点对比了CogFixtureTool与CogFixtureNPointToNPointTool的原理与应用。通过实战案例,阐述了如何利用坐标跟随(Fixturing)技术解决工业视觉检测中因零件位置偏移导致的测量不准问题,实现从“固定”到“随动”的思维跃迁,从而构建稳定、精准的检测程序。

weixin_29207439的博客 565

vue数据驱动

以最简单那的方式去理解vue源码的执行过程

weixin_44922480的博客 1460

RAGFlow + GraphRAG+ 知识图谱 + QwQ32B探索实践

RagFlow 是一款开源的 RAG(Retrieval-Augmented Generation)引擎,专注于深度文档理解,能够助力企业及个人构建高效的 RAG 工作流程。借助大语言模型(LLM),它可以精准处理各种复杂格式的数据,为用户提供可靠的问答服务,并附上详实的引用依据。

技术引领业务创新 8万+

vue之数据驱动

提示:这里对文章进行总结:例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

粥可温 806

MATLAB 2022b 用户专属:保姆级IPOPT求解器安装配置全流程(含OPTI工具箱)

本文为MATLAB 2022b用户提供IPOPT求解器的详细安装与配置指南,涵盖OPTI工具箱的适配安装、环境变量设置及常见问题解决方案。通过实战案例演示非线性优化问题的求解过程,帮助用户高效部署这一强大的优化工具,提升工程计算效率。

weixin_29092031的博客 407

Linux性能调优之CPU时间分布(亲和性、带宽、权重)配置

带宽权重亲和性进程级别Cgroup-v2服务级别systyemd我所渴求的,無非是將心中脫穎語出的本性付諸生活,為何竟如此艱難呢 ------赫尔曼·黑塞《德米安》

山河已无恙 1717

初始化vue中data中的数据

当组件的根元素使用了v-if的时候, 并不会初始化data中的数据 如果想完全销毁该组件并且初始化数据,需要在使用该组件的本身添加v-if 或者是手动初始化该组件中的数据。

greed 4261

EtherCAT主站SOEM -- 24 -- 基于STM32F767IGT6搭建自己的SOEM-EtherCAT主站

以上就是EtherCAT主站SOEM – 23 – 基于STM32F767IGT6搭建自己的SOEM-EtherCAT主站的内容。有不明白的地方欢迎留言;有建议欢迎留言,我后面编写文档好改进。创作不容,如果文档对您有帮助,记得给个赞。

常驻客栈的博客--嵌入式EtherCAT 3560

Vue2.x源码:data初始化

vue数据响应式

YanHSama的博客 423

部署与可视化系统:移动端落地保姆级教程:YOLOv8 转换为 NCNN 并封装进 Android App 调用摄像头实时检测

本文介绍了在移动端部署YOLOv8模型的最佳实践。通过实测数据对比,采用NCNN框架在骁龙865设备上可实现35+FPS的实时推理性能。文章详细分析了NCNN相比TFLite、MNN等框架的优势,包括更高的ARM CPU性能、更优的Vulkan GPU加速和更小的模型体积。同时提供了从PyTorch模型到NCNN格式的完整转换流程,包括FP16优化和INT8量化方法,并重点解析了YOLOv8特有的输出格式处理要点。建议开发者先在Python环境验证NCNN推理流程,再部署到Android平台,以确保最佳性能

ZXSXJ的博客 210

Java 实现 MP3 音频合并工具类:摆脱 FFmpeg,高效低 CPU 的纯本地方案【需求已解决】

本方案充分发挥了 Java 音频 SPI 的潜力,实现了轻量、跨平台、低 CPU 消耗的 MP3 音频合并工具类。 对于希望在服务器或桌面应用中集成音频处理功能的开发者而言,这无疑是一个可行且优雅的解决方案。

一键难忘的博客 546

说说Vue的核心特点(一,数据驱动)

e.js的核心思想之一就是数据驱动。所谓数据驱动就是视图是依靠数据驱动实现的,我们对视图的修改不会直接去操作DOM,而是通过操作数据,Vue帮我们修改视图。相比于传统的jQuery的原生DOM开发,大大简化了代码量,而且用户体验也更加友好,不需要花费大量精力去维护。尤其是在业务量十分复杂的时候,只关心数据的改变,会让代码的逻辑更加清晰,也更易于维护。...

weixin_46886227的博客 953

15 媒体存储的封装3

CMediaDisk类真正实现了录像文件的存储、查询和点播。存储录像文件时,需要满足以下的几个基本需求:1、异常断电不能丢失数据。2、录像既支持普通文件的查询方式,也支持时间轴的查询方式。3、修改设备时间时,不能够丢失已有录像和数据。向后修改时间时,如果当前时间已有录像,则不覆盖老数据;直到有未录像的时间段时,才再次写入新数据。4、支持删除媒体文件,也支持按时间段删除。5、支持标记录像文件、抓拍文件,也支持按时间段标记。6、支持自动覆盖最老的文件。7、支持多个设备、多路视频的录像存储。

hope_wisdom的技术博客 1124

vm options什么意思_vue2.0源码解读 数据驱动:new Vue() 发生了什么?

关注“前端学苑” ,坚持每天进步一点点「~vue2.0源码解读~」Vue 一个核心思想是数据驱动,相信大家一定对这个不陌生。所谓数据驱动,就是视图由数据驱动生成。相比传统的使用jQuery等前端库直接操作DOM,大大提高了开发效率,代码简洁易维护。本篇文章主要是 Vue源码探秘之 “ 数据驱动 ”。主题内容:new Vue()发生了什么?这同样也是Vue必考面试题之一。正文从这里...

weixin_39627408的博客 357

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

3.为什么我的vue数据修改后视图未更新,梳理Vue2和Vue3中常见的数据不更新场景

Vue数据不更新问题的核心在于响应式机制的实现。Vue2受限于Object.defineProperty,需通过辅助方法(如$set)弥补不足;而Vue3通过Proxy提供了更完善的响应式支持。无论是Vue2还是Vue3,理解其原理并遵循“数据驱动视图”的原则,配合最佳实践,都能有效避免视图更新异常。

qq_1401820561的博客 2683

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

vue响应式源码解析(一)

vue响应式源码解析(一) vue.js逐渐热门,在前端框架领域崭露头角这几年,也逐渐成为了前端面试的必考知识点,下面我会通过一系列源码解析的文章来帮助大家梳理这一部分的内容,如有对我的观点持有异议,请在评论区留言,友好交流。 vue的响应式是怎么实现的? 响应式,即vue的数据驱动视图,数据层面发生变化,导致UI进行重新渲染。数据响应式这部分涉及到的知识点有关依赖收集、依赖触发这两部分,核心思想就是将数据变换成observe的getter和setter形式。 依赖收集 我们都知道在JavaScript中,

weixin_43823603的博客 769

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

如何彻底理解Vue.js响应式原理:数据驱动视图的核心机制全解析

Vue.js作为当前最流行的前端框架之一,其核心优势在于**响应式系统**——当数据发生变化时,视图会自动更新,无需手动操作DOM。这一机制让开发者能够专注于数据逻辑而非DOM操作,极大提升了开发效率。本文将用通俗易懂的方式,带您揭开Vue.js响应式原理的神秘面纱,掌握数据变化如何驱动视图更新的全过程。 ## 响应式系统的三大核心角色 Vue.js的响应式系统可以简单理解为一个"数据变化→自

gitblog_00976的博客 446

jiangkun-vue-resources--思路梳理

vue-recoucervideo01 video01 live server vue简单使用及使用流程-为手写提供思路 vue.js替换页面模板 手写替换页面模板 手写替换页面模板的问题

weixin_45439379的博客 289

vue - 响应式原理梳理(一)

描述  我们通过一个简单的 Vue应用 来演示 Vue的响应式属性: html: <div id="app"> {{message}} </div> js: let vm = new Vue({ el: '#app', data: {...

weixin_33775572的博客 176

Vue 2.x 核心扩展特性源码解析:event、v-model、slot、keep-alive 与 transition

在本系列对 Vue.js 源码的前几章分析中,我们已经完整梳理了 Vue 的响应式数据驱动与编译渲染机制。而除了这些核心能力之外,Vue 还内置了一系列极具生产力的扩展特性(feature)——`event` 事件体系、`v-model` 双向绑定、`slot` 插槽、`keep-alive` 组件缓存、`transition` / `transition-group` 过渡动画等。深入理解这些特

gitblog_00795的博客 235

Vue.js 源码分析:createComponent 如何把组件选项对象变成组件 VNode

`createComponent` 是 Vue.js 渲染管线中连接"组件选项对象"与"虚拟 DOM"的关键函数:当渲染函数执行到 `createElement` 时,普通 HTML 标签会直接生成元素 VNode,而组件(无论是模板中注册的标签、`h(Component)` 直传的组件对象,还是 `data.is` 动态绑定的组件)最终都会汇入 `createComponent`,产出一个携带构

gitblog_00080的博客 429

图解:vue依赖收集及派发更新

前面也已经写过vue的依赖收集和派发更新的源码讲解了,但是估计很多不了解的人看的仍然一头雾水,还是不能明白整个流程是怎样的。因此我又花了一些时间来弄懂这块梳理成图,阅读性也更好一些。 话不多说,开始上图了: 依赖收集 这里给大家备注一下某些变量的作用: subs: Dep类创建的,用于存放所有被收集的依赖即watcher,将依赖存放在subs中,目的是后面数据发生变化的时候能够通知那些subs做出准备。 watcher中定义的: newDeps:表示新添加的Dep实例数组 deps:表示上一次添加的De

yezi__6的博客 1680

dom不刷新 vue 加数据后_老板,vue又双叒叕出bug了,dom总是获取不到。 详解Vue异步更新和nextTick__Vue.js...

天啦,vue出bug了,DOM又不刷新了?工作中,用vue开发,经常会碰到用数据驱动dom,然后操作dom却没有效果的情况。如果有用到tab切换加上echarts展示,肯定是气的想砸桌子。下面来谈谈vue中dom的刷新。什么是DOM异步更新?所谓异步更新,就是vue中用数据去驱动dom,数据变化了,DOM却不会立即的更新,而是在下一个Tick中更新dom。当然,vue中手动操作DOM,DOM是立即...

weixin_42523326的博客 698
上一篇: 前端架构: 实现脚手架处理简单的命令注册和参数解析
下一篇: Vue2源码梳理:关于vm.$mount的实现
Wang's Blog
博客等级 码龄12年 7434粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值