3-2-1vue响应式数据源码

1.Vue源码解析 --响应式原理
最好不要太看重细枝末节 支线 主要看主线

vue.js静态成员和实例成员初始化过程
首次渲染过程
数据响应式原理

2.准备工作
Vue源码的获取
分析Vue2.6
新版本发布后 一段过渡时期
dist 打包后的结果
examples 示例
src 源码部分
compiler 编译器 模板转换成 vnode函数
core
componets
global-api
instance vue的实例
observer 响应式数据
vdom 虚拟dom

		功能区分 拆分成小的模块
		
		
		引入了Flow  静态类型检查器
			最终编译成js
			代码编译之前检查编译正确
			通过类型推断 正确
			使用方法    
				//@flow  
				/* @flow*/   

3.源码如何打包 调试
打包
打包工具Rollup
比webpack轻量 ,webpack把模块文件作为模块处理
rollup只处理js文件
不生成冗余的代码

				安装依赖  npm i 
				设置sourcemap
					package.json中 dev脚本添加  --sourcemap
					-w 监视变化  
					
					vue.js
					vue.js.map

4.Vue不同的构建版本
UMD 通用版本 CommonJS ES Module
Full 完整版 编译器和运行时 vue.js vue.common.js vue.esm.js
Runtime-only 仅运行 vue.runtime.js vue.runtime.common.js vue.runtime.esm.js
Full(production) vue.min.js
Runtime-only(production) vue.runtime.min.js

	编译器 可以用模板
	运行时 不包含编译器  用render函数 生成  代替模板
	CommonJS 		  老版本
	ES Module   新版本   编译的时候可以解析模块间的依靠  tree-shking
	
	
	vue-cli
	output.js   webpack配置文件  由  vue inspect 指令生成
		      vue$: 'vue/dist/vue.runtime.esm.js'  可以看到版本文件
			  
	app.vue 单文件组件   不需要编译器

5.寻找入口文件
查看vue.js的构建过程
找config.js
target 版本 web-full-dev
script下的 config.js文件
genConfig函数 找到builds 对象 对应的target配置
‘web-full-dev’: {
entry: resolve(‘web/entry-runtime-with-compiler.js’), 入口 platform下
dest: resolve(‘dist/vue.js’), 输出
format: ‘umd’,
env: ‘development’,
alias: { he: ‘./entity-decoder’ },
banner 文件头
},

6.从入口开始
同时设置template 和render
入口文件逻辑
query 获取元素dom对象
vue不能是body或者html标签
先判断render 有就 mount 调用 mount 方法,渲染 DOM
没有就把 template 转换成render函数

			看call stack  了解执行顺序  前一个是在后一个里面调用的
							Vue.$mount  
						Vue.init
					Vue 
				index.html 实例化vue

7.Vue初始化的过程 四个导出vue的模块
1.入口文件 src\platforms\web\entry-runtime-with-compiler.js
1.1.web平台相关的入口
1.2.重写平台相关的$mount()方法
重写 $mount 函数
mount 渲染DOM
1.3.注册了 Vue.compile()方法,传递一个HTML字符串返回render函数 ----模板转换render函数
2.vue由 runtime/index 导入 src\platforms\web\runtime\index.js
2.1平台相关
2.2注册和平台相关的全局指令 v-model、v-show
extend 复制指令 platformDirectives
2.3注册和平台相关的全局组件 v-transition、v-transition-group
extend 复制指令 组件 platformComponents
2.4全局方法
patch 浏览器环境 或者noop
$mount方法
mountComponent 渲染DOM
3.vue 由core/index 导入 src\core\index.js
3.1与平台无关
3.2设置了 Vue的静态方法 initGlobalAPI
initGlobalAPI 给vue挂载 静态方法 由./global-api/index’
初始化以下属性
config 静态
util 避免调用 内部使用
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick

						observable 响应式数据
						options
				...渲染方法
				version 版本
				
				4. 由 './instance/index' 导入  创建了vue的构造函数,实例成员   拆分的很细   src\core\instance\index.js
					4.1与平台无关
					4.2定义了构造函数  调用 this._init(options)方法
					4.3注入了常用的实例成员
						为什么不用类?
							用类,挂载很多方法 用原型语法不搭
						判断环境
							调用init方法
						// 注册 vm 的 _init() 方法,初始化 vm
						initMixin(Vue)
						
						// 注册 vm 的 $data/$props/$set/$delete/$watch  混入成员
						stateMixin(Vue)
						// 初始化事件相关方法  初始化实例方法和属性
						// $on/$once/$off/$emit
						eventsMixin(Vue)
						-----------------------后面分析
						lifecycleMixin(Vue)
						// 混入 render
						// $nextTick/_render
						renderMixin(Vue)

