WebKit Insie: Active 样式表

news2024/9/22 1:10:40

WebKit Inside: CSS 样式表的匹配时机介绍了当 HTML 页面有不同 CSS 样式表引入时,CSS 样式表开始匹配的时机。后续文章继续介绍 CSS 样式表的匹配过程,但是在匹配之前,首先需要收集页面里面的 Active 样式表。

1 Active 样式表

在一个 HTML 文件里面,可能会使用<style>标签与<link>标签引入许多样式表,但是这些样式表并不一定都同时在文档里面生效。有时根据业务需求,可能会只使用页面里的部分样式表。比如有一个换肤需求,页面里面可能会使用<link>标签引入 4 张样式表,代码如下:

<link href="reset.css" rel="stylesheet" />

<link href="default.css" rel="stylesheet" title="Default Style" />
<link href="fancy.css" rel="alternate stylesheet" title="Fancy" />
<link href="basic.css" rel="alternate stylesheet" title="Basic" />

上面样式表reset.css所在的<link>标签有rel="stylesheet"属性,没有title属性,这种样式表被称为 Persisten 样式表,会一直被启用。

样式表default.css所在的<link>标签有rel="stylesheet"title属性,这种样式表被称为 Preferred 样式表。Preferred 样式表是默认启用。一个页面只能有一个 Preferred 样式表。

样式表fancy.cssbasic.css所在<link>标签有rel="alternate stylesheet"title属性,这种样式表被称为 Alternate 样式表。这些样式表默认下是不启用的,但是可以提供给用户选择。一旦用户选择了一个 Alternate 样式表,Preferred 样式表就会别禁用。

根据 <link>标签语法,样式表reset.cssdefault.css会在页面里面使用,而样式表fancy.cssbasic.css会暂时不使用。

一般这种场景会给一个按钮让用户切换皮肤,当用户选择切换到Fancy皮肤时,样式表default.css就失效,样式表fancy.css就会启用。但是不管用户如何切换,样式表reset.css始终有效。更多信息可以参考 MDN Alternative Style Sheet[1]

在用户没有换肤之前,样式表reset.cssdefault.css样式表就属于 Active 样式表,当用户选择切换之后,样式表reset.cssfancy.css就是 Active 样式表。

在进行 CSS 样式表匹配之前,WebKit 首先要收集页面里面所有的 Active 样式表,然后依次遍历这些 Active 样式表的 CSS Rule 进行匹配。

2 相关类图

image
上面类图里Style::SCope类持有负责进行样式表匹配的Style::Resolver类,同时它内部还有 3 个重要的数据成员:m_styleSheetCandidateNodes是一个哈希链表,用来按顺序存储 HTML 文件里面的 <style><link>节点,也就是 HTMLStyleElment对象和HTMLLinkElement对象。

m_activeStyleSheets是一个 Vector,类似数组,用来顺序存储页面里面的 Active 样式表。

m_styleSheetsForStyleSheetList也是一个 Vector,用来顺序存储页面里面的所有样式表。

3 获取 Candidate Node

无论内部样式表,还是外部样式表,当 WebKit 解析到 <style>标签或者<link>标签时,都会调用Style::Scope::addStyleSheetCandidateNode方法,将自己添加到Style::Scope的实例变量m_styleSheetCandidateNode里面。

以内部样式表为例,下面是调用堆栈:

image

函数Style::Scope::addStyleSheetCandidateNode的代码如下:

