目录构造
├── benchmarks 性能、基准测试
├── dist 构建打包的输入目录
├── examples 案例目录
├── flow flow 语法的类型申明
├── packages 一些额定的包,比方:负责服务端渲染的包 vue-server-renderer、配合 vue-loader 应用的 vue-template-compiler,还有 weex 相干的
│ ├── vue-server-renderer
│ ├── vue-template-compiler
│ ├── weex-template-compiler
│ └── weex-vue-framework
├── scripts 所有的配置文件的寄存地位,比方 rollup 的配置文件
├── src vue 源码目录
│ ├── compiler 编译器
│ ├── core 运行时的外围包
│ │ ├── components 全局组件,比方 keep-alive
│ │ ├── config.js 一些默认配置项
│ │ ├── global-api 全局 API,比方相熟的:Vue.use()、Vue.component() 等
│ │ ├── instance Vue 实例相干的,比方 Vue 构造函数就在这个目录下
│ │ ├── observer 响应式原理
│ │ ├── util 工具办法
│ │ └── vdom 虚构 DOM 相干,比方相熟的 patch 算法就在这儿
│ ├── platforms 平台相干的编译器代码
│ │ ├── web
│ │ └── weex
│ ├── server 服务端渲染相干
├── test 测试目录
├── types TS 类型申明
Vue 的初始化过程(new Vue(options))都做了什么?
1. 解决组件配置项
初始化根组件时进行了选项合并操作,将全局配置合并到根组件的部分配置上
初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以进步代码的执行效率
2. 初始化组件实例的关系属性,比方 $parent、$children、$root、$refs 等
3. 解决自定义事件
4. 调用 beforeCreate 钩子函数
5. 初始化组件的 inject 配置项,失去 ret[key] = val 模式的配置对象,而后对该配置对象进行浅层的响应式解决(只解决了对象第一层数据),并代理每个 key 到 vm 实例上
6. 数据响应式,解决 props、methods、data、computed、watch 等选项
7. 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
8. 调用 created 钩子函数
9. 如果发现配置项上有 el 选项,则主动调用 $mount 办法,也就是说有了 el 选项,就不须要再手动调用 $mount 办法,反之,没提供 el 选项则必须调用 $mount
10. 接下来则进入挂载阶段
总结:Vue 初始化次要就干了几件事件,合并配置,初始化生命周期,初始化事件核心,初始化渲染,初始化 data、props、computed、watcher 等等。
Vue 实例挂载的实现
Vue 中咱们是通过 $mount 实例办法去挂载 vm 的
$mount 办法实际上会去调用 mountComponent 办法
mountComponent 外围就是先实例化一个渲染 Watcher,在它的回调函数中会调用 updateComponent 办法,在此办法中调用 vm._render 办法学生成虚构 Node,最终调用 vm._update 更新 DOM
Watcher 起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数
最初判断为根节点的时候设置 vm._isMounted 为 true,示意这个实例曾经挂载了,同时执行 mounted 钩子函数
render
Vue 的 _render 办法是实例的一个公有办法,它用来把实例渲染成一个虚构 Node
写的比拟多的是 template 模板,在 mounted 办法的实现中,会把 template 编译成 render 办法
Virtual DOM
真正的 DOM 元素是十分宏大的,因为浏览器的规范就把 DOM 设计的非常复杂。当咱们频繁的去做 DOM 更新,会产生肯定的性能问题
而 Virtual DOM 就是用一个原生的 JS 对象去形容一个 DOM 节点,所以它比创立一个 DOM 的代价要小很多
在 Vue.js 中,Virtual DOM 是用 VNode 这么一个 Class 去形容
VNode 是对实在 DOM 的一种形象形容,它的外围定义无非就几个要害属性,标签名、数据、子节点、键值等,其它属性都是用来扩大 VNode 的灵活性以及实现一些非凡 feature 的。
因为 VNode 只是用来映射到实在 DOM 的渲染,不须要蕴含操作 DOM 的办法,因而它是十分轻量和简略的
Virtual DOM 除了它的数据结构的定义,映射到实在的 DOM 实际上要经验 VNode 的 create、diff、patch 等过程
Vue.js 利用 createElement 办法创立 VNode
createElement 创立 VNode 的过程,每个 VNode 有 children,children 每个元素也是一个 VNode,这样就造成了一个 VNode Tree,它很好的形容了咱们的 DOM Tree。
update
Vue 的 _update 是实例的一个公有办法,它被调用的机会有 2 个,一个是首次渲染,一个是数据更新的时候
从初始化 Vue 到最终渲染的整个过程(配图)
生命周期
beforeCreate & created
beforeCreate 和 created 函数都是在实例化 Vue 的阶段,在 _init 办法中执行的
在这两个钩子函数执行的时候,并没有渲染 DOM,所以咱们也不可能拜访 DOM,一般来说,如果组件在加载的时候须要和后端有交互,放在这俩个钩子函数执行都能够,如果是须要拜访 props、data 等数据的话,就须要应用 created 钩子函数
beforeMount & mounted
beforeMount 钩子函数产生在 mount,也就是 DOM 挂载之前,它的调用机会是在 mountComponent 函数中
在执行 vm._render() 函数渲染 VNode 之前,执行了 beforeMount 钩子函数,在执行完 vm._update() 把 VNode patch 到实在 DOM 后,执行 mounted 钩子
beforeUpdate & updated
beforeUpdate 和 updated 的钩子函数执行机会都应该是在数据更新的时候
beforeUpdate 的执行机会是在渲染 Watcher 的 before 函数中
留神,在组件曾经 mounted 之后,才会去调用 beforeUpdate 这个钩子函数
beforeDestroy & destroyed
beforeDestroy 和 destroyed 钩子函数的执行机会在组件销毁的阶段,最终会调用 $destroy 办法
在 $destroy 的执行过程中,它又会执行 vm.__patch__(vm._vnode, null) 触发它子组件的销毁钩子函数,这样一层层的递归调用,所以 destroy 钩子函数执行程序是先子后父,和 mounted 过程一样
activated & deactivated
activated 和 deactivated 钩子函数是专门为 keep-alive 组件定制的钩子
keep-alive 参考:https://www.jianshu.com/p/952…
响应式
初始化的过程,把原始的数据最终映射到 DOM 中, 数据的变更会触发 DOM 的变动
1. 数据渲染到页面
2. 解决用户交互
原生 js 做法
监听点击事件,批改数据,手动操作 DOM 从新渲染
vue 做法
利用 Object.defineProperty 原理,具体详见:https://developer.mozilla.org…
Object.defineProperty(obj, prop, descriptor)
对于 descriptor 中的 get 和 set,get 是一个给属性提供的 getter 办法,当咱们拜访了该属性的时候会触发 getter 办法;set 是一个给属性提供的 setter 办法,当咱们对该属性做批改的时候会触发 setter 办法
一旦对象领有了 getter 和 setter,咱们能够简略地把这个对象称为响应式对象
proxy
代理的作用是把 props 和 data 上的属性代理到 vm 实例上。定义了 props,能够通过 vm 实例拜访到它
let comP = {
props: {msg: 'hello'},
methods: {say() {console.log(this.msg)
}
}
}
在 say 函数中通过 this.msg 拜访到定义在 props 中的 msg,这个过程产生在 proxy 阶段, 实现原理如下:
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter () {return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
observe
observe 办法的作用就是给非 VNode 的对象类型数据增加一个 Observer,如果曾经增加过则间接返回,否则在满足肯定条件上来实例化一个 Observer 对象实例
Observer
Observer 是一个类,它的作用是给对象的属性增加 getter 和 setter,用于依赖收集和派发更新
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
// 对 value 做判断,对于数组会调用 observeArray 办法,否则对纯对象调用 walk 办法
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
// defineReactive 的性能就是定义一个响应式对象,给对象动静增加 getter 和 setter
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {
// observe 的性能就是用来监测数据的变动, 具体代码看源码
observe(items[i])
}
}
/**
* Define a property.
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
响应式原理总结:外围就是利用 Object.defineProperty 给数据增加了 getter 和 setter,目标就是为了在咱们拜访数据以及写数据的时候能主动执行一些逻辑:getter 做的事件是依赖收集,setter 做的事件是派发更新