共计 6349 个字符,预计需要花费 16 分钟才能阅读完成。
https://www.processon.com/vie…
https://www.processon.com/vie…
指标
- 把握源码学习办法
- vue 初始化原理解剖
- 深刻了解数据响应式
配置环境
- 拷贝代码: git clone https://github.com/vuejs/vue.git
- npm install
- 装置打包工具 rollup npm i -g rollup
- 批改 package.json 中 dev 的打包配置 ”dev”: “rollup -w -c scripts/config.js –sourcemap –environment TARGET:web-full-dev”,
- 执行脚本 npm run dev
- 在 examples 文件夹中增加 test 文件
文件目录
vue | |
├──dist #公布目录 | |
├──examples #范例,测试代码在这里 | |
├──flow #试代码 | |
├──packages #外围代码之外的独立库 | |
├──scripts #构建脚本 | |
├──src #源码 | |
├──test #ts 类型申明,下面 flow | |
└──types #对 flow 的类型申明 |
src # 源码 | |
├──compiler # 编辑器相干 | |
├──core # 外围代码 | |
├──────components # 通用组建如 keep-alive | |
├──────global-api # 全局 API | |
├──────instance # 构建函数等 | |
├──────observer # 响应式相干 | |
├──────vdom # 虚构 DOM 相干 | |
└──────platforms # 平台独特的代码 代码扩充 |
vue 源码源码剖析 - 初始化流程
入口
dev 脚本中 scripts/config.js 配置文件
web-full-dev 示意执行时的配置项
'web-full-dev': { | |
// 入口 | |
entry: resolve('web/entry-runtime-with-compiler.js'), | |
// 进口 | |
dest: resolve('dist/vue.js'), | |
// 格局 | |
format: 'umd', | |
env: 'development', | |
alias: {he: './entity-decoder'}, | |
banner | |
}, |
入口文件
门路 src/platforms/web/entry-runtime-with-compiler.js
首先是扩大 $mount 办法, 解决 template 或者是 el 选项
// 扩大 $mount | |
const mount = Vue.prototype.$mount | |
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean): Component {el = el && query(el) | |
if (el === document.body || el === document.documentElement) { | |
process.env.NODE_ENV !== 'production' && warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`) | |
return this | |
} | |
// 用户配置的选项 | |
const options = this.$options | |
// 首先判断 render 是否存在 | |
if (!options.render) { | |
// 获取 template 选项 | |
let template = options.template | |
// 判断 template 是否存在 如果 template 不存在判断 el 是否存在 | |
if (template) {} else if (el) {template = getOuterHTML(el) | |
} | |
if (template) {// 获取到 html 模板字符之后 执行编译过程} | |
} | |
// 执行挂载 | |
return mount.call(this, el, hydrating) | |
} |
Vue.prototype.$mount 的起源
门路: src/platforms/web/runtime/index.js
// 实现了一个 patch 函数 次要用于初始化和更新 | |
Vue.prototype.__patch__ = inBrowser ? patch : noop | |
// 实现了 $mount | |
Vue.prototype.$mount = function ( | |
el?: string | Element, | |
hydrating?: boolean | |
): Component {el = el && inBrowser ? query(el) : undefined | |
// 定义 $mount 挂载组件, 将 vnode 转化为 node | |
return mountComponent(this, el, hydrating) | |
} |
查找 vue
门路: src/core/index.js
// 初始化全局 api 比方 component/ fitter/ directive/ use/ mixin/ util/ extend | |
initGlobalAPI(Vue) |
查找 vue 引入
门路: src/core/instance/index.js
申明了 Vue 构造函数
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) // 混入了_init()办法 | |
stateMixin(Vue) // $data/$props/ $set() $delete() $watch() | |
eventsMixin(Vue) // $emit() / $on() $off() $once() 事件相干办法 | |
lifecycleMixin(Vue) // _update / $forceUpdate/ $destory 生命周期相干办法 | |
renderMixin(Vue) // $nextTick()/ _render() // 渲染相干 |
initMixin 办法
门路: src/core/instance/init.js
// 实现一个_init 办法 _init 的目标是供外部应用 | |
Vue.prototype._init = function (options?: Object) { | |
// 做选项合并 将传入的选项和 vue 的默认选项进行合并 | |
if (options && options._isComponent) { } else { | |
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), | |
options || {}, | |
vm | |
) | |
} | |
// 初始化外围代码 | |
vm._self = vm | |
initLifecycle(vm) // $parent $root $children $refs | |
initEvents(vm) // 自定义事件监听 | |
initRender(vm) // $slots/ $createElement 定义 $attrs 和 $listeners 响应式 | |
// 下面办法在 beforeCreate 之前, 进行初始筹备 | |
callHook(vm, 'beforeCreate') // 派发 beforeCreate, 在 beforeCreate 中能够拜访下面三个内容 | |
initInjections(vm) // resolve injections before data/props | |
initState(vm) // props/ methods/ data/ computed /watch 数据初始化 | |
initProvide(vm) // resolve provide after data/props | |
callHook(vm, 'created') | |
// 当设置了 el 选项时, 主动调用 $mount | |
if (vm.$options.el) {vm.$mount(vm.$options.el) | |
} | |
} |
整体流程:
new Vue() => 创立 vue 实例
_init() => 初始化数据, 属性, 事件
$mount() => 执行挂载, 将虚构 dom 转化成实在 dom
mountComponent()=> 将虚构 dom 转化成实在 dom | |
new Watcher() => 创立组件渲染 watcher | |
updateComponent() => 执行初始化或更新 | |
_update() => 初始化或更新, 将虚构 dom 转化成实在 dom | |
_render() 渲染组件,获取 vdom~~~~ | |
Vue 源码剖析 数据响应式
vue2 实现双向绑定, 应用了 js 对象的 Object.defineProperty()对 data 的 setter/getter 办法进行拦挡, 联合公布订阅模式, 在 getter 中进行订阅. 在 setter 中进行公布告诉, 让所有订阅者实现响应
具体实现是在初始化 Vue 时, 会调用 initState(vm)办法
initState(vm)
门路: src/core/instance/state.js
export function initState (vm: Component) {..... | |
// 判断 data 是否存在 | |
if (opts.data) { // 如果 data 存在,走这里,因为要看数据的双向绑定,所以找到 initData | |
initData(vm) | |
} else {observe(vm._data = {}, true /* asRootData */) | |
} | |
} |
initData(vm)
function initData (vm: Component) {.... | |
// 传进来的 data 可能是函数,或者对象,目标是避免数据净化;let data = vm.$options.data | |
data = vm._data = typeof data === 'function' | |
? getData(data, vm) | |
: data || {} | |
.... | |
// 代理、反复判断 | |
const keys = Object.keys(data) | |
while (i--) {.... | |
// 属性和办法不能是反复的判断 | |
if (process.env.NODE_ENV !== 'production') {} | |
if (props && hasOwn(props, key)) {}} | |
// 对 data 数据响应式解决 | |
observe(data, true /* asRootData */) | |
} |
对 data 数据响应式解决
门路: src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {.... | |
// 为传进来的对象 value,创立一个 observer 实例;任何一个对象都随同一个 observer 实例 | |
// 返回一个 observer 实例 | |
// 用 observer 实例,来判断类型 以及外部响应式解决 | |
// 如果做过响应式,那么_ob_ 存在,就间接返回 | |
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__} else if (...){...}else{... | |
// 如果未解决,就创立一个新的实例 ob 实例 | |
ob = new Observer(value) | |
} | |
} |
new Observer(value)
门路: src/core/observer/index.js
export class Observer {constructor (value: any) {// 能够了解为小管家:比方在 data 中的数据是对象包对象的存在,data:{obj:{foo:foo}}, 对对象来说,是对对象新 增和删除的告诉工作;由小管家来告诉的。// 当须要批改 obj 外面的属性,是在肚子里产生的事件,所以须要小管家来进行解决 | |
// 如果 obj 是一个数组,选项的删除也是须要小管家 | |
// 对象属性变动或者数组元素变动须要小管家告诉更新 | |
this.dep = new Dep() // 小管家 | |
// 判断传入的 value 类型,做相应的解决 | |
if (Array.isArray(value)) { // 当是 Array 时的解决 | |
// 笼罩数组实例的原型 | |
}else{ | |
// 对象的解决 | |
this.walk(value) | |
} | |
} | |
walk (obj: Object) {.... | |
const keys = Object.keys(obj) | |
// 遍历以后的所有 keys | |
for (let i = 0; i < keys.length; i++) { | |
// 对每个 key 进行拦挡 | |
defineReactive(obj, keys[i]) | |
} | |
} | |
} |
defineReactive
// 做递归解决 | |
Object.defineProperty(obj, key, { | |
enumerable: true, | |
configurable: true, | |
get: function reactiveGetter () { | |
// 依赖收集 | |
const value = getter ? getter.call(obj) : val | |
// Dep.target 能够了解为 watcher 的实例,每次触发 watcher 实例的时候,会手动触发,get 事件一下获取以后 值,将实例放在 Dep.target 上;if (Dep.target) {// dep 和 watcher 是 n 对 n 的关系,除了页面渲染 {{name}} 会触发 watcher 以外;还有可能是 computed 或者 w atch 触发的事件监听;// 双向增加两者关系 | |
dep.depend() | |
// 若存在子 ob | |
if (childOb) { | |
// 把以后的 watcher 和子 ob 中的 dep 建设关系 | |
childOb.dep.depend() | |
if (Array.isArray(value)) {dependArray(value) | |
} | |
} | |
} | |
return value | |
}, | |
set: function reactiveSetter (newVal) {}} | |
} |
dep.depend
门路:src/core/observer/dep.js
depend () {if (Dep.target) { | |
// 执行的是以后 watcher 的 addDep | |
Dep.target.addDep(this) | |
} | |
} |
Dep.target.addDep
找到 watcher 的 addDep 事件
门路:src/core/observer/watcher.js
addDep (dep: Dep) { //dep 和 wacter 互相保留的关系 | |
const id = dep.id | |
if (!this.newDepIds.has(id)) { // 是否和以后的 dep 建设关系 | |
// 没有就创立 dep 和 watcher 的关系 | |
this.newDepIds.add(id) | |
this.newDeps.push(dep) | |
if (!this.depIds.has(id)) { | |
// 创立 dep 和 watcher | |
dep.addSub(this) | |
} | |
} | |
} |
dep.addSub
src/core/observer/dep.js
export default class Dep { | |
// 往本人的 sub 外面去 push | |
addSub (sub: Watcher) {this.subs.push(sub) | |
} | |
} |