8.Vue.js的初始化
一些设置 红线 高亮显示 的问题

9.vue.js 静态成员 的初始化
initGlobalAPI 给vue挂载 静态方法 由./global-api/index’
初始化 vue.config对象
get
set 赋值会报错
observable 响应式方法

		Vue.options
		ASSET_TYPES  数组  常量 
			'component',
		    'directive',
		    'filter'
			注册到 Vue.options 上
		Vue.options._base 
		extend(Vue.options.components, builtInComponents)
			把 builtInComponents 给 Vue.options.components  组件
		静态方法 
			// 注册 Vue.use() 用来注册插件
			initUse(Vue)   初始化use方法  注册插件
				调用插件的方法 返回构造函数
			// 注册 Vue.mixin() 实现混入
			initMixin(Vue)	  混合选项
				this.options = mergeOptions(this.options, mixin)

			// 注册 Vue.extend() 基于传入的options返回一个组件的构造函数
			initExtend(Vue)  返回组件的构造函数
				VueComponent
			// 注册 Vue.directive()、 Vue.component()、Vue.filter()
			initAssetRegisters(Vue)
				遍历 ASSET_TYPES 
					Vue.directive()  注册获取全局指令
					Vue.filter() 	注册获取全局过滤器
					Vue.component() 注册获取全局组件			isPlainObject  是不是object
					
					存储在 this.options[type + 's'][id]  中

