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.jsconst 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 )概述 vue的一个核心思想,就是数据驱动 所谓数据驱动,就是指视图是由数据驱动生成的 对视图的修改并不会直接操作dom,而是通过修改数据 它相比我们传统的前端开发,如使用 jQuery 的前端库直接去修改 dom…...
【C++航海王:追寻罗杰的编程之路】关于模板,你知道哪些?
目录 1 -> 泛型编程 2 -> 函数模板 2.1 -> 函数模板概念 2.2 -> 函数模板格式 2.3 -> 函数模板的原理 2.4 -> 函数模板的实例化 2.5 -> 函数参数的匹配原则 3 -> 类模板 3.1 -> 类模板的定义格式 3.2 -> 类模板的实例化 1 -> 泛型编…...
分布式springboot 3项目集成mybatis官方生成器开发记录
文章目录 说明实现思路实现步骤第一步:创建generator子模块第二步:引入相关maven插件和依赖第三步:编写生成器配置文件第四步:运行查看结果 说明 该文章为作者开发学习记录,方便以后复习和交流主要内容为:…...
算法学习——LeetCode力扣回溯篇4
算法学习——LeetCode力扣回溯篇4 332. 重新安排行程 332. 重新安排行程 - 力扣(LeetCode) 描述 给你一份航线列表 tickets ,其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票…...
c++ STL系列——(六)multimap
C标准模板库(STL)是C编程中不可或缺的一部分,它提供了一系列的容器、算法和函数模板,以简化常见的数据结构和算法的实现。在STL中,multimap是一个非常有用的容器,它提供了一种键值对的存储方式,…...
Json-序列化字符串时间格式问题
序列化字符串时间格式问题 一、项目场景二、问题描述三、解决方案 一、项目场景 最近C#中需要将实体进行json序列化,使用了Newtonsoft.Json public static void TestJson(){DataTable dt new DataTable();dt.Columns.Add("Age", Type.GetType("Sys…...
HarmonyOS鸿蒙学习基础篇 - 自定义组件(一)
前言 在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离&#…...
开窗,挖槽,放电齿,拼版
我们在阻焊层画线,就相当于去掉绿油阻焊,开窗一般是用在大电流走线的时候。先画要走的导线,之后切换到TopSolder或者Bottom Solder层,然后Place->line 画一条和原来先粗细一样的线即可!但走电流的仍然是导线&#x…...
[Vue的组件通讯.sync修饰]Vue中.sync的使用方法和实现的方式 代码注释
目录 .sync的使用方法1. 在父组件中,将需要传递给子组件的数据使用v-bind绑定到子组件的props中,并在属性名后加上.sync修饰符,如下所示:2. 在子组件中,将需要传递给父组件的数据使用$emit方法触发一个名为update:valu…...
Java 基于springboot+vue在线外卖点餐系统,附源码
博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…...
Decian 12.x基于LNMP安装phpIPAM(IP管理系统)
phpipam是一个开源Web IP地址管理应用程序(IPAM)。其目标是提供轻便,且有用的IP地址管理系统。它是基于PHP的应用程序,具有MySQL数据库后端,使用jQuery库,ajax和HTML5 / CSS3功能。 在Debian 12中&…...
【多模态MLLMs+图像编辑】MGIE:苹果开源基于指令和大语言模型的图片编辑神器(24.02.03开源)
项目主页:https://mllm-ie.github.io/ 论文 :基于指令和多模态大语言模型图片编辑 2309.Guiding Instruction-based Image Editing via Multimodal Large Language Models (加州大学圣巴拉分校苹果) 代码:https://github.com/appl…...
hpp文件:C++开发中的利器
1 什么是hpp文件? hpp文件是C程序中一种特殊头文件,它可以包含类的声明和实现。与传统的h文件相比,hpp文件具有以下特点: 将类的声明和实现放在同一个文件里,减少了代码量,提高了代码的可读性。无需再将c…...
如何查看电脑连接的wifi的密码
问题 很多时候我们电脑连上wifi之后就把密码忘记了,这个时候如果同事问自己密码是多少,如果作为程序员说不知道是不是感觉有点不好意思,哈哈…… 解决 我使用的是windows电脑,就以windows为例说明下自己是如何查看的。 打开wi…...
QTabWidget和QTabBar控件样式设置(qss)
QTabWidget和QTabBar控件样式设置 1、QTabWidget样式可自定义的有哪些示例:效果图 2、QTabBar样式可自定义的有哪些示例效果图 1、QTabWidget样式可自定义的有哪些 QTabWidget::pane{} 定义tabWidgetFrameQTabWidget::tab-bar{} 定义TabBar的位置QTabWidget::tab{}定…...
【智能家居入门3】(MQTT服务器、MQTT协议、微信小程序、STM32)
前面已经写了三篇博客关于智能家居的,服务器全都是使用ONENET中国移动,他最大的优点就是作为数据收发的中转站是免费的。本篇使用专门适配MQTT协议的MQTT服务器,有公用的,也可以自己搭建(应该要钱)…...
C语言第二十四弹---指针(八)
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】 指针 1、数组和指针笔试题解析 1.1、字符数组 1.1.1、代码1: 1.1.2、代码2: 1.1.3、代码3: 1.1.4、代码4: 1…...
m1芯片xcode15编译cocos2dx一些报错处理
报错1: No matching function for call to ‘iconv’ No matching function for call to ‘iconv_close’ 解决: 强转: iconv_close((iconv_t)_iconv); iconv((iconv_t)_iconv, (char**)&pin, &inLen, &pout, &outLen); 报错2: Proper…...
代码+视频基于R语言进行K折交叉验证
我们在建立数据模型后通常希望在外部数据验证模型的检验能力。然而当没有外部数据可以验证的时候,交叉验证也不失为一种方法。交叉验验证(交叉验证,CV)则是一种评估模型泛化能力的方法,广泛应用…...
第一篇【传奇开心果系列】Python的pyttsx3库技术点案例示例:文本转换语言
传奇开心果短博文系列 系列短博文目录Python的pyttsx3库技术点案例示例系列 短博文目录前言一、pyttsx3主要特点和功能介绍二、pyttsx3文字转语音操作步骤介绍三、多平台支持介绍和示例代码四、多语言支持介绍和示例代码五、自定义语言引擎介绍和示例代码六、调整语速和音量介绍…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究
摘要:在消费市场竞争日益激烈的当下,传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序,探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式,分析沉浸式体验的优势与价值…...
PydanticAI快速入门示例
参考链接:https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...
shell脚本质数判断
shell脚本质数判断 shell输入一个正整数,判断是否为质数(素数)shell求1-100内的质数shell求给定数组输出其中的质数 shell输入一个正整数,判断是否为质数(素数) 思路: 1:1 2:1 2 3:1 2 3 4:1 2 3 4 5:1 2 3 4 5-------> 3:2 4:2 3 5:2 3…...
