likes
comments
collection
share

读vue3源码你需要提前知道的知识点

作者站长头像
站长
· 阅读数 41

前言

最近正好在复习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.对象的键只能是字符串或者SymbolsMap的键可以是任意值,包含函数、对象或任意基本类型。
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
评论
请登录