我们学习Vue都是从下面这个例子开始的

new Vue({render: h => h(App),
}).$mount('#app')

事实上,所有的Vue项目的组成组件都是一个Vue的实例,最后由根部的Vue实例去挂载到DOM上,当然这个"挂载"的操作可以针对不同的平台而有不同的行为,比如挂载到移动设备上就成了weex,挂载到小程序上就成了mpvue。所以首先我们要知道Vue实例里面含有哪些东西。

实际上,这个$mount就等同于React里的ReactDOM.render。

Vue构造函数

我是全局搜索关键词"function Vue"来定位到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

代码很简单,就是用function的方式定义了Vue类,之后只要new Vue一下就可以生成Vue的实例了。在构造函数里我们看到调用了_init( )方法,但是上下文中并没有看到_init( )方法的定义,往下看,有一堆的mixin方法,这些方法其实就是在Vue的prototype上增加各种方法,_init( )也就是在这些mixin调用过后添加的。

列举一下Vue里约定俗成前缀

_ 表示私有,这个和一般的约定一致。

$ 表示实例上的属性或方法,比如$mount。

另外还有一些方法是定义在全局的,也就是Vue构造函数上的,比如Vue.component。

initMixin

刚才看到的_init( )方法就是在这里定义出来的,抽掉一些不重要的代码后

Vue.prototype._init = function (options?: Object) {const vm: Component = thisvm._uid = uid++vm._isVue = trueif (options && options._isComponent) {initInternalComponent(vm, options)} else {vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},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')if (vm.$options.el) {vm.$mount(vm.$options.el)}}
}

这一段执行完成后,相当于生命周期的

大致干了这些事情

对于每一个生成的Vue实例在内部都用_uid来跟踪

合并option对象

initLifecycle方法里定义了许多和生命周期有关的变量,有些是内部的,有些是暴露在实例上的

export function initLifecycle (vm: Component) {const options = vm.$options// locate first non-abstract parentlet parent = options.parentif (parent && !options.abstract) {while (parent.$options.abstract && parent.$parent) {parent = parent.$parent}parent.$children.push(vm)}vm.$parent = parentvm.$root = parent ? parent.$root : vmvm.$children = []vm.$refs = {}vm._watcher = nullvm._inactive = nullvm._directInactive = falsevm._isMounted = falsevm._isDestroyed = falsevm._isBeingDestroyed = false
}

initEvents针对配置里传进来事件,做了一些操作。

initRender里初始化了一些跟虚拟DOM渲染有关的属性和方法,比如后面会登场的赫赫有名的_c方法和实例上的$createElement方法,而把方法连接到实例上其实就是为了在渲染的时候能获得实例的上下文

export function initRender (vm: Component) {vm._vnode = null // the root of the child treevm._staticTrees = null // v-once cached treesconst options = vm.$optionsconst parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent treeconst renderContext = parentVnode && parentVnode.contextvm.$slots = resolveSlots(options._renderChildren, renderContext)vm.$scopedSlots = emptyObjectvm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)}

接着callHook方法第一次登场,从名字就可以看出来callHook就是用来调用钩子函数。

export function callHook (vm: Component, hook: string) {// #7573 disable dep collection when invoking lifecycle hookspushTarget()const handlers = vm.$options[hook]const info = `${hook} hook`if (handlers) {for (let i = 0, j = handlers.length; i < j; i++) {invokeWithErrorHandling(handlers[i], vm, null, vm, info)}}if (vm._hasHookEvent) {vm.$emit('hook:' + hook)}popTarget()
}

它还是通过$emit来调用的,所以在后面的章节要睁大眼睛看一下Vue里事件的实现。

下面的Injection和Provider有点陌生,查了下官网上说

provideinject主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

而夹在中间的initState( )又是重中之重,初始化了Vue的几大响应式组成部分,计划下一篇文章就是探究这方面的原理的。

最后如果找到配置项里有el,就执行挂载的方法。当然也可以如之前的代码所示,调用Vue实例上的$mount来执行挂载,对应到生命周期图里的这个部分。

stateMixin

这里面定义了一个响应式里很重要的方法——$watch,也会放到后文来详述。

eventsMixin

实例上事件的四大方法

  • vm.$on
  • vm.$once
  • vm.$off
  • vm.$emit

先看$on

Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {const vm: Component = thisif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$on(event[i], fn)}} else {(vm._events[event] || (vm._events[event] = [])).push(fn)if (hookRE.test(event)) {vm._hasHookEvent = true}}return vm}

