响应式原理
- Object.defineProperty
- setter
- Proxy
var count = 1
var state = {}
Object.defineProperty(state , 'count',{get(){return count},set(val){count = val}
})
//弊端:不能主动监听到对象属性的新增或者删除,add/delete
ref和reactive 声明响应式数据
- ref:用于定义string、number、boolean --> set/get
function ref(value){let _value = vlauelet count = {get value(){return _value},set value(val){_value = val} }
}
- reactive:用于复杂数据类型(array、object、Map、Set) --> proxy数据劫持、代理
let proxy = new Proxy(state,{get:function(target,prop){return target[prop]},set:function(target,prop,value){target[prop] = value},deletePropety(target,prop){delete target[prop]}
})
vue文件在浏览器是怎么运行起来的?
- react -> jsx渲染函数
- vue -> template -> render 函数
模板的本质就是对HTML的增强 :增加了一些指令 v-if 、 {{}}
渲染函数本质就是js,注入了一些context
模板vs渲染函数
1、本质不同
2、表现能力不同,vue语法糖比react多很多
3、vue代码一致性比较弱(实现同一个功能方法路径太多)
4、性能,compiler优化
compiler编译时->
template -> AST抽象语法树 -> render => VNode
script -> js
style -> less/scss , scoped <style></style>
vue3源码结构
- compiler-core 编译器核心代码,且与平台无关,这个包主要功能是将代码解析成ast、然后转换成codegenNode对象、再编译生成可执行代码(render渲染函数)
- compiler-dom 针对浏览器(或DOM环境)的编译器。她在compiler-core基础上添加了一些特定于DOM的特性
- compiler-sfc 负责处理vue文件,将其分解为模板、脚本、样式等部分,并对这些部分进行相应的处理
- compiler-ssr 针对服务器渲染(SSR)的编译器。她在compiler-core基础上添加了一些特定与SSR的特性,如生成服务器渲染的代码
- reactivity 这个是vuejs的响应式系统,提供了一些基础API
- runtime-core 这是vuejs运行时核心,包括vuejs的主要功能。如响应式系统、组件系统、生命周期钩子等。这个包是平台无关的
- runtime-dom 这个是针对浏览器(或DOM环境)的运行时。它在@vue/runtime-core的基础上添加了一些特定与DOM的特性
- server-renderer 服务端渲染相关
- shared 共享常量、工具函数
- vue 人口包,整合各个子包
核心结构
结构之一: reactivity 响应式
响应式核心
手写实现一个简单的响应式
let object = {msg : 'hello world',age: 18
}
const render = (el)=>{el.innerHTML = 'msg:' + object.msg
}
let activeEffect = null
const effect = (fn)=>{const effectFn = ()=>{fn()}
}
effect(render)//副作用函数集合,用Set数据结构进行管理
var bucket = new Set()var trackMap = new WeakMap()
//为什么不使用new Map而是new WeakMap。 new WeakMap数据不能进行枚举,键只能是对象,值可以是任何类型
//在 JavaScript 里,map API 可以通过四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。
//这样在给这种 map 设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该 map 取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。
//但这样的实现会有两个很大的缺点,首先赋值和搜索操作都是 O(n) 的时间复杂度(n 是键值对的个数),因为这两个操作都需要遍历整个数组来进行匹配。
//另外一个缺点是可能会导致 内存泄漏,因为数组会一直引用着每个键和值。这种引用使得 垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。
//reactive api返回的响应式对象
function track(target,key){const depsMap = trackMap.get(target)if(!depsMap){// target.set(target,key)//获取target的key对应的Mapconst targetKeyMap = trackMap.set(target,key)//key -> 依赖对象数组let detSet = targetKeyMap.get(key)//detSet 新增副作用函数detSet.add(activeEffect) }
}const reactiveObject = new Proxy(object,{get(target,key){// bucket.add(render)track(target,key)return target[key]},set(target,key,newvalue){target[key] = newvaluebucket.forEach(fn=>fn())}
})
渲染器
-
渲染器初次渲染 createApp().mount()
-
二次更新:patch函数—dom diff
1、n1===n2,不更新2、n1不存在,n2存在,挂载(mount)n23、n1和n2类型不一样,卸载(unmount)n1,挂载(mount)n24、根据不同的节点类型,调用不同的process函数5、有点比较n1和n2的props变化,调用patchProps函数6、比较n1和n2的子节点的变化,调用patchChildren函数1、patchFlag和dynamicChildren优化
编译器
编译原理:从模板到渲染函数===> 源代码->词法分析->语义分析->AST->transform->目标代码