资讯专栏INFORMATION COLUMN

你想要的——vue源码分析(2)

objc94 / 2247人阅读

摘要:本次分析的版本是。的实例化由上一章我们了解了类的定义,本章主要分析用户实例化类之后,框架内部做了具体的工作。所以我们先看看的构造函数里面定义了什么方法。这个文件声明了类的构造函数,构造函数中直接调用了实例方法来初始化的实例,并传入参数。

背景

Vue.js是现在国内比较火的前端框架,希望通过接下来的一系列文章,能够帮助大家更好的了解Vue.js的实现原理。本次分析的版本是Vue.js2.5.16。(持续更新中。。。)

目录

Vue.js的引入

Vue的实例化

Vue数据处理(未完成)

。。。

Vue的实例化

由上一章我们了解了Vue类的定义,本章主要分析用户实例化Vue类之后,Vue.js框架内部做了具体的工作。

举个例子

</>复制代码

  1. var demo = new Vue({
  2. el: "#app",
  3. created(){},
  4. mounted(){},
  5. data:{
  6. a: 1,
  7. },
  8. computed:{
  9. b(){
  10. return this.a+1
  11. }
  12. },
  13. methods:{
  14. handleClick(){
  15. ++this.a ;
  16. }
  17. },
  18. components:{
  19. "todo-item":{
  20. template: "
  21. to do
  22. ",
  23. mounted(){
  24. }
  25. }
  26. }
  27. })

以上是一个简单的vue实例化的例子,用户通过new的方式创建了一个Vue的实例demo。所以我们先看看Vue的构造函数里面定义了什么方法。

src/core/instance/index.js

这个文件声明了Vue类的构造函数,构造函数中直接调用了实例方法_init来初始化vue的实例,并传入options参数。

</>复制代码

  1. import { initMixin } from "./init"
  2. import { stateMixin } from "./state"
  3. import { renderMixin } from "./render"
  4. import { eventsMixin } from "./events"
  5. import { lifecycleMixin } from "./lifecycle"
  6. import { warn } from "../util/index"
  7. // 声明Vue类
  8. function Vue (options) {
  9. if (process.env.NODE_ENV !== "production" &&
  10. !(this instanceof Vue)
  11. ) {
  12. warn("Vue is a constructor and should be called with the `new` keyword")
  13. }
  14. this._init(options)
  15. }
  16. // 将Vue类传入各种初始化方法
  17. initMixin(Vue)
  18. stateMixin(Vue)
  19. eventsMixin(Vue)
  20. lifecycleMixin(Vue)
  21. renderMixin(Vue)
  22. export default Vue

接下来我们看看这个_init方法具体做了什么事情。

src/core/instance/init.js

这个文件的initMixin方法定义了vue实例方法_init。

</>复制代码

  1. Vue.prototype._init = function (options?: Object) {
  2. // this指向Vue的实例,所以这里是将Vue的实例缓存给vm变量
  3. const vm: Component = this
  4. // a uid
  5. // 每一个vm有一个_uid,从0依次叠加
  6. vm._uid = uid++
  7. let startTag, endTag
  8. /* istanbul ignore if */
  9. if (process.env.NODE_ENV !== "production" && config.performance && mark) {
  10. startTag = `vue-perf-start:${vm._uid}`
  11. endTag = `vue-perf-end:${vm._uid}`
  12. mark(startTag)
  13. }
  14. // a flag to avoid this being observed
  15. // 表示vue实例
  16. vm._isVue = true
  17. // merge options
  18. // 处理传入的参数,并将构造方法上的属性跟传入的属性合并(merge)
  19. // 处理子组件的options,后续讲到组件会详细展开
  20. if (options && options._isComponent) {
  21. // optimize internal component instantiation
  22. // since dynamic options merging is pretty slow, and none of the
  23. // internal component options needs special treatment.
  24. initInternalComponent(vm, options)
  25. } else {
  26. vm.$options = mergeOptions(
  27. resolveConstructorOptions(vm.constructor),
  28. options || {},
  29. vm
  30. )
  31. }
  32. /* istanbul ignore else */
  33. // 添加vm的_renderProxy属性,非生产环境ES6的proxy代理,对非法属性获取进行提示
  34. if (process.env.NODE_ENV !== "production") {
  35. initProxy(vm)
  36. } else {
  37. vm._renderProxy = vm
  38. }
  39. // expose real self
  40. // 添加vm的_self属性
  41. vm._self = vm
  42. // 对vm进行各种初始化
  43. // 将vm自身添加到该vm的父组件的的$children数组中
  44. // 添加vm的$parent$root$children$refs,_watcher,_inactive,_directInactive,_isMounted,_isDestroyed,_isBeingDestroyed属性
  45. // 具体实现在 src/core/instance/lifecycle.js中,代码比较简单,不做展开
  46. initLifecycle(vm)
  47. // 添加vm._events,vm._hasHookEvent属性
  48. initEvents(vm)
  49. // 添加vm._vnode,vm._staticTrees,vm.$vnode,vm.$slots,vm.$scopedSlots,vm._c,vm.$createElement
  50. // 将vm上的$attrs$listeners 属性设置为响应式的
  51. initRender(vm)
  52. // 触发beforeCreate钩子,如果options中有beforeCreate的回调函数,则会被调用
  53. callHook(vm, "beforeCreate")
  54. initInjections(vm) // resolve injections before data/props
  55. // 初始化state,包括Props,methods,Data,Computed,watch;
  56. // 这块内容比较核心,所以会在下一章详细讲解,这里先大概描述一下
  57. // 对于prop以及data属性,将其设置为vm的响应式属性,即使用object.defineProperty绑定vm的prop和data属性并设置其getter&setter
  58. // 对于methods,则将每个method都挂载在vm上,并将this指向vm
  59. // 对于Computed,在将其设置为vm的响应式属性之外,还需要定义watcher,用于收集依赖
  60. // watch属性,也是将其设置为watcher实例,收集依赖
  61. initState(vm)
  62. // 初始化provide属性
  63. initProvide(vm) // resolve provide after data/props
  64. // 至此,所有数据的初始化工作已经做完,所有触发created钩子,在这个钩子的回调中可以访问之前所定义的所有数据
  65. callHook(vm, "created")
  66. /* istanbul ignore if */
  67. if (process.env.NODE_ENV !== "production" && config.performance && mark) {
  68. vm._name = formatComponentName(vm, false)
  69. mark(endTag)
  70. measure(`vue ${vm._name} init`, startTag, endTag)
  71. }
  72. // 调用vm上的$mount方法
  73. if (vm.$options.el) {
  74. vm.$mount(vm.$options.el)
  75. }
  76. }

