当前位置: 首页 > news >正文

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

数据驱动


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 uidvm._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 observedvm._isVue = true// merge optionsif (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 selfvm._self = vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(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); // Helloconsole.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.$optionsif (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.datadata = 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 instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (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 dataobserve(data, true /* asRootData */)
    }export function getData (data: Function, vm: Component): any {// #7573 disable dep collection when invoking data getterspushTarget()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
  • 后续如何调试,就忽略不再赘述了

相关文章:

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

数据驱动 1 &#xff09;概述 vue的一个核心思想&#xff0c;就是数据驱动 所谓数据驱动&#xff0c;就是指视图是由数据驱动生成的 对视图的修改并不会直接操作dom&#xff0c;而是通过修改数据 它相比我们传统的前端开发&#xff0c;如使用 jQuery 的前端库直接去修改 dom…...

【C++航海王:追寻罗杰的编程之路】关于模板,你知道哪些?

目录 1 -> 泛型编程 2 -> 函数模板 2.1 -> 函数模板概念 2.2 -> 函数模板格式 2.3 -> 函数模板的原理 2.4 -> 函数模板的实例化 2.5 -> 函数参数的匹配原则 3 -> 类模板 3.1 -> 类模板的定义格式 3.2 -> 类模板的实例化 1 -> 泛型编…...

分布式springboot 3项目集成mybatis官方生成器开发记录

文章目录 说明实现思路实现步骤第一步&#xff1a;创建generator子模块第二步&#xff1a;引入相关maven插件和依赖第三步&#xff1a;编写生成器配置文件第四步&#xff1a;运行查看结果 说明 该文章为作者开发学习记录&#xff0c;方便以后复习和交流主要内容为&#xff1a;…...

算法学习——LeetCode力扣回溯篇4

算法学习——LeetCode力扣回溯篇4 332. 重新安排行程 332. 重新安排行程 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票…...

c++ STL系列——(六)multimap

C标准模板库&#xff08;STL&#xff09;是C编程中不可或缺的一部分&#xff0c;它提供了一系列的容器、算法和函数模板&#xff0c;以简化常见的数据结构和算法的实现。在STL中&#xff0c;multimap是一个非常有用的容器&#xff0c;它提供了一种键值对的存储方式&#xff0c;…...

Json-序列化字符串时间格式问题

序列化字符串时间格式问题 一、项目场景二、问题描述三、解决方案 一、项目场景 最近C#中需要将实体进行json序列化&#xff0c;使用了Newtonsoft.Json public static void TestJson(){DataTable dt new DataTable();dt.Columns.Add("Age", Type.GetType("Sys…...

HarmonyOS鸿蒙学习基础篇 - 自定义组件(一)

前言 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合使用&#xff0c;而是需要考虑代码可复用性、业务逻辑与UI分离&#…...

开窗,挖槽,放电齿,拼版

我们在阻焊层画线&#xff0c;就相当于去掉绿油阻焊&#xff0c;开窗一般是用在大电流走线的时候。先画要走的导线&#xff0c;之后切换到TopSolder或者Bottom Solder层&#xff0c;然后Place->line 画一条和原来先粗细一样的线即可&#xff01;但走电流的仍然是导线&#x…...

[Vue的组件通讯.sync修饰]Vue中.sync的使用方法和实现的方式 代码注释

目录 .sync的使用方法1. 在父组件中&#xff0c;将需要传递给子组件的数据使用v-bind绑定到子组件的props中&#xff0c;并在属性名后加上.sync修饰符&#xff0c;如下所示&#xff1a;2. 在子组件中&#xff0c;将需要传递给父组件的数据使用$emit方法触发一个名为update:valu…...

Java 基于springboot+vue在线外卖点餐系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…...

Decian 12.x基于LNMP安装phpIPAM(IP管理系统)

phpipam是一个开源Web IP地址管理应用程序&#xff08;IPAM&#xff09;。其目标是提供轻便&#xff0c;且有用的IP地址管理系统。它是基于PHP的应用程序&#xff0c;具有MySQL数据库后端&#xff0c;使用jQuery库&#xff0c;ajax和HTML5 / CSS3功能。 在Debian 12中&…...

【多模态MLLMs+图像编辑】MGIE:苹果开源基于指令和大语言模型的图片编辑神器(24.02.03开源)

项目主页&#xff1a;https://mllm-ie.github.io/ 论文 :基于指令和多模态大语言模型图片编辑 2309.Guiding Instruction-based Image Editing via Multimodal Large Language Models &#xff08;加州大学圣巴拉分校苹果&#xff09; 代码&#xff1a;https://github.com/appl…...

hpp文件:C++开发中的利器

1 什么是hpp文件&#xff1f; hpp文件是C程序中一种特殊头文件&#xff0c;它可以包含类的声明和实现。与传统的h文件相比&#xff0c;hpp文件具有以下特点&#xff1a; 将类的声明和实现放在同一个文件里&#xff0c;减少了代码量&#xff0c;提高了代码的可读性。无需再将c…...

如何查看电脑连接的wifi的密码

问题 很多时候我们电脑连上wifi之后就把密码忘记了&#xff0c;这个时候如果同事问自己密码是多少&#xff0c;如果作为程序员说不知道是不是感觉有点不好意思&#xff0c;哈哈…… 解决 我使用的是windows电脑&#xff0c;就以windows为例说明下自己是如何查看的。 打开wi…...

QTabWidget和QTabBar控件样式设置(qss)

QTabWidget和QTabBar控件样式设置 1、QTabWidget样式可自定义的有哪些示例&#xff1a;效果图 2、QTabBar样式可自定义的有哪些示例效果图 1、QTabWidget样式可自定义的有哪些 QTabWidget::pane{} 定义tabWidgetFrameQTabWidget::tab-bar{} 定义TabBar的位置QTabWidget::tab{}定…...

【智能家居入门3】(MQTT服务器、MQTT协议、微信小程序、STM32)

前面已经写了三篇博客关于智能家居的&#xff0c;服务器全都是使用ONENET中国移动&#xff0c;他最大的优点就是作为数据收发的中转站是免费的。本篇使用专门适配MQTT协议的MQTT服务器&#xff0c;有公用的&#xff0c;也可以自己搭建&#xff08;应该要钱&#xff09;&#xf…...

C语言第二十四弹---指针(八)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 指针 1、数组和指针笔试题解析 1.1、字符数组 1.1.1、代码1&#xff1a; 1.1.2、代码2&#xff1a; 1.1.3、代码3&#xff1a; 1.1.4、代码4&#xff1a; 1…...

m1芯片xcode15编译cocos2dx一些报错处理

报错1: No matching function for call to ‘iconv’ No matching function for call to ‘iconv_close’ 解决&#xff1a; 强转&#xff1a; iconv_close((iconv_t)_iconv); iconv((iconv_t)_iconv, (char**)&pin, &inLen, &pout, &outLen); 报错2: Proper…...

代码+视频基于R语言进行K折交叉验证

我们在建立数据模型后通常希望在外部数据验证模型的检验能力。然而当没有外部数据可以验证的时候&#xff0c;交叉验证也不失为一种方法。交叉验验证&#xff08;交叉验证&#xff0c;&#xff23;&#xff36;&#xff09;则是一种评估模型泛化能力的方法&#xff0c;广泛应用…...

第一篇【传奇开心果系列】Python的pyttsx3库技术点案例示例:文本转换语言

传奇开心果短博文系列 系列短博文目录Python的pyttsx3库技术点案例示例系列 短博文目录前言一、pyttsx3主要特点和功能介绍二、pyttsx3文字转语音操作步骤介绍三、多平台支持介绍和示例代码四、多语言支持介绍和示例代码五、自定义语言引擎介绍和示例代码六、调整语速和音量介绍…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

嵌入式常见 CPU 架构

架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集&#xff0c;单周期执行&#xff1b;低功耗、CIP 独立外设&#xff1b;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel&#xff08;原始…...