mini-vue 的设计

news2024/11/15 23:42:59

mini-vue 的设计

mini-vue 使用流程与结果预览:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <button class="btn">执行patch</button>
    <hr></hr>
    <button class="refreshBtn">刷新页面</button>
  </body>
  <script src="./renderer.js"></script>
  <script src="./mount.js"></script>
  <script src="./patch.js"></script>
  <script>
    /**
     * 思路:
     * 1. 通过 h 函数创建 vnode
     * 2. 通过 mount 函数挂载
     */

    // 1. 生成 vnode
    const vnode = h(
      "div",
      { class: "youxiaobei", id: "oldId", del: "这是将被删除的" },
      [
        h("ul", null, [
          h("li", null, "我是一个小li"),
          h("li", null, "我是一个小li"),
          h("li", null, "我是一个小li"),
          h("li", null, "我是一个小li"),
        ]),
        h("button", null, "这是将被保留的小小按钮"),
        h("h4", null, "以上内容都会被比较为不同然后删除,包括我")
      ]
    );

    // 2. 挂载,生成真实 dom,添加到 container 容器中
    const container = document.getElementById("app");
    mount(vnode, container);

    // 3. 新节点 01 (最外层 tagName 不一样,直接都被替换了)
    const newVnode = h("h2", { class: "newNode", id: "newId" }, [
      h("button", null, "我是你后来加的小按钮"),
    ]);

    const btn = document.querySelector('.btn')
    btn.addEventListener("click", () => {
      patch(vnode, newVnode);
      btn.disabled = true;
    },true);
    const refreshBtn = document.querySelector('.refreshBtn')
    refreshBtn.addEventListener("click", () => {
      location.reload()
    })
  </script>

  <style>
    /* 新的节点背景色是红色的 */
    .newNode {
      background-color: red; 
    }
  </style>
</html>

执行 patch 前:

img01

执行后:

img02

1. h 函数

h 函数也就是 render 函数,作用简单:返回一个 Vnode 虚拟节点,但很重要!

/**
 * h 函数
 * 功能:返回vnode
 *
 * @param {String} tagName  - 标签名
 * @param {Object | Null} props  - 传递过来的参数
 * @param {Array | String} children  - 子节点
 * @return {vnode} 虚拟节点
 */
const h = (tagName, props, children) => {
  // 直接返回一个对象,里面包含vnode结构
  return {
    tagName,
    props,
    children,
  };
};

2. 响应式

考虑以下功能:

  1. 收集依赖某个数据的函数
  2. 当数据变化后,重新执行依赖此数据的函数
/**
 * 实现一个类
 * 构造一个 订阅列表 subscribers set 对象
 * 收集者 addEffect  添加影响的函数,往 subscribers 里面添加
 * 通知者 notifier 通知函数, 依次执行 subscribers 里面的函数
 */
class Dep {
  constructor() {
    // 1.订阅列表 构造一个 subscribers set 对象
    this.subscribers = new Set();
  }

  // 2. 收集者 addEffect 添加影响的函数

  addEffect(effect) {
    this.subscribers.add(effect);
  }

  // 3. 通知者 notifier

  notifier() {
    this.subscribers.forEach((effect) => {
      effect();
    });
  }
}

const dep = new Dep();

let count = 1;

const addFun = function () {
  console.log(++count);
};

addFun(); // 2

// 收集订阅
dep.addEffect(addFun);

// 当数据改变后
count = 100;

// 通知者通知函数重执行
dep.notifier(); // 101

3. mount 函数

挂载 Vnode 为真实的 DOM 元素

/**
 * mount 函数
 * 功能:挂载 vnode 为 真实dom
 * 重点:递归调用处理子节点
 *
 * @param {Object} vnode -虚拟节点
 * @param {elememt} container -需要被挂载节点
 */
