react如何遍历并比较_[前端进阶] 这可能是最通俗易懂的React 渲染原理及性能优化...

如今的前端,框架横行,出去面试问到框架是常有的事。

 我比较常用React, 这里就写了一篇 React 基础原理的内容, 面试基本上也就问这些, 分享给大家。

React 是什么

ae215e35668b45aa4ef925b22e680177.png

React是一个专注于构建用户界面的 Javascript Library.

一、React做了什么?

  • Virtual Dom模型

  • 生命周期管理

  • setState机制

  • Diff算法

  • React patch、事件系统

  • React的 Virtual Dom模型

virtual dom 实际上是对实际Dom的一个抽象,是一个js对象。react所有的表层操作实际上是在操作Virtual dom。

经过 Diff 算法会计算出 Virtual DOM 的差异,然后将这些差异进行实际的DOM操作更新页面。

二、React  总体架构

adfb6c4242dbb6f70a574b4a210e20d4.png三、几点要了解的知识

  • JSX 如何生成Element

  • Element 如何生成DOM

1JSX 如何生成Element

先看一个例子, Counter :

934541ca1d6967a608b7cddb7679a6a2.png

App.js 就做了一件事情,就是把 Counter 组件挂在 #root 上.

8faa1d2d42b24b8c0803fc77bbc439f0.png

Counter 组件里面定义了自己的 state, 这是个默认的 property ,还有一个 handleclick 事件和 一个 render 函数。

看到 render 这个函数里,竟然在 JS 里面写了 html ! 

这是一种 JSX 语法。React 为了方便 View 层组件化,承载了构建 html 结构化页面的职责。

这里也简单的举个例子:

295f6386f496ccc4de52d98ab4935ea5.png

将 html 语法直接加入到 javascript 代码中,再通过翻译器转换到纯 javascript 后由浏览器执行。

这里调用了 React 和 createElement 方法,这个方法就是用于创建虚拟元素 Virtual Dom 的。

19840f25991ceeb27691047341727618.png

React 把真实的 DOM 树转换成 Javascript 对象树,也就是 Virtual Dom。

每次数据更新后,重新计算 Virtual Dom ,并和上一次生成的 virtual dom 做对比,对发生变化的部分做批量更新。

而 React 是通过创建与更新虚拟元素 Virtual Element 来管理整个Virtual Dom 的。

 虚拟元素可以理解为真实元素的对应,它的构建与更新都是在内存中完成的,并不会真正渲染到 dom 中去。

回到我们的计数器 counter 组件:

c42abeda1e5f1685f179c23fbc47fa65.png

注意下 a 标签 createElement 的返回结果, 这里 CreateElement 只是做了简单的参数修正,返回一个 ReactElemet 实例对象。

Virtual element 彼此嵌套和混合,就得到了一颗 virtual dom 的树:

dc1b83f1950434f332507eab8e660728.png

2Element 如何生成DOM

15b176debbefae517fdb18ed7e719bbf.png

现在我们有了由 ReactElement 组成的 Virtual Dom 树,接下来我们要怎么我们构建好的 Virtual dom tree 渲染到真正的 DOM 里面呢?

这时可以利用 ReactDOM.render 方法,传入一个 reactElement 和一个 作为容器的 DOM 节点。

看进去 ReactDOM.render 的源码,里面有两个比较关键的步骤:

第一步是 instantiateReactComponent。

7f99310c7e45d5a9290e06020b968855.png

这个函数创建一个 ReactComponent 的实例并返回,也可以看到 ReactDOM.render 最后返回的也是这个实例。

4460d3c6b6ae2fd16aaa596fdc6b4c60.png

instantiateReactComponent 方法是初始化组件的入口函数,它通过判断 node 的类型来区分不同组件的入口。

  1. 当 node 为空的时候,初始化空组件。

  2. 当 node 为对象,类型 type 字段标记为是字符串,初始化 DOM 标签。否则初始化自定义组件。

  3. 当 node 为字符串或者数字时,初始化文本组件。