10.Vue.js初始化 —实例成员 ‘./instance/index’
instanced index.js 混入成员 实例 或者方法
// 注册 vm 的 _init() 方法,初始化 vm
initMixin(Vue)
_init
// 注册 vm 的 d a t a / data/ data/props/ s e t / set/ set/delete/ w a t c h s t a t e M i x i n ( V u e ) O b j e c t . d e f i n e P r o p e r t y ( V u e . p r o t o t y p e , ′ watch stateMixin(Vue) Object.defineProperty(Vue.prototype, ' watchstateMixin(Vue)Object.defineProperty(Vue.prototype,data’, dataDef)
Object.defineProperty(Vue.prototype, ' p r o p s ′ , p r o p s D e f ) V u e . p r o t o t y p e . props', propsDef) Vue.prototype. props,propsDef)Vue.prototype.set = set
Vue.prototype. d e l e t e = d e l V u e . p r o t o t y p e . delete = del Vue.prototype. delete=delVue.prototype.watch
// 初始化事件相关方法
// o n / on/ on/once/ o f f / off/ off/emit
eventsMixin(Vue)
$on event为数组或者字符串
$once
$off
e m i t / / 初 始 化 生 命 周 期 相 关 的 混 入 方 法 / / u p d a t e / emit // 初始化生命周期相关的混入方法 // _update/ emit////update/forceUpdate/$destroy
lifecycleMixin(Vue)
_update
patch 方法
$forceUpdate 强制更新 watch
$destroy 销毁 实例销毁
// 混入 render
// $nextTick/_render
renderMixin(Vue)
installRenderHelpers 渲染的方法 _v 方法
n e x t T i c k r e n d e r v n o d e = r e n d e r . c a l l ( v m . r e n d e r P r o x y , v m . nextTick _render vnode = render.call(vm._renderProxy, vm. nextTickrendervnode=render.call(vm.renderProxy,vm.createElement)
vm.$createElement h函数内部

11.实例成员 init 分模块 清晰
vue构造函数调用 _init方法
分析 initMixin()方法 里面定义了_init方法
vm常量
_uid
_isVue 当前实例是vue的实例 响应式数据 不做处理
options._isComponent 组件判断
initInternalComponent
如果不是 则 resolveConstructorOptions

				两个方法都是对 vm.$options  和 options 用户传入的 进行合并处理
			initProxy  如果有proxy对象 
				没有  _renderProxy = vm
			_renderProxy  渲染时候的代理对象
			
		方法:
			// vm 的生命周期相关变量初始化
			// $children/$parent/$root/$refs 
			initLifecycle(vm)   找到父节点 并存在父节点上
			// vm 的事件监听初始化, 父组件绑定在当前组件上的事件
			initEvents(vm)   初始化当前组件的事件    存储事件名称和对应的函数
				updateComponentListeners  获取父组件的事件 给当前组件注册事件
			// vm 的编译render初始化
			// $slots/$scopedSlots/_c/$createElement/$attrs/$listeners
			initRender(vm)			
				$createElement   就是h函数  将虚拟dom转换为真实dom
			// beforeCreate 生命钩子的回调
			callHook(vm, 'beforeCreate')	钩子函数 
				
			// 把 inject 的成员注入到 vm 上
			initInjections(vm) // resolve injections before data/props
				
			// 初始化 vm 的 _props/methods/_data/computed/watch
			initState(vm)	
			// 初始化 provide
			initProvide(vm) // resolve provide after data/props
			// created 生命钩子的回调   钩子函数 
			callHook(vm, 'created')

12.调试vue的初始化过程

12.initState
// 初始化 vm 的 _props/methods/_data/computed/watch
initState(vm)
props 通过 initProps 方法 属性 vm._props 转化响应式数据 给vue实例
methods 通过 initMethods 方法 必须是function 给vm实例
data 通过 initData 方法 给vm实例
获取props 和 method是 的属性是否重名 并警告
proxy 注入实例
observe 转化成响应式对象
computed 通过 initComputed 方法 计算属性 给vue实例
watch 通过 initComputed 方法 监听器 给vue实例

13.调试vue的初始化过程
watch : vue
src\core\instance\index.js
initMixin(vue)
_isVue
判断组件
合并options mergeOptions
initProxy
调用$mount 渲染 进入第四个js文件
stateMixin
eventsMixin
lifecycleMixin
renderMixin

	src\core\index.js
		initGlobalAPI		./global-api/index' 
			config
			util
			setdelete
			nextTick
			boservable
			options           组件 指令 过滤器
			_base 	构造函数
			放到option
				initUse
				initMixin
				initExtend
				initAssertRegisters
			
	src\platforms\web\runtime\index.js
		组件和指令
		_patch_
		$mount
			mountComponent			文件
				beforeMount  钩子函数
				定义 updateComponent方法  更新组件
					调用_render方法 调用 render方法
					虚拟dom转换成真实dom
				new watcher 调用 updateComponent    
					watcher.js  进入文件
						isRenderWatcher 是否是渲染watcher
						lazy   是否延迟更新视图
						expOrFn  function  或者字符串
							getter =expOrFn
							
						this.value = this.lazy
						  ? undefined
						  : this.get()
						  
						  get  调用 updateComponent
						  执行完返回 liefcycle.js文件
							
				mounted  钩子函数
	src\platforms\web\entry-runtime-with-compiler.js
		重写$mount  模板转换render函数
			判断模板是否存在
				模板是否字符串
					模板第一个字符是否 #
						idToTemplate
				模板是否是节点
			判断el是否存在
				getOuterHTML	返回设置的div
			compileToFunctions 模板转换render函数
			返回 mount   进入第三个js 
		compile  手工模板转换render函数

14.首次渲染过程–总结
1.vue 初始化 即对实例成员和静态成员进行初始化
2.创建 new Vue()
3.进入 this.init()
4.进入 vm.KaTeX parse error: Undefined control sequence: \platforms at position 15: mount() 在 src\̲p̲l̲a̲t̲f̲o̲r̲m̲s̲\web\entry-runt…el.snode) 挂载真实DOM
记录vm.$el

15.响应式实现原理
问题:
重新给属性赋值,是否是响应式
给数组元素赋值,视图是否会更新
修改数组的length,视图是否更新
添加数组中的一项,视图书否更新
先找到响应式数据的入口
src\core\instance\index.js
initState(vm) vm状态的初始化
初始化了_data 数据 _props _methods
state.js 文件
initState方法里
observe(vm._data) 调用了
1.判断value是否是vnode 直接返回
2.如果 value 有 ob(observer对象) 属性 直接返回
else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 创建一个 Observer 对象
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}

