vue 版本为 2.6.11 博客的篇幅有点大,若是以为比较繁琐的,能够跳着看,里面也没有粘大量的源码,我会吧git上源码的连接贴出来,你们能够对照着源码连接或者把源码下载下来对照着看
看了好久的vue 的源码,也看了好多关于源码的贴子,本身也尝试了写了好几回vue源码的帖子,一是以为写的没有章法思路不够清晰,二是以为vue3都出了我如今写vue2的源码学习有点晚了因此没有发表,后来想一想本身写出来能够沉淀一些东西,而且也给你们提供一个阅读源码的思路, 但愿能写出一篇对我和对你们都有益处的帖子html
首先设定目标, 咱们不可能一行不差的把vue
的源码都看一遍(若是每行都看很快就会失去方向和兴趣),因此咱们要知道咱们看源码的目标是什么,以及看到什么程度就认为吧vue 的真正的核心代码和思想都学会了。
我认为把下面的这些点都了解的透彻了就算是真正的学到了vue 的精髓vue
上面的点就是咱们学习的目标,必定要先设定目标,要不就不知道咱们学习源码的意义,学着学着就放弃了,咱们要学会以目标为导向。node
首先咱们找到 vue
项目的入口而后咱们从我认为vue中最重要的最核心的内容响应式原理
开始学起react
若是想要知道如何找到入口请看 找入口的思路,若是以为不必看能够跳过 git
入口文件为platforms/web/entry-runtime.js
或者 platforms/web/entry-runtime-with-compiler.js
前者为不带 compiler
的后者为带有 compiler
的,由于咱们的目标设定中有 compiler
的内容,因此咱们就选后者为咱们本次源码学习的入口,对文件的依赖进行分析找到 VUe 的构造函数文件在 core/instance/index.js
这个文件中github
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) // $set $delete $watch $data $props eventsMixin(Vue) //$on $once $off $emit lifecycleMixin(Vue) //_update $forceUpdate $destroy renderMixin(Vue) //$nextTick _render
这个文件的主要做用是定义了Vue 并丰富了他的原型上的方法(我在上面的注释中标注了每一个方法分别对应了添加了那些原型方法),看一下vue的构函数,发现只调用了 _init
方法,整个Vue的入口就是 _init
这个方法了web
咱们对 _init
方法的内容进行分析面试
Vue.prototype._init = function (options?: Object) { ... if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm) } initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') if (vm.$options.el) { vm.$mount(vm.$options.el) } ... }
我这里省略了一部分源码,你们能够对照着本身下载下来的源码看,这个 _init
方法作了作了好多的事情,咱们不可能在这里一次说清楚,而且,咱们要知道咱们真正想要看的是什么,找到这个文件的目的是为了让咱们更好的达成目标(固然不是说你们就不用看阿,只是不要太深究,由于每个方法都有特别深的调用链,若是看下去确定会失去方向跑偏的哦),经过语义上看,咱们要看的响应式原理应该从 initState
方法入手 往下深刻的学习(vue的命名规范很是的好,经过命名我就能轻松的看出来每一个方法实现的功能,也是咱们值得学习的地方)。express
如何找到入口呢,其实写框架和咱们日常写代码同样,想一想,咱们若是接触到一个陌生的项目应该如何找到项目的入口呢,确定是先看 package.json
,而后分析其中的 script
,根据经验和语义的判断得出(你们也能够看看vue 的开发者文档之类的也能找到一些介绍,我以为找一个入口不想太费时间全部就没有那么严谨的去看),vue 打包的命令应该为 "build": "node scripts/build.js",
,咱们如今分析这个文件,这个文件不用太仔细的看,若是真的想要学习 rollup 打包的话能够深刻的看一看,咱们的目的是找到项目真真的入口,在这里深刻的去学习只会把咱们带偏。json
let builds = require('./config').getAllBuilds()
const builds = { ... 'web-runtime-esm': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.esm.js'), ... }, 'web-full-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.js'), }, ... }
上面我粘出来的代码就是咱们所寻找的入口文件,我是如何定位到这两个的呢,经过看package.json
中的 main,module,unpkg
这三个配置分别为咱们使用vue 库时候的 cjs,esm,cdn
的引入文件,他们对应的entry
就是入口文件了。
首先咱们看vue
的官方文档,对响应式原理的介绍很是的详细(必定要看,必定要看,而且要记到内心,深入理解,面试的时候会问的哦 ),我这里就不在多作赘述 官方文档
首先用一句话来解释响应式原理 当组件的数据发生变化的时候触发组件的从新渲染
响应式原理核心的点有哪些,也就是咱们学习响应式原理最终要学会什么,也就是咱们细分的 目标
我认为吧上面的几个点搞明白也就真正的搞明白了响应式原理
下面咱们从源码入手开始分析响应式原理
原本准备从 _init
中调用的 _initState
中深刻分析的,可是其实你在看了 _initState
方法以后你会发现,这里面大量的依赖了 watcher observer 和dep
这几个类,因此咱们先把这几个类彻底的搞清楚再看 _initState
方法这样有助于咱们的阅读
阅读源码不必定非要按照调用栈一层层的扒代码学习,当你发现这部分代码强烈的依赖另外一部分代码的时候,能够先搞懂他依赖的那一部分,这样更方便咱们的理解。
咱们首先想一想 vue 的实现响应式原理的逻辑,再去看这三个类,若是咱们忘了核心的思想去看代码会特别的迷茫,就像没有需求写代码同样,而且在咱们本身写代码的过程当中也是先想清楚了这个类要实现什么功能,再动手去写这个类,回顾一下思想, 大体流程是 为data设置getter 和setter, getter 的时候收集依赖,setter 的时候调用依赖的回调。
源代码通常篇幅比较大调用栈比较深,当遇到不理解的问题时回顾基本原理和目标,就不会丢了方向了。
若是不想看代码细节的同窗,能够跳过下面的代码细节,这里总结了一下 Observer watcher 和dep 各自的主要功能和他们之间的关系
observer
主要功能,将传入的data 转换为Observer data ,就是设置新的get 和set 方法
dep
对象主要是数据和 watcher 之间的一个桥梁,存放的是数据更新时须要调用的 watcher,并在数据更新的时候触发全部watcher 的update
watcher
对象是监控数据变化并调用回调,与dep的关系是,dep触发watcher 的更新,一个watcher能够有多个dep
咱们大体的浏览这个中的内容,导出了一个 Observer
类,defineReactive 和 observe
这个方法,其余的方法先不看,等用到的时候再看。
observe方法
__ob__
则 ob = value.__ob__
shouldObserve==true
加上其余的一些校验经过的话 ob = new Observer(value)
asRootData && ob
则 vmCount++
若是是组件的跟data对象 记录一下 vmCount总的来讲这个方法就是 new Observer 接下来咱们看一下Observer 类
Observer类
构造函数 constructor(value: any)
this.dep = new Dep()
this.vmCount = 0
记录依赖组件的数量__ob__
属性上observeArray
不然调用 walk
walk(obj: Object) 方法
循环对象的keys,调用 defineReactive
从新设置属性的 get和set
observeArray(items: Array<any>) 方法
循环数组对象,调用observe(item[i]),吧数组中的每个对象都设置为响应式数据
defineReactive方法
主要参数: obj 对象, key修改的字段
主要功能: 1.获取当前传入key对应值的 Observer 实例 2. 为 传入对象的key属性设置get和set
咱们知道vue 响应式是在 get 的时候收集依赖,在set 的时候调用回调的,咱们看看这里是如何收集和调用回调的呢?
const dep = new Dep() ... let childOb = !shallow && observe(val) ... get: function reactiveGetter() { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() }
get 方法调用 dep 的depend 的方法收集依赖
set 方法调用 dep 的 notify 发通知更改
总结:Observer 的使命就是吧data 转换为一个响应式的数据,数据添加 __ob__
和 get set
方法收集依赖和通知更新
属性介绍 1. 是id
从0 开始计数,每次new 一个新实例加一 2. subs:指订阅了该dep的全部watcher 对象
方法介绍
addSub
添加订阅者removeSub
删除指定订阅者depend
收集依赖notify
通知更新,遍历全部的订阅者,调用订阅者(watcher)的update
方法
静态变量target
当前的系统中的 watcher
对象 ,同一时刻只能有一个存在,经过调用 pushTarget 和 popTarget
这两个方法来设置
构造函数逻辑
isRenderWatcher 状况为vm._watcher = this
赋值 (在render的时候会用到能够先不关注)vm._watchers
对象中,vm._watchers
中包含当前vm的全部 watcher对象把options 的值赋值到 this 对象上,须要特别关注的几个属性
expOrFn
转化而来的一个 function
,若是expOrFn
的值为一个function
则getter 为该方法,不然调用parsePath
方法将表达式转换为一个方法,此时getter方法的返回值为监控的数据 (line 79~91)get方法的逻辑
dep 中的 pushTarget
方法把dep中的 Dep.target
设置为当前的 watcher 对象,表示后面全部的依赖收集都收集到当前的 watcher
对象上this.getter
方法实现依赖的收集,this.getter 方法执行过程当中全部被observer
过的对象的依赖都会收集到当前的能够 watcher
对象上,deep为true
则调用traverse
方法对最终的value进行递归,实现对全部的子属性的依赖收集,最后调用 dep中的popTarget
方法,关掉收集,并调用 cleanupDeps
方法addDep方法的逻辑
cleanupDeps方法的逻辑
get 方法调用的
update方法的逻辑
dep 的notify 方法中调用的
在监听数据发生变化的时候更新,若是 lazy==true
则this.dirty = true
不更新,若是sync为true 则同步更新不然调用 queueWatcher(this)
方法放入队列执行
run方法
run方法主要是从新计算value, 并执行回调
evaluate 方法
这个方法只在lazy为true的时候调用
计算value 的值, 并吧dirty 设置为false
teardown方法
清空watcher 对象上的依赖,以及清空 dep 上的subs 的watcher 对象,而且吧 this.active 设置为 false
入口方法_init
中调用的initState
方法
export function initState(vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
initState 方法定义了 _watchers
变量, 并调用 initProps, initMethods, initData ,initComputed, initWatcher
分别对 props, methods, data,computed,watcher
进行初始化
initProps
vm._props
proxy(vm, '_props', key)
这个把propsData 中的数据经过代理的方式挂到 vm 上initData
vm._data
赋值,若是 options.data
是一个方法则调用 getData(data,vm)
不然直接返回 options.data
, getData
方法主要是执行了 options.data
方法proxy(vm, "_data", key)
方法,将_data上的属性使用代理的方式挂到vm 上observe(data, true);
方法吧 data对象转换为 observe 以后的对象initComputed
vm._computedWatchers
options.computed
将 computed 中的每个方法 new 一个 Watcher 对象放到了 vm._computedWatchers
中,须要注意的是 这里 watcher 对象没有回调,而且设置了 options的{lazy: true}
这意味着,computed 的方法不是当即被调用的defineComputed
方法把 compute 上的每一个key 做为一个变量挂到的 vm
上,变量的get方法是经过调用 createComputedGetter
这个方法返回的一个方法createComputedGetter
方法:根据传入的key 调用_computedWatchers[key]的 get方法, 最后返回watcher 的 valueinitWatch
循环 options.watcher
而且调用 createWatcher
,createWatcher
又调用了vm.$watch
, 这个方法 new 了一个 Watcher对象
initMethods
内容比较简单主要是将 options.methods
中的方法挂载到 vm
对象上并别将 this 指向了 vm
我尚未看vue3 的源码等看完以后在补充上来