const mount = (vnode, container) => {
  // 1. 创建出真实元素, 给 vnode 添加 el 属性
  const el = (vnode.el = document.createElement(vnode.tagName));

  // 2. 处理 props
  if (vnode.props) {
    for (const key in vnode.props) {
      const value = vnode.props[key];

      // 2.1 prop 是函数
      if (key.startsWith("on")) {
        el.addEventListener(key.slice(2).toLowerCase(), value);
      } else {
        // 2.2 prop 是字符串
        el.setAttribute(key, value);
      }
    }
  }

  // 3. 处理 children
  if (vnode.children) {
    // 3.1 如果 children 是字符串,直接设置文本内容
    if (typeof vnode.children === "string") {
      el.textContent = vnode.children;
    }
    // 3.2 如果 children 是数组,递归挂载每个子节点
    else {
      // 先拿到里面的每一个 vnode
      vnode.children.forEach((item) => {
        // 再把里面的vnode递归调用
        mount(item, el);
      });
    }
  }
  // 4. 挂载
  container.appendChild(el);
};

04. patch 函数

patch 对比节点数组,优化性能

/**
 * 节点比较
 * 调用时机:节点发生变化(数量,内容)
 * 功能:比较节点数组,尽可能减少 DOM 操作
 */

/**
 * @param {Vnode} n1 - 旧节点
 * @param {Vnode} n2 - 新节点
 */