void Scope::addStyleSheetCandidateNode(Node& node, bool createdByParser)
{
    if (!node.isConnected())
        return;
    
    // Until the <body> exists, we have no choice but to compare document positions,
    // since styles outside of the body and head continue to be shunted into the head
    // (and thus can shift to end up before dynamically added DOM content that is also
    // outside the body).
    // 1. createByParser 代表当前的 node 是从 HTML 文件里解析出来的,而不是通过 JavaScript 代码动态创建的;
    // m_document.bodyOrFrameset 方法判断当前页面是否解析出了 <body> 标签和 <frameset> 标签;
    // 如果前这两个条件为真,那么节点直接添加到变量 m_styleSheetCandidateNodes;
    // 还有一种情形 m_styleSheetCandidateNodes 当前还没有添加任何节点
    if ((createdByParser && m_document.bodyOrFrameset()) || m_styleSheetCandidateNodes.isEmptyIgnoringNullReferences()) {
        m_styleSheetCandidateNodes.add(node);
        return;
    }

    // Determine an appropriate insertion point.
    // 2. 如果上述条件不满足,就会走到这里,这里会将当前节点与 m_styleSheetCandidateNodes 里已有的 <style> 或者 <link>
    // 节点进行位置比较,以便按照正确的顺序把当前节点插入到 m_styleSheetCandidateNodes.
    auto begin = m_styleSheetCandidateNodes.begin();
    auto end = m_styleSheetCandidateNodes.end();
    auto it = end;
    RefPtr<Node> followingNode;
    do {
        // 3. // 从后向前遍历
        --it;
        Ref<Node> n = *it;
        unsigned short position = n->compareDocumentPosition(node);
        // 4. DOCUMENT_POSITION_FOLLOWING 表示当前节点 node 位于节点 n 后面
        if (position == Node::DOCUMENT_POSITION_FOLLOWING) {
            if (followingNode)
                // 5. 注意 followwingNode 位于节点 n 的后面,这里将节点 node 插入到 followwingNode 前面,
                // 也就是刚好插儒道节点 n 的后面
                m_styleSheetCandidateNodes.insertBefore(*followingNode, node);
            else
                // 6. 如果节点 node 位于节点 n 后面,但是节点插入之前节点 n 后面已经没有其他节点了,那么就直接
                // 将节点 node 添加到节点 n 后面
                m_styleSheetCandidateNodes.appendOrMoveToLast(node);
            return;
        }
        followingNode = WTFMove(n);
    } while (it != begin);

    LOG_WITH_STREAM(StyleSheets, stream << "Scope " << this << " addStyleSheetCandidateNode() " << node);

    // 7. 如果遍历到 m_styleSheetCandidateNodes 最前面,上面代码也没有找到合适的位置,
    // 那么就将节点 node 插入到最前面.
    m_styleSheetCandidateNodes.insertBefore(*followingNode, node);
}

上面代码注释 1 是向变量m_styleSheetCandidateNodes添加 node 节点的第一处代码。变量createByParser代表当前节点 node 是从 HTML 文件里解析出来的,而不是通过 JavaScript 代码动态创建出来的。函数document.bodyOrFrameset代表当前是否已经解析出了<body>标签后者<frameset>标签。如果满足前面这两个条件,或者当前m_styleSheetCandidateNodes里为空,那么就将当前 node 添加进去。

如果上面条件都不满足,代码会运行到注释 2 处。注释 2 后面的代码会将当前 node 节点与m_styleSheetCandidateNodes变量里已有的<style>后者<link>标签的位置相比较,以便按照正确的位置将当前节点 node 插入到m_styleSheetCandidateNodes

那什么是正确的位置呢?

因为样式表的位置影响着样式表里 CSS Rule 中声明的优先级。比如 HTML 页面通过<link>标签引入了 2 个样式表 A 与 B,其中样式表 B 位于 样式表 A 后面。如果样式表 A 有如下 CSS Rule:

div {
	background-color: red;
}

样式表 B 的 CSS Rule 和样式表一样,只是设置背景色为蓝色:

div {
	background-color: blue;
}

由于样式表 A 和 B 都是 Author 样式表[2],而且 Specificity[3] 也一样,因此声明的优先级取决于它们所在的位置。由于样式表 B 比样式表 A 更靠后,因此最终会应用样式表 B 中的背景色。

上面代码注释 3 处就是从后向前遍历m_styleSheetCandidateNodes,以便找到这个正确位置。

注释 4 处比较节点n与节点node的位置关系[4]。如果节点node位于节点n的后面,也就是DOCUMENT_POSITION_FOLLOWING,那么就可以插入节点。