16.observe 里的实现逻辑
1.判断value是否是对象 不是直接返回
2.如果 value 有 ob(observer对象) 属性 直接返回 判断之前是否做个响应式处理
else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 创建一个 Observer 对象
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}

类 Observer     getter/setters   收集依赖 派发更新
	属性:
	  // 观测对象
	  value: any;
	  // 依赖对象
	  dep: Dep;
	  // 实例计数器
	  vmCount: number;


	1.def(value, '__ob__', this)  
		Object.defineProperty
			enumerable: !!enumerable,  !第一个 取反 第二个! 是默认为true
			
	2.判断是否是数组
		做响应式处理
	3.否则  walk方法
		遍历walk属性
			defineReactive(obj, keys[i])

17.defineReactive 方法 为一个对象定义一个响应式的属性
1.dep new Dep() 创建依赖对象实例
2.property getOwnPropertyDescriptor 获取 obj 的属性描述符对象
3.getter 从property 获取 用户设置的
setter 从property 获取 用户设置的
4. let childOb = !shallow && observe(val) 监视多层属性
5.defineProperty 与之前手写情形差不多 只是考虑更多
get
如果用户设置了get 用 用户的 没有 则创建
set
如果用户设置了get 用 用户的 没有 则创建
判断新旧值是否相等 特殊情况 NAN
if (getter && !setter) return 只读
dep.notify() 派发通知

18.依赖收集 + 调试
每一个组件对应一个watcher对象
// 如果存在当前依赖目标,即 watcher 对象,则建立依赖
if (Dep.target) { watcher 对象的get方法了 有 pushtarget方法
设置Dep.target赋值
dep.depend() 收集依赖
addDep 添加依赖
addSub 添加依赖 添加到sub的数组中
// 如果子观察目标存在,建立子对象的依赖关系
if (childOb) {
childOb.dep.depend()
// 如果属性是数组,则特殊处理收集数组对象依赖
if (Array.isArray(value)) {
dependArray(value)
}
}
}

19.数组的响应式过程 构建oboserve
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)

——————————————————————————————————          
protoAugment(value, arrayMethods) 改变对象的原型

  protoAugment  设置数组的原型参数
  arrayMethods
	pu数组原型的方法 
	observeArray
	ob.dep.notify()

copyAugment(value, arrayMethods, arrayKeys)   把修补过后的方法 设置到原型上

observeArray  将数组中元素遍历并且转换成响应式数据

20.数组实例

vm.arr[0]=100   页面未更新
	并没有遍历数组的所有属性
		而是遍历了数组的所有元素  是对象的元素转化成了响应式的对象
		没有处理数组对象的属性
vm.arr.length=0

vm.arr.splice(0,1)  页面变化
vm.arr.push(100)  页面变化
	因为对数组的原型的方法进行了响应式数据的变换

21.Watcher + 调试
渲染的过程
改变的过程

