Vue源码学习 - new Vue初始化都做了什么?
目录
- 前言
- 一、创建一个 Vue 实例
- 二、找到 Vue 构造函数
- 三、源码分析 - Vue.prototype._init
- 四、源码分析 - 调用 $mount 方法,进入挂载阶段
- 五、总结
前言
使用Vue也有一段时间了,最近去阅读了Vue的源码,想总结分享下学到的新东西。
如果觉得直接看源码很枯燥,可以结合前人总结的文章或者视频来看,相信会事半功倍。
源码这个东西,一定要多看多思考,要想精通,一遍两遍肯定是不够的。有的时候可能看着一个问题就会想通之前看过但是不明白的另个问题。
打算出一个Vue源码系列性的文章,算是我个人学习源码的一个历程。
首先找到Vue项目 github 地址:vue2.x源码链接,git clone xxx
下载源码。
一、创建一个 Vue 实例
新建一个 html 文件引入vue。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><div id="app"></div><script src="./vue-2.7.14/dist/vue.js"></script></script><script>new Vue({el:'#app',})</script>
</body>
</html>
vue 初始化就从这里开始了。
二、找到 Vue 构造函数
// src/core/instance/index.tsimport { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
import type { GlobalAPI } from 'types/global-api'// Vue构造函数的声明
function Vue(options) {if (__DEV__ && !(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword')}// 初始化方法this._init(options)
}// 从文件中可以看出 上面的 _init() 是从下面的混入中获得的,那么具体从哪个中得到的需要分析一下// 初始化混入
initMixin(Vue)
// state的混入
stateMixin(Vue)
// events的混入
eventsMixin(Vue)
// 生命周期的混入
lifecycleMixin(Vue)
// 渲染函数的混入
renderMixin(Vue)// 上面的这些混入其实就是初始化实例的方法和属性
// 其实通过名字不难发现, _init() 方法肯定是在初始化的混入中:initMixin()export default Vue as unknown as GlobalAPI
其实通过名字不难发现, _init()
方法肯定是在 初始化的混入中: initMixin()
,那就继续看 initMixin()
所在的文件。
三、源码分析 - Vue.prototype._init
// src/core/instance/init.tsexport function initMixin(Vue: typeof Component) {// 负责 Vue 的初始化过程;接收用户传进来的选项:optionsVue.prototype._init = function (options?: Record<string, any>) {// vue的实例const vm: Component = this// 每个 vue 实例都有一个 _uid,并且是依次递增的vm._uid = uid++let startTag, endTagif (__DEV__ && config.performance && mark) {startTag = `vue-perf-start:${vm._uid}`endTag = `vue-perf-end:${vm._uid}`mark(startTag)}// vue标志, 避免被 Observe 观察vm._isVue = truevm.__v_skip = truevm._scope = new EffectScope(true)vm._scope._vm = true// 选项合并:用户选项和系统默认的选项需要合并// 处理组件的配置内容,将传入的options与构造函数本身的options进行合并(插件的策略都是默认配置和传入配置进行合并)if (options && options._isComponent) {// 子组件:优化内部组件(子组件)实例化,且动态的options合并相当慢,这里只有需要处理一些特殊的参数属性。减少原型链的动态查找,提高执行效率initInternalComponent(vm, options as any)} else {// 根组件: 将全局配置选项合并到根组件的配置上,其实就是一个选项合并vm.$options = mergeOptions(// 获取当前构造函数的基本optionsresolveConstructorOptions(vm.constructor as any),options || {},vm)}if (__DEV__) {initProxy(vm)} else {vm._renderProxy = vm}vm._self = vm// 下面的方法才是整个初始化最重要的核心代码initLifecycle(vm) // 初始化实例的属性、数据:$parent, $children, $refs, $root, _watcher...等initEvents(vm) //初始化事件:$on, $off, $emit, $onceinitRender(vm) // 初始化render渲染所需的slots、渲染函数等。其实就两件事1、插槽的处理、2、$createElm 也就是 render 函数中的 h 的声明callHook(vm, 'beforeCreate', undefined, false /* setContext */) // 调用生命周期的钩子函数,在这里就能看出一个组件在创建之前和之后分别做了哪些初始化// provide/inject 隔代传参// provide:在祖辈中可以直接提供一个数据 // inject:在后代中可以通过inject注入后直接使用initInjections(vm) // 在 data/props之前执行;隔代传参时 先inject。作为一个组件,在要给后辈组件提供数据之前,需要先把祖辈传下来的数据注入进来initState(vm) // 数据响应式的重点,处理 props、methods、data、computed、watch初始化initProvide(vm) // 在 data/props之后执行;在把祖辈传下来的数据注入进来以后 再provide// 总而言之,上面的三个初始化其实就是:对组件的数据和状态的初始化callHook(vm, 'created') // created 初始化完成,可以执行挂载了if (__DEV__ && config.performance && mark) {vm._name = formatComponentName(vm, false)mark(endTag)measure(`vue ${vm._name} init`, startTag, endTag)}// 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount,反之,没有 el 则必须手动调用 $mountif (vm.$options.el) {// 调用 $mount 方法,进入挂载阶段vm.$mount(vm.$options.el)}}
}
四、源码分析 - 调用 $mount 方法,进入挂载阶段
打开 $mount
,看看它做了什么。把一些多余的 代码简化一下。
// src/platforms/web/runtime-with-compiler.tsimport config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import {shouldDecodeNewlines,shouldDecodeNewlinesForHref
} from './util/compat'
import type { Component } from 'types/component'
import type { GlobalAPI } from 'types/global-api'// 获取宿主元素的方法
const idToTemplate = cached(id => {const el = query(id)return el && el.innerHTML
})// 扩展 $mount 方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {el = el && query(el)// 获取选项 $options const options = this.$options/*** 编译权重:* 优先看有没有render函数,如果有直接用* 如果没有render函数就看有没有template模板* 如果都没有就直接获取el的outerHTML作为渲染模板*/// 如果 render 选项不存在if (!options.render) {// 则查找 templatelet template = options.template// 如果 template 存在if (template) {// 则判断一下 template 的写法if (typeof template === 'string') { // 如果是字符串模板 例如:"<div> template </div>"if (template.charAt(0) === '#') { // 如果是宿主元素的选择器,例如:"#app"// 则调用上面的 idToTemplate() 方法查找template = idToTemplate(template)if (__DEV__ && !template) {warn(`Template element not found or is empty: ${options.template}`,this)}}// 如果是一个dom元素} else if (template.nodeType) {// 则使用它的 innerHTMLtemplate = template.innerHTML} else {if (__DEV__) {warn('invalid template option:' + template, this)}return this}// 如果设置了 el } else if (el) {// 则以 el 的 outerHTML 作为 templatetemplate = getOuterHTML(el)}// 如果存在 template 选项,则编译它获取 render 函数if (template) {// 编译的过程:把 template 变为 render 函数const { render, staticRenderFns } = compileToFunctions(template,{outputSourceRange: __DEV__,shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments},this)// 最终获得的 render 函数将赋值给 选项 optionsoptions.render = renderoptions.staticRenderFns = staticRenderFns// 执行默认的挂载return mount.call(this, el, hydrating)
}/*** 总结一下:* new Vue({* el: "#app",* template: "<div> template </div>",* template: "#app",* render(h){ return h("div", "render")},* data: {}* })* 在用户同时设置了 el、template、render的时候,优先级的判断为:render > template > el*/ // 获取 outerHTML 的方法
function getOuterHTML(el: Element): string {if (el.outerHTML) {return el.outerHTML} else {const container = document.createElement('div')container.appendChild(el.cloneNode(true))return container.innerHTML}
}Vue.compile = compileToFunctionsexport default Vue as GlobalAPI
上面代码 主要 实现了 vue 渲染过程中很重要的一步,得到 render
函数。
如果我们使用的 template
进行编写 HTML 代码,Vue 内部会把模板编译成 Vue 可识别的 render
函数,如果有写 render 则可以省去编译过程。( 直接写 render 函数对 vue 编译效率会更好 )
上面 entry-runtime-with-compiler.js 文件中的Vue来自于 ‘./runtime/index’,那我们自己分析 ‘./runtime/index’ 文件。
// src/platforms/web/runtime/index.ts// 能看到 Vue也不是在这里定义的,一样是导入的,那么这个文件主要做了什么呢?
import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'import {query,mustUseProp,isReservedTag,isReservedAttr,getTagNamespace,isUnknownElement
} from 'web/util/index'import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'
import type { Component } from 'types/component'//...// 安装了一个 patch 函数,也可以叫补丁函数或者更新函数。主要的作用就是把:虚拟dom 转化为真实的dom(vdom => dom)
Vue.prototype.__patch__ = inBrowser ? patch : noop// 实现了 $mount 方法:其实就只调用了一个mountComponent()方法
// $mount的最终目的就是:把虚拟dom 转化为真实的dom,并且追加到宿主元素中去(vdom => dom => append)
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {el = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating)
}export default Vue
下面打开源码 src/core/instance/lifecycle.js 找到 mountComponent 方法
// src/core/instance/lifecycle.tsexport function mountComponent(...): Component {// 调用生命周期钩子函数callHook(vm, 'beforeMount')let updateComponent// 创建一个更新渲染函数; 调用 _update 对 render 返回的虚拟 DOM 进行 patch(也就是 Diff )到真实DOM,这里是首次渲染updateComponent = () => {vm._update(vm._render(), hydrating)}// 当触发更新的时候,会在更新之前调用const watcherOptions: WatcherOptions = {before() {// 判断 DOM 是否是挂载状态,就是说首次渲染和卸载的时候不会执行if (vm._isMounted && !vm._isDestroyed) {// 调用生命周期钩子函数callHook(vm, 'beforeUpdate')}}}//生成一个渲染 watcher 每次页面依赖的数据更新后会调用 updateComponent 进行渲染new Watcher(vm,updateComponent,noop,watcherOptions,true )// 没有老的 vnode,说明是首次渲染if (vm.$vnode == null) {vm._isMounted = true// 渲染真实 dom 结束后调用 mounted 生命周期callHook(vm, 'mounted')}return vm
}
五、总结
到此整个Vue初始化就完成了,具体的细化代码这里不展示了,主要的就是做标注的代码,这里再做个总结吧。
从上面的函数看来,new Vue所做的事情,就像一个流程图一样展开了,分别是
选项合并
,处理组件的配置内容,将传入的options与构造函数本身的options进行合并(用户选项和系统默认的选项进行合并)- 初始化
vue实例生命周期
相关的属性,组件关系属性的初始化,定义了比如$parent
、$children
、$root
、$refs
等。 - 初始化
事件
,若存在父监听事件,则添加到该实例上。 - 初始化
render渲染
所需的slots、渲染函数等。其实就两件事:插槽的处理 和 $createElm的声明,也就是 render 函数中的 h 函数的声明。 - 调用
beforeCreate
钩子函数,在这里就能看出一个组件在创建前和后分别做了哪些初始化。 - 初始化注入数据,隔代传参时 先inject。作为一个组件,在要给后辈组件提供数据之前,需要先把祖辈传下来的数据注入进来。
- 对
props
,methods
,data
,computed
,watch
进行初始化,包括响应式的处理。 - 再把祖辈传下来的数据注入进来以后 再初始化provide。
- 调用
created
钩子函数,初始化完成,可以执行挂载了。 - 挂载到对应
DOM
元素上。如果组件构造函数设置了el选项,会自动挂载,所以就不用再手动调用$mount
去挂载。
可参考:
Vue源码系列(二):Vue初始化都做了什么?
vue源码阅读解析(超详细)
相关文章:
Vue源码学习 - new Vue初始化都做了什么?
目录 前言一、创建一个 Vue 实例二、找到 Vue 构造函数三、源码分析 - Vue.prototype._init四、源码分析 - 调用 $mount 方法,进入挂载阶段五、总结 前言 使用Vue也有一段时间了,最近去阅读了Vue的源码,想总结分享下学到的新东西。 如果觉得…...