e7b628cc47927548f35a3e8245cbe071.png

虽然 Component 有多种类型,但是它们具有基本的数据结构:ReactComponent 类。

注意到这里的 setState, 这也是重点之一。

4c23af12e28acfe9e00894bab0ac91a8.png

创建了 Component 实例后,调用 component 的 mountComponent 方法,注意到这里是会被批量 mount 的,这样组件就开始进入渲染到 DOM 的流程了。

四、React生命周期

512fcf13eda4b0e4d73a46162cca862a.pngReact 组件基本由三个部分组成,

  1. 属性 props

  2. 状态 state

  3. 生命周期方法


React 组件可以接受参数props, 也有自身状态 state。
一旦接受到的参数 props 或自身状态 state 有所改变,React 组件就会执行相应的生命周期方法。React 生命周期的全局图526a2b81442cdaed71a6c3b56f5f339d.png

首次挂载组件时,按顺序执行

  1. componentWillMount、

  2. render

  3. componentDidMount

卸载组件时,执行 componentDidUnmount

当组件接收到更新状态,重新渲染组件时,执行

  1. componentWillReceiveProps

  2. shouldComponentUpdate

  3. componentWillUpdate

  4. render  

  5. componentDidUpdate

更新策略

8d5b4373290f38337c6a3bcd37368f90.png

通过 updateComponent 更新组件,首先判读上下文是否改变,前后元素是否一致,如果不一致且组件的 componentWillReceiveProps 存在,则执行。然后进行 state 的合并。

调用 shouldComponentUpdate 判断是否需要进行组件更新,如果存在 componentWillUpdate 则执行。

后面的流程跟 mountComponent 相似,这里就不赘述了。

五、setState机制

为避免篇幅过长,这部分可移步我的专题文章:

六、Diff算法

Diff算法用于计算出两个virtual dom的差异,是React中开销最大的地方。

传统diff算法通过循环递归对比差异,算法复杂度为 O(n3)。

React diff算法制定了三条策略,将算法复杂度从 O(n3)降低到O(n)。

  • 1. UI中的DOM节点跨节点的操作特别少,可以忽略不计。

  • 2. 拥有相同类的组件会拥有相似的DOM结构。拥有不同类的组件会生成不同的DOM结构。

  • 3. 同一层级的子节点,可以根据唯一的ID来区分。

1. Tree Diff

29a331c3bcd4f5127d2236a3f3dd0282.png

对于策略一,React 对树进行了分层比较,两棵树只会对同一层次的节点进行比较。

只会对相同层级的 DOM 节点进行比较,当发现节点已经不存在时,则该节点及其子节点会被完全删除,不会用于进一步的比较。

如果出现了 DOM 节点跨层级的移动操作。

如上图这样,A节点就会被直接销毁了。

Diif 的执行情况是:create A -> create C -> create D -> delete A

2. Element Diff
  1. 当节点处于同一层级时,diff 提供了 3 种节点操作:插入、移动和删除。

  2. 对于同一层的同组子节点添加唯一 key 进行区分。

1f61e9231d82feeed6250d6429b1e1c3.png

通过 diff 对比后,发现新旧集合的节点都是相同的节点,因此无需进行节点删除和创建,只需要将旧集合中节点的位置更新为新集合中节点的位置.

七、原理解析

几个概念

  • 对新集合中的节点进行循环遍历,新旧集合中是否存在相同节点

  • nextIndex: 新集合中当前节点的位置

  • lastIndex: 访问过的节点在旧集合中最右的位置(最大位置)

  • If (child._mountIndex < lastIndex)

对新集合中的节点进行循环遍历,通过 key 值判断,新旧集合中是否存在相同节点,如果存在,则进行移动操作。

在移动操作的过程中,有两个指针需要注意,

一个是 nextIndex,表示新集合中当前节点的位置,也就是遍历新集合时当前节点的坐标。