DOCUMENT_POSITION_FOLLOWING的意义是按照 DOM 树的 Tree Order[5][6] 进行遍历,节点node位于位于节点n之后。Tree Order 就是按照先序-深度优先(preorder,depth-first)遍历。

假设有如下的 HTML:

<html>
	<head>
		<link rel="stylesheet" href="./test1.css" />
		<link rel="stylesheet" href="./test2.css" />
	</head>
	<body>
		<div>Hello</div>
		<p>World</p>
	</body>
</html>

其 DOM 树结构如下:

image
按照 Tree Order 先序-深度优先遍历,那么就是先遍历根节点,然后遍历从左起第一棵子树,然后是第二棵子树,然后是第三棵子树...。

遍历的顺序如上图所示,遍历结果如下:html->head->title->link->link->body->div->p。从遍历结果可以看到,第 2 个 <link>标签位于第 1 个<link>标签后面,也就是第 2 个 <link>标签following 第 1 个 <link>标签。

从遍历结果上看,按照先序-深度优先遍历的位置关系,正好是 HTML 文件里面各标签的书写位置关系。

代码注释 5、6、7 都是将节点node插入到m_styleSheetCandidateNodes合适的位置,也就是说 HTML 里面是按照什么顺序引入的样式表,m_styleSheetCandidateNodes就是按照同样的顺序存储的<style>或者<link>标签节点。

4 获取 Active 样式表

无论内部样式表还是外部样式表,当其解析完成之后,都会调用对应的checkLoaded方法。

内部样式表的调用如下:

void InlineStyleSheetOwner::createSheet(Element& element, const String& text)
{
    ...
    auto contents = StyleSheetContents::create(String(), parserContextForElement(element));
    m_sheet = CSSStyleSheet::createInline(contents.get(), element, m_startTextPosition);
    ...
    // 1. 解析内部样式表
    contents->parseString(text);
    ...
    // 2. 调用 checkLoaded 方法
    contents->checkLoaded();
    ...
}

上面代码注释 1 解析内部样式表。
代码注释 2 调用checkLoaded方法。

外部样式表的调用如下:

void HTMLLinkElement::setCSSStyleSheet(const String& href, const URL& baseURL, const String& charset, const CachedCSSStyleSheet* cachedStyleSheet)
{
    ...
    auto styleSheet = StyleSheetContents::create(href, parserContext);
    initializeStyleSheet(styleSheet.copyRef(), *cachedStyleSheet, MediaQueryParserContext(document()));

    // FIXME: Set the visibility option based on m_sheet being clean or not.
    // Best approach might be to set it on the style sheet content itself or its context parser otherwise.
    // 1. 解析外部样式表
    if (!styleSheet.get().parseAuthorStyleSheet(cachedStyleSheet, &document().securityOrigin())) {
       ...
    }
    ...
    // 2. 调用 checkLoaded 方法
    styleSheet.get().checkLoaded();
    ...
}

上面代码注释 1 解析外部样式表。

注释 2 解析调用checkLoaded方法。

这两种情形的 checkLoaded方法最终会调用到Scope::didChangeActiveStyleSheetCandidates方法,代码如下:

void Scope::didChangeActiveStyleSheetCandidates()
{
    scheduleUpdate(UpdateType::ActiveSet);
}

Scope::didChangeActiveStyleSheetCandidates方法内部只调用了一个方法Scope::scheduleUpdate,传给它的参数是UpdateType::ActiveSet

UpdateType类型是一个枚举,定义在StyleScope.h,其定义如下:

// 定义在 StyleScope.h
  enum class UpdateType : uint8_t { 
    ActiveSet, // 代表一个样式表解析完成,称为了 Active 样式表
    ContentsOrInterpretation 
};

枚举UpdateType::ActiveSt代表一个 Active 样式表可用了。

方法 Scope::scheduleUpdate方法如下:

void Scope::scheduleUpdate(UpdateType update)
{
    ...
    if (!m_pendingUpdate || *m_pendingUpdate < update) {
        // 1. 这里设置 m_pendingUpdate 
        m_pendingUpdate = update;
        ...
    }
    ...
     // 2. 启动 Timer,Timer 的回调函数触发 Active 样式表的收集.
    // 参数 0 代表不延时,立即触发
    m_pendingUpdateTimer.startOneShot(0_s);
}