类型:
	Computed Watcher    init
	用户Watcher 		init
	渲染Watcher
		lifecyle.js 中 mountComponent方法
			new Watcher
			
	Watcher 类  构造函数
		vm
		expOrFn   function或者字符串   updateComponent
			function
				this.getter = expOrFn
			字符串
				parsePath
		cb		
		options
		isRenderWatcher  是否是渲染watcher
		以上为入参  
		vm._watchers.push(this)  存储所有watcher  
		uid  自增
		
		get()  渲染watcher就直接调用
			pushTarget 便于父子组件嵌套的情况
			
			this.cleanupDeps() 移除当前dep
			
数据更新的时候
	dep.js	
		notify
			根据uid 排序watcher
				然后更新 subs[i].update()
					判断lazy和sync
						queueWatcher(this)
							判断watcher是否被处理  has对象
								判断flushing 是否正在被处理
									queue.push(watcher)   没有进入队列
								判断队列是否在执行   waiting
									flushSchedulerQueue  遍历队列中的所有watcher
										进入方法后 对队列进行排序
											目的
											1.组件更新顺序是从父组件到子组件  因为父组件先于子组件创建
											2.用户watcher在渲染watcher之前进行 因为用户watcher先于渲染watcher创建
											3.如果一个组件的父组件被销毁了,应该被跳过
												判断before 触发钩子函数
												has[id] = null
												调用run方法
													判断active
														调用get方法
															执行pushTarget方法  收集依赖
															用户offer继续执行,调用cb

22.数组响应式原理 调试
1.特殊方法的处理
创建observer
记录到__ob__属性
判断数组
判断浏览器支持原型
将原型属性 即数组 的原型 赋值给对象
执行walk方法
调用用defineReactive
给每个属性创建dep对象
给每个属性的值调用observe方法

2.收集依赖的处理  属性  对象的属性   数组的属性并没有转换响应式数据
	dep.depend()  把当前watcher对象添加到dep的subs里面
	判断childOb
		dependArray
			遍历数组 是否是ob对象
				如果对象发送变化 发送通知
				
3.notify方法
	slice 返回 副本
		调用watcher对象的update方法
			调用 queuewatcher 方法

23.响应式处理过程总结
1.通过调用initState() -->initData() -->observe()到第二步 初始化数据 转化为响应式数据
2.在 observe(value) 中
位置: src\core\observer\index.js
功能:
1.判断value是否是对象,不是对象直接返回
2.判断value对象是否有__ob__,如果有表示已为响应式数据 直接返回
3.如果没有 创建observer对象
4.返回observer对象 到第三步
3.Observer对象
位置: src\core\observer\index.js
功能:
1.给value对象定义不可以枚举的__ob__属性,记录当前的observer对象
2.数组的响应式处理
设置数组的几个特殊方法 找到数组的observer对象 调用notify
对每个成员对象判断 是的话作响应式处理
3.对象的响应式处理,调用walk()方法 遍历每一个属性 调用defineReactive 方法 到第4步
4.defineReactive
位置: src\core\observer\index.js
功能:
1.为每一个属性创建Dep对象
2.如果当前的属性是对象,调用observe 回到第3步
3.定义getter
1.收集依赖 详细看第五步
2.返回属性的值
4.定义setter
1.保存新值
2.如果新的对象是observe,调用observe
3.发送通知,调用dep.notify() 详细看第6步

5.依赖收集 发布订阅模式
	1.在 Watcher 对象的get方法中调用 pushTarget记录Dep.target属性
	2.访问data中的成员的时候收集依赖,defineReactive 的getter中收集依赖
	3.把属性对应的watcher对象添加到dep的subs数组中
	4.给childOb收集依赖,目的是子对象添加和删除成员时发送通知
6.Watcher  主要负责调用对应的回调函数并且进行相关dom处理和更新
	1.dep.notify 在调用watcher对象的update方法
	2.queueWatcher()判断watcher对象是否被处理,没有就添加到队列中,并调用 flushSchedulerQueue()
	3.flushSchedulerQueue
		1.触发beforeUpdate钩子函数
		2.调用watcher.run()   run ()-->get-->getter()-->updateComponent
		3.清空上一次的依赖
		4.触发actived钩子函数
		5.触发updated钩子函数