另一个是 lastIndex,表示访问过的节点在旧集合中最右的位置,

更新流程:

1

9eabf497a8a15c1963c45564004762b2.png

( 如果新集合中当前访问的节点比 lastIndex 大,证明当前访问节点在旧集合中比上一个节点的位置靠后,则该节点不会影响其他节点的位置,即不进行移动操作。只有当前访问节点比 lastIndex 小的时候,才需要进行移动操作。)

首先,我们开遍历新集合中的节点, 当前 lastIndex = 0, nextIndex = 0,拿到了 B.

此时在旧集合中也发现了 B,B 在旧集合中的 mountIndex 为 1 , 比当前 lastIndex 0 要大,不满足 child._mountIndex < lastIndex,对 B 不进行移动操作,更新 lastIndex = 1, 访问过的节点在旧集合中最右的位置,也就是 B 在旧集合中的位置,nextIndex++ 进入下一步。

2

829252b545c451c011f2eda06f6794fa.png

当前 lastIndex = 1, nextIndex = 1,拿到了 A,在旧集合中也发现了 A,A 在旧集合中的 mountIndex 为 0 , 比当前 lastIndex 1 要小,满足 child._mountIndex < lastIndex,对 A 进行移动操作,此时 lastIndex 依然 = 1, A 的 _mountIndex 更新为 nextIndex = 1, nextIndex++, 进入下一步.

3

deb216bf03878a1cba6918a241c2bbb7.png

这里,A 变成了蓝色,表示对 A 进行了移动操作。

当前 lastIndex = 1, nextIndex = 2,拿到了 D,在旧集合中也发现了 D,D 在旧集合中的 mountIndex 为 3 , 比当前 lastIndex 1 要大,不满足 child._mountIndex < lastIndex,不进行移动操作,此时 lastIndex = 3, D 的 _mountIndex 更新为 nextIndex = 2, nextIndex++, 进入下一步.

4

2076b0781913696cca95854f063fa1cc.png

当前 lastIndex = 3, nextIndex = 3,拿到了 C,在旧集合中也发现了 C,C 在旧集合中的 mountIndex 为 2 , 比当前 lastIndex 3 要小,满足 child._mountIndex < lastIndex,要进行移动,此时 lastIndex不变,为 3, C 的 _mountIndex 更新为 nextIndex = 3.

5

ff72f4ef7f3b54eadcddc686b8be2511.png

由于 C 已经是最后一个节点,因此 diff 操作完成.

这样最后,要进行移动操作的只有 A C。

648b2951b2895199f2f83518636a9532.png另一种情况

刚刚说的例子是新旧集合中都是相同节点但是位置不同。

那如果新集合中有新加入的节点且旧集合存在需要删除的节点,

那 diff 又是怎么进行的呢?比如:

374cf608cfee168f8cb9678b6552513f.png

1

9ecd1231199722b0a2e2d8fe4cdec0bf.png

首先,依旧,我们开遍历新集合中的节点, 当前 lastIndex = 0, nextIndex = 0,拿到了 B,此时在旧集合中也发现了 B,B 在旧集合中的 mountIndex 为 1 , 比当前 lastIndex 0 要大,不满足 child._mountIndex < lastIndex,对 B 不进行移动操作,更新 lastIndex = 1, 访问过的节点在旧集合中最右的位置,也就是 B 在旧集合中的位置,nextIndex++ 进入下一步。

2

2a47c7ec9724111f74258804620a81ca.png

当前 lastIndex = 1, nextIndex = 1,拿到了 E,发现旧集合中并不存在 E,此时创建新节点 E,nextIndex++,进入下一步

3

09fc06cfb9a1c9b77ed51b62c0c7a2d5.png

当前 lastIndex = 1, nextIndex = 2,拿到了 C,在旧集合中也发现了 C,C 在旧集合中的 mountIndex 为 2 , 比当前 lastIndex 1 要大,不满足 child._mountIndex < lastIndex,不进行移动,此时 lastIndex 更新为 2, nextIndex++ ,进入下一步

