【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记(持续更新)

从高层设计的角度去探讨框架需要关注的问题。

参考:速读《Vue.js 设计与实现》 - 掘金 (juejin.cn)

系列目录:

标题博客
第一篇:框架设计概览【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记
第二篇:响应系统【Vue.js设计与实现】第二篇:响应系统-阅读笔记
第三篇:渲染器【Vue.js设计与实现】第三篇:渲染器-阅读笔记
第四篇:组件化【Vue.js设计与实现】第四篇:组件化-阅读笔记
第五篇:编译器【Vue.js设计与实现】第五篇:编译器-阅读笔记
第六篇:服务端渲染【Vue.js设计与实现】第六篇:服务端渲染-阅读笔记

第一篇:框架设计概览

  • 第 1 章 权衡的艺术
  • 第 2 章 框架设计的核心要素
  • 第 3 章 Vue.js 3 的设计思路

文章目录

    • 第一章 权衡的艺术
      • 命令式和声明式
      • 性能与可维护性的权衡
      • 运行时和编译时
    • 第二章:框架设计的核心要素
      • `__DEV__`:在开发环境中为用户提供友好的警告信息的同时,不会增加生产环境代码的体积
      • ` /*#__PURE__*/`与Tree Shaking
      • 框架应该输出怎样的构建产物
      • 错误处理
      • 良好的TypeScript类型支持
      • 总结
    • 第三章:Vue.js 3 的设计思路
      • 声明式地描述UI
      • 初识渲染器
      • 组件的本质
      • 模板的工作原理
      • 总结

第一章 权衡的艺术

框架的设计,本身就是一种权衡的艺术。

命令式和声明式

命令式关注过程,声明式关注结果

命令式:

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封装了命令式的过程,对外暴露出了声明式的结果

性能与可维护性的权衡

从性能的角度上看,命令式的性能>声明式的性能。

原因:

命令式的代码通过原生的JS实现,声明式的代码要实现同样功能时,还要再调用相同的命令式代码。
声明式代码的更新性能消耗 = 找出差异的性能消耗+直接修改的性能消耗.
所谓的虚拟 DOM,就是为了最小化找出差异这一步的性能消耗而出现的。

显然命令式的性能更高。此时vue还要对外暴露出声明式的接口,原因是声明式的可维护性,远大于命令式的可维护性

而且,vue在性能优化之下,它并不会比纯命令式的性能差太多。

在前端领域,用JS修改HTML的方式主要有3种:原生JS,innerHTML,虚拟DOM。

心智负担:虚拟DOM < innerHTML< 原生JS
性能:innerHTML < 虚拟DOM < 原生JS
可维护性:原生JS < innerHTML < 虚拟DOM

虚拟DOM的性能并不是最高的,但是vue依然选择虚拟DOM来进行渲染层的构架。

这也是性能与可维护性的权衡。

运行时和编译时

都是框架设计的一种方式,可单独出现,也可组合使用。

  • 运行时:runtime

利用render函数,把虚拟DOM转化为真实DOM的方式。

  • 编译时:compiler

把template模板中的内容,转化为真实DOM。
注意,存在编译过程,可以分析用户提供的内容。同时,没有运行时理论上性能会更好。

  • 运行时+编译时

过程分两步:

  1. 先把template模板转化成render函数,即编译时
  2. 再利用render函数,把虚拟DOM转化为真实DOM,即运行时

两者的结合,可以:

  • 在编译时:分析用户提供的内容
  • 在运行时,提供足够的灵活性

这也是vue的主要实现方式。

第二章:框架设计的核心要素

__DEV__:在开发环境中为用户提供友好的警告信息的同时,不会增加生产环境代码的体积

有一个常量__DEV__,存在于所有的console.warn中:

if (__DEV__ && !res) {
	warn(
		`Failed to mount app: mount target selector "${container}"
    returned null.`
	);
}

在开发环境中__DEV__永远为true,在生产环境中__DEV__永远为false。永远不会执行的代码成为dead code,不会出现在最终产物中。

/*#__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__*/ 注释。此注释也可以应用在语句上。

框架应该输出怎样的构建产物

用户能够使用 <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'))

错误处理

举个例子,一个模块导出一个方法,参数是一个回调函数:

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 = () => {
	// 错误处理程序
};

良好的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 的设计思路

声明式地描述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 的工具函数

初识渲染器