24.动态添加一个响应属性
vm.set
vm.$set
增加原型方法
应该避免给新增属性

25.源码 $set + 调试
两个方法是一个方法 set
1.目标对象是否是未定义或者原始值会警告
2.判断是否是数组
将最大值key 索引 定位数组的长度
进入splice方法
splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。没有第三个参数表示删除第二个参数索引的元素
3.判断属性是否在对象上 且不是原型属性
把值设置给属性 返回值
4.给目标设置__ob__
5.如果是ob对象 则警告 返回值
6.如果不是响应式对象 知己赋值
7.将key设置为响应式属性
8.发送通知 返回

26.vm.$delete方法 + 源码
如果对象的属性时 如果是响应式的 确保删除能触发更新视图 目标不能是vue实例或vue实例的根数据对象
基本同set
没有属性则返回
不需要更新视图 则直接return

27.vm.$watch(expOrFn,callback,[options])
功能:
观察 Vue 实例变化的一个表达式或计算属性函数。
回调函数得到的参数为新值和旧值。
表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。
参数:
expOrFn:要监视的 $data 中的属性,可以是表达式或函数
callback:数据变化后执行的函数:回调函数
对象:具有 handler 属性(字符串或者函数),如果该属性为字符串则 methods 中相应
的定义
options:可选的选项
deep:布尔类型,深度监听 可以监听对象的属性
immediate:布尔类型,是否立即执行一次回调函数 立即执行

28.三种类型的Watcher
没有静态方法,因为 w a t c h 方 法 中 要 是 用 V u e 的 实 例 W a t c h e r 分 三 种 : 计 算 属 性 W a t c h e r 、 用 户 W a t c h e r ( 侦 听 器 ) 、 渲 染 W a t c h e r 创 建 顺 序 : 计 算 属 性 W a t c h e r c o m p u t e d 、 用 户 W a t c h e r ( 侦 听 器 ) w a t c h 、 渲 染 W a t c h e r i s R e n d e r W a t c h e r v m . watch 方法中要是用Vue的实例 Watcher 分三种:计算属性 Watcher、用户 Watcher (侦听器)、渲染 Watcher 创建顺序:计算属性 Watcher computed、用户 Watcher (侦听器) watch 、渲染 Watcher isRenderWatcher vm. watchVueWatcherWatcherWatcher()WatcherWatchercomputedWatcher()watchWatcherisRenderWatchervm.watch()
src\core\instance\state.js
浏览器的Call Stack
看调试 创建顺序

		执行顺序:前面可知  Watcher被执行的时候会按照uid排序 所以也是创建顺序

29.watch 源码分析
src\core\instance\state.js
initcomputed
computedWatcherOptions :lazy :true

initWatch
	createWatcher
		$watch
			用到了vm的实例 
			
			创建user watcher
				
			判断是否立即执行cb immediate
			
			在run里面 调用user watcher的cb
 src\platforms\web\runtime\index.js
调用
	mountComponent
		创建渲染watcher

30.nextTick 异步更新队列 获取DOM上最新的数据
vue更新DOM是异步执行的,批量的
在下次DOM更新循环结束之后 执行延迟回调。在修改数据之后立即使用这个方法,即更新后的DOM

vm.$nextTick(function(){操作dom})
Vue.nextTick		
	源码:
	手动调用vm.$nextTick
	在watcher的queueWatcher 中执行nextTick()
	静态	src\core\instance\render.js
	实例	src\core\instance\render.js
			方法  src\core\util\next-tick.js  
				nextTick(cb,obj)
					callbacks push  cb 进去
					队列是否正在被处理    pending
						timerFunc()  
							通过promise 调用 flushCallbacks   微任务 
												isUsingMicroTask  属性  是否微任务
													可以为promise  或者 MutationObserver
													
													timerFunc 把cb放到cb数组  用微任务执行 如果不支持微任务 用宏任务
																											setImmediate 比 setTimeout 先执行

你可能感兴趣的:(粗笔记)