4

e17e3412b11bd1f06bd4c0192aabf609.png

当前 lastIndex = 2, nextIndex = 3,拿到了 A,在旧集合中也发现了 A,A 在旧集合中的 mountIndex 为 0 , 比当前 lastIndex 2 要小,不满足 child._mountIndex < lastIndex,进行移动,此时 lastIndex 不变, nextIndex++ ,进入下一步

5

e30fe0c82ca00bb4a954b8a91c83fdc6.png

当完成新集合中所有节点的差异化对比后,还需要对旧集合进行循环遍历,判断是否勋在新集合中没有但旧集合中存在的节点。

此时发现了 D 满足这样的情况,因此删除 D。

Diff 操作完成。

整个过程还是很繁琐的, 明白过程即可。

二、性能优化

由于react中性能主要耗费在于update阶段的diff算法,因此性能优化也主要针对diff算法。

1减少diff算法触发次数

减少diff算法触发次数实际上就是减少update流程的次数。

正常进入update流程有三种方式:

1、setState

setState机制在正常运行时,由于批更新策略,已经降低了update过程的触发次数。

因此,setState优化主要在于非批更新阶段中(timeout/Promise回调),减少setState的触发次数。

常见的业务场景即处理接口回调时,无论数据处理多么复杂,保证最后只调用一次setState。

2、父组件render

父组件的render必然会触发子组件进入update阶段(无论props是否更新)。此时最常用的优化方案即为shouldComponentUpdate方法。

最常见的方式为进行this.props和this.state的浅比较来判断组件是否需要更新。或者直接使用PureComponent,原理一致。

需要注意的是,父组件的render函数如果写的不规范,将会导致上述的策略失效。

// Bad case
// 每次父组件触发render 将导致传入的handleClick参数都是一个全新的匿名函数引用。
// 如果this.list 一直都是undefined,每次传入的默认值[]都是一个全新的Array。
// hitSlop的属性值每次render都会生成一个新对象
class Father extends Component {
onClick() {}
render() {
return <Child handleClick={() => this.onClick()} list={this.list || []} hitSlop={{ top: 10, left: 10}}/>
}
}
// Good case
// 在构造函数中绑定函数,给变量赋值
// render中用到的常量提取成模块变量或静态成员
const hitSlop = {top: 10, left: 10};
class Father extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.list = [];
}
onClick() {}
render() {
return <Child handleClick={this.onClick} list={this.list} hitSlop={hitSlop} />
}
}

3forceUpdate

forceUpdate方法调用后将会直接进入componentWillUpdate阶段,无法拦截,因此在实际项目中应该弃用。

其他优化策略

   1.  shouldComponentUpdate

     使用shouldComponentUpdate钩子,根据具体的业务状态,减少不必要的props变化导致的渲染。如一个不用于渲染的props导致的update。
另外, 也要尽量避免在shouldComponentUpdate 中做一些比较复杂的操作, 比如超大数据的pick操作等。

2. 合理设计state,不需要渲染的state,尽量使用实例成员变量。

     不需要渲染的 props,合理使用 context机制,或公共模块(比如一个单例服务)变量来替换。

2正确使用 diff算法
  • 不使用跨层级移动节点的操作。

  • 对于条件渲染多个节点时,尽量采用隐藏等方式切换节点,而不是替换节点。

  • 尽量避免将后面的子节点移动到前面的操作,当节点数量较多时,会产生一定的性能问题。

648b2951b2895199f2f83518636a9532.png看个具体的例子

6f6c63b8265f22d4ef773db1135740f9.png

这时一个 List 组件,里面有标题,包含 ListItem 子组件的members列表,和一个按钮,绑定了一个 onclick 事件.

然后我加了一个插件,可以显示出各个组件的渲染情况。

现在我们来点击改变标题, 看看会发生些什么。

