读vue3源码你需要提前知道的知识点
前言
最近正好在复习vue3的源码,就把一些知识点,简单整理了一下,掌握这些知识点,对vue3源码的解读会更加方便。
挂载会用到的知识点
1.虚拟Dom
简称vdom,是一个用来表示真实DOM的对象,它在 Vue.js 中可以描述不同类型的节点,比如普通元素节点、组件节点等。
vdom是由一个个虚拟节点(vnode)组成的树型结构,任意一个vnode都可以是一棵子树。
真实dom
<div class="article">
<p class="title">标题</p>
<div class="content">内容</div>
</div>
虚拟dom
let vDOM = { // 虚拟DOM
type: 'div', // 标签名
props: { // 标签属性
class: 'article'
},
children: [ // 标签子节点
{
type: 'p', props: { class: 'title' }, children: ['标题']
},
{
type: 'div', props: { class: 'content' }, children: ['内容']
}
]
}
2.container 容器
渲染器挂载需要提供一个容器给它,这样它才知道挂载在哪个位置,我们会提供一个 DOM 元素来作为这个容器。
3.mount 挂载
渲染器把虚拟 DOM 节点渲染成真实 DOM 节点的过程就叫做挂载,Vue中也提供了一个 mounted
钩子在这个挂载完成时触发,可以让我们拿到真实的 DOM 节点。
4.shapFlag
是Vue3中定义的枚举。
作用是创建vnode的时候会对vnode打上shapFlag标签用于标记和描述当前虚拟节点vnode的类型,方便在path的时候通过位运算判断vnode的类型。
下面是shapFlag类型的定义
export const enum ShapeFlags {
ELEMENT = 1, // 1 普通元素
FUNCTIONAL_COMPONENT = 1 << 1, // 2 函数组件
STATEFUL_COMPONENT = 1 << 2, // 4 动态绑定style的元素
TEXT_CHILDREN = 1 << 3, // 8 后代为文本元素
ARRAY_CHILDREN = 1 << 4, // 16 后代为列表元素
SLOTS_CHILDREN = 1 << 5, // 32 后代为插槽元素
TELEPORT = 1 << 6, // 64 teleport组件
SUSPENSE = 1 << 7, // 128 suspense组件
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 256 需要被keep alive的路由组件
COMPONENT_KEPT_ALIVE = 1 << 9, // 512 已经被keep alive的组件
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 普通组件
}
5.patchFlag
patchFlag也是Vue3中定义的一种枚举,用于标识一个节点该如何进行更新。
同样在创建vnode的时候也会对vnode打上patchFlag标签,方便在patchElement的时候通过位运算来判个节点该如何进行更新。
export const enum PatchFlags {
// 动态文字内容
TEXT = 1,
// 动态 class
CLASS = 1 << 1,
// 动态样式
STYLE = 1 << 2,
// 动态 props
PROPS = 1 << 3,
// 有动态的key,也就是说props对象的key不是确定的
FULL_PROPS = 1 << 4,
// 合并事件
HYDRATE_EVENTS = 1 << 5,
// children 顺序确定的 fragment
STABLE_FRAGMENT = 1 << 6,
// children中有带有key的节点的fragment
KEYED_FRAGMENT = 1 << 7,
// 没有key的children的fragment
UNKEYED_FRAGMENT = 1 << 8,
// 只有非props需要patch的,比如`ref`
NEED_PATCH = 1 << 9,
// 动态的插槽
DYNAMIC_SLOTS = 1 << 10,
// SPECIAL FLAGS -------------------------------------------------------------
// 以下是特殊的flag,不会在优化中被用到,是内置的特殊flag
// 表示他是静态节点,他的内容永远不会改变,对于hydrate的过程中,不会需要再对其子节点进行diff
HOISTED = -1,
// 用来表示一个节点的diff应该结束
BAIL = -2,
}
依赖收集会用到的知识点
1.Set
也是ES6新出的数据结构和array很像,但是成员必须是唯一的,Set本身是一个构造函数,用来生成Set数据结构。
let arr = ['1',1,1,2,'2',3];
// 用于数组去重,但是数据类型不会发生转换,'1'和1在set中是不同的元素
let set = new Set(arr); // ['1',1,2,'2',3];
// 新增 set的顺序也是 插入顺序
set.add({}); // ['1',1,2,'2',3 ,{}];
//获取长度
se.size //6
// 在set中对象都是不想等的 即使都是空对象
set.add({}); // ['1',1,2,'2',3 ,{} ,{}];
// 在set中两个对象只有地址相等,放到set中才会想等
let objA = {test:1};
let objB = objA;
set.add(objA); // ['1',1,2,'2',3 ,{},{test:1}];
set.add(objB); // ['1',1,2,'2',3 ,{},{test:1}];
//删除
set.delete({}) // ['1',1,2,'2',3 ,{test:1}];
//判断是否包含某个值
set.has(objA) // true
// 清空
set.clear() //没有返回值
vue3中主要用set来存储effect
2.Map
ES6新出的数据结构,本质上是键值对的集合(Hash结构),它的键和值可以是任何数据类型。
和object相对比
// 共同点
1.都允许按照键存一个值
2.都可以删除键值
3.都可以判断一个键是否绑定了某个值
4.当Map的键是简单类型的值时(数字、字符串、布尔值)只要两个值严格相等(===),Map也将其视为一个键
// 区别
1.对象的键是无序的,Map的key是有序的,根据插入顺序返回key值
2.Map可以通过size属性可以得到键值对的个数,对象键值对的个数只能用其他方式
3.Map在频繁增删键值对的场景下性能更好
4.对象的键只能是字符串或者Symbols,Map的键可以是任意值,包含函数、对象或任意基本类型。
5.当Map的键是对象时,Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。
使用方式
let jayMap = new Map;
let jay = {name: '周杰伦',job:'歌手', address:'中国台湾' };
let getJob = () =>{
return `我叫${jay.name},是${jay.job}`
}
let getAddress = () =>{
return `我叫${jay.name},是${jay.job}`
}
// 赋值 分别存储 jay的每个属性被那些方法用到了
jayMap.set('name', [...new Set([getJob,getAddress])]) // Map(1) {'name' => [getJob,getAddress]}
jayMap.set('job', [...new Set([getJob])]) // Map(2) {'name' => [getJob,getAddress], 'job'=>[getJob]}
jayMap.set('address', [...new Set([getAddress])]) // Map(3) {'name' => [getJob,getAddress], 'job'=>[getJob]}
// 取值
jayMap.get("name")
// 获取所有键名
jayMap.keys()
// 获取所有键值
jayMap.values()
// 获取所有键值对成员
jayMap.entries()
// 获取Map对象的键值对的数量
jayMap.size // 3
// Map实例是否包含键对应的值,返回一个布尔值
jayMap.has('name') // true
// 移除与该键关联的值 如果有这个值会删除成功并返回true,没有返回false
jayMap.delete('job') // true
//清空map
jayMap.clear();
在Vue3源码中就是使用Map数据结构,去收集每个对象他属性的依赖集合。
3.WeakMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合。
WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。
WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
// 与Map的区别
//1.WeakMap只接受对象作为键名
const wm = new WeakMap();
wm.set(1, 2) // TypeError: 1 is not an object!
//2.WeakMap键名所指的对象是若应用,不计入垃圾回收机制
//一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。
const el= document.getElementById('test'); //引用数 1
wm.set(el, {count:1}); //引用数还是1
wm.get(el) // " {count:1} "
// 为dom el添加点击事件
el.addEventListener('click', function() {
let data = wm.get(el);
data.count++;
}, false);
//3.没有遍历操作,也没有size属性
//4.没有clear方法,无法清空
Vue 3 为了能将 不再使用的数据进行正确的垃圾回收使用 WeakMap
来作为响应式对象的缓冲区
4.Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
// 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
const jay = {name:'jay' ,job:'歌手'}
// handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
const handler = {
get(target, key, receiver){
console.log(target) // 原来的jay
console.log(key) // 属性名
console.log(receiver) // 代理后的proxyJay
return target[key];
},
set(target, key, value, receiver) {
console.log(target) // 原来的jay
console.log(key) // 属性名
console.log(value) // 设置的值
console.log(receiver) // 代理后的proxyJay
return target[key] = value;
}
}
const proxyJay = new Proxy(jay, handler)
let info = `${proxyJay.name}-${proxyJay.job}`; // 会触发handler的get方法
proxyJay.name = '周杰伦' // 会触发handler的set方法 但是会报错
proxyJay.address = "中国台湾"; // *同样也会触发handler的set方法 但是会报错
Vue3的响应式主要就是基于Proxy,同时结合Reflect一起使用
5.Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers (en-US)的方法相同。Reflect
不是一个函数对象,因此它是不可构造的。
与大多数全局对象不同Reflect
并非一个构造函数,所以不能通过new 运算符对其进行调用,或者将Reflect
对象作为一个函数来调用。Reflect
的所有属性和方法都是静态的(就像Math
对象)。
1.将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
2.修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc)则会返回 false。
3.让 Object 操作都变成函数行为。某些 Object 操作是命令式,比如 name in obj 和 delete obj[name],而 Reflect.has(obj, name)和 Reflect.deleteProperty(obj, name)让它们变成了函数行为。
4.Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为。
// 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
const jay = {name:'jay' ,job:'歌手'}
// handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
const handler = {
get(target, key, receiver){
console.log(target) // 原来的jay
console.log(key) // 属性名
console.log(receiver) // 代理后的proxyJay
return Reflect.get(target, key, receiver); // proxy 应该结合Reflect来使用解决this指向的问题
},
set(target, key, value, receiver) {
console.log(target) // 原来的jay
console.log(key) // 属性名
console.log(value) // 设置的值
console.log(receiver) // 代理后的proxyJay
return Reflect.get(target, key, value, receiver)
}
}
const proxyJay = new Proxy(jay, handler)
Vue3主要用Reflect解决Proxy this指向的问题
转载自:https://juejin.cn/post/7134972457802268680