vue源码分析之骨架

Posted mrzhu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue源码分析之骨架相关的知识,希望对你有一定的参考价值。

运行时与编译时

  vue运行时和编译时的区别,编译时即增加了对于template的解析,而运行时假定你只是使用render函数进行编写,如果使用webpack的vue-loader 他会自动把*.vue转换成render,所以只需要运行时就可以了,运行时比编译时减少30%

vue模块

  vue分为核心模块和扩展模块,核心模块实现vue基础功能,扩展模块则根据不同的需求扩展不同的功能

  核心模块:core

  扩展模块:

    platforms/web web扩展

    platforms/weex 混合开发扩展

web扩展的两个入口文件

  因为vue分为编译时和运行时,所以vue会打两个包,编译时包和运行时包,两个入口文件分别是:

    1:编译时入口文件 platforms/web/entry-runtime.js

    2:运行时入口文件 platforms/web/entry-runtime-with-compiler.js

vue工具函数分析:

  vue分为通用函数库和业务函数库,其中shared/util.js是通用函数库,不同模块都会用到,而不同模块之下还会存在针对该模块的业务函数库

web模块目录分析:

  web/runtime/index.js 运行时的入口文件
  web/runtime/components 扩展vue组件功能
  web/runtime/directives 扩展vue指令功能
  web/runtime/class-util.js class相关操作
  web/runtime/node-ops.js dom节点相关操作

$mount方法:

  $mount是vue核心库暴露出来的,便于扩展库初始化信息用的,即vue核心库会在自身初始化完成之后调用$mount方法,而扩展库通过在原型上附加该方法 实现自身的初始化

这应该是原型模式的一个应用场景,即通过在原型上附加方法实现,扩展库与核心库的初始化连接功能

疑问?

  扩展库的$mount方法会调用核心库的mountComponent方法,而且mountComponent方法放在核心库里面一个比较隐晦的文件里面,如果是多人开发的话,一个人负责开发扩展库 一个人负责开发核心库,开发扩展库的人是如何得知需要调用mountComponent方法来完成组件的初始化呢?即vue本身的初始化 扩展库的初始化 vue组件的初始化这三个应该属于不同的模块,是否应该将vue组件的初始化单独拿出来维护,以便于开发扩展库的人可以明确知道为何他在自身初始化完成之后还必须调用mountComponent完成组件的初始化

vue核心库的初始化:

  core/index.js 是入口文件,其中instance/index是vue的定义,global-api/index 是给vue附加api

vue核心库中instance:

  定义了vue对象,并且在内部初始化的时候调用_init
  initMixin方法 给vue添加init方法
  stateMixin方法 给vue添加$data,$props,set del等
  eventsMixin方法 给vue添加事件模型 on emit等
  lifecycleMixin方法 给vue添加特定生命周期方法 _update(后面mountComponent内部会调用) $forceUpdate等
  renderMixin方法 给vue添加render支持

这里再次使用了原型模式,通过原型模式将vue的一些初始化信息给解耦到不同模块

vue核心库中_init:

  将传入的options给merge到vue.$options上
  initLifecycle 初始化声明周期标记
  initEvents 初始化事件基础信息
  initRender 初始化render基础支持
  hook beforeCreate
  初始化 Injections Provide
  开始构建props(将props附加到_props属性上) data(将data附加到_data属性上) methods(将methods附加到vue属性上) computed watch等
  hook created方法
  触发扩展模块的$mount方法(一般扩展模块的$mount又会触发了mountComponent,mountComponent内部hook了beforeMount,mounted)

vue和辛苦中的组件初始化:

  在扩展模块的$mount中都会触发mountComponent,

  初始化$el

  初始化vm.$options.render

  callHook(vm, ‘beforeMount‘)

  初始化updateComponent(updateComponent会调用_update方法,而_update方法早在lifecycleMixin的时候就已经初始化好了)

  new Watcher

  callHook(vm, ‘mounted‘)

updateComponent方法:

  updateComponent方法实际上是调用_update方法,_update方法中调用了__patch__然后执行restoreActiveInstance,应该是充矫正当前实例,关于__patch__方法网上已经有很多介绍了,这里不再详述,实际上就是虚拟dom的实现

new Watcher介绍:

  在new Watcher里面会调用到updateComponent方法,所以Watcher才是关键,这是实现观察者模式,这个网上也有很多介绍,观察者模式的运用和虚拟dom一直是vue的核心。Watcher在初始化的时候会给data添加observer监听,在data改变之后,并不会立即触发更修,而是会将改变放置到更新队列中,在下一次微任务执行的时候统一进行虚拟dom的修改,而在浏览器端微任务是本次宏任务的结束时候会调用,而宏任务是一次事件机制的遍历结束,因为js是单进程的,所以js会把所有事件组成一个队列,然后依次遍历执行,js引擎会不断的遍历事件队列,看看是否存在待执行的事件,而每次遍历操作实际上就是一次宏任务,所以vue利用js的事件机制在每次遍历的时候才会去执行虚拟dom的patch。

 

vue中可以学到的知识点有很多,如设计模式的运用,观察者模式的实现,虚拟dom的实现,nextTick的实现,项目代码的组织,对象的初始化与属性的附加的分离,核心库与扩展库的分工,render函数的实现,template的编译等等,而我最关注的还是vue整个系统的设计以及代码的规范:

  vue中运用了原型模式 观察者模式 模块模式 构造器模式 ,同时vue实现了mixin模式。

  在代码规范中我们发现

    vue的tab是两个

    而且vue并没有强制使用分号

    vue在每段代码之间都会再加一个回车用来可以分开

    使用$属性作为vue的static属性的标记

    vue存在业务函数库与通用函数库的区分

    vue的单行注释都是写在上面

    vue也会大量采用for循环

    vue中使用了Object.create(null)

    多个import的时候按照短的在前 长的在后排列

    import多个属性的时候,多个属性并没有放在一行,而是每个属性占一行

    在一个模块中,并没有在模块最上面提前把所有会用到的变量提前声明好,而是在需要使用该变量的函数的前面去声明该变量

    直接使用export function 而不是在最后通过export 导出多个

    使用_表示私有方法,这通常在同时会存在一个同名的公有方法的时候,会采用_与共有方法区分开来

    将一个复杂函数内部的代码抽离成多个小函数的组合

   在代码组织中,我们仅仅查看core文件:

    core分为instance实例,global-api全局api,observer观察者模式实现。vdom虚拟dom实现,util业务方法库,config.js配置文件等几大模块

    除了vdom其他模块都会存在index.js作为模块的入口文件

    instance作为整个库的核心文件,会调用global-api进行vue的api的附加,同时util vdom observer三大模块会在instance中不同文件中得到使用,所以我们说instance是主模块,其他模块是辅助模块

   设计模式中:

    vue大量使用hook模式,作为vue本身与开发者的交互,vue使用hook模式不断通知开发者,目前到了哪一步,开发者可以通过hook钩子 完成指定操作

 

以上是关于vue源码分析之骨架的主要内容,如果未能解决你的问题,请参考以下文章

vue2.0源码分析之理解响应式架构

vue插件开发之-vue.use()的源码分析。

vue源码分析之目录架构

vue源码分析:响应式之依赖收集

Vue源码分析基础之响应式原理

Vue源码分析基础之响应式原理