其实就是把传入的方法回调,放在Vue的实例上,而事件的名字就作为键,对应的值是一个数组,表明同一个事件可以调用多个回调方法,传入的event也能是一个数组,不过这种不同的事件上调用同一个回调的事情现实中应该不多吧。

可以做一下实验

有$on就必有$off

Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {const vm: Component = this// allif (!arguments.length) {vm._events = Object.create(null)return vm}// array of eventsif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$off(event[i], fn)}return vm}// specific eventconst cbs = vm._events[event]if (!cbs) {return vm}if (!fn) {vm._events[event] = nullreturn vm}// specific handlerlet cblet i = cbs.lengthwhile (i--) {cb = cbs[i]if (cb === fn || cb.fn === fn) {cbs.splice(i, 1)break}}return vm}

原理也很简单,就是寻找——删除。

而$once本质就是在这个事件回调上做了替换,先调用一下这个传入的回调方法,执行完成后调用一下$off把它删除,达到只调用一次的目的。

Vue.prototype.$once = function (event: string, fn: Function): Component {const vm: Component = thisfunction on () {vm.$off(event, on)fn.apply(vm, arguments)}on.fn = fnvm.$on(event, on)return vm}

$emit的话就是寻找——调用。

Vue.prototype.$emit = function (event: string): Component {const vm: Component = thislet cbs = vm._events[event]if (cbs) {cbs = cbs.length > 1 ? toArray(cbs) : cbsconst args = toArray(arguments, 1)const info = `event handler for "${event}"`for (let i = 0, l = cbs.length; i < l; i++) {invokeWithErrorHandling(cbs[i], vm, args, vm, info)}}return vm}

注意一下这几个方法调用返回的都是Vue实例本身,说明实际中可以写成链式调用的形式。

lifecycleMixin

定义了$forceUpdate,强行在Vue实例上的所有Watcher对象都调用一把更新,后面讲响应式原理的时候会谈到什么是Watcher对象。

Vue.prototype.$forceUpdate = function () {const vm: Component = thisif (vm._watcher) {vm._watcher.update()}}

又定义了$destroy

Vue.prototype.$destroy = function () {const vm: Component = thisif (vm._isBeingDestroyed) {return}callHook(vm, 'beforeDestroy')vm._isBeingDestroyed = true// remove self from parentconst parent = vm.$parentif (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {remove(parent.$children, vm)}// teardown watchersif (vm._watcher) {vm._watcher.teardown()}let i = vm._watchers.lengthwhile (i--) {vm._watchers[i].teardown()}// remove reference from data ob// frozen object may not have observer.if (vm._data.__ob__) {vm._data.__ob__.vmCount--}// call the last hook...vm._isDestroyed = true// invoke destroy hooks on current rendered treevm.__patch__(vm._vnode, null)// fire destroyed hookcallHook(vm, 'destroyed')// turn off all instance listeners.vm.$off()// remove __vue__ referenceif (vm.$el) {vm.$el.__vue__ = null}// release circular reference (#6759)if (vm.$vnode) {vm.$vnode.parent = null}}

对应了生命周期图上的这个部分

清理工作还是挺多的

  1. 和父组件的引用关系中拆分出来
  2. 清理所有Watcher对象
  3. 清理所有Observer对象
  4. 移除所有事件
  5. 清理挂载的DOM元素

renderMixin

主要是定义了跟虚拟DOM渲染有关的属性($vnode)和私有方法(_render),在后面讲到虚拟DOM的时候再来讲。

下一篇文章要讲Vue响应式原理了,想想都激动。

for里面调用方法 vue_Vue源码阅读连载之Vue实例相关推荐

  1. 推荐一本Vue源码阅读书籍《Vue.js技术内.幕》

    1. 概述 这幅图大家应该都很清楚: 但这个过程在Vue框架中是如何实现的呢? 是否考虑过如果是自己,该如何设计呢?而这本<Vue.js技术内幕>就是一本非常好的关于Vue框架源码学习的书 ...

  2. MyBatis 源码阅读 -- 核心操作篇

    核心操作包是 MyBatis 进行数据库查询和对象关系映射等工作的包.该包中的类能完成参数解析.数据库查询.结果映射等主要功能.在主要功能的执行过程中还会涉及缓存.懒加载.鉴别器处理.主键自增.插件支 ...

  3. android+仿iphone,Android编程实现仿iphone抖动效果的方法(附源码)

    Android编程实现仿iphone抖动效果的方法(附源码) 时间:2021-05-20 本文实例讲述了Android编程实现仿iphone抖动效果的方法.分享给大家供大家参考,具体如下: 布局文件: ...

  4. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

  5. Transformers包tokenizer.encode()方法源码阅读笔记

    Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode

  6. android源码阅读笔记1-配置源码路径/阅读源码方法讨论

    开始之前 android studio中配置android源码路径 android studio中有源码的路径,你只需要打开SDK Manager下载源码然后重启android studio即可查看源 ...

  7. 大神手把手教源码阅读的方法、误区以及三种境界

    丁威 中间件兴趣圈 读完需要 1 分钟 速读仅需 1 分钟 在技术职场中普遍存在如下几种现象: 对待工作中所使用的技术不需要阅读源码,只需在开发过程中能够熟练运用就行 看源码太费时间,而且容易忘记,如 ...

  8. 【转载】ubuntu下linux内核源码阅读工具和调试方法总结

    http://blog.chinaunix.net/space.php?uid=20940095&do=blog&cuid=2377369 一 linux内核源码阅读工具 window ...

  9. 应用监控CAT之cat-client源码阅读(一)

    CAT 由大众点评开发的,基于 Java 的实时应用监控平台,包括实时应用监控,业务监控.对于及时发现线上问题非常有用.(不知道大家有没有在用) 应用自然是最初级的,用完之后,还想了解下其背后的原理, ...

最新文章

  1. Linux平台Oracle安装脚本
  2. Java.util.Random 各种方法介绍
  3. 搜索打表大找规律 (hdu2045)
  4. HTML、CSS、JavaScript能实现的功能汇总!
  5. CNN中的卷积操作与权值共享
  6. The Learning route of GNN
  7. ge linux安装apt_linux – 一个通用的bash脚本,用于安装apt-ge...
  8. Pr使用技巧,如何使用pr去水印?
  9. P6039A高压衰减棒使用方法及注意事项
  10. HackerRank - Stock Maximize
  11. Cholesky分解
  12. 运放电流检测采样电路电压采样电路
  13. 如何用金山打字通等软件练习准确地打JAVA语言
  14. Bochs、虚拟软盘与BootLoader
  15. 大一C语言总结贴(持更) Part 10 随机步法
  16. 前后端分离项目全环境搭建(Ruoyi框架)
  17. OverLoad和 OverWrite区别
  18. 如何选购笔记本电脑?
  19. javaee实训报告总结_JavaEE实训报告.doc
  20. OSChina 周六乱弹 ——对,假期的最后一天咯~!

热门文章

  1. 基于裁判文书与犯罪案例文本挖掘项目
  2. 【搜索/推荐排序】总结
  3. 配置opencv cmake
  4. day32 并发编程之锁
  5. ElasicSearch(3) 安装elasticsearch-head
  6. 10个绕过反病毒的恶意用户技巧
  7. springmvc中@RequestMapping的使用
  8. DOM编程练习(慕课网题目)
  9. volatile的适用场合
  10. 二 jQuery 语法