const patch = (n1, n2) => {
  // 节点不相同,卸载旧节点,挂载新节点
  if (n1.tagName !== n2.tagName) {
    const parentElementNode = n1.el.parentElement;
    parentElementNode.removeChild(n1.el);
    mount(n2, parentElementNode);
  } else {
    // 1. 取出 element 并保存到 n2
    const el = (n2.el = n1.el);

    // 2. 处理 props
    const oldProps = n1.props || {};
    const newProps = n2.props || {};
    for (const key in newProps) {
      const oldValue = oldProps[key];
      const newValue = newProps[key];
      // 2.1 值不同才替换
      if (oldValue !== newValue) {
        if (key.startsWith("on")) {
          el.addEventListener(key.slice(2).toLowerCase(), newValue);
        } else {
          // 2.2 prop 是字符串
          el.setAttribute(key, newValue);
        }
      }
    }
    // 3. 删除旧的 props
    for (const key in oldProps) {
      // 如果旧 key 不在新的 props 里
      if (!(key in newProps)) {
        const oldValue = oldProps[key];
        if (key.startsWith("on")) {
          el.removeEventListener(key.slice(2).toLowerCase(), oldValue);
        } else {
          // 2.2 prop 是字符串
          el.removeAttribute(key, oldValue);
        }
      }
    }

    // 4. 处理 children
    const oldChildren = n1.children;
    const newChildren = n2.children;
    // children 字符串
    if (typeof newChildren === "string") {
      // 4.1 如果新 children 是字符串,直接设置文本内容
      if (oldChildren !== newChildren) {
        el.textContent = newChildren;
      } else {
        el.innerHTML = newChildren;
      }
    } else {
      // 4.2 如果新 children 是数组,递归挂载每个子节点
      // 如果旧 children 的是字符串
      if (typeof oldChildren === "string") {
        el.innerHTML = "";
        // 遍历 children
        newChildren.forEach((item) => {
          mount(item, el);
        });
      } else {
        // 两个都是数组,开始 diff 算法
        // n1: [a,b,d]
        // n2: [b,a,c,f]

        /**
         * 没有 key
         */
        if (!n1.props.key && !n2.props.key) {
          // 4.3.1 获取两个 vnode 数组的公共长度,比较相同的
          const commonLength = Math.min(oldChildren.length, newChildren.length);
          for (let i = 0; i < commonLength; i++) {
            patch(oldChildren[i], newChildren[i]);
          }

          // 4.3.2 新的长度多于旧的,挂载
          if (oldChildren.length < newChildren.length) {
            newChildren.slice(oldChildren.length).forEach((item) => {
              mount(item, el);
            });
          }
          // 4.3.3 旧的长度多于新的,卸载
          if (oldChildren.length > newChildren.length) {
            oldChildren.slice(newChildren.length).forEach((item) => {
              el.removeChild(item.el);
            });
          }
        } else {
          /**
           * 有 key
           */
          // 4.4.1 根据 key 创建一个映射表,方便查找和比较
          const keyMap = {};
          oldChildren.forEach((child) => {
            if (child.props.key) {
              keyMap[child.props.key] = child;
            }
          });

          // 4.4.2 遍历新的 children 数组
          newChildren.forEach((newChild, index) => {
            const oldChild = keyMap[newChild.props.key];
            if (oldChild) {
              // 4.4.2.1 如果旧的 children 存在对应的 key,对比并更新子节点
              patch(oldChild, newChild);
              oldChildren[index] = oldChild; // 更新旧的 children 数组,方便后续删除处理
            } else {
              // 4.4.2.2 如果旧的 children 中没有对应的 key,说明是新增的节点,直接挂载
              mount(newChild, el, index);
            }
          });

          // 4.4.3 删除旧的 children 中没有对应的 key 的子节点
          oldChildren.forEach((oldChild) => {
            if (!oldChildren.find((child) => child.props.key === oldChild.props.key)) {
              el.removeChild(oldChild.el);
            }
          });
        }
      }
    }
  }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1201328.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLM 面试总结

溜一遍 MLStack.Cafe - Kill Your Next Machine Learning & Data Science Interview https://www.llmforce.com/llm-interview-questions MLStack.Cafe - Kill Your Next Machine Learning & Data Science Interview An interview with a language model, ChatGPT - W…

阿里云国际站:专有宿主机

文章目录 一、专有宿主机的概念 二、专有宿主机的优势 三、专有宿主机的应用场景 一、专有宿主机的概念 专有宿主机&#xff08;Dedicated Host&#xff0c;简称DDH&#xff09;是阿里云专为企业用户定制优化的解决方案。具有物理资源独享、部署更灵活、配置更丰富、性价比…

python实现一个简单的桌面倒计时小程序

本章内容主要是利用python制作一个简单的桌面倒计时程序&#xff0c;包含开始、重置 、设置功能。 目录 一、效果演示 二、程序代码 一、效果演示 二、程序代码 #!/usr/bin/python # -*- coding: UTF-8 -*- """ author: Roc-xb """import tkin…

分类预测 | Matlab实现PSO-BiLSTM粒子群算法优化双向长短期记忆神经网络的数据多输入分类预测

分类预测 | Matlab实现PSO-BiLSTM粒子群算法优化双向长短期记忆神经网络的数据多输入分类预测 目录 分类预测 | Matlab实现PSO-BiLSTM粒子群算法优化双向长短期记忆神经网络的数据多输入分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO-BiLSTM粒子…

sqli-labs关卡13(基于post提交的单引号加括号的报错盲注)通关思路

文章目录 前言一、回顾第十二关知识点二、靶场第十三关通关思路1、判断注入点2、爆显位3、爆数据库名4、爆数据库表5、爆数据库列6、爆数据库关键信息 总结 前言 此文章只用于学习和反思巩固sql注入知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;…

Spark 资源调优

1 资源规划 1.1 资源设定考虑 1、总体原则 以单台服务器128G内存&#xff0c;32线程为例。 先设定单个Executor核数&#xff0c;根据Yarn配置得出每个节点最多的Executor数量&#xff0c;每个节点的yarn内存/每个节点数量单个节点的数量 总的executor数单节点数量*节点数。 2、…

php的api接口token简单实现

<?php // 生成 Token function generateToken() {$token bin2hex(random_bytes(16)); // 使用随机字节生成 tokenreturn $token; } // 存储 Token&#xff08;这里使用一个全局变量来模拟存储&#xff09; $tokens []; // 验证 Token function validateToken($token) {gl…

SolidWorks绘制花瓶教程

这个花瓶是我学习solidworks画图以来用时最长的一个图形了&#xff0c;特此记录一下&#xff0c;用了我足足两个早晨才把他给画出来&#xff0c;我这是跟着哔站里的隔壁老王学习的&#xff0c;下面是视频地址&#xff1a;点击我一下看视频教程 下面是我的绘图过程&#xff0c;…

wordpress是什么?快速搭网站经验分享

​作者主页 &#x1f4da;lovewold少个r博客主页 ⚠️本文重点&#xff1a;c入门第一个程序和基本知识讲解 &#x1f449;【C-C入门系列专栏】&#xff1a;博客文章专栏传送门 &#x1f604;每日一言&#xff1a;宁静是一片强大而治愈的神奇海洋&#xff01; 目录 前言 wordp…

【刷题篇】动态规划(四)

文章目录 1、珠宝的最高价值2、下降路径最小和3、最小路径和4、地下城游戏5、按摩师6、打家劫舍|| 1、珠宝的最高价值 现有一个记作二维矩阵 frame 的珠宝架&#xff0c;其中 frame[i][j] 为该位置珠宝的价值。拿取珠宝的规则为&#xff1a; 只能从架子的左上角开始拿珠宝 每次…

ConstraintLayout的基本用法

ConstraintLayout的基本用法 1、基线对齐——Baseline 有时候我们需要这样一个场景&#xff1a; app:layout_constraintBaseline_toBaselineOf"id/30"2、链——Chains 用于将多个控件形成一条链&#xff0c;可以用于平分空间。 <?xml version"1.0"…

sqli-labs关卡14(基于post提交的双引号闭合的报错注入)通关思路

文章目录 前言一、回顾上一关知识点二、靶场第十四关通关思路1、判断注入点2、爆显位3、爆数据库名4、爆数据库表5、爆数据库列6、爆数据库关键信息 总结 前言 此文章只用于学习和反思巩固sql注入知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不…

react 组件进阶

目标&#xff1a;1.能够使用props接收数据 2.能够实现父子组建之间的通讯 3.能够实现兄弟组建之间的通讯 4.能够给组建添加props校验 5.能够说出生命周期常用的钩子函数 6.能够知道高阶组件的作用 一&#xff0c;组件通讯介绍 组件是独立且封闭的单元&#xff0c;默认情况下&a…

【pandas】数据清洗的几种方法

引言 在数据处理和分析过程中&#xff0c;数据清洗是至关重要的一步。Pandas是Python中用于数据处理和分析的强大库&#xff0c;提供了多种数据清洗方法。本文将介绍几种常用的数据清洗方法&#xff1a;缺失值处理、重复值处理、异常值处理。 准备 这里准备了一份数据集&…

从0到0.01入门React | 009.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

网络安全在文档管理中的重要作用

无论它们存放在哪里&#xff0c;确保它们的安全都应该是首要任务。 随着文档管理继续从物理文件柜向数字数据库和云的长期过渡&#xff0c;网络威胁的可能性随着每一步和每次迁移而增加。因此&#xff0c;组织了解并解决文档管理和网络安全之间的联系至关重要。 文档管理的安…

docker搭建etcd集群

最近用到etcd&#xff0c;就打算用docker搭建一套&#xff0c;学习整理了一下。记录在此&#xff0c;抛砖引玉。 文中的配置、代码见于https://gitee.com/bbjg001/darcy_common/tree/master/docker_compose_etcd 搭建一个单节点 docker run -d --name etcdx \-p 2379:2379 \…

初始MySQL(四)(查询加强练习,多表查询(未完))

目录 查询加强 where加强 order by加强 group by 分页查询 总结 多表查询(重点) 笛卡尔集及其过滤 自连接 子查询 #先创建三张表 #第一张表 CREATE TABLE dept(deptno MEDIUMINT NOT NULL DEFAULT 0,dname VARCHAR(20) NOT NULL DEFAULT ,loc VARCHAR(13) NOT NULL D…

【KVM】硬件虚拟化技术(详)

前言 大家好&#xff0c;我是秋意零。 经过前面章节的介绍&#xff0c;已经知道KVM虚拟化必须依赖于硬件辅助的虚拟化技术&#xff0c;本节就来介绍一下硬件虚拟化技术。 &#x1f47f; 简介 &#x1f3e0; 个人主页&#xff1a; 秋意零&#x1f525; 账号&#xff1a;全平…

SpringBoot2.X整合集成Dubbo

环境安装 Dubbo使用zookeeper作为注册中心&#xff0c;首先要安装zookeeper。 Windows安装zookeeper如下&#xff1a; https://blog.csdn.net/qq_33316784/article/details/88563482 Linux安装zookeeper如下&#xff1a; https://www.cnblogs.com/expiator/p/9853378.html Sp…