2b5b693f90d0027ea90d4d269a209df4.png

奇怪的事情发生了,为什么我只改了标题,  为什么不相关的 ListItem 组件也会重新渲染呢?

我们可以回到组件生命周期看看为什么。

cf6f5b597ff4007d29b527cf8e6306cf.png

还记得这个组件更新的生命周期流程图嘛,这里的重点在于这个 shouldComponentUpdate。

只有这个方法返回 true 的时候,才会进行更新组件的操作。我们进步一来看看源码。

可以看到这里,原来如果组件没有定义 shouldComponentUpdate 方法,也是默认认为需要更新的。

当然,我们的 ListItem 组件是没有定义这个 shouldComponentUpdate 方法的。

然后我们使用PureComponent :

f8f97da6c71905bd1bc891f681ca07d0.png

1f59d442a90c7d83575353b2f07a4c07.png

0f0018991e2ee7bc48bd45f7a6313aa4.png

d84c3e8208215730c2b4c815c9bdc2e6.png

其原理为重新实现了 shouldComponentUpdate 生命周期方法,让当前传入的 props 和 state 之前做浅比较,如果返回 false ,那么组件就不会更新了。

这里也放上一张官网的例图:

fa6a2edbb9b9ab7e24d587de38fa8205.png

根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。

如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq)。

如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;

如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。

相似的APi还有React.memo:

0371ed56cdd46581b81e609eb6b09c1e.png

回到组件

再次回到我们的组件中, 这次点击按钮, 把第二条数据换掉:

7000ec90f48a0164e3b9a6b4cbf23bee.png

奇怪的事情发生了,为什么我只改了第二个 listItem, 还是全部 10 个都重新渲染了呢?

原因在于 shallow compare , 浅比较。

前面说到,我们不能直接修改 this.state 的值,所以我们把

this.state.members 拷贝出来再修改第二个人的信息。

很明显,因为对象的比较是引用地址,显然是不相等的。

因此 shoudComponentUpdate 方法都返回了 false, 组件就进行了更新。

那么我们怎么能避免这种情况的发生呢?

其中一个方法是做深比较,但是如果对象或数组层级比较深和复制,那么这个代价就太昂贵了。

我们就可以用到 Immutable.js 来解决这个问题,进一步提高组件的渲染性能。

 Immutable Data 就是一旦被创建,就是不能再更改的数据。

0a8125209c528b902cd07d4f1400aba4.png

首先,我们定义了一个 Immutable 的 List 对象,List 对应于原生 JS 的 Array,对 Immutable 对象进行修改、添加或删除操作,都会返回一个新的 Immutable 对象,所以这里 bbb 不等于 aaa。

但是同时为了避免深拷贝吧所有节点都复制一遍带来的性能消耗,Immutable 使用了结构共享,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其他节点则进行共享。

结果也是我们预期的那样。

84bdfdb00c72449c37a3b509a11ef21b.png

648b2951b2895199f2f83518636a9532.png性能分析

2aa1a3cbc8ffbeb115a75fe4e758790c.png

用好火焰图, 该优化的时候再优化。

八、Hooks  及其后续更新

为避免篇幅过长,这部分可移步我的专题文章:

最后

e9957675305f7e95c2be58a1ff5f8eff.png

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

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

相关文章

基于git的工作流程

本文针对的是追求极致、快速的产品响应团队的。以下的观点和内容都是围绕这个主题&#xff0c;暂时不涉及个人学习和团队学习。 在说工作流程之间&#xff0c;想说一下我们平常工作中遇到的一些困惑或者说现象 在一个团队里&#xff0c;同时有好多事件要解决。有的是产品迭代&a…

c 自定义实现string类 clear_CC++语言15|类的继承和派生实现代码重用、扩充