渲染器的工作原理其实很简单,是使用一些我们熟悉的 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 对象,如果 keyon 字符开头,说明它是一个事件
  • 处理 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]
			);
		}
	}

	// 处理children
	if (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,点击它:

在这里插入图片描述

上述仅仅只是创建节点,渲染器的精髓在更新节点:渲染器只更新修改的部分,而不是重新渲染全部节点。

组件的本质

虚拟 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]
			);
		}
	}

	// 处理children
	if (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) {
	// 获取虚拟DOM
	const 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);
	}
}

模板的工作原理

总结


在Vue中,UI形式主要分为两种:

  • 声明式的模板描述:本质上是template模板。它会被编译器编译,得到渲染函数render
<div :id="Id" :class="{Class:true}" @click="onClick">
  • 命令式的render函数
import {h} from 'vue'

export default {
	render(){
		return h('h1',{onClick:handler}) //虚拟DOM
	}
}

注意,渲染器和渲染函数,不一样

渲染器是:

  • 函数createRenderer的返回值,是一个对象,被叫做renderer
  • renderer对象中有一个方法叫render,即渲染函数

即,渲染函数是渲染器的返回对象的一个方法

render渲染函数接受两个参数:VNodecontainer

VNode即虚拟DOM,本质上是一个JS对象。
container是一个容器,即被挂载的位置。
render的作用:把VNode挂载到container上。

同时,Vue以组件代表最小颗粒度,vue内部的渲染,本质上是大量的组件渲染
而组件本质上是一组DOM的集合,因此,vue渲染一个组件就是在渲染一组DOM。
即,以组件为介质,来完成针对一组组DOM的渲染

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

远程教育:低代码在教育技术领域的重塑之力

新冠肺炎大流行对世界各地的行业产生了影响&#xff0c;其中一些行业的影响远远超过其他行业。食品、零售、供应链、娱乐和航空业是受影响最大的行业&#xff0c;为确保不间断运营&#xff0c;这引发了一场数字革命。相信&#xff0c;这种数字化的采用将长期保持下去&#xff0…

如何使用 Google 搜索引擎保姆级教程(附链接)

一、介绍 "Google语法"通常是指在 Google 搜索引擎中使用一系列特定的搜索语法和操作符来精确地定义搜索查询。这些语法和操作符允许用户过滤和调整搜索结果&#xff0c;提高搜索的准确性。 二、安装 Google 下载 Google 浏览器 Google 官网https://www.google.c…

Android 使用高德地图

一、获取高德平台key 【1】基于application包名&sha1值在高德控制台获取key值&#xff0c;详情参考&#xff1a; 获取Key-创建工程-开发指南-Android 地图SDK | 高德地图API 【2】在manifest中声明权限 【3】将拿到的key值在manifest中进行声明 <!--允许程序打开网络…

沈阳地铁四号线部署智和信通运维方案,实现对工业交换机统一纳管

沈阳地铁4号线一期工程线路起于正新路站&#xff0c;途经和平区、沈河区、大东区、苏家屯区、浑南区&#xff0c;贯穿沈阳北站、沈阳南站、太原街、沈阳大学、长白岛、浑南产业区等区域&#xff0c;止于创新路站&#xff0c;大致呈南北走向。 项目现状 沈阳地铁4号线一期工程线…

点餐APP开发需要用到的一些源代码

在数字化日益普及的今天&#xff0c;点餐APP已经成为人们日常生活的重要组成部分&#xff0c;从校园食堂到高级餐厅&#xff0c;从快餐外卖到定制化营养餐&#xff0c;点餐APP的多样性和便利性满足了不同用户的需求&#xff0c;本文将深入探讨在开发一款点餐APP时可能用到的源代…

