Vue源码解析:Vue实例(一)

为什么要写这个


用了很久的JS,也用了很久的Vue.js,从最初的Angular到React到现在在用的Vue。学习路径基本上是:

  • 别人告诉我,这个地方你要用ng-module,那么我就用ng-module,至于ng-modul的功能是什么,我不知道
  • 带我的大佬不厌其烦了,教授了我查阅API的方法(找到官网,一般都有),自从开始阅读API以后,我会的方法越来越多,心情非常激动的使用一个又一个新功能
  • 开始去思考每一个框架的实现细节原理

所以就有现在我想要去研究Vue的源码,研究的方法是跟着Vue官网的教程,一步步的找到教程中功能的实现代码分析实现的代码细节,并且会详细解释代码中涉及的JS(ES6)知识。即使是前端新人也可以轻松阅读

你能得到什么


你可以得到以下知识:

  • Vue.js 源码知识
  • ES5、ES6基础知识

面对对象


  • 前端新人
  • 不想花大量时间阅读源码但是想快速知道Vue.js实现细节的人
  • 我自己

话不多说,下面就开始我的第一节笔记,对应官网教程中的Vue实例

Vue实例

Vue实例包含

  • 创建一个Vue实例

      var vm = new Vue({// 选项 options})
    复制代码
  • 数据与方法

      // 该对象被加入到一个 Vue 实例中var vm = new Vue({data: data})
    复制代码
  • 实例生命周期钩子

      new Vue({data: {a: 1},created: function () {// `this` 指向 vm 实例console.log('a is: ' + this.a)}})// => "a is: 1"
    复制代码
  • 生命周期图示

创建一个Vue实例

每个Vue应用都是通过Vue函数创建一个新的Vue实例开始:

var vm = new Vue({// 选项})
复制代码

我们从Github下载到Vue.js源码后解压打开,探索new Vue创建了一个什么东西

打开下载的vue-dev,找到vue-dev/src/core/index.js

// vue-dev/src/core/index.jsimport Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'initGlobalAPI(Vue)Object.defineProperty(Vue.prototype, '$isServer', {get: isServerRendering
})Object.defineProperty(Vue.prototype, '$ssrContext', {get () {/* istanbul ignore next */return this.$vnode && this.$vnode.ssrContext}
})// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {value: FunctionalRenderContext
})Vue.version = '__VERSION__'export default Vue
复制代码

Vue是从这里定义的,在vue-dev/src/core/index.js的头部找到

import Vue from './instance/index'
复制代码

打开vue-dev/src/core/instance/index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'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)
}export default Vue
复制代码

Vue实例在这里得到了各种初始化(init),在这里申明了一个构造器(Constructor)Vue,在构造器里调用了_init方法,并向_init方法中传入了options

 this._init(options)
复制代码

显然_init不是Function原型链中的方法,必定是在某处得到定义。紧接着后面看到一系列的Mixin函数调用

   initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)
复制代码

显然是这一堆Mixin方法赋予了Vue实例一个_init方法(之后会有单独的一篇笔记讲述Mixin是怎样的一种设计思维,相关知识会从原型链讲起)

顾名思义,根据函数名字猜测_init是来自于initMixin方法,根据

import { initMixin } from './init'
复制代码

找到vue-dev/src/core/instance/init.js(由于实在是太长了全粘贴过来不方便阅读,故根据需要粘贴相应的节选,如果想要全览的小伙伴可以去下载源码来看完整的)

在vue-dev/src/core/instance/init.js中我们搜索_init,找到下面这个方法

export function initMixin (Vue: Class<Component>) {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)}}
}
复制代码

其中

export function initMixin (Vue: Class<Component>) {}
复制代码

里的

Vue: Class<Component>
复制代码

来自于flow语法,一个不错的静态类型检测器