在C中&#xff0c;继承是一个对象自动获取其父对象的所有属性和行为的过程。通过继承&#xff0c;您可以重用&#xff0c;扩展或修改在其他类中定义的属性和行为。通过继承&#xff0c;可以实现函数重写以及多态。在C中&#xff0c;继承另一个类的成员的类称为派生类&#xff0…

超几何分布_常见概率分布

离散分布退化分布 若r.v. 只取常数值c&#xff0c;即 ,这时分布函数为&#xff1a; 把这种分布称为退化分布或者单点分布。伯努利分布 在一次实验中&#xff0c;事件A出现的概率为 ,不出现的概率为 ,若用 记事件A出现的次数&#xff0c;则 仅取值0或1&#xff0c;相应的…

struts学习

Structs2配置文件概述&#xff1a; <constant name"" value""></constant>详解&#xff1a;&#xff08;两个看不见的value都是设置true/false&#xff09; package的相关使用&#xff1a; &#xff08;name是包名。action相当于以前的servl…

里怎么做页眉页脚_这年头县城里在家做的电商利润怎么样

这年头县城里在家做的电商利润怎么样 mcfg6ek这年头县城里在家做的电商利润怎么样 通过上面的信息大家是否已经对多用户商城有什么优势有所了解了呢&#xff0c;如果还想了解更多多用户商城的信息&#xff0c;站进行查看咨询哦。店系统怎么进行推广。店系统的推广方式介绍我们都…

计算机如果算积分排名,超级电脑预测英超积分榜:蓝军守住第4 曼联无缘欧冠...

还有6天的时间&#xff0c;2019-20赛季的英超联赛就要重启了。虽然冠军的悬念已经不大&#xff0c;但欧冠席位以及降级名额仍有很大的变数&#xff0c;这让外界仍无比期待接下来的比赛。今日&#xff0c;超级计算机对剩余的比赛做了预测&#xff0c;并算出了最终的积分榜&#…

环形队列出队的元素怎么输出出来_队列:队列在线程池等有限资源池中的应用...

我们知道&#xff0c;CPU资源是有限的&#xff0c;任务的处理速度与线程个数并不是线性正相关的。相反&#xff0c;过多的线程反而会导致CPU频繁切换&#xff0c;处理性能下降。所以&#xff0c;线程池的大小一般都是综合考虑要处理任务的特点和硬件环境&#xff0c;来事先设置…

英语答题测试的软件叫什么,英语做题软件哪个好 有答案解析的英语做题软件分享...

对于一些即将参与重要英语考试的考生来说&#xff0c;每天刷题练习肯定是不可避免的事情&#xff0c;但如果你想要更高效的刷题&#xff0c;让自己的刷题时间更有价值&#xff0c;那就来看看推荐给你的这些英语做题软件。类型&#xff1a;学习 语言&#xff1a;简体中文星级&am…

raid卡组不同raid_RAID磁盘阵列是如何运作的?

本文编辑&#xff1a;意哥专业指导&#xff1a;葵芳一凡 RAID是英文Redundant Array of Independent Disks的缩写&#xff0c;中文简称为独立冗余磁盘阵列。简单的说&#xff0c;RAID是一种把多块独立的硬盘&#xff08;物理硬盘&#xff09;按不同的方式组合起来形成一个硬盘组…

java 析构函数_C++虚函数

码字不易&#xff0c;欢迎给个赞&#xff01;C虚函数是多态性实现的重要方式&#xff0c;当某个虚函数通过指针或者引用调用时&#xff0c;编译器产生的代码直到运行时才能确定到底调用哪个版本的函数。被调用的函数是与绑定到指针或者引用上的对象的动态类型相匹配的那个。因此…

象过河软件试用版_比肩许银川蒋川王天一,象棋软件下出神一样的残局,看完叹为观止...

古语有云&#xff0c;残局&#xff0c;是一盘棋的命脉所在。这对于现代象棋而言&#xff0c;也是如此&#xff0c;君不见&#xff0c;许银川有着鬼魅残功&#xff0c;蒋川有着魔衣血刀&#xff0c;王天一有着天外飞仙&#xff0c;都是一等一的残棋神器。而被誉为棋界战斗力最为…

