当前位置: 首页 > news >正文

Reading:Deep dive into the OnPush change detection strategy in Angular

原文连接:IndepthApp

今天深入阅读并总结Angualr中onPush更新策略。

1. 两种策略 & whats Lview?

Angular 实现了两种策略来控制各个组件级别的更改检测行为。这些策略定义为DefaultOnPush

被定义为枚举:

export enum ChangeDetectionStrategy {OnPush = 0,Default = 1
}

Angular使用这些策略来确定在运行父组件的变更检测时,是否应该检查子组件。为组件定义的策略会影响所有子指令,因为它们作为检查宿主组件的一部分而被检查。定义的策略无法在运行时被覆盖。

默认策略,内部称为CheckAlways,意味着除非视图被显式分离,否则组件会进行常规的自动变更检测。而被称为OnPush策略,内部称为CheckOnce,意味着只有在组件被标记为脏时才会进行变更检测。Angular实现了自动将组件标记为脏的机制。在需要时,可以使用ChangeDetectorRef上暴露的markForCheck方法手动将组件标记为脏。

当我们使用@Component()装饰器定义策略时,Angular的编译器会通过defineComponent函数将其记录在组件的定义中。例如,对于像这样的组件:

@Component({selector: 'a-op',template: `I am OnPush component`,changeDetection: ChangeDetectionStrategy.OnPush
})
export class AOpComponent {}

编译器生成的定义如下所示:

原文:When Angular instantiates a component, it’s using this definition to set a corresponding flag on the LView instance that represents the component’s view

对应的拓展理解:

LView(即 "Logical View")是 Angular 中的一个概念,表示组件视图的逻辑表示。它是一个内部数据结构,用于跟踪和管理组件的状态、变更检测以及视图的渲染。

LView 包含了组件视图的各种信息,例如组件的模板、绑定的数据、指令和组件实例等。它是一个类似于树状结构的数据结构,用于表示组件视图的层次结构。

在 Angular 的内部实现中,LView 是由一系列的数组和索引组成的。这些数组存储了组件视图中各个部分的状态和数据。通过使用这些数组和索引,Angular 可以高效地访问和操作组件视图的各个部分。

总而言之,LView 是 Angular 中用于表示组件视图的逻辑结构的概念。它是一个内部数据结构,用于跟踪和管理组件的状态、变更检测以及视图的渲染。通过使用 LView,Angular 可以有效地处理和操作组件视图的各个方面。

我理解为:当Angular实例化一个组件时,它使用这个定义来在表示组件视图的LView实例上设置相应的标志。这句话的意思是,Angular在创建组件的实例时,会根据组件的定义,在LView实例上设置一个标志。这个标志可能用来表示组件的状态或其他信息,以便Angular在后续的变更检测和渲染过程中使用。这个标志可以帮助Angular追踪和管理组件的状态,并根据需要执行相应的操作。 

Angular 实现了两种策略来控制各个组件级别的更改检测行为。这些策略定义为DefaultOnPush

export enum ChangeDetectionStrategy {OnPush = 0,Default = 1
}

Angular 使用这些策略来确定在对父组件运行更改检测时是否应检查子组件。为组件定义的策略会影响所有子指令,因为它们是作为检查主机组件的一部分进行检查的。定义的策略不能在运行时被覆盖。

默认策略(内部称为CheckAlways)意味着对组件进行定期自动更改检测,除非显式分离视图。所谓的OnPush策略(内部称为 策略)CheckOnce意味着除非组件被标记为脏,否则将跳过更改检测。Angular 实现了自动将组件标记为脏的机制。需要时,可以使用 上公开的markForCheck方法手动将组件标记为脏ChangeDetectorRef

当我们使用装饰器定义策略时,Angular 的编译器通过DefineComponent@Component()函数将其记录在组件的定义中。例如,对于这样的组件:

@Component({
  selector: 'a-op',
  template: `I am OnPush component`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AOpComponent {}

编译器生成的定义如下所示:

当 Angular 实例化组件时,它使用此定义在表示组件视图的实例上设置相应的标志:LView

这意味着LView为此组件创建的所有实例都将设置CheckAlways或Dirty标志。对于该OnPush策略,该Dirty标志将在第一次更改检测通过后自动取消设置。

当 Angular 确定是否应检查组件时,会在刷新视图LView函数内检查设置的标志:

function refreshComponent(hostLView, componentHostIdx) {// Only attached components that are CheckAlways or OnPush and dirty // should be refreshedif (viewAttachedToChangeDetector(componentView)) {const tView = componentView[TVIEW];if (componentView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {refreshView(tView, componentView, tView.template, componentView[CONTEXT]);} else if (componentView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) {// Only attached components that are CheckAlways // or OnPush and dirty should be refreshedrefreshContainsDirtyView(componentView);}}
}

 

默认策略

默认更改检测策略意味着如果检查了其父组件,则将始终检查子组件。该规则的唯一例外是,如果您像这样分离子组件的更改检测器:

@Component({
  selector: 'a-op',
  template: `I am OnPush component`
})
export class AOpComponent {constructor(private cdRef: ChangeDetectorRef) {
    cdRef.detach();
  }
}

请注意,我特别突出显示了有关正在检查的父组件的部分。如果未选中父组件,则 Angular 不会为子组件运行更改检测,即使它使用默认的更改检测策略也是如此。这是因为 Angular 在检查其父组件时会运行对子组件的检查。

Angular 不会强制开发人员执行任何工作流程来检测组件状态何时发生更改,这就是为什么默认行为是始终检查组件的原因。强制工作流程的一个示例是通过绑定传递的对象不变性@Input。这就是策略所用的内容OnPush,我们接下来将探讨它。

这里我们有一个由两个组件组成的简单层次结构:

@Component({
  selector: 'a-op',
  template: `
    <button (click)="changeName()">Change name</button>
    <b-op [user]="user"></b-op>
  `,
})
export class AOpComponent {user = { name: 'A' };changeName() {this.user.name = 'B';
  }
}@Component({
  selector: 'b-op',
  template: `<span>User name: {{user.name}}</span>`,
})
export class BOpComponent {
  @Input() user;
}

当我们单击按钮时,Angular 会运行一个事件处理程序,我们在其中更新user.name. 作为运行后续更改检测循环的一部分,将检查子B组件并更新屏幕:

虽然对对象的引用user没有改变,但它的内部已经发生了变化,但我们仍然可以看到屏幕上呈现的新名称。这就是为什么默认行为是检查所有组件。如果没有 Angular 的对象不变性限制,就无法知道输入是否已更改并导致组件状态更新。

OnPush 又名 CheckOnce 策略

虽然 Angular 不会强制我们实现对象不变性(immutability,但它为我们提供了一种机制,可以将组件声明为具有不可变输入,以减少检查组件的次数。这种机制以变化检测策略的形式出现OnPush,是一种非常常见的优化技术。在内部,此策略称为CheckOnce,因为它意味着跳过组件的更改检测,直到将其标记为脏,然后检查一次,然后再次跳过。可以使用方法自动或手动将组件标记为脏markForCheck

让我们以上面的示例为例,声明组件OnPush的更改检测策略B

@Component({...})
export class AOpComponent {user = { name: 'A' };changeName() {this.user.name = 'B';}
}@Component({selector: 'b-op',template: `<span>User name: {{user.name}}</span>`,changeDetection: ChangeDetectionStrategy.OnPush
})
export class BOpComponent {@Input() user;
}

当我们运行应用程序时,Angular 不再选择 a 中的更改user.name

您可以看到该B组件在引导期间仍然检查一次 - 它呈现初始名称A。但在后续的更改检测运行期间不会检查它,因此单击按钮时您不会看到名称从 更改为AB发生这种情况是因为对传递user给组件的对象的B引用@Input没有改变。

在我们了解组件被标记为脏的不同方式之前,这里列出了 Angular 用于测试行为 OnPush的不同场景:

should skip OnPush components in update mode when they are not dirty
should not check OnPush components in update mode when parent events occur
should check OnPush components on initialization
should call doCheck even when OnPush components are not dirty
should check OnPush components in update mode when inputs change
should check OnPush components in update mode when component events occur
should check parent OnPush components in update mode when child events occur
should check parent OnPush components when child directive on a template emits event
  1. 当 OnPush 组件不是脏的时候,在更新模式下应该跳过它们。
  2. 当父级事件发生时,不应该在更新模式下检查 OnPush 组件。
  3. 在初始化时应该检查 OnPush 组件。
  4. 即使 OnPush 组件不是脏的,也应该调用 doCheck 方法。
  5. 当输入发生变化时,应该在更新模式下检查 OnPush 组件。
  6. 当组件事件发生时,应该在更新模式下检查 OnPush 组件。
  7. 当子级事件发生时,应该在更新模式下检查父级 OnPush 组件。
  8. 当模板上的子指令触发事件时,应该检查父级 OnPush 组件。

最后一批测试场景确保在以下场景中发生将组件标记为脏的自动过程:

  • 参考@Input已更改
  • 接收到组件本身触发的绑定事件

现在让我们来探讨一下这些。

@Input() 绑定

在大多数情况下,只有当子组件的输入发生变化时,我们才需要检查它。对于其输入仅通过绑定来的纯表示组件来说尤其如此。

我们举一个之前的例子:

@Component({...})
export class AOpComponent {user = { name: 'A' };changeName() {this.user.name = 'B';}
}@Component({selector: 'b-op',template: `<span>User name: {{user.name}}</span>`,changeDetection: ChangeDetectionStrategy.OnPush
})
export class BOpComponent {@Input() user;
}

正如我们在上面看到的,当我们单击按钮并在回调中更改名称时,新名称不会在屏幕上更新。这是因为 Angular 对输入参数执行浅比较,并且对对象的引用user没有改变。直接改变对象不会产生新的引用,也不会自动将组件标记为脏。

我们必须更改userAngular 对象的引用来检测绑定的差异@Input。如果我们创建一个新实例user而不是改变现有实例,一切都会按预期工作:

@Component({...})
export class AOpComponent {user = { name: 'A' };changeName() {this.user = {...this.user,name: 'B',}}
}

您可以使用递归Object.freeze实现轻松地强制对象的不变性:

export function deepFreeze(object) {const propNames = Object.getOwnPropertyNames(object);for (const name of propNames) {const value = object[name];if (value && typeof value === 'object') {deepFreeze(value);
    }
  }return Object.freeze(object);
}

拓展: 如何理解对象的不可变性?angualr中这个概念的意义是什么?

在计算机科学中,不可变性(immutability)是指一个对象在创建之后不能被修改或者说是不可变的。在 JavaScript 中,对象的不可变性可以通过 Object.freeze 方法来实现。当一个对象被冻结之后,它的属性不能被修改、添加或删除。

在 Angular 中,不可变性是一个非常重要的概念,因为它可以帮助 Angular 更好地实现变更检测和优化。当 Angular 检测到一个组件或者指令的输入属性发生变化时,它会比较新旧两个对象的引用,如果引用发生了变化,则认为输入属性发生了变化,需要重新渲染组件或者指令。

如果输入属性是可变的对象,则可能会在对象内部发生变化,但是对象本身的引用并没有变化,这时 Angular 就无法检测到变化。为了解决这个问题,可以使用不可变对象来表示输入属性,这样即使对象内部发生了变化,但是对象本身的引用并没有变化,Angular 仍然可以检测到变化并进行相应的优化。

因此,在 Angular 中使用不可变对象可以帮助提高应用的性能和稳定性。同时,也可以使代码更加易于理解和维护,因为不可变对象的状态是固定的,不会随时发生变化。

绑定 UI 事件

所有本机事件在当前组件上触发时,都会将该组件的所有祖先(直到根组件)标记为脏。假设事件可能会触发组件树中的更改。Angular 不知道父母是否会改变。这就是为什么 Angular 总是在事件触发后检查每个祖先组件。

想象一下你有一个像这样的组件树层次结构OnPush

AppComponent
    HeaderComponent
    ContentComponent
        TodoListComponent
            TodoComponent

如果我们在TodoComponent模板内附加一个事件监听器:

@Component({
  selector: 'todo',
  template: `
    <button (click)="edit()">Edit todo</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoComponent {edit() {}
}

Angular 在运行事件处理程序之前将所有祖先组件标记为脏:

因此,组件的层次结构被标记为检查一次,如下所示:

Root Component -> LViewFlags.Dirty
     |
    ...
     |
 ContentComponent -> LViewFlags.Dirty
     |
     |
 TodoListComponent  -> LViewFlags.Dirty
     |
     |
 TodoComponent (event triggered here) -> markViewDirty() -> LViewFlags.Dirty

TodoComponent在下一个更改检测周期中,Angular 将检查的祖先组件的整个树:

AppComponent (checked)
    HeaderComponent
    ContentComponent  (checked)
        TodosComponent  (checked)
            TodoComponent (checked)

请注意,HeaderComponent未检查 ,因为它不是 的祖先TodoComponent

Manually marking components as dirty

Let’s come back to the example where we changed the reference to the user object when updating the name. This enabled Angular to pick up the change and mark B component as dirty automatically. Suppose we want to update the name but don’t want to change the reference. In that case, we can mark the component as dirty manually.

For that we can inject changeDetectorRef and use its method markForCheck to indicate for Angular that this component needs to be checked:

@Component({...})
export class BOpComponent {@Input() user;constructor(private cd: ChangeDetectorRef) {}someMethodWhichDetectsAndUpdate() {this.cd.markForCheck();}
}

What can we use for someMethodWhichDetectsAndUpdate? The NgDoCheck hook is a very good candidate. It's executed before Angular will run change detection for the component but during the check of the parent component. This is where we'll put the logic to compare values and manually mark component as dirty when detecting the change.

The design decision to run NgDoCheck hook even if a component is OnPush often causes confusion. But that’s intentional and there's no inconsistency if you know that it's run as part of the parent component check. Keep in mind that ngDoCheck is triggered only for top-most child component. If the component has children, and Angular doesn't check this component, ngDoCheck is not triggered for them.

Don’t use ngDoCheck to log the checking of the component. Instead, use the accessor function inside the template like this {{ logCheck() }}.

So let’s introduce our custom comparison logic inside the NgDoCheck hook and mark the component dirty when we detect the change:

@Component({...})
export class AOpComponent {...}@Component({selector: 'b-op',template: `<span>User name: {{user.name}}</span>`,changeDetection: ChangeDetectionStrategy.OnPush
})
export class BOpComponent {@Input() user;previousUserName = '';constructor(private cd: ChangeDetectorRef) {}ngDoCheck() {if (this.user.name !== this.previousUserName) {this.cd.markForCheck();this.previousUserName = this.user.name;}}
}

Remember that markForCheck neither triggers nor guarantees change detection run. See the chapter on manual control for more details.

这段话主要讲解了在 Angular 中如何手动标记组件为脏的过程。当我们想要更新一个组件的状态,但是又不想改变组件的引用时,我们可以通过手动标记组件为脏来告诉 Angular 这个组件的状态已经发生了变化,需要重新渲染。

为了实现手动标记组件为脏的过程,我们可以使用 ChangeDetectorRef 中的 markForCheck 方法。这个方法可以告诉 Angular 这个组件需要被检查,从而触发变更检测。

通常情况下,我们可以在组件的 NgDoCheck 生命周期钩子中进行比较操作,并在检测到变化时手动标记组件为脏。需要注意的是,即使一个组件采用了 OnPush 变更检测策略,它的 NgDoCheck 钩子仍然会被执行。这是因为 NgDoCheck 钩子是在父级组件检测时执行的,而不是在当前组件检测时执行的。

值得注意的是,手动标记组件为脏并不会立即触发变更检测,它只是告诉 Angular 这个组件需要被检查。如果想要立即触发变更检测,可以使用 ApplicationRef.tick 方法。

总而言之,手动标记组件为脏是 Angular 中非常重要的一个概念。通过手动标记组件为脏,我们可以告诉 Angular 这个组件的状态已经发生了变化,需要重新渲染。这对于实现高效的变更检测和优化非常重要。

ApplicationRef.tick 在angular 9 中还存在吗

是的,ApplicationRef.tick 方法在 Angular 9 中仍然存在。它是 Angular 应用程序中的一个重要方法,用于触发变更检测并强制更新视图。

在 Angular 中,当组件或指令的输入属性发生变化时,Angular 会自动触发变更检测,并更新视图。但是,有些情况下,可能需要手动触发变更检测,比如在异步操作完成后需要更新视图的情况下。这时可以使用 ApplicationRef.tick 方法来触发变更检测。

ApplicationRef.tick 方法会遍历整个应用程序,并检查每个组件的变更检测状态。如果有组件的状态发生了变化,就会触发变更检测并更新视图。需要注意的是,ApplicationRef.tick 方法会遍历整个应用程序,因此在性能敏感的场景中需要慎重使用。

总之,在 Angular 9 中,ApplicationRef.tick 方法仍然是一个非常重要的方法,用于触发变更检测并强制更新视图。

import { Component, NgModule, ApplicationRef } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';@Component({selector: 'app-root',template: `<h1>{{ title }}</h1><button (click)="updateTitle()">Update Title</button>`,
})
export class AppComponent {title = 'Hello World!';constructor(private appRef: ApplicationRef) {}updateTitle() {this.title = 'Hello Angular!';this.appRef.tick(); // 触发变更检测}
}@NgModule({imports: [BrowserModule],declarations: [AppComponent],bootstrap: [AppComponent],
})
export class AppModule {}

ApplicationRef 是 Angular 框架中的一个服务,它代表整个应用程序的引用。它提供了一些方法和功能,用于管理应用程序的生命周期和变更检测。

ApplicationRef 的主要职责包括:

1. 启动应用程序:ApplicationRef 可以通过其 `bootstrap` 方法来启动应用程序,并将根组件加载到浏览器中。

2. 注册根组件:通过 ApplicationRef 的 `attachView` 方法,可以将根组件的视图附加到应用程序的视图层次结构中。

3. 执行变更检测:ApplicationRef 提供了 `tick` 方法,用于触发变更检测。当应用程序中的数据发生变化时,Angular 会自动执行变更检测,但有时可能需要手动触发变更检测。

4. 管理应用程序的生命周期:ApplicationRef 提供了一些方法,例如 `run` 和 `isStable`,用于管理应用程序的生命周期。它可以检测应用程序是否稳定(即没有异步操作正在进行),并在应用程序稳定时执行一些操作。

总之,ApplicationRef 是 Angular 中非常重要的一个服务,它提供了管理应用程序生命周期和触发变更检测的功能。通过 ApplicationRef,我们可以控制和管理整个应用程序的运行。

相关文章:

Reading:Deep dive into the OnPush change detection strategy in Angular

原文连接&#xff1a;IndepthApp 今天深入阅读并总结Angualr中onPush更新策略。 1. 两种策略 & whats Lview&#xff1f; Angular 实现了两种策略来控制各个组件级别的更改检测行为。这些策略定义为Default和OnPush&#xff1a; 被定义为枚举&#xff1a; export enum…...

野火霸天虎 STM32F407 学习笔记_1 stm32介绍;调试方法介绍

STM32入门——基于野火 F407 霸天虎课程学习 前言 博主开始探索嵌入式以来&#xff0c;其实很早就开始玩 stm32 了。但是学了一段时间之后总是感觉还是很没有头绪&#xff0c;不知道在学什么。前前后后分别尝试了江协科技、正点原子、野火霸天虎三次 stm32 的课程学习。江协科…...

@reduxjs/toolkit配置react-redux解决createStore或将在未来被淘汰警告

通常 我们用redux都需要通过 createStore 但目前 你去用它 基本都会被划线 甚至有点厉害的的编辑器 他会直接告诉你这个东西基本快被弃用了 这个应该大家都知道 最好不要用已经被明确未来或弃用的语法 因为一旦弃用这个系统就需要维护 而且说 一般会被淘汰的语法 本身也就是有…...

致敬1024天前的自己

今早打开手机就收到了来自CSDN的消息&#xff0c;哦&#xff0c;距离我发表第一篇技术博客已经过去1024个日夜了。 我第一次发技术博客是我大二做完我第一个网站时写的。因为网站需要上线服务器&#xff0c;涉及到不少linux相关的知识&#xff0c;我在自学的过程中走了不少弯路…...

〖Python网络爬虫实战㊱〗- JavaScript 网站加密和混淆

订阅:新手可以订阅我的其他专栏。免费阶段订阅量1000+python项目实战 Python编程基础教程系列(零基础小白搬砖逆袭) 说明:本专栏持续更新中,订阅本专栏前必读关于专栏〖Python网络爬虫实战〗转为付费专栏的订阅说明作者:爱吃饼干的小白鼠。Python领域优质创作者,2022年度…...

基于单片机设计的电子柜锁

一、前言 随着现代社会的不断发展&#xff0c;电子柜锁的应用越来越广泛。传统的机械柜锁存在一些不便之处&#xff0c;例如钥匙容易丢失、密码容易泄露等问题。设计一款基于单片机的电子柜锁系统成为了一个有趣而有意义的项目。 该电子柜锁系统通过电磁锁作为柜锁的开关&…...

Windows安装tensorflow-gpu=1.14.0CUDA=10.0cuDNN=7.4 (多版本CUDA共存)

文章目录 0. 前置说明1. 查看版本对应关系2. 安装 cuda3. 安装 cudnn4. 添加环境变量5. 安装 tensorflow 0. 前置说明 本机&#xff08;Windows 11&#xff09;已安装CUDA 11.7 使用命令查看显卡驱动&#xff1a; nvidia-smi这里显示的CUDA Version: 11.7说明支持安装11.7版本…...

CodeWhisperer 初体验

文章作者&#xff1a;1颗 orange 最近用了一个叫 CodeWhisperer 的插件&#xff0c;这个软件对于来说开发人员&#xff0c;插件有好多实用的功能&#xff0c;编码更高效&#xff0c;代码质量也提升了很多。 CodeWhisperer 简介 CodeWhisperer 是亚⻢逊出品的一款基于机器学习…...

HNU-算法设计与分析-讨论课1

第一次小班讨论 &#xff08;以组为单位&#xff0c;每组一题&#xff0c;每组人人参与、合理分工&#xff0c;ppt中标记分工&#xff0c;尽量都有代码演示&#xff09; 1.算法分析题 2-10、2-15(要求&#xff1a;有ppt&#xff08;可代码演示&#xff09;) 2.算法实现题 2-4、…...

java连接zookeeper

API ZooKeeper官方提供了Java API&#xff0c;可以通过Java代码来连接zookeeper服务进行操作。可以连接、创建节点、获取节点数据、监听节点变化等操作&#xff0c;具体有以下几个重要的类&#xff1a; ZooKeeper&#xff1a;ZooKeeper类是Java API的核心类&#xff0c;用于与…...

2023-11-01 node.js-electron-环境配置-记录

摘要: 2023-11-01 node.js-electron-环境配置-记录 相关文档: Node.js Build cross-platform desktop apps with JavaScript, HTML, and CSS | Electron node.js的国内源 - Python技术站 node.js 下载地址: https://nodejs.org/dist/v20.9.0/ 说明: 最好使用最新版本当前我使…...

使用 ElementUI 组件构建 Window 桌面应用探索与实践(WinForm)

零、实现原理与应用案例设计 1、原理 基础实例 Demo 可以参照以下这篇博文&#xff0c; 基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用-CSDN博客文章浏览阅读291次。基于 .Net CEF 库&#xff0c;能够使用 Vue 等前端技术栈构建 Windows 窗体应用https://blog.c…...

使用C++构建安全队列

1 背景 STL的容器不是线程安全的&#xff0c;我们经常会有需求要求数据结构线程安全&#xff0c;比如写生产者消费者模型的时候&#xff0c;就要求队列线程安全。利用std::queue和C线程标准库的一些组件&#xff08;mutex&#xff0c;condition_variable&#xff09;&#xff…...

EasyFlash移植使用- 关于单片机 BootLoader和APP均使用的情况

目前&#xff0c;我的STM32单片机&#xff0c;需要在BootLoader和APP均移植使用EasyFlash&#xff0c;用于参数管理和IAP升级使用。 但是由于Flash和RAM限制&#xff0c;减少Flash占用&#xff0c;我规划如下&#xff1a; BootLoader中移植EasyFlash使用旧版本&#xff0c;因为…...

python捕获异常和scapy模块的利用

Python捕获异常 ​ 当程序运行时&#xff0c;因为遇到未知的错误而导致中止运行&#xff0c;便会出现Traceback 消息&#xff0c;打印异常。异常即是一个事件&#xff0c;该事件会在程序执行过程中发生&#xff0c;影响程序的正常执行。一般情况下&#xff0c;在Python 无法正…...

CSS+Javascript+Html日历控件

最近&#xff0c;因需要用HTMLJAVASCRIPTCSS实现了一个日历控件&#xff0c;效果如下&#xff1a; 单击上月、下月进行日历切换。当前日期在日历中变颜色标注显示。还是老老套路、老方法&#xff0c;分HMLCSSJAVASCRIPT三部分代码。 一、html代码 <h1>学习计划</h1…...

让企业的数据用起来,数据中台=数据治理?

加gzh“大数据食铁兽”&#xff0c;了解更多数据治理信息。 先说结论&#xff1a;数据中台是数据管理/治理的工具之一&#xff0c;数据治理是3分技术7分管理及运营。 数据中台的定义&#xff1a; 狭义的数据中台指在企业内部通过对数据半成品、算法、模型、工具等能力的积累&a…...

【人工智能Ⅰ】5-粒子群算法

【人工智能Ⅰ】5-粒子群算法 文章目录 【人工智能Ⅰ】5-粒子群算法5.1 粒子群算法PSO基本思想5.2 PSO介绍5.3 PSO求最优解5.4 算法流程5.5 PSO构成要素群体大小m权重因子最大速度Vm停止准则粒子空间的初始化领域的拓扑结构 5.6 PSO应用5.7 PSO改进动态调整惯性权重收缩因子法 5…...

软考高项-49个项目管理过程输入、输出和工具技术表

知识领域数量五大过程组启动规划执行监控收尾整体7制订项目章程制订项目管理计划指导与管理项目工作 管理项目知识 监控项目工作 实施整体变更控制 结束项目或阶段范围6规划范围管理 收集需求 定义范围 创建WBS 确认范围 控制范围 进度6规划进度管理 定义活动...

《C和指针》(7)函数

问题 具有空函数体的函数可以作为存根使用。你如何对这类函数进行修改&#xff0c;使其更加有用&#xff1f; 答&#xff1a;当存根函数被调用时&#xff0c;打印一条消息&#xff0c;显示它已被调用&#xff0c;或者也可以打印作为参数传递给它的值。 .如果在一个函数的声明中…...

四、Sqoop 导入表数据子集

作者&#xff1a;IvanCodes 日期&#xff1a;2025年6月4日 专栏&#xff1a;Sqoop教程 当不需要将关系型数据库中的整个表一次性导入&#xff0c;而是只需要表中的一部分数据时&#xff0c;Sqoop 提供了多种方式来实现数据子集的导入。这通常通过过滤条件或选择特定列来完成。 …...

vue3 vite.config.js 引入bem.scss文件报错

[sass] Can’t find stylesheet to import. ╷ 1 │ use “/bem.scss” as *; │ ^^^^^^^^^^^^^^^^^^^^^^ ╵ src\App.vue 1:1 root stylesheet 分析 我们遇到了一个在Vue3项目中使用Vite时&#xff0c;在vite.config.js中引入bem.scss文件报错的问题。错误信息指出在App.vue…...

数据库管理与高可用-MySQL高可用

目录 #1.1什么是MySQL高可用 1.1.1MySQL主主复制keepalivedhaproxy的高可用 1.1.2优势 #2.1MySQL主主复制keepalivedhaproxy的实验案例 1.1什么是MySQL高可用 MySQL 高可用是指通过技术手段确保 MySQL 数据库在面临硬件故障、软件错误、网络中断、人为误操作等异常情况时&…...

设备驱动与文件系统:05 文件使用磁盘的实现

从文件使用磁盘的实现逻辑分享 我们现在讲第30讲&#xff0c;内容是文件使用磁盘的具体实现&#xff0c;也就是相关代码是如何编写的。上一节我们探讨了如何从字符流位置算出盘块号&#xff0c;这是文件操作磁盘的核心。而这节课&#xff0c;我们将深入研究实现这一核心功能的…...

嵌入式面试提纲

一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责把数据帧(Frame)在相邻节点间传输。 网络层(Internet Layer) 最典型的是 IP 协议 (IPv4/IPv6)。负责 路由选路、分片与重组。 其他:ICMP(Ping、目的不可达等)…...

大话软工笔记—需求调研的准备

需求调研前需做好充分的准备&#xff1a; 1. 背景资料来源 可以通过企业官网、宣传资料、人员沟通获取客户企业信息。 ​​​​​​​2. 背景资料汇总 根据获得的信息做出一份背景分析报告&#xff0c;主要包含以下内容&#xff1a; 2.1 企业基本信息 企业发展愿景&#…...

实验四:图像灰度处理

实验四 图像处理实验报告 目录 实验目的实验内容 原理描述Verilog HDL设计源代码Testbench仿真代码及仿真结果XDC文件配置下板测试 实验体会实验照片 实验目的 在实验三的基础上&#xff0c;将图片显示在显示器上&#xff0c;并进行灰度处理。 实验内容 原理描述 1. 图片的…...

TM中,return new TransactionManagerImpl(raf, fc);为什么返回是new了一个新的实例

这是一个典型的 构造器注入 封装资源的用法 &#x1f9e9; 代码片段 return new TransactionManagerImpl(raf, fc);✅ 简单解释&#xff1a; 这行代码的意思是&#xff1a; 使用已经打开的 RandomAccessFile 和 FileChannel&#xff0c;创建并返回一个新的 TransactionManag…...

卫星接收天线G/T值怎么计算?附G/T计算excel表格链接

我们在进行无线通信链路设计时&#xff0c;都会涉及接收天线最重要的参数G/T。今天&#xff0c;咱们就来聊聊G/T值该怎么计算&#xff0c;计算过程中有哪些需要留意的地方&#xff0c;以及当你看到产品说明书中标注了G/T指标&#xff0c;还需要进一步了解哪些信息。 G/T的含义 …...

基于Scala实现Flink的三种基本时间窗口操作

目录 代码结构 代码解析 (1) 主程序入口 (2) 窗口联结&#xff08;Window Join&#xff09; (3) 间隔联结&#xff08;Interval Join&#xff09; (4) 窗口同组联结&#xff08;CoGroup&#xff09; (5) 执行任务 代码优化 (1) 时间戳分配 (2) 窗口大小 (3) 输出格式…...