这个initMixin方法里只干了一件事,就是给Vue.prototype._init赋值,即在所有Vue实例的原型链中添加了_init方法。这个_init方法又做了些什么呢?

  • 它给Vue实例添加了很多的属性,比如$options

  • 它给vm初始化了代理

      initProxy(vm)
    复制代码
  • 它给vm初始化了很多

      initLifecycle(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')
    复制代码
  • 它甚至偷偷的唤起了钩子函数

      callHook(vm, 'beforeCreate')callHook(vm, 'created')
    复制代码

实例生命周期钩子 & 生命周期图示

所谓的唤起钩子函数callHook是做什么的呢?我们找到

import { initLifecycle, callHook } from './lifecycle'
复制代码

打开这个文件lifecycle.js

export function callHook (vm: Component, hook: string) {// #7573 disable dep collection when invoking lifecycle hookspushTarget()const handlers = vm.$options[hook]if (handlers) {for (let i = 0, j = handlers.length; i < j; i++) {try {handlers[i].call(vm)} catch (e) {handleError(e, vm, `${hook} hook`)}}}if (vm._hasHookEvent) {vm.$emit('hook:' + hook)}popTarget()
}
复制代码

可以看到,callHook函数的作用是,调用option里用户设定的生命周期函数。例如

new Vue({data: {a: 1},created: function () {// `this` 指向 vm 实例console.log('a is: ' + this.a)}
})
// => "a is: 1"
复制代码

new Vue() 到 beforeCreate 到 created


它在'beforeCreate'和'created'之间干了什么呢?

 callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(vm, 'created')
复制代码

对应生命周期图示来看代码

    initLifecycle(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')
复制代码

在new Vue()之后,调用了_init(),在_init()内,调用了

    initLifecycle(vm)initEvents(vm)initRender(vm)
复制代码

这点正好对应官网生命周期图示中new Vue()与生命周期钩子'beforeCreate'之间的Init Events & Lifecycle,也就是说我们在option中设置的钩子函数,会在这个生命周期节点得到调用,是因为这个callHook(vm, 'beforeCreate')(vue-dev/src/core/instance/init.js),而在这个时间节点之前完成Init Events & Lifecycle的正是

    initLifecycle(vm)initEvents(vm)initRender(vm)
复制代码

除了官方提到的Events和Lifecycle的Init之外,还在这个生命周期节点完成了Render的Init

之后是Init injections & reactivity,对应的函数调用是

initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
复制代码

这段函数调用之后_init()还没有结束,后面有

if (vm.$options.el) {vm.$mount(vm.$options.el)
}
复制代码

对应生命周期示意图中的

依据options中是否包含el来决定是否mount(挂载)这个el

毫无疑问,$mount函数必定是完成下一步的关键,在src文件夹中搜索$mount的定义,在/vue-dev/src/platforms/web/runtime/index.js中找到了

Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {el = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating)
}
复制代码

Vue.$mount函数内包含两个重要的函数

  • query()
  • mountComponent()

其中

// src/platforms/web/util/index.jsexport function query (el: string | Element): Element {if (typeof el === 'string') {const selected = document.querySelector(el)if (!selected) {process.env.NODE_ENV !== 'production' && warn('Cannot find element: ' + el)return document.createElement('div')}return selected} else {return el}
}
复制代码

可以看到,query()是对document.querySelector()的一个包装,作用是依据new Vue(options)optionsel设定的元素选择器进行DOM内元素的选取,并设定了相应的容错、报错方法

created 到 beforeMount 到 mounted


// src/core/instance/lifecycle.jsexport function mountComponent (vm: Component,el: ?Element,hydrating?: boolean
): Component {vm.$el = elif (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||vm.$options.el || el) {warn('You are using the runtime-only build of Vue where the template ' +'compiler is not available. Either pre-compile the templates into ' +'render functions, or use the compiler-included build.',vm)} else {warn('Failed to mount component: template or render function not defined.',vm)}}}callHook(vm, 'beforeMount')let updateComponent/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst startTag = `vue-perf-start:${id}`const endTag = `vue-perf-end:${id}`mark(startTag)const vnode = vm._render()mark(endTag)measure(`vue ${name} render`, startTag, endTag)mark(startTag)vm._update(vnode, hydrating)mark(endTag)measure(`vue ${name} patch`, startTag, endTag)}} else {updateComponent = () => {vm._update(vm._render(), hydrating)}}// we set this to vm._watcher inside the watcher's constructor// since the watcher's initial patch may call $forceUpdate (e.g. inside child// component's mounted hook), which relies on vm._watcher being already definednew Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)hydrating = false// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hookif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm
}
复制代码

当options里不存在render函数的时候,会执行createEmptyVNode,添加到vm.$options.render,之后执行生命周期钩子函数callHook(vm, 'beforeMount'),即对应的生命周期为

 if (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||vm.$options.el || el) {warn('You are using the runtime-only build of Vue where the template ' +'compiler is not available. Either pre-compile the templates into ' +'render functions, or use the compiler-included build.',vm)} else {warn('Failed to mount component: template or render function not defined.',vm)}}}