新零售数字化商业模式如何建立?新零售数字化营销怎么做?
随着零售行业增速放缓、用户消费结构升级,企业需要需求新的价值增长点进行转型升级,从而为消费者提供更为多元化的消费需求、提升自己的消费体验。在大数据、物联网、5G及区块链等技术兴起的背景下,数字化新零售系统应运而生。 开利网络认为&…...

C++语法(26)--- 特殊类设计
C语法(25)--- 异常与智能指针_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131537799?spm1001.2014.3001.5501 目录 1.特殊类设计 1.设计一个类,不能被拷贝 C98 C11 2.设计一个类,只能在堆上…...

YAML+PyYAML笔记 2 | YAML缩进、分离、注释简单使用
2 | YAML缩进、分离、注释简单使用 1 简介2 缩进3 分离4 多行文本4.1 折叠块4.2 字面块4.3 引用块 5 注释5.1 行内注释5.2 块注释5.3 完美注释示例 1 简介 YAML 不是一种标记语言,而是一种数据格式;使用缩进和分离来表示数据结构,不需要使用…...

Array(20) 和 Array.apply(null, {length: 20})
1.Array(20) 其结果是: 创建了一个长度为20,但元素均为 empty 的数组。 2.Array.apply(null, { length: 20 }) 其结果是: 创建了一个长度为20,但元素均为 undefined 的数组。 3.异同 3.1相同 console.log(arr1[0] arr2[0]) /…...

