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
  • 后续如何调试,就忽略不再赘述了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/682374.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 基于 SpringBoot+Vue 的智慧外贸平台的研究与实现,附源码

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

【Vue】Vue基础入门

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Vue ⛺️稳重求进&#xff0c;晒太阳 Vue概念 是一个用于构建用户界面的渐进式框架优点&#xff1a;大大提高开发效率缺点&#xff1a;需要理解记忆规则 创建Vue实例 步骤&#xff1a; …

Arm发布新的人工智能Cortex-M处理器

Arm发布了一款新的Cortex-M处理器&#xff0c;旨在为资源受限的物联网&#xff08;IoT&#xff09;设备提供先进的人工智能功能。这款新的Cortex-M52声称是最小的、面积和成本效率最高的处理器&#xff0c;采用了Arm Helium技术&#xff0c;使开发者能够在单一工具链上使用简化…

小结与数字的魅力的开篇

小结 本系列主要介绍了一些排序算法&#xff0c;包括冒泡排序、快速排序、直接插入排序、希尔排序、简单选择排序、堆排序、归并排序、计数排序、桶排序和基数排序。 排序算法本身并不难&#xff0c;但其涉及的知识点却星罗棋布&#xff0c;其变化莫测的思路更让人难以捉摸&am…

Socket.D 开源输传协议 v2.4.0 发布

Socket.D 协议 是基于"事件"和"语义消息""流"的网络应用层传输协议。有用户说&#xff0c;“Socket.D 之于 Socket&#xff0c;尤如 Vue 之于 Js、Mvc 之于 Http”。支持 tcp, udp, ws, kcp 传输。协议特点可参考《官网介绍》。 pyton 已开发完…

移动机器人激光SLAM导航(五):Cartographer SLAM 篇

参考 Cartographer 官方文档Cartographer 从入门到精通 1. Cartographer 安装 1.1 前置条件 推荐在刚装好的 Ubuntu 16.04 或 Ubuntu 18.04 上进行编译ROS 安装&#xff1a;ROS学习1&#xff1a;ROS概述与环境搭建 1.2 依赖库安装 资源下载完解压并执行以下指令 https://pa…

Spring Boot 笔记 009 创建接口_更新用户基本信息

1.1.1 给User实体类添加校验 package com.geji.pojo;import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta…

【Java程序设计】【C00260】基于Springboot的企业客户信息反馈平台(有论文)

基于Springboot的企业客户信息反馈平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的企业客户信息反馈平台 本系统分为平台功能模块、管理员功能模块以及客户功能模块。 平台功能模块&#xff1a;在平台首页可…

FastAI 之书(面向程序员的 FastAI)(六)

原文&#xff1a;www.bookstack.cn/read/th-fastai-book 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十三章&#xff1a;卷积神经网络 原文&#xff1a;www.bookstack.cn/read/th-fastai-book/44d8848dfac0c1b0.md 译者&#xff1a;飞龙 协议&#xff1a;CC BY-N…

【Java程序设计】【C00257】基于Springboot的校园二手书交易平台(有论文)

基于Springboot的校园二手书交易平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的乐校园二手书交易管理系统 本系统分为系统功能模块、管理员功能模块、卖家用户功能模块以及用户功能模块。 系统功能模块&…

【教程】C++语言基础学习笔记(九)——指针

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【C语言基础学习】系列文章 第一章 《项目与程序结构》 第二章 《数据类型》 第三章 《运算符》 第四章 《流程控制》 第五章…

TCP和UDP面试题提问

@TOC TCP(传输控制协议)和UDP(用户数据报协议)是两种计算机网络通信协议,它们在网络通信中起着不同的作用。 TCP TCP 是面向连接的协议,它在数据传输之前需要在发送端和接收端建立一条连接。TCP 提供可靠的数据传输,它使用确认和重传机制来确保数据的可靠性和完整性。…

消息中间件特点

1.  消息中间件概念 消息中间件是消息传递的过程中保存消息的容器。 主要目的&#xff1a;提供路由并保证消息的传递&#xff1b;如果发送消息时接受者不可用&#xff0c;消息队列会保留信息&#xff0c;直到可以成功传递为止。 消息中间件保存消息也是有期限的。 2.  消息…

4.Swift可选类型

Swift 可选类型 在 Swift 中&#xff0c;可选类型是一种特殊的类型&#xff0c;用于表示一个值可能存在&#xff0c;也可能不存在&#xff08;即为 nil&#xff09;。可选类型在处理可能缺失数值的情况下非常有用&#xff0c;它可以帮助开发者避免空指针异常等问题。以下是关于…

车载软件架构 —— Adaptive AUTOSAR软件架构

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师&#xff08;Wechat&#xff1a;gongkenan2013&#xff09;。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 本就是小人物&#xff0c;输了就是输了&#…

【Zigbee课程设计系列文章】Zigbee开发环境搭建

【Zigbee课程设计系列文章】Zigbee开发环境搭建 前言IAR 下载安装Z-Stack协议栈安装 &#x1f38a;项目专栏&#xff1a;【Zigbee课程设计系列文章】&#xff08;附详细使用教程完整代码原理图完整课设报告&#xff09; 前言 &#x1f451;由于无线传感器网络&#xff08;也即…

每日一练——月落乌啼算钱

题目&#xff1a; 举例&#xff1a; 输入&#xff1a;6&#xff0c;输出&#xff1a;8.00 最开始看到这道题还有点蒙&#xff0c;但是看到他的公式想起了斐波那契数列 1,1,2,3,5,8...... 由前两个数相加得到第三个数&#xff0c;为An2An1An。 可以得出这个题目中所给的通项就…

大更新!gpt加入记忆功能,可以记忆自己的提问啦!

2月14日凌晨&#xff0c;OpenAI在官网宣布&#xff0c;正在测试ChatGPT记住用户提问内容的能力&#xff0c;同时可以自由控制其内存。 该功能使用户不必频繁地提问相同的内容&#xff0c;ChatGPT都将记住那些内容并对长对话、个性化聊天等&#xff0c;例如&#xff0c;写长文小…

基于Python的HTTP隧道安全性分析:魔法背后的锁与钥匙

当我们谈论基于Python的HTTP隧道时&#xff0c;不禁让人想起那些神秘的魔法门。但是&#xff0c;在魔法背后&#xff0c;我们也需要确保安全性&#xff0c;就像需要确保魔法不会落入邪恶之手一样。那么&#xff0c;基于Python的HTTP隧道在安全性方面表现如何呢&#xff1f;让我…

小区开店的成功秘诀:如何满足老年人的需求?

在鲜奶吧行业摸爬滚打 5 年&#xff0c;我深切体会到&#xff0c;要在小区成功开店&#xff0c;满足老年人的需求至关重要。以下是我总结的一些经验&#xff0c;希望能给想开鲜奶吧或正在创业的朋友们提供有价值的参考。 一、产品特色与健康理念 1、产品特色&#xff1a; 提…