Vue源码---虚拟Dom

Vue源码—虚拟Dom

真实dom

浏览器引擎渲染工作流程大致分为5步,创建dom树 -> 创建style Rules -> 创建render树 -> 布局layout -> 绘制painting

虚拟dom

虚拟dom节点,通过js的object 对象模拟dom中的节点,然后通过特定的render方法渲染成真实的dom节点

  1. 真实dom性能开销大
let div = document.createElement('div')
let str = ''
for(var key in div) {str += key +''
}

真实的dom元素非常庞大。

使用正则表达式,解析出ast树

代码如下

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`);   // 开始标签<xxx>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)  // 结束标签</xxx>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/  // 匹配属性
const startTagClose = /^\s*(\/?)>/
export function parseHTML(html) {    // 解析一个删除一个,直到全部解析完成const ELEMENT_TYPE = 1const TEXT_TYPE = 3const stack = []  // 用于存放元素let currentParent    // 指向栈中的最后一个let root// 生成AST节点function createASTElememt(tag, attrs) {return {tag,type: ELEMENT_TYPE,children: [],attrs,parent: null}}function start(tag, attrs) {let node = createASTElememt(tag, attrs)    // 创建一个ast节点if (!root) {  // 判断是否为空树root = node     // 如果为空,则当前的树为根节点}if (currentParent) {node.parent = currentParentcurrentParent.children.push(node)}stack.push(node)currentParent = node     // currentParent为栈中的最后一个}// 匹配文本function chars(text) {      // 文本直接放到当前指向的节点中text = text.replace(/\s/g, '')text && currentParent.children.push({type: TEXT_TYPE,text,parent: currentParent})}// 结束function end() {let node = stack.pop()currentParent = stack[stack.length - 1]}// 对这个文件中html字符串进行减少,作为判断后续while循环结束的标记function advance(n) {html = html.substring(n)}//匹配开始标签function parseStartTag() {    // 获取开始标签const start = html.match(startTagOpen)if (start) {const match = {tagName: start[1],   //标签名attrs: []                // 属性}advance(start[0].length)let attr, endwhile (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {   // 匹配属性advance(attr[0].length)match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5]})}if (end) {advance(end[0].length)}console.log("match",match);return match}return false  // 不是开始标签}// 循环实现ast树的构建while (html) {// 如果textEnd == 0 说明是开始标签或者结束标签// 如果textEnd > 0 说明就是文本的结束位置debuggerlet textEnd = html.indexOf('<')if (textEnd == 0) {const startTagMatch = parseStartTag()   // 开始标签的匹配结果if (startTagMatch) {  // 解析到的开始标签start(startTagMatch.tagName, startTagMatch.attrs)continue}let endTagMatch = html.match(endTag)if (endTagMatch) {advance(endTagMatch[0].length)end(endTagMatch[1])continue}}if (textEnd > 0) {let text = html.substring(0, textEnd)if (text) {chars(text)advance(text.length)}}}// console.log(root)return root
}

这个函数返回的是一个ast树的格式。attrs 表示属性,children表示嵌套的子盒子, parent表示嵌套中的父盒子,tag表示标签,即该节点表示的盒子的类型。type表示节点的类型,如果type = 1 表示的是html标签,如果是type = 3,则表示的是文本类型

将ast树转换为模板字符串,将编译出来的模板,形成渲染函数

import { parseHTML } from "./parse";const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g    // {{xxx}}
function gen(node) {if (node.type === 1) {    // 元素return codegen(node)} else {// 文本let text = node.textif (!defaultTagRE.test(text)) {return `_v(${JSON.stringify(text)})`} else {let tokens = []let matchdefaultTagRE.lastIndex = 0let lastIndex = 0while (match = defaultTagRE.exec(text)) {let index = match.indexif (index > lastIndex) {tokens.push(JSON.stringify(text.slice(lastIndex, index)))}tokens.push(`_s(${match[1].trim()})`)lastIndex = index + match[0].length}if(lastIndex<text.length){tokens.push(JSON.stringify(text.slice(lastIndex)))}return `_v(${tokens.join('+')})`}}
}
// children
function genChildren(children) {return children.map(child => gen(child)).join(',')
}// 属性
function genProps(attrs) {let str = ''for (let i = 0; i < attrs.length; i++) {let attr = attrs[i]if (attr.name == 'style') {let obj = {}attr.value.split(';').forEach(item => {     // qs库let [key, value] = item.split(':')obj[key] = value})attr.value = obj}str += `${attr.name}:${JSON.stringify(attr.value)},`}return `{${str.slice(0, -1)}}`
}function codegen(ast) {let children = genChildren(ast.children)let code = `_c('${ast.tag}',${ast.attrs.length > 0 ? genProps(ast.attrs) : null}${ast.children.length ? `,${children}` : ''})`return code
}
export function compileToFunction(template) {const ast = parseHTML(template)let code = codegen(ast)console.log(code)// 模板引擎的实现原理  with + new Functioncode= `with(this){return ${code}}`console.log(code)let render = new Function(code)console.log(render)// 根据代码生成render函数return render
}
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`);   // 开始标签<xxx>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)  // 结束标签</xxx>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/  // 匹配属性
const startTagClose = /^\s*(\/?)>/
export function parseHTML(html) {    // 解析一个删除一个,直到全部解析完成const ELEMENT_TYPE = 1const TEXT_TYPE = 3const stack = []  // 用于存放元素let currentParent    // 指向栈中的最后一个let root// 生成AST节点function createASTElememt(tag, attrs) {return {tag,type: ELEMENT_TYPE,children: [],attrs,parent: null}}function start(tag, attrs) {let node = createASTElememt(tag, attrs)    // 创建一个ast节点if (!root) {  // 判断是否为空树root = node     // 如果为空,则当前的树为根节点}if (currentParent) {node.parent = currentParentcurrentParent.children.push(node)}stack.push(node)currentParent = node     // currentParent为栈中的最后一个}// 匹配文本function chars(text) {      // 文本直接放到当前指向的节点中text = text.replace(/\s/g, '')text && currentParent.children.push({type: TEXT_TYPE,text,parent: currentParent})}// 结束function end() {let node = stack.pop()currentParent = stack[stack.length - 1]}// 对这个文件中html字符串进行减少,作为判断后续while循环结束的标记function advance(n) {html = html.substring(n)}//匹配开始标签function parseStartTag() {    // 获取开始标签const start = html.match(startTagOpen)if (start) {const match = {tagName: start[1],   //标签名attrs: []                // 属性}advance(start[0].length)let attr, endwhile (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {   // 匹配属性advance(attr[0].length)match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5]})}if (end) {advance(end[0].length)}// console.log("match",match);return match}return false  // 不是开始标签}// 循环实现ast树的构建while (html) {// 如果textEnd == 0 说明是开始标签或者结束标签// 如果textEnd > 0 说明就是文本的结束位置let textEnd = html.indexOf('<')if (textEnd == 0) {const startTagMatch = parseStartTag()   // 开始标签的匹配结果if (startTagMatch) {  // 解析到的开始标签start(startTagMatch.tagName, startTagMatch.attrs)continue}let endTagMatch = html.match(endTag)if (endTagMatch) {advance(endTagMatch[0].length)end(endTagMatch[1])continue}}if (textEnd > 0) {let text = html.substring(0, textEnd)if (text) {chars(text)advance(text.length)}}}console.log(root)return root
}