上面代码注释 1 设置Style::Scope对象的一个变量m_pendingUpdate,这个变量在后续触发 Active 样式表收集使用。

代码注释 2 启用一个 Timer,Timer 的回调函数触发 Active 样式表的收集流程。

Timer 的回调函数如下:

void Scope::pendingUpdateTimerFired()
{
    /// 1. 触发 Active 样式表收集
    flushPendingUpdate();
}

上面代码注释 1 调用Style::Scope::flushPendingUpdate方法触发 Active 样式表收集。

方法Style::Scope::flushPendingUpdate代码如下:

inline void Scope::flushPendingUpdate()
{
    ...
    // 1. m_pendingUpdate 已经在方法 Style::Scope::scheduleUpdate 里设置
    if (m_pendingUpdate)
        flushPendingSelfUpdate();
}

上面代码注释 1 处变量m_pendingUpdate已经在方法Style::Scope::scheduleUpdate里面设置成了UpdateType::ActiveSset,所以这里直接调用方法Style::Scope::flushPendingSelfUpdate方法。

Style::Scope::flushPendingSelfUpdate方法代码如下:

void Scope::flushPendingSelfUpdate()
{
    ASSERT(m_pendingUpdate);
    auto updateType = *m_pendingUpdate;
    // 1. 清除 m_pendingUpdate 变量,给其置空
    clearPendingUpdate();
    // 2. 收集 Active 样式表
    updateActiveStyleSheets(updateType);
}

上面代码注释 1 首先清除变量m_pendingUpdate,给其置空。

代码注释 2 调用Style::Scope::updateActiveStyleShhets方法开始真正的收集 Active 样式表。

方法Style::Scope::updateActiveStyleSheets代码如下:

void Scope::updateActiveStyleSheets(UpdateType updateType)
{
    ...
    // 1. 收集 Active 样式表.
    // collection 变量里面存储着收集到的 Active 样式表和页面里面所有样式表.
    auto collection = collectActiveStyleSheets();
    // 2. 变量 activeCSSStyleSheets 里面存储收集到的 Active 样式表,collection 变量里的 Active 样式表会赋值给这个变量
    Vector<RefPtr<CSSStyleSheet>> activeCSSStyleSheets;
    // 3. 上面已经收集了最新添加的 Active 样式表,这里进行过滤,剔除那些比如样式表长度为 0 的样式表,
    // 过滤后的结果存储在 activeCSSStyleSheets 中.
    filterEnabledNonemptyCSSStyleSheets(activeCSSStyleSheets, collection.activeStyleSheets);
    ...
    // 4. 将 Active 样式表存储到 m_activeStyleSheets
    m_activeStyleSheets.swap(activeCSSStyleSheets);
    // 5. 将所有样式表存储到 m_styleSheetsForStyleSheetList
    m_styleSheetsForStyleSheetList.swap(collection.styleSheetsForStyleSheetList);
    ...
}

上面代码注释 1 调用方法Style::Scope::collectActiveStyleSheet收集页面里面的 Active 样式表和所有样式表,将结果存储在变量collection中。

代码注释 2 声明的变量activeCSSStyleSheets会存储变量collection中的 Active 样式表。

代码注释 3 现将变量collection中的 Active 样式表进行过滤,剔除那些比如样式表长度为 0 的样式表,过滤后的结果存储在activeCSSStyleSheets变量中。

代码注释 4 将变量activeCSStyleSheet的值交换给实例变量m_activeStyleSheets,也就是m_activeStyleSheets现在存储着页面里面的 Active 样式表。

同理,代码注释 5 将页面里面所有的样式表存储在实例变量m_styleSheetsForStyleSheetList里。

下面看一下 Active 样式表的收集过程,也就是函数Style::Scope::collectActiveStyleSheet,代码如下:

auto Scope::collectActiveStyleSheets() -> ActiveStyleSheetCollection
{
    ...
    // 1. 存储 Active 样式表
    Vector<RefPtr<StyleSheet>> sheets;
    // 2. 存储 HTML 页面里面所有样式表
    Vector<RefPtr<StyleSheet>> styleSheetsForStyleSheetsList;

    // 3. 遍历之前存储在 m_styleSheetCandidateNodes 里的 <style> 或者 <link> 标签节点对象
    for (auto& node : m_styleSheetCandidateNodes) {
        RefPtr<StyleSheet> sheet;
        if (is<ProcessingInstruction>(node)) {
            // 4. ProcessingInstruction 就是诸如 <?xml> 这样的标签
            ...
            
        } else if (is<HTMLLinkElement>(node) || is<HTMLStyleElement>(node) || is<SVGStyleElement>(node)) {
            Element& element = downcast<Element>(node);
            ...
            // Get the current preferred styleset. This is the
            // set of sheets that will be enabled.
            if (is<SVGStyleElement>(element))
                sheet = downcast<SVGStyleElement>(element).sheet();
            else if (is<HTMLLinkElement>(element))
                // 5. 获取外部样式表
                sheet = downcast<HTMLLinkElement>(element).sheet();
            else
                // 6. 获取内部样式表
                sheet = downcast<HTMLStyleElement>(element).sheet();

            if (sheet)
                // 7. 将样式表添加到 styleSheetsForStyleSheetsList
                styleSheetsForStyleSheetsList.append(sheet);

            // Check to see if this sheet belongs to a styleset
            // (thus making it PREFERRED or ALTERNATE rather than
            // PERSISTENT).
            auto& rel = element.attributeWithoutSynchronization(relAttr);
            if (!enabledViaScript && sheet && !title.isEmpty()) {
                ...
                // 8. 如果 <link> 标签的 rel 属性包含 alternate,并且有 title,这里将 sheet 设置为 null,
                // 后面也添加不到 Active 样式表了.
                if (title != m_preferredStylesheetSetName)
                    sheet = nullptr;
            }
            // 9. 如果 <link> 标签的 rel 属性包含了 alternate,并且没有 title 属性,那么也将 sheet 设置为 null,
            // 后面也添加不到 Active 样式表了.
            if (rel.contains("alternate"_s) && title.isEmpty())
                sheet = nullptr;
            ...
        }
        if (sheet)
            // 10. 将当前样式表添加到 Active 样式表中
            sheets.append(WTFMove(sheet));
    }
    ...
    // 11. 将结果返回
    // sheets 存储 Active 样式表
    // styleSheetsForStyleSheetsList 存储所有样式表
    return { WTFMove(sheets), WTFMove(styleSheetsForStyleSheetsList) };
}

上面代码注释 1 声明变量sheets用来存储页面里面的 Active 样式表。

代码注释 2 声明变量styleSheetsForStyleSheetsList用来存储页面里面的所有样式表。

代码注释 3 遍历之前存储在m_styleSheetCandidateNodes实例变量里面的<style>标签和<link>标签节点对象。

代码注释 4 处理Processing Instruct[7],不在收集 Active 样式表考虑之内。

代码注释 5 和代码注释 6 根据遍历的节点对象,从其上面获取到对应的样式表对象sheet

代码注释 7 将上面获取到的注释表对象存储到变量styleSheetsForStyleSheetsList,这样styleSheetsForStyleSheetsList里面就是存储的是页面里面所有的样式表。

代码注释 8 处理 Alternate 样式表,也就是<link>标签的rel属性包含alternate,并且title属性有值,此时代码将变量sheet设置为null,这样后续这张样式表就添加不到 Active 样式表里面了。

代码注释 9 同样也是处理 Alternate 样式表,使其后续无法添加到 Active 样式表里面。

代码注释 10 将获取到的样式表添加到sheets变量,也就是 Active 样式表中。

代码注释 11 将收集的 Active 样式表和页面里面所有样式表返回出去。

5 小结

要获取 HTML 样式表里的 Active 样式表,首先就要获取页面里面的<style>标签节点对象和<link>标签节点对象。这些节点对象在<style>标签和<link>标签插入到 DOM 树时按照 TreeOrder 顺序存储在Style::Scope的实例变量m_styleSheetCandidateNodes中。