复制代码

如果render function存在,则直接调用beforeMount生命周期钩子函数,如果不存在,则通过createEmptyVNodeCompile template into render function Or compile el's outerHTML as template。

下一步就是看createEmptyVNode是如何做到compile something into render function的。

// src/core/vdom/vnode.jsexport const createEmptyVNode = (text: string = '') => {const node = new VNode()node.text = textnode.isComment = truereturn node
}
复制代码

createEmptyVNode通过new VNode()返回了VNode实例

VNode是一个很长的class,这里只放VNode的constructor作参考

constructor (tag?: string,data?: VNodeData,children?: ?Array<VNode>,text?: string,elm?: Node,context?: Component,componentOptions?: VNodeComponentOptions,asyncFactory?: Function) {this.tag = tagthis.data = datathis.children = childrenthis.text = textthis.elm = elmthis.ns = undefinedthis.context = contextthis.fnContext = undefinedthis.fnOptions = undefinedthis.fnScopeId = undefinedthis.key = data && data.keythis.componentOptions = componentOptionsthis.componentInstance = undefinedthis.parent = undefinedthis.raw = falsethis.isStatic = falsethis.isRootInsert = truethis.isComment = falsethis.isCloned = falsethis.isOnce = falsethis.asyncFactory = asyncFactorythis.asyncMeta = undefinedthis.isAsyncPlaceholder = false}
复制代码

事实上VNode是Vue虚拟的DOM节点,最后这个虚拟DOM节点被挂载到vm.$options.render,到这里

 vm.$el = elif (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||vm.$options.el || el) {warn('You are using the runtime-only build of Vue where the template ' +'compiler is not available. Either pre-compile the templates into ' +'render functions, or use the compiler-included build.',vm)} else {warn('Failed to mount component: template or render function not defined.',vm)}}}callHook(vm, 'beforeMount')
复制代码

唤起生命周期钩子函数beforeMount,正式进入beforeMount to mounted的阶段

现在要做的是Create vm.$el and replace "el" with it

  let updateComponent/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst startTag = `vue-perf-start:${id}`const endTag = `vue-perf-end:${id}`mark(startTag)const vnode = vm._render()mark(endTag)measure(`vue ${name} render`, startTag, endTag)mark(startTag)vm._update(vnode, hydrating)mark(endTag)measure(`vue ${name} patch`, startTag, endTag)}} else {updateComponent = () => {vm._update(vm._render(), hydrating)}}// we set this to vm._watcher inside the watcher's constructor// since the watcher's initial patch may call $forceUpdate (e.g. inside child// component's mounted hook), which relies on vm._watcher being already definednew Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)hydrating = false// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hookif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}
复制代码

倒着来找callHook(vm, 'mounted')的触发,在这之前,做了这么几件事

  • 定义updateComponent,后被Watcher使用
  • 调用构造函数Watcher产生新实例
  • 判断vm.$vnode 是否为null,如果是,则callHook(vm, 'mounted')