python爬取小说出现乱码_详解Python解决抓取内容乱码问题(decode和encode解码)

一、乱码问题描述 经常在爬虫或者一些操作的时候&#xff0c;经常会出现中文乱码等问题&#xff0c;如下原因是源网页编码和爬取下来后的编码格式不一致 二、利用encode与decode解决乱码问题 字符串在Python内部的表示是unicode编码&#xff0c;在做编码转换时&#xff0c;通常…

北方股份无人驾驶矿卡_踏歌智行完成B轮2亿元融资,无人驾驶矿山赛道爆发在即...

作者 / 李笠10 月 30 日&#xff0c;矿山无人驾驶运输的领军企业踏歌智行完成了 2 亿元 B 轮融资。这是无人驾驶矿山赛道迄今为止最大的一笔融资。据悉&#xff0c;本轮融资由前海母基金和宝通投资共同领投&#xff0c;清研资本、蓝焱资本等跟投。这也是踏歌智行继 2019 年连续…

计算机视觉子方向,计算机视觉方向简介 | 人脸识别中的活体检测算法综述

原标题&#xff1a;计算机视觉方向简介 | 人脸识别中的活体检测算法综述本文转载自“SIGAI人工智能学习与实践平台”(ID&#xff1a;SIGAICN)导言1. 什么是活体检测&#xff1f;判断捕捉到的人脸是真实人脸&#xff0c;还是伪造的人脸攻击(如&#xff1a;彩色纸张打印人脸图&am…

黑客们的故事(连载三) 因为好奇

上期说到的肯和丹尼斯在黑客文化中的高知名度&#xff0c;不仅是因为他们开发出的操作系统和编程语言&#xff0c;更在于他们行为所体现出的价值观&#xff1a;求知、探索&#xff0c;以及追求极致。这些是黑客精神的精髓——虽然黑客们贯彻这些精神时的方式并不总是合理合法的…

css3禅密花园叫什么名字_新生儿起名:2021元旦出生男孩叫什么名字

2021年元旦节又是新的一年开始&#xff0c;在这辞旧迎新的日子里出生的男孩&#xff0c;都带有极好的寓意兆头&#xff0c;父母们如果能顺借此时机&#xff0c;取一个具有出生纪念意义的名字&#xff0c;那必然能让男孩的成长有着不同与众的历程。2021元旦出生男孩叫什么名字比…

10个让人很舒服的沟通技巧

转载于:https://www.cnblogs.com/yymn/p/4605237.html

idea将远程代码更新合并到本地_idea 本地调试远程服务器代码

基本原理本机和远程主机的两个 VM 之间使用 Debug 协议通过 Socket 通信&#xff0c;传递调试指令和调试信息。 被调试程序的远程虚拟机&#xff1a;作为 Debug 服务端&#xff0c;监听 Debug 调试指令。jdwp是Java Debug Wire Protocol的缩写。 调试程序的本地虚拟机&#xff…

安卓手机网页 字体垂直方向对齐_初学Excel办公软件字体对齐调整

今天我们依然讲解Excel办公软件的字体对齐调整&#xff0c;上一节我们讲过在菜单栏的上方就可以看到十个功能&#xff0c;然后我们根据这十个功能的调解我们所需要的文字对齐&#xff0c;或者是居中对齐&#xff0c;顶端对齐等等。还有另外一种操作方法&#xff0c;也是方便快捷…

英伟达_如何超越英伟达?

从AI热潮中率先获益的英伟达&#xff0c;其GPU参数和性能频繁出现在不少AI芯片发布会的对比图中。这是在缺乏AI芯片衡量的标准时&#xff0c;新的AI芯片证明自己实力的不错方式。不过&#xff0c;声称性能超越英伟达GPU的创新AI芯片不少&#xff0c;但想要超越英伟达非常困难。…