vue2.0 源码自学
好事总会发生在下个转弯。希望你的愿望都能一一实现。
本文纯属个人理解,学习记录知识的笔记,如有错误请指出,我会及时改正谢谢!!
一.文件目录(主要是src文件夹下)
1.compiler文件夹
主要是编译相关功能内容所在的文件夹,包括把模板转化成语法树,语法树优化,和把templete模板写法转化成render(vue中有两种写法一种是templete模板写法和render函数写法,其实最终都是render函数渲染,推荐使用templete模板写法,因为compiler中有很多优化templete模板转为render函数的优化算法)
涉及vue脚手架新建项目compiler选择RuntimeOnly和Runtime+Compiler的区别和所在位置
参考答案:
1.Runtime Only
我们在使用 Runtime Only 版本的 Vue.js 的时候,通常需要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成 JavaScript,因为是在编译阶段做的,所以它只包含运行时的 Vue.js 代码,因此代码体积也会更轻量。 在将 .vue 文件编译成 JavaScript的编译过程中会将组件中的template模板编译为render函数,所以我们得到的是render函数的版本。所以运行的时候是不带编译的,编译是在离线的时候做的。
2.Runtime+Compiler
我们如果没有对代码做预编译,但又使用了 Vue 的 template 属性并传入一个字符串,则需要在客户端编译模板
在选择runtime-compiler vue的运行过程会将 template --解析--> 成抽象语法树(ast)--编译成--> render函数 --->虚拟dom树 ---->渲染成真实dom
而在runtime-only render函数 ---> 虚拟dom --->真实dom ,runtime-only跳过了template解析成抽象语法树的步骤,代码量更少,性能更高
在runtime-only中render的值是一个函数,并且这个函数的参数是一个createElement函数用来创建元素的
所以通常我们更推荐使用 Runtime-Only 的 Vue.js。
只有以下情况会用到compiler:
1.有指定template;
2.没指定template,也没指定render(这时候使用的就是被挂载元素的outerHtml)。
所以,没有使用到compiler的情况只有:没有指定template,但指定了render。
2.core文件夹vue的核心代码组成包括
3.platforms文件夹主要存放的是跨平台渲染的代码
4.server文件夹主要是服务端渲染逻辑的模块代码
5.sfc文件夹是.vue文件的解析器的代码
6.shared文件夹全局不同模块间工具方法的文件夹(模块内的公用方法用utils)
2.vue的本体所在的文件目录为## vue/src/core/instance/index.js
1.initMixin主要是提供一个init方法通过外部传入参数进行挂载和配置合并,还做了初始化
初始化主要是合并配置,初始化生命周期,初始化事件相关的内容, 初始化render初始化渲染相关的内容(主要看defineRactive), 初始化state(包括props,methods, data, computed, watch)(初始化props和data再遍历后对数据进行响应式处理,通过proxy函数做一个代理(不用写this.props而是直接用this.)),初始化inject,初始化provide(这三部分是在beforeCreaated和created中间做的内容)
2.stateMixin主要是挂载属性,挂在了delete, props,set主要是可以将属性进行双向数据绑定(可以在依赖收集没收集到的情况下进行数据响应式)
3.eventsMixin绑定了emit,once(只执行一次)事件
4.lifecycleMixin包含_update方法,destory(beforeDestory和destory之间做了什么可以看这部分)
5.renderMixin包涵_render和$nexTick(的callback是在数据渲染完成后执行的)
二.数据更新
1.vm.options.el)实例挂载到option上
面试题:vue实例能不能挂载在body或者html根节点上,答案显而易见是不能的如果挂载在上面会出现报错,(源码中$moutnt执行前会先进行对挂载对象进行判断,会判断是不是根节点,由于会将生成的虚拟dom对挂载对象进行替换,如果是根节点则会产生影响,所以不能是根节点)
先进行判断挂载节点是否是根节点,将templte模板转换为render函数在用$mount進行挂载, 执行mountcomponent方法
(1)call beforemount (生命周期)
(2)new watcher
进行了updateComponent方法 其中进行了两部分内容
1.vm._update(更新生成实际的dom): 进行vm.__patch__方法里面调用了createPatchFunction中进行patchVnode然后调用createElement使用浏览器中的createElement生成实际的dom同时利用createChildrn遍历所有子节点再使用patchVnode在生成子节点的真实dom
2.vm._render(生成虚拟dom): 运行_createElement方法中通过normalizeChildre方法将children进行标准化处理,统一转化为vnode类型的数组,然后对当前节点类型进行判断 如果是string类型则会生成一个新的vNode 采用(new vNode) 注:对tag进行判断 如果tag是字符串类型可能是内置节点(直接创建普通的vNode)可能是已注册的组件名(运用createComponents来创建组件的类型)可能是未知节点(创建一个未知标签类型的节点)还可能直接创建组件的虚拟节点
(3)call mounted (生命周期)
三.组件化
1.组件渲染调用createComponent方法来进行渲染
2.组件配置对组件的option进行合并
四.响应式
1.依赖收集 主要体现在initState(initstate和inintprops)
2.派发更新
defineReactive这个是defineProperty的封装 首先要创建一个Dep的类(其实是watcher的管理者,利用静态属性来维护全局的唯一性,)
watcher类:new watcher会执行pustarget方法会将自身实例放入全局的target,在depend的方法中执行addDep,把this挂载到Dep中的subs中,然后利用循环把子组件中的数据都挂载到Dep中
和一个observe(是new observer生产的实例是一个观察者模式生成了__ob__,生成了一个new Dep和一个def)
利用notify方法进行派发更新,会执行Dep中的subs里面的各个watcher的updata方法进行更新,更新的时候会为维护一个更新的缓存队列