前言:
上篇已经了解到了,defineProperty的定义、使用场景、解决的问题,接下来
,我们一起来看下vue源码,篇幅有点大,希望你能耐心的看下去,然后有收获,有任何问题,我们可以沟通交流
复制代码
接下来你将会了解到以下内容
1、依赖收集
2、依赖触发
3、响应式
4、观察者
5、侦听器
6、代理
7、订阅者
8、函数渲染
9、虚拟dom的使用
复制代码
源码:下载地址 github.com/vuejs/vue.g…
框架结构
- 树状目录
.
|-- benchmarks // 处理复杂业务、大数据
| |--big-table // 大数据table
| |--dbmon // 数据库监视器 调试器
| |--reorder-list // 重排列表
| |--ssr // 服务器端渲染数据
| |--svg // 渲染大量svg
| |--uptime // 大批量更新时间
|-- dist // 编译后版本
|-- examples // vue例子demo
|-- flow // vue的flow检查,声明文件
|-- node_modules // npm包存放的地方
|-- packages // 插件 主要有四个vue相关的插件
|-- scripts // 存放一些npm脚本的配置文件
| |--git-hooks
|-- src // 源码目录
| |-- compiler // 解析模板相关代码
| |-- codegen // 把AST转换成Render函数
| |-- directives // 解析组件跨平台生成
| |-- parser // 解析模板成AST
| |-- core
| |-- components // 组件相关属性 (main: Keep-Alive)
| |-- global-api // 全局API for: Vue.extend, Vue.mixin等
| |-- instance // 实例化相关信息,生命周期、事件等【核心】
| |-- observer // 响应式数据核心目录【观察者-订阅者】
| |-- util // 工具方法
| |-- vdom // 虚拟dom相关内容(借鉴Snabbdom)
! |-- config.js // 静态配置文件
| |-- index.js // 静默导出Vue实例
| |-- platforms
| |-- web
| |--compiler // web端编译器相关代码,用来编译模板render函数
| |--runtime // web端运行时相关代码,用来创建vue实例等
| |--server // 服务器端渲染
| |--util // 相关工具类
| |-- weex // 和web端类似,可支持跨终端
| |-- server // 服务器端渲染(ssr)相关
| |-- sfc // 转换单文件组件(*.vue)
| |-- shared // 全局共享的一些方法和常量
| |-- test // 测试用例目录 可以用 npm run dev:test启动
| |-- types // typescript的声明 新版本支持
| |-- .babelrc.js // 转义器配置【语法转义器,补丁转义器、jsx和flow(这类转义器,是用来转换JSX语法和移除类型声明的)】 例如:es6代码转换成浏览器能够识别的代码
| |-- package.json // 配置项目相关信息
复制代码
- 思维导图
核心代码剖析
1、vue的核心代码,集中在src目录下
2、实例化相关属性、生命周期,以及事件等的入口在core/instance目录下
复制代码
在instance下找到,init.js文件,看看里面都有哪些东西
不难看出一堆初始化的方法,我们大概已经猜出vue所有的
方法、属性、状态、代理、渲染函数、声明周期、依赖注入、观察者、提供者等都是在这个文件里初始化的
复制代码
我们先来看下,defineReactive方法下的Object.defineProperty下,这两个对象get和set,相信你已经猜到了,就是这两个对象实现了依赖收集和依赖触发
export function defineReactive (
obj: Object,
key: string
) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 删减
},
set: function reactiveSetter (newVal) {
// 删减
}
})
}
复制代码
依赖收集--get
get代码
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
}
复制代码
get 函数做了两件事:
1、正确地返回属性值
2、收集依赖
复制代码
看下代码第一行
const value = getter ? getter.call(obj) : val
复制代码
上面代码做了下面的事情:
1、用三元运算,首先判断是否存在 getter,
2、如果 getter 存在那么直接调用该函数,并以该函数的返回值作为属性的值,保证属性的原有读取操作正常运作。【getter 常量中保存的是属性原有的 get 函数】
3、如果 getter 不存在则使用 val 作为属性的值,返回属性值。
复制代码
核心的代码来了
if (Dep.target) { // 有需要被收集的依赖
dep.depend() // 此方法执行,依赖被收集
if (childOb) { // 是否存在__ob__【新创建的属性】
childOb.dep.depend() // 触发新属性的依赖收集
if (Array.isArray(value)) { // 是数组
dependArray(value) // 逐个触发数组每个元素的依赖收集
}
}
}
复制代码
上面代码主要做了下面的事情:
1、判断是否有需要被收集的依赖,如果有走依赖收集逻辑
2、判断是否存在__ob__属性【介绍相应的时候会细说】如果存在,触发新属性的依赖收集
3、如果value是数组,则逐个触发数组每个元素的依赖收集
复制代码
- 依赖触发--set
在 get 函数中收集了依赖之后,接下来我们就要看一下在 set 函数中是如何触发依赖的,即当属性被修改的时候如何触发依赖。
复制代码
set代码
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()
}
复制代码
以上代码我进行六部分说明:
one
const value = getter ? getter.call(obj) : val // 与get函数类似,得到原值
复制代码
two
if (newVal === value || (newVal !== newVal && value !== value)) { // 新值与原值相等 或者 新值与新值不等 老值与老值不等 (NaN === NaN // false 的情况) 满足直接返回
return
}
复制代码
three
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter() // 非生产环境,打印辅助信息
}
复制代码
four
if (getter && !setter) return // 无需要修改的setter属性,直接返回
复制代码
five
if (setter) { // 有setter直接触发setter方法,并把新值以参数的形式传入该方法体,改变该属性的值
setter.call(obj, newVal)
} else { // 如果没有原始set则直接进行赋改原值
val = newVal
}
// 修改的属性值 setter.call(obj, newVal)
// 修改的setter【PropertyDescriptor】中value
interface PropertyDescriptor {
configurable?: boolean;
enumerable?: boolean;
value?: any;
writable?: boolean;
get?(): any;
set?(v: any): void;
}
复制代码
six
childOb = !shallow && observe(newVal) // 深度观测的前提下,对新值进行观测
dep.notify() // 触发收集的依赖
复制代码
上面主要做下面的事:
1、与get函数类似,得到get中的原始值
2、拿到的新值原始值进行对比,如果相当直接返回 或者新值与新值 原始值原始值进行比对(NaN === NaN // false 的情况)
3、非生产环境下,可以通过customSetter方法打印辅助信息
4、判断属性中是否有原始get方法,如果有并且没有原始的set方法,此时直接返回
5、判断属性中是否有原始set方法,如果有直接触发原始set方法,并把新值以参数的形式传入该方法体,改变该属性的值。保证属性原有的设置操作不受影响,如果没有原始set则直接进行覆盖原始数据
6、假如我们为属性设置的新值是一个数组或者纯对象,那么该数组或纯对象是未被观测的,所以需要对新值进行观测【在深度观测的前提下,新的观测对象重写childOb】
7、最后触发收集的依赖
复制代码
至此,依赖收集和依赖触发就讲完了,具体的依赖收集、触发里面的代码细节,以及
3、响应式
4、观察者
5、侦听器
6、代理
7、订阅者
8、函数渲染
9、虚拟dom的使用
复制代码
待下一篇介绍响应式的时候细说,以上如有不对的地方欢迎批评指正
待续。。。
vue源码解析响应式的流程实现细节(干货)【三】
参考:
1、vue官网
2、vue项目中加入flow类型校验
3、Vue框架构造