Android Studio 出现 “Index is not created for `Stubs‘“ 的原因,如何解决?

亲爱的朋友们&#xff01;大家好我是咕噜铁蛋&#xff01;&#xff0c;今天我将与大家分享一个在Android Studio开发过程中可能遇到的问题&#xff1a;“Index is not created for Stubs”。这个问题看似不起眼&#xff0c;但实际上可能会对开发效率产生不小的影响。下面&#…

c++之说_9_6|自定义类型 struct operator 重载运算符关键字

c中 struct 和 class 特性几乎一模一样 所以很多都是共有的特性 篇幅就很多了 c中 我觉得最牛皮的概念之一 就是 重载运算符 operator 关键字 这个东西 能将 我们的 运算符 &#xff08;-*/&#xff09; new delete 类型转换 写在类里面当成员函数 并制定 该类型变…

大数据-Spark-关于Json数据格式的数据的处理与练习

上一篇&#xff1a; 大数据-MapReduce-关于Json数据格式的数据的处理与练习-CSDN博客 16.7 Json在Spark中的引用 依旧利用上篇的数据去获取每部电影的平均分 {"mid":1,"rate":6,"uid":"u001","ts":15632433243} {"m…

linux系统查看占用cpu程序

目录 一&#xff1a;top 二&#xff1a; ps 三&#xff1a;perf 四&#xff1a;/proc/stat 五&#xff1a;pidstat 一&#xff1a;top 使用 top 命令&#xff1a;在终端中输入 top 命令&#xff0c;系统会显示当前正在运行的进程和它们的资源占用情况。默认情况下&#…

使用AKStream对接gb28181

优点&#xff1a;功能比较多&#xff0c;C#开发的&#xff0c;容易修改&#xff0c;内嵌入了zlmk流媒体服务品&#xff0c;启动简单 缺点&#xff1a;sip对摄像头兼容还有问题&#xff0c;大华接入非常不稳定&#xff0c;注册等待时间久&#xff0c;对海康是正常&#xff0c;占…

Oracle 的闪回技术是什么

什么是闪回 Oracle 数据库闪回技术是一组独特而丰富的数据恢复解决方案&#xff0c;能够有选择性地高效撤销一个错误的影响&#xff0c;从人为错误中恢复。闪回是一种数据恢复技术&#xff0c;它使得数据库可以回到过去的某个状态&#xff0c;可以满足用户的逻辑错误的快速恢复…

LabVIEW电液伺服控制系统

介绍了如何利用ARM微处理器和LabVIEW软件开发一个高效、精准的电液伺服控制系统。通过结合这两种技术&#xff0c;我们能够提高系统的数字化程度、集成化水平&#xff0c;以及控制精度&#xff0c;从而应对传统电液伺服控制器面临的问题。 该电液伺服控制系统由多个关键部分组…

TypeScript(八) number和string

1. TypeScript number 1.1. 描述 Number对象是原始数值的包装对象。 1.2.语法 var num new Number(value);;注意&#xff1a;如果一个参数值不能转换为一个数字&#xff0c;将返回NaN&#xff08;非数字值&#xff09;。 1.3. 对象属性 属性描述MAX_VALUE可表示的最大的数…

Postman-接口测试教程

接口是软件开发中常用的概念&#xff0c;是软件生产过程中比较核心的任务。对于接口开发者&#xff0c;调试接口是一件较为繁琐的事情&#xff0c;很多时候需要线上线下来回切换。在这里&#xff0c;我就跟大家介绍一个只需要在本地就可以调试接口的方法&#xff0c;即使用post…

Dubbo 3.x源码(17)—Dubbo服务发布导出源码(6)

基于Dubbo 3.1&#xff0c;详细介绍了Dubbo服务的发布与引用的源码。 此前我们学习了Dubbo 3.x源码(16)—Dubbo服务发布导出源码(5)&#xff0c;也就是Dubbo远程服务在导出远程服务得到Exporter之后&#xff0c;继续通过Registry将其注册到远程注册中心的源码。 实际上&#x…

git的分支操作

目录 简介&#xff1a; 操作&#xff1a;查看 操作&#xff1a;创建 操作&#xff1a;切换​编辑 操作&#xff1a;本地分支推送到远程 操作&#xff1a;git merge [name]合并分支​编辑 简介&#xff1a; 在Git中&#xff0c;可以通过分支来管理和处理不同的版本和功能。分…

DSP系统时钟总结

一、stm32中断偏移向量介绍 1.1 为什么要设置中断向量偏移 上图可以看出程序上电先进入0x08000000开始运行&#xff0c;紧接着执行复位中断向量&#xff0c;然后执行复位中断程序&#xff0c;然后进入main函数。 如果想要app的中断正常运行&#xff0c;那就必须手动设置中断向…

在本地电脑上打开服务器里面的localhost网址

远程连接服务器&#xff0c;启动了一个服务 显示访问地址为&#xff1a;http://127.0.0.1:7860 在本地浏览器将127.0.0.1改成服务器ip但是无法访问 解决办法&#xff1a; 1. ssh新建一个远程连接&#xff0c;将服务器的7860端口重定向到本机 ssh -L 18097:127.0.0.1:7860 us…

LeetCode 54 螺旋矩阵

题目描述 螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#x…

qt-C++笔记之QStringList、QList<QString>、QString、QChar、QList<QChar>区别

qt-C笔记之QStringList、QList、QString、QChar、QList区别 —— 杭州 2024-01-30 凌晨0:27 参考博文&#xff1a;qt-C笔记之QStringList code review! 文章目录 qt-C笔记之QStringList、QList<QString>、QString、QChar、QList<QChar>区别1.Qt的字符容器类1.QSt…