接下来分析一下vm上的$mount方法具体做了什么事情

platforms/web/entry-runtime-with-compiler.js

</>复制代码

  1. // 缓存Vue.prototype上的$mount方法到变量mount上
  2. const mount = Vue.prototype.$mount
  3. Vue.prototype.$mount = function (
  4. el?: string | Element,
  5. hydrating?: boolean
  6. ): Component {
  7. // 获取dom上的元素
  8. el = el && query(el)
  9. /* istanbul ignore if */
  10. if (el === document.body || el === document.documentElement) {
  11. process.env.NODE_ENV !== "production" && warn(
  12. `Do not mount Vue to or - mount to normal elements instead.`
  13. )
  14. return this
  15. }
  16. const options = this.$options
  17. // resolve template/el and convert to render function
  18. if (!options.render) {
  19. // 获取&生成模板
  20. let template = options.template
  21. if (template) {
  22. if (typeof template === "string") {
  23. if (template.charAt(0) === "#") {
  24. template = idToTemplate(template)
  25. /* istanbul ignore if */
  26. if (process.env.NODE_ENV !== "production" && !template) {
  27. warn(
  28. `Template element not found or is empty: ${options.template}`,
  29. this
  30. )
  31. }
  32. }
  33. } else if (template.nodeType) {
  34. template = template.innerHTML
  35. } else {
  36. if (process.env.NODE_ENV !== "production") {
  37. warn("invalid template option:" + template, this)
  38. }
  39. return this
  40. }
  41. } else if (el) {
  42. template = getOuterHTML(el)
  43. }
  44. if (template) {
  45. /* istanbul ignore if */
  46. if (process.env.NODE_ENV !== "production" && config.performance && mark) {
  47. mark("compile")
  48. }
  49. // 根据模板生成相关的render,staticRenderFns方法
  50. // 这块内容涉及的内容比较多,会在后面的其他章节中有详细讲解
  51. const { render, staticRenderFns } = compileToFunctions(template, {
  52. shouldDecodeNewlines,
  53. shouldDecodeNewlinesForHref,
  54. delimiters: options.delimiters,
  55. comments: options.comments
  56. }, this)
  57. // 将render,staticRenderFns方法添加到options上
  58. options.render = render
  59. options.staticRenderFns = staticRenderFns
  60. /* istanbul ignore if */
  61. if (process.env.NODE_ENV !== "production" && config.performance && mark) {
  62. mark("compile end")
  63. measure(`vue ${this._name} compile`, "compile", "compile end")
  64. }
  65. }
  66. }
  67. // 调用前面缓存的mount方法
  68. return mount.call(this, el, hydrating)
  69. }

接下来看看缓存的$mount方法的实现

platforms/web/runtime/index.js

</>复制代码

  1. Vue.prototype.$mount = function (
  2. el?: string | Element,
  3. hydrating?: boolean
  4. ): Component {
  5. // 获取相关的dom元素,执行mountComponent方法
  6. el = el && inBrowser ? query(el) : undefined
  7. return mountComponent(this, el, hydrating)
  8. }

看看mountComponent方法的实现