Mind+积木编程控制小水泵给宠物喂水
前期用scratch,带着小朋友做了大鱼吃小鱼、桌面弹球、小学生计算器3个作品,小朋友收获不小。关键是小家伙感兴趣,做出来后给家人炫耀了一圈后,兴趣大增,嚷嚷着要做更好玩的。 最近,娃妈从抖音上买了个小猫喝…...

【Linux从入门到精通】进程的控制(进程替换)
本篇文章会对进程替换进行讲解。希望本篇文章会对你有所帮助 文章目录 一、进程替换概念 二、进程替换函数 2、1 execl 2、2 execlp 2、3 execv 2、3 execle 2、4 execve 三、总结 🙋♂️ 作者:Ggggggtm 🙋♂️ 👀 专栏&…...

rancher平台上强制删除pod服务操作
背景: 在日常paas平台运维工作中需要对rancher平台进行巡检的工作,在巡检时发现在rancher管理界面无法删除异常的pod服务, 处理: 像这样的情况就是k8s集群的pod无法通过默认的方式去删除掉pod服务,这时候只能是手工强制…...

【Docker】Docker的通信安全
Docker的通信安全 前言一、Docker 容器与虚拟机的区别1. 隔离与共享2. 性能与损耗 二、Docker 存在的安全问题1. Docker 自身漏洞2. Docker 源码问题 三、Docker 架构缺陷与安全机制1. 容器之间的局域网攻击2. DDoS 攻击耗尽资源3. 有漏洞的系统调用4. 共享 root 用户权限 四、…...
c# 函数中可选参数太多,想设置最后一个参数,又不想修改前面默认参数
C#中,你可以使用命名参数来指定你想要设置的可选参数,而保留其他参数的默认值不变。通过使用命名参数,你可以根据需要选择要为哪些参数提供值,而无需按照它们在函数签名中的顺序提供参数值。 以下是一个示例,演示如何…...
openvino资料(1)
1、c++ - OpenVino model outputs zeroes - Stack Overflow 2、https://chinait-intel.oss-cn-beijing.aliyuncs.com/OpenVINO/Ubuntu20.04%E7%8E%AF%E5%A2%83%E4%B8%8B%E4%BD%BF%E7%94%A8OpenVINO%E9%83%A8%E7%BD%B2BiSeNetV2%E6%A8%A1%E5%9E%8B.pdf 3、c++ - How to cre...