然后,当内部样式表或者外部样式表解析成功之后,会触发 Active 样式表的收集,收集过程就是遍历Style::Scope的实例变量m_styleSheetCandidateNodes,将<style>标签节点或者<link>标签节点关联的样式表收集到Style::Scope的实例变量m_activeStyleSheets中。


  1. MDN Alternative Style Sheet ↩︎

  2. MDN Introducing the CSS Cascade ↩︎

  3. MDN Specificity ↩︎

  4. MDN compareDocumentPosition ↩︎

  5. https://dom.spec.whatwg.org/#concept-tree-order ↩︎

  6. https://dom.spec.whatwg.org/#dom-node-document_position_following ↩︎

  7. MDN ProcessingInstruction ↩︎

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

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

相关文章

Windows下Mosquitto服务配置监听任何IP,搭配使用MQTTX

Mosquitto 默认只监听本地地址&#xff0c;想让它监听其他的IP和端口需要以下操作在mosquitto.conf文件最后加如下内容 allow_anonymous true listener 1883 0.0.0.0 在启动的时候只需要 .\mosquitto.exe -c .\mosquitto.conf -v 就可以了

2023年中国临床信息系统市场规模及细分市场结构分析[图]

临床信息系统(ClinicalInformationSystem&#xff09;&#xff0c;其主要目标是支持医院医护人员的临床活动&#xff0c;收集和处理病人的临床医疗信息&#xff0c;丰富和积累临床医学知识&#xff0c;并提供临床咨询、辅助诊疗、辅助临床决策。传统上&#xff0c;一些人把直接…

自动化测试面试题

常规问题 1、如何快速深入的了解移动互联网领域的应用 &#xff08;答案&#xff1a;看http协议 restful api知识 json加1分&#xff09; 2、对xx应用自己会花多久可以在业务上从入门到精通&#xff0c;说说自己的方法 &#xff08;答案&#xff1a;看api和数据库的加1分&…

解决远程视频会议卡顿问题,优化企业网络办公体验

视频会议、在线语音这种交互类的应用都是对网络链路质量有高要求的场景&#xff0c; 而造成视频会议卡顿的原因也是有多方面因素的&#xff0c;比如视频应用服务器或者终端的原因造成&#xff0c;网络当然也是其中很重要的一个因素&#xff0c;比如网络线路质量不稳定&#xff…

c语言文件操作详解:fgetc,fputc,fgets,fputs,fscanf,,fprintf,fread,fwrite的使用和区别

前言&#xff1a;在对于c语言的学习中&#xff0c;我们为了持续使用一些数据&#xff0c;为了让我们的数据可以在程序退出后仍然保存并且可以使用&#xff0c;我们引入了文件的概念和操作&#xff0c;本文旨在为大家分享在文件操作中常用的输入输出函数的使用方式和技巧&#x…

OJ练习第183题——移动机器人

移动机器人 力扣链接&#xff1a;2731. 移动机器人 题目描述 示例 官解思路 当两个机器人相撞时&#xff0c;它们会沿着原本相反的方向移动。由于机器人之间并没有任何区别&#xff0c;相撞可以看做是穿透&#xff0c;原本左边的机器人相撞后交换为右边的机器人&#xff0c…

短视频视频号矩阵系统源码独立部署开发对接

一、多账号矩阵管理功能&#xff08;基于api接口开发与没有官方接口开发的区别&#xff09; 基于API接口开发&#xff0c;可以通过调用官方提供的接口获取账号信息、创建新账号、更新账号设置等操作&#xff0c;实现自动化的账号管理绑定授权&#xff0c;通过相关的接口开发绑定…

Data security.隐私保护-多方安全计算技术基础

文章目录 Data security.隐私保护-多方安全计算技术基础一、多方安全计算的背景1.定义2.分类2.1不诚实参与方数量2.2敌手行为2.3敌手计算能力2.4输出可达性2.5计算模型2.6腐化策略&#xff08;攻击者确定攻破并控制参与方的策略&#xff09;2.7通信网络 3.设计方法3.1秘密共享&…

镜像仓库harbor安装部署

基础配置 systemctl stop firewalld && systemctl disable firewalld setenforce 0 sed -i s/SELINUXenforcing/SELINUXdisabled/ /etc/selinux/configharbor wget http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum install -y docker-ce docke…

设计模式 - 中介者模式

目录 一. 前言 二. 实现 三. 优缺点 一. 前言 中介者模式又叫调停模式&#xff0c;定义一个中介角色来封装一系列对象之间的交互&#xff0c;使原有对象之间的耦合松散&#xff0c;且可以独立地改变它们之间的交互。 中介者模式可以使对象之间的关系数量急剧减少&#xff0…

成功保研复旦大学!

Datawhale干货 作者&#xff1a;Kiren Wang知乎 个人背 景 背景&#xff1a;纯种三无——四非无rk1无强竞赛无中稿论文 学校&#xff1a;广东地区四非&#xff08;非深大&#xff09; 绩点&#xff1a;第五学期9/815&#xff0c;第六学期6/824&#xff08;学院排名&#xff09…

华为云云耀云服务器L实例评测 | 实例使用教学之高级使用:使用私有镜像、共享镜像创建 HECS

华为云云耀云服务器L实例评测 &#xff5c; 实例使用教学之高级使用&#xff1a;使用私有镜像、共享镜像创建 HECS 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀云服务…

Sentinel Dashboard 接入 Nacos 动态数据源 Zuul 接入 Sentinel 实战

背景 Sentinel Dashboard 默认将限流、熔断等规则保存在内存中&#xff0c;然后同步给连接 Dashboard 的客户端&#xff0c;客户端也是保存在内存中。 那么如果当 Sentinel Dashboard 异常重启&#xff0c;那么之前配置的规则将全部丢失&#xff0c;需要重新进行配置。 其中&a…

第1讲:MyBatis简介与入门

目录 了解MyBatis掌握MyBatis与Hibernate的区别安装使用MyBatis了解MyBatis的基本构架掌握以XML和Java使用MyBatis掌握MyBatis的XML配置文件的使用完成第一个MyBatis程序的编写 什么是MyBatis MyBatis 是一款优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级…

TortoiseSVN安装教程结合内网穿透实现公网提交文件到本地SVN服务器

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…

uni-app:实现滚动条效果

效果 前&#xff08;这里使用到了强制不换行white-space: nowrap;&#xff09; 后 核心代码 overflow: auto; 或 overflow-x: auto; /* 横向滚动条 */ 注&#xff1a;使用 overflow: auto 属性时&#xff0c;如果内容没有超出容器的宽度或高度&#xff0c;则不会显示对应的滚动…

HT8310:内置电荷泵升压音频功放

HT8310具有AB类和D类的自Y切换功能&#xff0c;在受到D类功放EMI干扰困扰时&#xff0c;可随时切换至AB类音频功放模式&#xff08;此时电荷泵升压功能关闭&#xff09;。HT8310内部固定28dB增益&#xff0c;内置的关断功能使待机电流Z小化&#xff0c;还集成了输出端过流保护、…

百度SEO 5个技巧:通过HelpLook让您的文章收录量实现飞跃!

通过百度SEO&#xff0c;可以让百度搜索引擎更好地了解您的网页。比如&#xff1a; 网页上有什么样的内容 网页的内容匹配何种关键词 网页内容对其他有类似问题的用户是否有帮助 当百度判断您的网页符合用户的搜索意图&#xff0c;就会提升您的网页的结果排名&#xff0c;从…

【密码学】Java实现DH函数时出现“Unsupported secret key algorithm: AES“错误

问题描述 jdk版本&#xff1a;8 使用DH和AES算法&#xff0c;实现密钥的交换和加密&#xff0c;测试时报错 java.security.NoSuchAlgorithmException: Unsupported secret key algorithm: AESat com.sun.crypto.provider.DHKeyAgreement.engineGenerateSecret(DHKeyAgreement…