</>复制代码

  1. export function mountComponent (
  2. vm: Component,
  3. el: ?Element,
  4. hydrating?: boolean
  5. ): Component {
  6. vm.$el = el
  7. if (!vm.$options.render) {
  8. vm.$options.render = createEmptyVNode
  9. if (process.env.NODE_ENV !== "production") {
  10. /* istanbul ignore if */
  11. if ((vm.$options.template && vm.$options.template.charAt(0) !== "#") ||
  12. vm.$options.el || el) {
  13. warn(
  14. "You are using the runtime-only build of Vue where the template " +
  15. "compiler is not available. Either pre-compile the templates into " +
  16. "render functions, or use the compiler-included build.",
  17. vm
  18. )
  19. } else {
  20. warn(
  21. "Failed to mount component: template or render function not defined.",
  22. vm
  23. )
  24. }
  25. }
  26. }
  27. // 调用beforeMount钩子
  28. callHook(vm, "beforeMount")
  29. // 设置updateComponent方法
  30. let updateComponent
  31. /* istanbul ignore if */
  32. if (process.env.NODE_ENV !== "production" && config.performance && mark) {
  33. updateComponent = () => {
  34. const name = vm._name
  35. const id = vm._uid
  36. const startTag = `vue-perf-start:${id}`
  37. const endTag = `vue-perf-end:${id}`
  38. mark(startTag)
  39. const vnode = vm._render()
  40. mark(endTag)
  41. measure(`vue ${name} render`, startTag, endTag)
  42. mark(startTag)
  43. vm._update(vnode, hydrating)
  44. mark(endTag)
  45. measure(`vue ${name} patch`, startTag, endTag)
  46. }
  47. } else {
  48. updateComponent = () => {
  49. vm._update(vm._render(), hydrating)
  50. }
  51. }
  52. // we set this to vm._watcher inside the watcher"s constructor
  53. // since the watcher"s initial patch may call $forceUpdate (e.g. inside child
  54. // component"s mounted hook), which relies on vm._watcher being already defined
  55. // 创建watcher对象,具体watch的实现会在下一章详细分析
  56. // 简单描述一下这个过程:初始化这个watcher对象,执行updateComponent方法,收集相关的依赖
  57. // updateComponent的执行过程:
  58. // 先执行vm._render方法,根据之前生成的render方法,生成相关的vnode,也就是virtual dom相关的内容,这个会在后续渲染的章节详细讲解
  59. // 通过生成的vnode,调用vm._update,最终将vnode生成的dom插入到父节点中,完成组件的载入
  60. new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
  61. hydrating = false
  62. // manually mounted instance, call mounted on self
  63. // mounted is called for render-created child components in its inserted hook
  64. if (vm.$vnode == null) {
  65. vm._isMounted = true
  66. // 调用mounted钩子,在这个钩子的回调函数中可以访问到真是的dom节点,因为在上述过程中已经将真实的dom节点插入到父节点
  67. callHook(vm, "mounted")
  68. }
  69. return vm
  70. }

OK,以上就是Vue整个实例化的过程,多谢观看&欢迎拍砖。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93985.html

相关文章

  • 想要——vue源码分析(1)

    摘要:本次分析的版本是。持续更新中。。。目录的引入的实例化的引入这一章将会分析用户在引入后,框架做的初始化工作创建这个类,并往类上添加类属性类方法和实例属性实例方法。 背景 Vue.js是现在国内比较火的前端框架,希望通过接下来的一系列文章,能够帮助大家更好的了解Vue.js的实现原理。本次分析的版本是Vue.js2.5.16。(持续更新中。。。) 目录 Vue.js的引入 Vue的实例化...

    jifei 评论0 收藏0
  • vue-cli 3.0 源码分析

    摘要:写在前面其实最开始不是特意来研究的源码,只是想了解下的命令,如果想要了解命令的话,那么绕不开写的。通过分析发现与相比,变化太大了,通过引入插件系统,可以让开发者利用其暴露的对项目进行扩展。 showImg(https://segmentfault.com/img/bVboijb?w=1600&h=1094); 写在前面 其实最开始不是特意来研究 vue-cli 的源码,只是想了解下 n...

    yiliang 评论0 收藏0
  • Vue 源码分析之二:Vue Class

    摘要:但没办法,还是得继续。因为这边返回的是一个,所以会执行如下代码然后回到刚才的里面,,额,好吧。。。 这段时间折腾了一个vue的日期选择的组件,为了达成我一贯的使用舒服优先原则,我决定使用directive来实现,但是通过这个实现有一个难点就是我如何把时间选择的组件插入到dom中,所以问题来了,我是不是又要看Vue的源码? vue2.0即将到来,改了一大堆,Fragment没了,所以vu...

    toddmark 评论0 收藏0
  • 入口文件开始,分析Vue源码实现

    摘要:一方面是因为想要克服自己的惰性,另一方面也是想重新温故一遍。一共分成了个基础部分,后续还会继续记录。文章中如果有笔误或者不正确的解释,也欢迎批评指正,共同进步。最后地址部分源码 Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行。 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一些疑问,可...

    Karrdy 评论0 收藏0
  • 入口文件开始,分析Vue源码实现

    摘要:一方面是因为想要克服自己的惰性,另一方面也是想重新温故一遍。一共分成了个基础部分,后续还会继续记录。文章中如果有笔误或者不正确的解释,也欢迎批评指正,共同进步。最后地址部分源码 Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行。 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一些疑问,可...

    nidaye 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<