第71篇:某银行外网打点到内网核心区红队评估复盘
Part1 前言 大家好,我是ABC_123。本期分享一篇ABC_123曾经做的针对一家银行的红队评估项目,持续时间两周,难度非常大,但是最终打到了银行核心业务区,今天就复盘一下全过程,希望红蓝双方都能得到一些启示&a…...
网络安全 Day21-数据库知识
数据库知识 1. 什么是数据库2. 为什么需要数据库(分类不清晰)3. 数据库的种类3.1 关系型数据库3.2 NOSQL 数据库3.3 new sql (国产数据库)分布式数据库3.4 云数据库 4. mysql 关系型数据库5. 安装mariadb6. 为mariadb设置密码7. M…...

python测试开发面试常考题:装饰器
目录 简介 应用 第一类对象 装饰器 描述器descriptor 资料获取方法 简介 Python 装饰器是一个可调用的(函数、方法或类),它获得一个函数对象 func_in 作为输入,并返回另一函数对象 func_out。它用于扩展函数、方法或类的行为。 装饰器模式通常用…...

语音同声翻译软件让你不再为语言障碍困扰
从前有一个叫黄俊的小伙子,他有一个大梦想:环游世界!但是,他只会说中文,而去到外国又怎么跟当地人交流呢?为了实现自己的梦想,黄俊开始了寻找能帮他解决问题的捷径。这时,方娜向他介…...
又有一个手艺人震惊了B站用户
飞瓜数据(B站版)【热门视频榜】周榜显示,霸占全站视频流量第一的是来自UP主爱捣鼓的邢志磊发布的作品《我花了半年时间给猫做了个房子》。 视频在一周时间内新增播放1232.2万,新增点赞139.4万。 根据视频详细数据显示,…...

HDFS的设计目标和重要特性
HDFS的设计目标和重要特性 设计目标HDFS重要特性主从架构分块存储机制副本机制namespace元数据管理数据块存储 设计目标 硬件故障(Hardware Failure)是常态,HDFS可能有成百上千的服务器组成,每一个组件都有可能出现故障。因此古见检测和自动快速恢复的H…...

【JMeter】JMeter添加插件
目录 一、前言 二、插件管理器 三、推荐插件 1.Custom Thread Groups (1)Ultmate Thread Group (2)Stepping Thread Group 2.3 Basic Graph 资料获取方法 一、前言 在我们的工作中,我们可以利用一些插件来帮…...
测牛学堂:车载测试面试总结之语音助手相关
车载语音助手的工作原理? 语音助手的工作原理总结下来可以分为4个步骤: 1 通过麦克风采集驾驶员的语音指令 2将语音信号转换为数字信号 3过语音识别技术将语音指令转换为计算机可以理解的指令 4 通过语音合成技术将计算机的回应转换为语音输出 车载…...

Android开发之Fragment动态添加与管理
文章目录 主界面布局资源两个工具Fragment主程序 主界面布局资源 在activity_main.xml中,声明两个按钮备用,再加入一个帧布局,待会儿用来展示Fragment。 <?xml version"1.0" encoding"utf-8"?> <LinearLayo…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...