【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记(完结)
从高层设计的角度去探讨框架需要关注的问题。
参考:速读《Vue.js 设计与实现》 - 掘金 (juejin.cn)
系列目录:
标题 | 博客 |
---|---|
第一篇:框架设计概览 | 【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记 |
第二篇:响应系统 | 【Vue.js设计与实现】第二篇:响应系统-阅读笔记 |
第三篇:渲染器 | 【Vue.js设计与实现】第三篇:渲染器-阅读笔记 |
第四篇:组件化 | 【Vue.js设计与实现】第四篇:组件化-阅读笔记 |
第五篇:编译器 | 【Vue.js设计与实现】第五篇:编译器-阅读笔记 |
第六篇:服务端渲染 | 【Vue.js设计与实现】第六篇:服务端渲染-阅读笔记 |
第一篇:框架设计概览
- 第 1 章 权衡的艺术
- 第 2 章 框架设计的核心要素
- 第 3 章 Vue.js 3 的设计思路
文章目录
- 第一章 权衡的艺术
- 1.1 命令式和声明式
- 1.2 性能与可维护性的权衡
- 1.3 运行时和编译时
- 第二章:框架设计的核心要素
- 2.1 `__DEV__`:在开发环境中为用户提供友好的警告信息的同时,不会增加生产环境代码的体积
- 2.2 ` /*#__PURE__*/`与Tree Shaking
- 2.3 框架应该输出怎样的构建产物
- 2.4 错误处理
- 2.5 良好的TypeScript类型支持
- 总结
- 第三章:Vue.js 3 的设计思路
- 3.1 声明式地描述UI
- 3.2 初识渲染器
- 3.3 组件的本质
- 3.4 模板的工作原理
- 3.5 Vue.js 是各个模块组成的有机整体
- 总结
第一章 权衡的艺术
框架的设计,本身就是一种权衡的艺术。
1.1 命令式和声明式
命令式关注过程,声明式关注结果
命令式:
const div=document.querySelector('#app') //获取div
div.innerText='hello world' //设置文本内容
div.addElementListener('click',()=>{alert('OK')}) //绑定事件
声明式:
<div @click="()=>alert('OK')">Hello world</div>
vue的内部是命令式的,而我们使用vue的时候是声明式的。
即,vue封装了命令式的过程,对外暴露出了声明式的结果。
1.2 性能与可维护性的权衡
从性能的角度上看,命令式的性能>声明式的性能。
原因:
命令式的代码通过原生的JS实现,声明式的代码要实现同样功能时,还要再调用相同的命令式代码。
声明式代码的更新性能消耗 = 找出差异的性能消耗+直接修改的性能消耗.
所谓的虚拟 DOM,就是为了最小化找出差异这一步的性能消耗而出现的。
显然命令式的性能更高。此时vue还要对外暴露出声明式的接口,原因是声明式的可维护性,远大于命令式的可维护性
而且,vue在性能优化之下,它并不会比纯命令式的性能差太多。
在前端领域,用JS修改HTML的方式主要有3种:原生JS,innerHTML,虚拟DOM。
心智负担:虚拟DOM < innerHTML< 原生JS
性能:innerHTML < 虚拟DOM < 原生JS
可维护性:原生JS < innerHTML < 虚拟DOM
虚拟DOM的性能并不是最高的,但是vue依然选择虚拟DOM来进行渲染层的构架。
这也是性能与可维护性的权衡。
1.3 运行时和编译时
都是框架设计的一种方式,可单独出现,也可组合使用。
- 运行时:
runtime
利用render函数,把虚拟DOM转化为真实DOM的方式。
- 编译时:
compiler
把template模板中的内容,转化为真实DOM。
注意,存在编译过程,可以分析用户提供的内容。同时,没有运行时理论上性能会更好。
- 运行时+编译时
过程分两步:
- 先把template模板转化成render函数,即编译时
- 再利用render函数,把虚拟DOM转化为真实DOM,即运行时
两者的结合,可以:
- 在编译时:分析用户提供的内容
- 在运行时,提供足够的灵活性
这也是vue的主要实现方式。
第二章:框架设计的核心要素
2.1 __DEV__
:在开发环境中为用户提供友好的警告信息的同时,不会增加生产环境代码的体积
有一个常量__DEV__
,存在于所有的console.warn中:
if (__DEV__ && !res) {warn(`Failed to mount app: mount target selector "${container}"returned null.`);
}
在开发环境中__DEV__
永远为true
,在生产环境中__DEV__
永远为false
。永远不会执行的代码成为dead code
,不会出现在最终产物中。
2.2 /*#__PURE__*/
与Tree Shaking
Tree Shaking:消除那些永远不会被执行的代码。
想要实现Tree Shaking,模块必须是ESM。
Tree Shaking的关键点:副作用。如果一个函数调用会产生副作用,那么就不能将其移除。副作用就是,当调用函数的时候会对外部产生影响,例如修改了全局变量。
举个例子:
如果 obj 对象是一个通过
Proxy
创建的代理对象,那么当我们读取对象属性时,就会触发代理对象的 get 夹子(trap),在 get 夹子中是可能产生副作用的,例如我们在 get 夹子中修改了某个全局变量。而到底会不会产生副作用,只有代码真正运行的时候才能知道,JavaScript 本身是动态语言,因此想要静态地分析哪些代码是 dead code 很有难度
静态地分析 JavaScript 代码很困难,所以像 rollup.js 这类工具都会提供一个机制,让我们能明确地告诉 rollup.js:这段代码没有副作用,可以移除。
import {foo} from './utils'
/*#__PURE__*/ foo()
注释代码/*#__PURE__*/
,就是告诉rollup.js,对于foo的调用不会产生副作用,可以Tree-Shaking。
实际上,通常产生副作用的代码都是模块内函数的顶级调用。
foo() // 顶级调用function bar(){foo() // 函数内调用
}
可以看到,对于顶级调用来说,是可能产生副作用的;对于函数内调用来说,只要函数 bar 没有调用,那么 foo 函数的调用自然不会产生副作用。
因此,在 Vue.js 3 的源码中,基本都是在一些顶级调用的函数上使用 /*#__PURE__*/
注释。此注释也可以应用在语句上。
2.3 框架应该输出怎样的构建产物
用户能够使用 <script>
标签直接引入 ESM 格式的资源,如:
<script type="module" src="/path/to/vue.esm-browser.js">
</script>
ESM格式的资源中,文件会有一个-browser
字样。其实对于ESM格式的资源来说,Vue.js还会输出一个vue.esm-bundler.js
文件。这样做的原因是:在寻找资源时,如果package.json中存在module字段,那么会优先使用module字段指向的资源来代替main字段指向的资源。
如Vue.js源码中的packages/vue/package.json
:
{"main":"index.js","module":"dist/vue.runtime.esm-bundler.js"
}
带有
-bundler
字样的 ESM 资源是给 rollup.js 或 webpack 等打包工具使用的,而带有-browser
字样的 ESM 资源是直接给<script type="module">
使用的
当我们构建提供给打包工具的ESM格式的资源时,不能直接把__DEV__
设置为true或false,而是:process.env.NODE_ENV!=='production'
。即,当前环境不是生产环境.
如源码:
if (__DEV__)
在带有 -bundler
字样的资源中会变为:
if ((process.env.NODE_ENV !== 'production'))
2.4 错误处理
举个例子,一个模块导出一个方法,参数是一个回调函数:
import utils from 'utils.js'utils.foo(()=>{
//...
})
如果用户在执行回调函数时出错了,怎么办?有两个方法:让用户自己处理、vue代替用户统一处理错误。
用户自己处理:
utils.foo(()=>{try{//...}catch(e){//...}
})
会增加用户的负担。不建议。
vue代替用户统一处理错误
将错误处理程序封装为一个函数,如:callWithErrorHanding
export default {foo(fn) {callWithErrorHanding(fn);},bar(fn) {callWithErrorHanding(fn);},
};function callWithErrorHanding(fn) {try {fn && fn();} catch (e) {console.log(e);}
}
这样做的好处:为用户提供统一的错误处理接口。
提供 registerErrorHandler
函数,用户可以使用它注册错误处理程序,在callWithErrorHanding
捕获错误后,把错误传给用户注册的错误处理程序:
export default {foo(fn) {callWithErrorHanding(fn);},// 用户可以调用该函数注册统一的错误处理函数registerErrorHandler(fn) {handleError = fn;},
};function callWithErrorHanding(fn) {try {fn && fn();} catch (e) {// 将捕获到的错误传递给用户的错误处理程序handleError(e);}
}
这时错误处理的能力完全由用户控制,用户既可以选择忽略错误,也可以调用上报程序将错误上报给监控系统。
在Vue.js中,我们也可以注册统一的错误处理函数:
import App from "App.vue";
const app = createApp(app);
app.config.errorHandler = () => {// 错误处理程序
};
2.5 良好的TypeScript类型支持
使用TS 编写代码与对TS 类型支持友好是两件事。
举个例子:
function foo(val: any) {return val;
}const res = foo('str')
当我们把鼠标指针悬浮到 res 常量上时,可以看到其类型是 any
,而不是string
——返回值类型丢失。
为了达到理想状态,需要做出一些修改:
function foo<T extends any>(val: T):T {return val;
}
由此可见,框架想要做到完善的类型支持,需要付出相当大的努力。
总结
开发体验是衡量一个框架的重要指标之一。大多数情况下“框架”要比开发者更清楚问题出在哪里,因此在框架层面抛出有意义的警告信息是非常必要的。
我们通过预定义 __DEV__
常量,从而实现仅在开发环境中打印警告信息—— Tree-Shaking 机制。这使得线上代码不会因为警告信息而体积过大。
Tree-Shaking是一种排除 dead code 的机制。一些工具能够识别/*#__PURE__*/
注释,可以利用此注释来辅助构建工具进行 Tree-Shaking。
对于框架的输出产物,不同类型的产物是为了满足不同的需求。为了让用户能够通过 <script>
标签直接引用并使用,我们需要输出 IIFE 格式的资源,即立即调用的函数表达式。为了让用户能够通过 <script type="module">
引用并使用,我们需要输出ESM 格式的资源。
需要注意的是,ESM 格式的资源有两种:用于浏览器的 esm-browser.js
和用于打包工具的 esm-bundler.js
。它们的区别在于对预定义常量 __DEV__
的处理,前者直接将 __DEV__
常量替换为字面量 true 或 false,后者则将 __DEV__
常量替换为process.env.NODE_ENV !== 'production'
语句。
有时出于灵活性和兼容性的考虑,对于同样的任务,框架提供了两种解决方案,如组合式API/选项式API。不被使用的方案会被 Tree-Shaking 机制排除。
框架的错误处理做得好坏直接决定了用户应用程序的健壮性,同时还决定了用户开发应用时处理错误的心智负担。框架需要为用户提供统一的错误处理接口,这样用户可以通过注册自定义的错误处理函数来处理全部的框架异常。
第三章:Vue.js 3 的设计思路
3.1 声明式地描述UI
Vue.js 3是一个声明式的UI框架。前端页面涉及的东西:
- DOM 元素:例如是 div 标签还是 a 标签。
- 属性:如 a 标签的 href 属性,再如 id、class 等通用属性。
- 事件:如 click、keydown 等。
- 元素的层级结构:DOM 树的层级结构,既有子节点,又有父节点。
Vue是如何声明式地描述上述内容的:
使用模板来声明式地描述UI
- 使用与 HTML 标签一致的方式来描述 DOM 元素、属性和层级结构
- 使用
:
或v-bind
来描述动态绑定的属性 - 使用
@
或v-on
来描述事件
使用JavaScript 对象来声明式地描述UI
const title = {// 标签名称tag: "h1",// 标签属性props: {onClick: handler,},// 子节点children: [{ tag: "span" }],
};
对应到Vue模板:
<h1 onClick="handler"><span></span>
</h1>
使用 JavaScript 对象描述UI比模板描述更加灵活。如:表示一个标题,根据标题级别的不同,会分别采用 h1~h6 这几个标签,JS对象描述:
let level = 3;
const title = {tag: `h${level}`,
};
用模板描述,只能穷举:
<h1 v-if="level === 1"></h1>
<h2 v-else-if="level === 2"></h2>
<h3 v-else-if="level === 3"></h3>
<h4 v-else-if="level === 4"></h4>
<h5 v-else-if="level === 5"></h5>
<h6 v-else-if="level === 6"></h6>
使用 JavaScript 对象来描述 UI的方式,就是虚拟DOM。
Vue.js组件中手写的渲染函数就是使用虚拟 DOM 来描述 UI 的,如:
import { h } from "vue";
export default {render() {return h("h1", { onClick: handlder }); // 虚拟DOM},
};
这里的h函数返回值是一个对象,其作用是让我们编写虚拟DOM更加轻松。也可以这样写:
export default {render() {return {tag: "h1",props: { onClick: handler },};},
};
h 函数,一个辅助创建虚拟 DOM 的工具函数
3.2 初识渲染器
渲染器的工作原理其实很简单,是使用一些我们熟悉的 DOM 操作 API 来完成渲染工作。
虚拟 DOM:用 JavaScript对象来描述真实的 DOM 结构。
渲染器:把虚拟 DOM 渲染为真实 DOM。
举个例子,有虚拟DOM如下:
const vnode = {tag: "div",props: {onClick: () => alert("hello"),},children: "click me",
};
- tag:标签名称
- props:一个对象,描述属性、事件等
- children:标签的子节点
实际上,你完全可以自己设计虚拟 DOM 的结构,例如可以使用tagName 代替 tag,因为它本身就是一个 JavaScript 对象,并没有特殊含义
分析渲染器 renderer 的实现思路
- 创建元素:
vnode.tag
作为标签名 - 为元素添加属性和事件:遍历
vnode.props
对象,如果key
以on
字符开头,说明它是一个事件 - 处理
children
:若children
是一个数组,就递归地调用renderer
继续渲染
具体代码如下:
渲染器:
vnode
:虚拟DOM节点container
:真实 DOM 元素,作为挂载点,渲染器会把虚拟 DOM 渲染到该挂载点下
function renderer(vnode, container) {// 使用 vnode.tag 作为标签名称创建 DOM 元素const el = document.createElement(vnode.tag);// 遍历 vnode.props,将属性、事件添加到 DOM 元素for (const key in vnode.props) {// on开头的事件名if (/^on/.test(key)) {el.addEventListener(key.substring(2).toLowerCase(),vnode.props[key]);}}// 处理childrenif (typeof vnode.children === "string") {// 文本子节点el.appendChild(document.createTextNode(vnode.children));} else if (Array.isArray(vnode.children)) {// 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点vnode.children.forEach((child) => renderer(child, el));}// 将元素添加到挂载点下container.appendChild(el);
}
如:
<div id="div"></div>
const div = document.getElementById("div");
renderer(vnode, div);
打开浏览器,会发现渲染出了click me,点击它:
上述仅仅只是创建节点,渲染器的精髓在更新节点:渲染器只更新修改的部分,而不是重新渲染全部节点。
3.3 组件的本质
虚拟 DOM 除了能够描述真实 DOM 之外,还能够描述组件。组件就是一 组 DOM 元素的封装。
定义一个函数来代表组件,而函数的返回值就代表组件要渲染的内容。
这里tag
用来描述组件函数。
const MyComponent = function () {return {tag: "div",props: {onClick: () => alert("hello"),},children: "click me",};
};const vnode = {tag: MyComponent,
};
总体代码:
function renderer(vnode, container) {// 渲染标签/组件if (typeof vnode.tag === "string") {mountElement(vnode, container);} else if (typeof vnode.tag === "function") {mountComponent(vnode, container);}
}function mountElement(vnode, container) {// 使用 vnode.tag 作为标签名称创建 DOM 元素const el = document.createElement(vnode.tag);// 遍历 vnode.props,将属性、事件添加到 DOM 元素for (const key in vnode.props) {// on开头的事件名if (/^on/.test(key)) {el.addEventListener(key.substring(2).toLowerCase(),vnode.props[key]);}}// 处理childrenif (typeof vnode.children === "string") {// 文本子节点el.appendChild(document.createTextNode(vnode.children));} else if (Array.isArray(vnode.children)) {// 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点vnode.children.forEach((child) => renderer(child, el));}// 将元素添加到挂载点下container.appendChild(el);
}function mountComponent(vnode, container) {// 获取虚拟DOMconst subtree = vnode.tag();renderer(subtree, container);
}
实际上,组件不一定得是函数,也可以是对象。对象里有一个render
函数,返回一个虚拟DOM。
const MyComponent = {render() {return {tag: "div",props: {onClick: () => alert("hello"),},children: "click me",};},
};const vnode = {tag: MyComponent,
};function mountComponentObj(vnode, container) {const subtree=vnode.tag.render()renderer(subtree, container);}function renderer(vnode, container) {// 渲染标签/组件if (typeof vnode.tag === "string") {mountElement(vnode, container);} else if (typeof vnode.tag === "function") {mountComponent(vnode, container);} else if (typeof vnode.tag === "object") {mountComponentObj(vnode, container);}
}
3.4 模板的工作原理
这里只需要清楚编译器的作用及角色,具体之后会着重讲。
编译器:将模板编译为渲染函数
一个 .vue 文件就是一个组件,以.vue文件举例:
<template><div @click="handler">click me</div>
</template><script>
export default {};
</script>
其中 <template>
里的内容就是模板内容,编译器会把模板内容编译成渲染函数并添加到 <script>
标签块的组件对象上,所以最终在浏览器里运行的代码为:
export default {render() {return h("div", { onClick: handler }, "click me");},
};
对于组件来说,它要渲染的内容都是渲染函数产生的,然后渲染器再把渲染函数返回的虚拟 DOM 渲染为真实 DOM。这就是Vue.js 渲染页面的流程。
3.5 Vue.js 是各个模块组成的有机整体
编译器和渲染器可以配合工作。假设有模板如下:
<template><div id="foo" :class="cls"></div>
</template>
这里cls
是一个变量,通过v-bind
动态绑定到class
上。渲染器的作用之一就是寻找并且只更新变化的内容。在这里,编译器能识别出哪些是静态属性,哪些是动态属性,在生成代码的时候完全可以附带这些信息,它生成的虚拟DOM如下:
render() {return {tag: "div",props: {id: "foo",class: cls,},patchFlags: 1, // 假设数字 1 代表 class 是动态的};
},
这样,渲染器看到patchFlags: 1
就知道class是动态的了。
编译器和渲染器之间是存在信息交流的,它们互相配合使得性能进一步提升,而它们之间交流的媒介就是虚拟 DOM 对象
总结
Vue.js 是一个声明式的框架。声明式的好处在于,它直接描述结果,用户不需要关注过程。描述UI:
- 采用模板的方式(直观)
- 支持虚拟DOM的方式(灵活)
渲染器的作用:把虚拟 DOM 对象渲染为真实 DOM 元素。工作原理是:递归地遍历虚拟 DOM 对象,并调用原生 DOM API 来完成真实 DOM 的创建。精髓:后续的更新,它会通过 Diff
算法找出变更点,并且只会更新需要更新的内容。
组件的本质:一组虚拟 DOM 元素的封装。它可以是一个返回虚拟 DOM 的函数,也可以是一个对象,但这个对象下必须要有一个函数用来产出组件要渲染的虚拟 DOM。
编译器把Vue.js 的模板编译为渲染函数。 编译器、渲染器都是 Vue.js 的核心组成部分,它们共同构成一个有机的整体,不同模块之间互相配合,进一步提升框架性能。
相关文章:

【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记(完结)
从高层设计的角度去探讨框架需要关注的问题。 参考:速读《Vue.js 设计与实现》 - 掘金 (juejin.cn) 系列目录: 标题博客第一篇:框架设计概览【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记第二篇:响应系统【Vue.…...

数据结构—动态查找表
动态查找介绍 1. 动态查找的引入:当查找表以线性表的形式组织时,若对查找表进行插入、删除或排序操作,就必须移动大量的记录,当记录数很多时,这种移动的代价很大。 2. 动态查找表的设计思想:表结构本身是…...

Hbase-2.4.11_hadoop-3.1.3集群_大数据集群_SSH修改默认端口22为其他端口---记录025_大数据工作笔记0185
其实修改起来非常简单,但是在大数据集群中,使用到了很多的脚步,也需要修改, 这里把,大数据集群,整体如何修改SSH端口,为22022,进行总结一下: 0.hbase-2.4.11的话,hbase集群修改默认SSH端口22,修改成22022,需要修改 需要修改/opt/module/hbase-2.4.11/conf/hbase-env.sh 这里…...

c++学习第十四讲---STL常用容器---vector容器
vector容器: 1.vector基本概念: vector功能与数组类似,与数组不同的是,vector可以动态扩展。 2.vector构造函数: vector<T> v; //默认构造函数,创建数据类型T的容器 ve…...

数据结构-内部排序
简介 排序(Sorting):将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列 排序算法分为内部排序和外部排序 内部排序:在排序期间数据对象全部存放在内存的排序 外部排序&am…...

Qt加载网页崩溃 ASSERT:“m_adapterClient“ in file ...
1、软件启动后加载网页无异常,点击按钮,加载新网页时崩溃 崩溃代码: QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) { Q_UNUSED(type); return this; } 2、原因 Qt只是调用谷歌的浏览器引擎ÿ…...

合约短线高胜率策略-扭转乾坤指标使用说明
扭转乾坤指标使用说明 行情判断 双绿线 多趋势双红线 空趋势大绿线 小红线 多震荡大红线 小绿线 空震荡 进场条件 趋势行情进场 多趋势 多信号 底金叉 做多空趋势 空信号 顶死叉 做空 震荡行情进场 多震荡 多信号 底金叉 做多多震荡 空信号 顶死叉 做空空…...

DAY37:贪心算法738
今天写了一道题目,顺便看了一个很好的总结,这篇博客可以跳过。 Leetcode:738 单调递增的数字 因为最大的数字是9,当出现后面位数的数字比前面位数的数字小的时候,就把后面的数字都变成9,前面那个数字--。…...

计算机中的缓存与内存
在现代计算机系统中,缓存和内存扮演着至关重要的角色,它们共同协作以实现高性能和高效率的数据处理。本文将深入探讨缓存和内存的概念、功能以及它们在计算机系统中的作用。 缓存与内存:概念与功能 1. 内存(RAM)&…...

2.1总结
还是一样水更一天,就随便做了几个题,有一个周期有点长,后面更一篇长的 随手刷的一道水题,就不往今天的行程单添了 问题:最大公约数 题解:题目太水了,就是求三个数,其中两组的最大公…...

探索Pyecharts:绘制多彩日历图的艺术与技巧
Pyecharts绘制多种炫酷日历图参数说明代码实战 导言 在数据可视化领域,日历图是一种直观展示时间和数据关系的方式。Pyecharts是一个基于Echarts的Python库,可以方便地绘制各种图表,包括炫酷的日历图。本篇博客将介绍Pyecharts中绘制多种炫…...

响应标头Allow-Headers和Expose-Headers的区别和用法
Access-Control-Allow-Headers和Access-Control-Expose-Headers,简单的说,这两者都是前端和后端之间通过header传递数据的,主要的区别就是方向。 Access-Control-Allow-Headers 举个例子,如果我们前端向后端发起请求,…...

<网络安全>《13 上网行为管理》
1 概念 上网行为管理是指帮助互联网用户控制和管理对互联网的使用。其包括对网页访问过滤、上网隐私保护、网络应用控制、带宽流量管理、信息收发审计、用户行为分析等。 随着计算机、宽带技术的迅速发展,网络办公日益流行,互联网已经成为人们工作、生活…...

安全通道堵塞识别摄像机
当建筑物的安全通道发生堵塞时,可能会给人员疏散和救援带来重大隐患。为了及时识别和解决安全通道堵塞问题,专门设计了安全通道堵塞识别摄像机,它具有监测、识别和报警功能,可在第一时间发现通道堵塞情况。这种摄像机通常安装在通…...

2022 年全国职业院校技能大赛高职组云计算赛项试卷
【赛程名称】云计算赛项第二场-容器云 说明: 完成本任务需要两台安装了 CentOS7.9 操作系统的云主机: master 和 node。Chinaskill_Cloud_PaaS.iso 镜像包中有本次容器云部署所需的所有文件,运维所需的文件见附件。 某公司技术部产品开发上线…...

Android开发中,Vue 3处理回退按键事件
vue3有一些变化,按照网上有些文章的方法,发现行不通,通过一段时间的打印、尝试后,发现以下方法可行。 第一步)首先定义一个处理回退事件的js函数,一定是vue.methods中的函数,否则找不到this&am…...

three.js CSS3DRenderer、CSS3DSprite渲染HTML标签
有空的老铁关注一下我的抖音: 效果: <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red;position: relative;"></div><…...

【BBF系列协议】TR369管理平台软件设计
一、介绍 旨在促进CPE和IoT的多供应商管理平台的发展。遵循TR-369协议的任何设备都可以进行管理。主要目标是促进并统一设备管理,为最终用户和服务提供商带来无数好处,减少当前技术所需的要求:设备互连、数据收集、速度、可用性等等。 二、 TR-069 ---> TR-369 物联网…...

微信小程序 仿微信聊天界面
1. 需求效果图 2. 方案 为实现这样的效果,首先要解决两个问题: 2.1.点击输入框弹出软键盘后,将已有的少许聊天内容弹出,导致看不到的问题 点击输入框弹出软键盘后,将已有的少许聊天内容弹出,导致看不到的问…...

中国社会科学院大学-新加坡社科大学 招生简章
Singapore University of Social Sciences--University of Chinese Academy of Social Sciences Doctor of Business Administration (DBA) Programme in Global Strategy and Leadership 一、项目简介 全球经济正在经历由科技进步和创新、政治和人口剧烈变化所带来的巨大的不…...

js中继承的详解(一文读懂)
文章目录 一、是什么二、实现方式原型链继承构造函数继承组合继承原型式继承寄生式继承寄生组合式继承 三、总结参考文献 一、是什么 继承(inheritance)是面向对象软件技术当中的一个概念。 如果一个类别B“继承自”另一个类别A,就把这个B称…...

Android studio使用svg矢量图
https://www.iconfont.cn/ https://www.jyshare.com/more/svgeditor/ https://editor.method.ac/ https://inloop.github.io/svg2android/ Pattern Monster - SVG 图案生成器 Android studio使用svg矢量图自适应不同的分辨率, svg矢量图绘制以及转换为And…...

《Access Path Selectionin a Relational Database Management System》论文笔记
以下是根据论文归纳出的一些查询优化器公式和知识点,有没有用不知道,先码起来。 SQL执行优化过程 处理SQL语句是从解析用户输入的SQL语句开始,经过一系列优化过程,最终生成机器代码并执行的过程。这个过程涉及到多个复杂的步骤&…...

【AI_Design】Midjourney学习笔记
目录 后缀解析Promot合格使用prompt关键词描述 关键词化合作用关键词网站推荐 联合Chatgpt使用总结 后缀解析 –ar:宽高比设置–c:多样性设置(数值0-100,默认值0)–s:风格化设置(数值0-1000&am…...

面试宝典之深谈JVM
面试宝典之深谈JVM 1.为什么需要JVM,不要JVM可以吗? 1.JVM可以帮助我们屏蔽底层的操作系统 一次编译,到处运行 2.JVM可以运行Class文件 2.JDK,JRE以及JVM的关系 3.我们的编译器到底干了什么事? 仅仅是将我们的 .ja…...

idea配置tomcat
推荐链接:IntelliJ IDEA中配置Tomcat(超详细)_idea怎么配置tomcat服务器-CSDN博客 1,官员下载链接:Apache Tomcat - Welcome! 附本人下载的 tomcat9 的百度网盘链接 链接:https://pan.baidu.com/s/1DpyBGnG4mUGTm5Z…...

【MyBatis】操作数据库——入门
文章目录 为什么要学习MyBatis什么是MyBatisMyBatis 入门创建带有MyBatis框架的SpringBoot项目数据准备在配置文件中配置数据库相关信息实现持久层代码单元测试 为什么要学习MyBatis 前面我们肯定多多少少学过 sql 语言,sql 语言是一种操作数据库的一类语言&#x…...

免费分享一套SpringBoot+Vue药店(药房)管理系统,帅呆了~~
大家好,我是java1234_小锋老师,看到一个不错的SpringBootVue药店(药房)管理系统 ,分享下哈。 项目视频演示 【免费】SpringBootVue药店(药房)管理系统 Java毕业设计_哔哩哔哩_bilibili【免费】SpringBootVue药店(药房)管理系统 Java毕业设计…...

视频怎么加水印?分享两个简单的加水印的方法
在数字媒体时代,视频已经成为信息传播的重要方式。许多人在创作视频是会加上自己独特的水印,防止视频被盗用。水印作为数字版权保护技术的一种,可以有效地防止视频被非法复制、传播或篡改,从而保护创作者的权益和利益。下面我分享…...

Apache Commons Collection3.2.1反序列化分析(CC1)
Commons Collections简介 Commons Collections是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队…...