将生成的render函数,转换为虚拟dom

import {createElementVNode, createTextVNode} from "./vdom/index";export function initLiftCycle(Vue) {Vue.prototype._update = function (vnode) {const vm = thisconst el = vm.$elvm.$el = patch(el, vnode)}Vue.prototype._render = function () {return this.$options.render.call(this)}Vue.prototype._c = function () {return createElementVNode(this, ...arguments)}Vue.prototype._v = function () {return createTextVNode(this, ...arguments)}Vue.prototype._s = function (value) {if (typeof value != 'object') return valuereturn JSON.stringify(value)}
}
function patch(oldVNode, vnode) {const isRealElement = oldVNode.nodeTypeif (isRealElement) {const elm = oldVNodeconst parentElm = elm.parentNodelet newElm = createElm(vnode)parentElm.insertBefore(newElm,elm.nextSibling)parentElm.removeChild(elm)return newElm}else {// diff算法}
}
function createElm(vnode) {let { tag, data, children, text } = vnodeif (typeof tag == 'string') {vnode.el = document.createElement(tag)   // 将真实节点和虚拟节点对应起来patchProps(vnode.el, data)children.forEach(child => {vnode.el.appendChild(createElm(child))});} else {vnode.el = document.createTextNode(text)}return vnode.el
}
function patchProps(el, props) {   // 处理属性的方法for (let key in props) {for (let key in props) {if (key === 'style') {for (let styleName in props.style) {el.style[styleName] = props.style[styleName]}} else {console.log(key, props[key])el.setAttribute(key, JSON.stringify(props[key]))}}}
}
export function mountComponent(vm, el) {vm.$el = elvm._update(vm._render())console.log(vm._update)
}
function vnode(vm, tag, key, data,children, text) {return {vm,tag,key,data,children,text}
}
export function createElementVNode(vm, tag, data = {}, ...children) {if (data == null) {data = {}}let key = data.keyif (key) {delete data.key}return vnode(vm, tag, key ,data, children)
}
export function createTextVNode(vm, text) {return vnode(vm, undefined, undefined, undefined, undefined, text)
}

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

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

相关文章

seldom 实战技巧

seldom 是我一直在维护的自动化测试框架。目前GitHub已经 500 star。 最近在项目中使用Seldom poium 编写自动化测试用例。接下来&#xff0c;我就分享一些使用技巧。 如何参数化测试用例 网站的首页&#xff0c;如上面的导航。于是&#xff0c;开始利用poium编写元素定位。…

Leetcode 3098. Find the Sum of Subsequence Powers

Leetcode 3098. Find the Sum of Subsequence Powers 1. 解题思路2. 代码实现 题目链接&#xff1a;3098. Find the Sum of Subsequence Powers 1. 解题思路 这一题思路上的话还是比较直接的&#xff0c;由于我们只需要求出每一个可能的power值&#xff0c;然后求出对应的po…

GitHub常用命令

GitHub常用命令 远程仓库相关命令拉取项目到本地拉取远程仓库推送远程仓库 分支操作相关命令查看所有本地分支查看所有远程分支查看所有本地分支和远程分支切换分支创建本地分支创建远程分支(本地分支push到远程)创建新分支并切换到新分支合并指定分支到当前分支删除分支 代码提…

基于 FFmpeg 和 SDL 的音视频同步播放器

基于 FFmpeg 和 SDL 的音视频同步播放器 基于 FFmpeg 和 SDL 的音视频同步播放器前置知识音视频同步简介复习DTS、PTS和时间基 程序框架主线程解复用线程音频解码播放线程视频解码播放线程 音视频同步逻辑源程序结果工程文件下载参考链接 基于 FFmpeg 和 SDL 的音视频同步播放器…

蓝桥杯备考随手记: Math 类中常用方法

Java的Math类是一个包含数学操作方法的实用工具类。它提供了许多用于执行各种数学计算的静态方法。 下面是Math类中一些常用的方法&#xff1a; abs()&#xff1a;返回参数的绝对值。 int absoluteValue Math.abs(-10); System.out.println(absoluteValue); // Output: 10 c…

EtherCAT主站SOEM -- 25 -- STM32F767-SOEM通过 PV模式(速度模式)控制一个电机转圈圈

EtherCAT主站SOEM -- 25 -- STM32F767-SOEM通过 PV模式(速度模式)控制一个电机转圈圈 0 QT-SOEM视频预览及源代码下载:0.1 QT-SOEM视频预览0.2 QT-SOEM源代码下载0.3 STM32F7-SOEM视频预览0.4 STM32F7-SOEM源代码下载1 程序文件修改替换1.1 allvalue.h1.2 allvalue.c1.3 mot…

BaseDao入门使用

目录 一、什么是BaseDao?BaseDao的优点&#xff1a;BaseDao用来做什么操作&#xff1f; 二、BaseDao封装增删改查 案例演示&#xff1a;1、java与数据库进行连接2、连接后可对其进行操作&#xff08;增、删、改&#xff09;返回影响行数3、查询 查询一个字段&#xff08;返回一…

python基础练习题6

1、找出10000以内能被5或6整除&#xff0c;但不能被两者同时整除的数&#xff08;函数&#xff09; def find_numbers(m,n):result []for num in range(m,n):if (num % 5 0 or num % 6 0) and not (num % 5 0 and num % 6 0):result.append(num)return resultprint(find_…

Linux: 进程优先级

Linux: 进程优先级 一、进程优先级概念二、如何查看进程优先级三、如何修改进程的优先级&#xff08;PRL vs NI&#xff09;四、为何优先级PRL必须限定范围五、进程其他特性 一、进程优先级概念 优先级的本质就是排队&#xff0c;而排队则是资源不足所引起的。在计算机中&#…

static修饰的方法为什么不能被覆盖?

在Java中&#xff0c;static方法不能被覆盖&#xff08;overridden&#xff09;的根本原理可以从以下几个角度进行解释&#xff1a; 静态绑定与动态绑定&#xff1a; 非静态方法&#xff08;非static方法&#xff09;的调用遵循动态绑定原则&#xff0c;即在运行时根据对象的实…

分布式系统概述(重要关键词加粗)

学习目标&#xff1a; 了解分布式 学习内容&#xff1a; 分布式系统是由多个计算机节点组成的系统&#xff0c;这些节点通过网络相互连接并协同工作&#xff0c;以完成共同的任务或服务。在分布式系统中&#xff0c;每个节点都运行着一部分应用程序&#xff0c;并且通常拥有自…

Leetcode 3100. Water Bottles II

Leetcode 3100. Water Bottles II 1. 解题思路2. 代码实现 题目链接&#xff1a;3100. Water Bottles II 1. 解题思路 这一题就是按照题目翻译一下&#xff0c;按照题中给出的规则不断进行bottle的兑换&#xff0c;直至无法兑换为止&#xff0c;即可得到最终的答案。 2. 代…

【3.31】

智乃想考一道完全背包(Easy version) 思路&#xff1a;虚拟物品的思路。可以把 l ∈ [ 1 , k ] , r ∈ [ k , n ] l\in[1, k], r\in[k, n] l∈[1,k],r∈[k,n] 的区间 ( l , r ) (l, r) (l,r) 看作一个虚拟物品&#xff0c;体积和价值为区间的体积和与价值和。这样做完全背包…

【项目技术介绍篇】若依开源项目RuoYi-Cloud前端技术介绍

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

鸿蒙ARKTS--简易的购物网站

目录 一、media 二、string.json文件 三、pages 3.1 登录页面&#xff1a;gouwuPage.ets 3.2 PageResource.ets 3.3 商品页面&#xff1a;shangpinPage.ets 3.4 我的页面&#xff1a;wodePage.ets 3.5 注册页面&#xff1a;zhucePage.ets 3. 购物网站主页面&#xff…

STM32学习笔记(10_2)- I2C通信协议MPU6050简介

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 本期开…

[Linux_IMX6ULL驱动开发]-基础驱动

驱动的含义 如何理解嵌入式的驱动呢&#xff0c;我个人认为&#xff0c;驱动就是嵌入式上层应用操控底层硬件的桥梁。因为上层应用是在用户态&#xff0c;是无法直接操控底层的硬件的。我们需要利用系统调用&#xff08;open、read、write等&#xff09;&#xff0c;进入内核态…

synchronized的使用方式

1、修饰实例方法 public synchronized void A(){} 这个时候锁的是当前的实例对象。多线程操作同一个实例的实例方法时&#xff0c;才会阻塞。 2、修饰静态方法 public synchronized static void A(){}这个时候锁的是整个类下所有的实例对象&#xff0c;静态方法是存在于方法…

自然语言处理:大模型LLM论文整理

LLMs 九层妖塔 地址&#xff1a;https://github.com/km1994/LLMsNineStoryDemonTower LLMs 千面郎君 地址&#xff1a;https://github.com/km1994/LLMs_interview_notes LLMs 论文学习笔记&#xff1a;https://gitee.com/km601/llms_paper NLP 百面百搭 地址&#xff1a;htt…

os模块篇(三)

文章目录 os.putenv(key, value, /)os.setegid(egid, /)os.seteuid(euid, /)os.setgid(gid, /)os.setgroups(groups, /)os.setns(fd, nstype0)os.setpgrp()os.setpgid(pid, pgrp, /)os.setpriority(which, who, priority) os.putenv(key, value, /) os.putenv(key, value) 是 …