src/core/observer/watcher.js里可以找到Watcher的定义,这里展示它的constructor

    constructor (vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vmif (isRenderWatcher) {vm._watcher = this}vm._watchers.push(this)// optionsif (options) {this.deep = !!options.deepthis.user = !!options.userthis.computed = !!options.computedthis.sync = !!options.syncthis.before = options.before} else {this.deep = this.user = this.computed = this.sync = false}this.cb = cbthis.id = ++uid // uid for batchingthis.active = truethis.dirty = this.computed // for computed watchersthis.deps = []this.newDeps = []this.depIds = new Set()this.newDepIds = new Set()this.expression = process.env.NODE_ENV !== 'production'? expOrFn.toString(): ''// parse expression for getterif (typeof expOrFn === 'function') {this.getter = expOrFn} else {this.getter = parsePath(expOrFn)if (!this.getter) {this.getter = function () {}process.env.NODE_ENV !== 'production' && warn(`Failed watching path: "${expOrFn}" ` +'Watcher only accepts simple dot-delimited paths. ' +'For full control, use a function instead.',vm)}}if (this.computed) {this.value = undefinedthis.dep = new Dep()} else {this.value = this.get()}}
复制代码

(累死我了,要休息一哈,第一次写,对于部分细节是否要深入把握不好。深入的话太深了一个知识点要讲好多好多好多,可能一天都说不完。讲太浅了又觉得啥干活都没有,不好把握各位谅解。要是有错误的望各位提出来,我也算是抛砖引玉了)

转载于:https://juejin.im/post/5aeab3b5518825672565b469

Vue源码解析:Vue实例相关推荐

  1. Vue源码解析(一)

    前言:接触vue已经有一段时间了,前面也写了几篇关于vue全家桶的内容,感兴趣的小伙伴可以去看看,刚接触的时候就想去膜拜一下源码~可每次鼓起勇气去看vue源码的时候,当看到几万行代码的时候就直接望而却 ...

  2. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  3. [Vue源码解析] patching算法

    [Vue源码解析] patching算法 pathching算法:通过对比新旧VNode的不同,然后找出需要更新的节点进行更新 操作:1.创建新增节点 2.删除废弃节点 3.修改需要更新的节点 创建节 ...

  4. Vue源码解析(尚硅谷)

    视频地址:Vue源码解析系列课程 一.Vue源码解析之mustache模板引擎 1. 什么是模板引擎 模板引擎是将数据要变为视图最优雅的解决方案 历史上曾经出现的数据变为视图的方法 2. mustac ...

  5. 注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-evict/ ...

  6. Vue源码分析--Vue.component

    Vue源码分析–Vue.component 我将非 Vue.component 的部分去掉了 export function initAssetRegisters (Vue: GlobalAPI) { ...

  7. Vue源码解析之数组变异

    力有不逮的对象 众所周知,在 Vue 中,直接修改对象属性的值无法触发响应式.当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变. 这是什么原因? 原因在于: Vue 的响应式 ...

  8. Vue源码解析-$mount

    前言 Vue实例是通过mount方法进行挂载的,上一篇说了new Vue这篇分析一下mount挂载方法.mount的定义在Vue源码中有多处,这是因为Vue需要适配不同的平台以及构建方式,本文这里主要 ...

  9. Vue源码解析:虚拟dom比较原理

    通过对 Vue2.0 源码阅读,想写一写自己的理解,能力有限故从尤大佬2016.4.11第一次提交开始读,准备陆续写: 模版字符串转AST语法树 AST语法树转render函数 Vue双向绑定原理 V ...

  10. vue源码解析:vue生命周期方法$destory方法的实现原理

    我们知道vue生命周期的最后一个阶段是销毁阶段,那么vue会调用自己的destory函数,那么$destory函数的实现原理是什么?且往下看. 用法: vm.$destroy() 作用: 完全销毁一个 ...

最新文章

  1. JavaScript 定义类时如何将方法提取出来
  2. wdcp3.2.6版 https全站跳转 标记待细化
  3. S2系统相关-uptime命令总结(S代表系统相关)
  4. Handler研究2-AsyncTask,AsyncQueryHandler分析
  5. 个人开发者微信支付和支付宝支付
  6. 基于MIPS架构的BackTrace实现
  7. 087_html5表单元素
  8. 原创|我以为我对Mysql索引很了解,直到我遇到了阿里的面试官
  9. 配置tomcat 7控制台账号
  10. django框架之模板系统
  11. 老男孩Python高级全栈开发工程师【高清全套完整】
  12. Shopee虾皮电商平台考试题附答案
  13. 2019年支付宝集五福秘笈!内含攻略及互助群
  14. 基于Eviews的稳定性检验——以个股的β系数为例(含ADF检验步骤及结果分析)
  15. 胡彦斌and音乐密码 MUSIC CODE
  16. urp综合教务系统 php 课表,URP综合教务系统教师录入成绩指南
  17. ZOJ 3549 Little keng
  18. 进行分词时,报错omw-1.4安装包未找到?
  19. 2020年8月4日王者服务器维修,2020年8月4日小鸡正确的答案
  20. C-Kermit在linux 下的安装和使用

热门文章

  1. 内联函数的常识性问题
  2. linux opendir路径_Linux C/C++ ----目录文件的操作(opendir,readdir,closedir)
  3. EDSR:Enhanced Deep Residual Networks for Single Image Super-Resolution
  4. 无线通信基础(一):高斯随机变量
  5. 安装Ubuntu18
  6. MapReduce如何使用多路输出
  7. caffe的finetuning是如何更新网络参数的?
  8. Neural NILM: Deep Neural Networks Applied to Energy Disaggregation
  9. 整体二分算法完整总结
  10. 边缘保留滤波matlab,【DIP】各种边缘保留滤波器一览