文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

一些前端基础知识整理汇总

2024-04-02 19:55

关注

这篇文章主要介绍“一些前端基础知识整理汇总”,在日常操作中,相信很多人在一些前端基础知识整理汇总问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”一些前端基础知识整理汇总”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

react 生命周期

React v16.0前的生命周期

初始化(initialization)阶段

此阶段只有一个生命周期方法:constructor。

constructor()

用来做一些组件的初始化工作,如定义this.state的初始内容。如果不初始化  state 或不进行方法绑定,则不需要为 React  组件实现构造函数。为什么必须先调用super(props)?因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

class Checkbox extends React.Component {   constructor(props) {     // ? 这时候还不能使用this     super(props);     // ✅ 现在开始可以使用this     console.log(props);      // ✅ {}     console.log(this.props); // ✅ {}     this.state = {};    } }

为什么super要传 props?

把 props 传进 super 是必要的,这使得基类 React.Component 可以初始化 this.props。

然而,即便在调用 super() 时没有传入 props 参数,你依然能够在 render 和其它方法中访问 this.props。

其实是 React 在调用你的构造函数之后,马上又给实例设置了一遍 props。

// React 内部 class Component {   constructor(props) {     this.props = props; // 初始化 this.props     // ...   } }  // React 内部 const instance = new Button(props); instance.props = props; // 给实例设置 props  // Button类组件 class Button extends React.Component {   constructor(props) {     super(); // ? 我们忘了传入 props     console.log(props);      // ✅ {}     console.log(this.props); // ? undefined   } }

挂载(Mounting)阶段

此阶段生命周期方法:componentWillMount => render => componentDidMount

1. componentWillMount():

在组件挂载到DOM前调用,且只会被调用一次。

每一个子组件render之前立即调用;

在此方法调用this.setState不会引起组件重新渲染,也可以把写在这边的内容提前到constructor()中。

2. render(): class 组件唯一必须实现的方法

当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:

render() 函数应该为纯函数,这意味着在不修改组件 state  的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。不能在里面执行this.setState,会有改变组件状态的副作用。

3. componentDidMount

会在组件挂载后(插入 DOM 树中)立即调用, 且只会被调用一次。依赖于 DOM 节点的初始化应该放在这里。

render之后并不会立即调用,而是所有的子组件都render完之后才会调用。

更新(update)阶段

此阶段生命周期方法:componentWillReceiveProps => shouldComponentUpdate =>  componentWillUpdate => render => componentDidUpdate。

react组件更新机制

setState引起的state更新或父组件重新render引起的props更新,更新后的state和props相对之前无论是否有变化,都将引起子组件的重新render。

1. 父组件重新render

直接重新渲染。每当父组件重新render导致的重传props,子组件将直接跟着重新渲染,无论props是否有变化。可通过shouldComponentUpdate方法优化。

更新state再渲染。在componentWillReceiveProps方法中,将props转换成自己的state,调用 this.setState()  将不会引起第二次渲染。

因为componentWillReceiveProps中判断props是否变化了,若变化了,this.setState将引起state变化,从而引起render,此时就没必要再做第二次因重传props引起的render了,不然重复做一样的渲染了。

2. 自身setState

组件本身调用setState,无论state有没有变化。可通过shouldComponentUpdate方法优化。

生命周期分析

1. componentWillReceiveProps(nextProps)

此方法只调用于props引起的组件更新过程中,响应 Props 变化之后进行更新的唯一方式。

参数nextProps是父组件传给当前组件的新props。根据nextProps和this.props来判断重传的props是否改变,以及做相应的处理。

2. shouldComponentUpdate(nextProps, nextState)

根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是  state 每次发生变化组件都会重新渲染。

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。

首次渲染或使用 forceUpdate() 时不会调用该方法。

此方法可以将 this.props 与 nextProps 以及 this.state 与nextState  进行比较,返回true时当前组件将继续执行更新过程,返回false则跳过更新,以此可用来减少组件的不必要渲染,优化组件性能。

请注意,返回 false 并不会阻止子组件在 state 更改时重新渲染。

3. componentWillUpdate(nextProps, nextState)

此方法在调用render方法前执行,在这边可执行一些组件更新发生前的工作,一般较少用。

4. render

render同上

5. componentDidUpdate(prevProps, prevState)

此方法在组件更新后立即调用,可以操作组件更新的DOM。

prevProps和prevState这两个参数指的是组件更新前的props和state。

卸载阶段

此阶段只有一个生命周期方法:componentWillUnmount

componentWillUnmount

此方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清楚组件中使用的定时器,清楚componentDidMount中手动创建的DOM元素等,以避免引起内存泄漏。

componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

React v16.0 后的生命周期

React v16.0刚推出的时候,增加了一个componentDidCatch生命周期函数,这只是一个增量式修改,完全不影响原有生命周期函数;

React v16.3,引入了两个新的生命周期:getDerivedStateFromProps,getSnapshotBeforeUpdate,  废弃掉componentWillMount、componentWillReceiveProps 以及 componentWillUpdate  三个周期(直到React 17前还可以使用,只是会有一个警告)。

为什么要更改生命周期?

生命周期函数的更改是因为 16.3 采用了 Fiber 架构,在新的 Fiber 架构中,组件的更新分为了两个阶段:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. render phase:这个阶段决定究竟哪些组件会被更新。

  3. commit phase:这个阶段是 React 开始执行更新(比如插入,移动,删除节点)。

commit phase 的执行很快,但是真实 DOM 的更新很慢,所以 React 在更新的时候会暂停再恢复组件的更新以免长时间的阻塞浏览器,这就意味着  render phase 可能会被执行多次(因为有可能被打断再重新执行)。

这些生命周期都属于 render phase,render phase 可能被多次执行,所以要避免在 render phase  中的生命周期函数中引入副作用。在 16.3 之前的生命周期很容易引入副作用,所以 16.3 之后引入新的生命周期来限制开发者引入副作用。

getDerivedStateFromProps(nextProps, prevState)

React v16.3中,static  getDerivedStateFromProps只在组件创建和由父组件引发的更新中调用。如果不是由父组件引发,那么getDerivedStateFromProps也不会被调用,如自身setState引发或者forceUpdate引发。

在React v16.4中改正了这一点,static getDerivedStateFromProps会在调用 render  方法之前调用,并且在初始挂载及后续更新时都会被调用。

特点:

getDerivedStateFromProps前面要加上static保留字,声明为静态方法,不然会被react忽略掉。

getDerivedStateFromProps里面的this为undefined。

static静态方法只能Class来调用,而实例是不能调用,所以React  Class组件中,静态方法getDerivedStateFromProps无权访问Class实例的this,即this为undefined。

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate() 只会调用一次,在最近一次渲染输出(提交到 DOM  节点)之前调用,,所以在这个生命周期能够获取这一次更新前的 DOM 的信息。此生命周期的任何返回值将作为 componentDidUpdate() 的第三个参数  “snapshot” 参数传递, 否则componentDidUpdate的第三个参数将为 undefined。

应返回 snapshot 的值(或 null)。

错误处理

当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

它接收两个参数:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. error —— 抛出的错误。

  3. info —— 带有 componentStack key 的对象

生命周期比较

16.0 前生命周期

一些前端基础知识整理汇总

16.0 后生命周期:

一些前端基础知识整理汇总

参考:

浅析 React v16.3 新生命周期函数

react 16做了哪些更新

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. react作为一个ui库,将前端编程由传统的命令式编程转变为声明式编程,即所谓的数据驱动视图。如果直接更新真实dom,比如将生成的html直接采用innerHtml替换,会带来重绘重排之类的性能问题。为了尽量提高性能,React团队引入了虚拟dom,即采用js对象来描述dom树,通过对比前后两次的虚拟对象,来找到最小的dom操作(vdom  diff),以此提高性能。

  3. 上面提到的reactDom diff,在react 16之前,这个过程我们称之为stack  reconciler,它是一个递归的过程,在树很深的时候,单次diff时间过长会造成JS线程持续被占用,用户交互响应迟滞,页面渲染会出现明显的卡顿,这在现代前端是一个致命的问题。

  4. 所以为了解决这种问题,react 团队对整个架构进行了调整,引入了fiber架构,将以前的stack reconciler替换为fiber  reconciler。采用增量式渲染。引入了任务优先级(expiration)和requestIdleCallback的循环调度算法,简单来说就是将以前的一根筋diff更新,首先拆分成两个阶段:reconciliation与commit;第一个reconciliation阶段是可打断的,被拆分成一个个的小任务(fiber),在每一侦的渲染空闲期做小任务diff。然后是commit阶段,这个阶段是不拆分且不能打断的,将diff节点的effectTag一口气更新到页面上。

  5. 由于reconciliation是可以被打断的,且存在任务优先级的问题,所以会导致commit前的一些生命周期函数多次被执行,  如componentWillMount、componentWillReceiveProps 和  componetWillUpdate,但react官方已声明,在React17中将会移除这三个生命周期函数。

由于每次唤起更新是从根节点(RootFiber)开始,为了更好的节点复用与性能优化。在react中始终存workInprogressTree(future  vdom) 与 oldTree(current  vdom)两个链表,两个链表相互引用。这无形中又解决了另一个问题,当workInprogressTree生成报错时,这时也不会导致页面渲染崩溃,而只是更新失败,页面仍然还在。

React hooks原理

在React 16前,函数式组件不能拥有状态管理?因为16以前只有类组件有对应的实例,而16以后Fiber  架构的出现,让每一个节点都拥有对应的实例,也就拥有了保存状态的能力。

Hooks的本质就是闭包和两级链表。

hooks 链表

一个组件包含的hooks  以链表的形式存储在fiber节点的memoizedState属性上,currentHook链表就是当前正在遍历的fiber节点的。nextCurrentHook  就是即将被添加到正在遍历fiber节点的hooks的新链表。

let currentHook: Hook | null = null; let nextCurrentHook: Hook | null = null;  type Hooks = {   memoizedState: any, // 指向当前渲染节点 Fiber   baseState: any, // 初始化 initialState, 最新的state   baseUpdate: Update<any> | null,   // 当前需要更新的 Update ,每次更新完之后,会赋值上一个 update,方便 react 在渲染错误的边缘,数据回溯   queue: UpdateQueue<any> | null,// 可以让state变化的,即update或dispach产生的update   next: Hook | null, // link 到下一个 hooks }

state

其实state不是hooks独有的,类操作的setState也存在。

memoizedState,cursor 是存在哪里的?如何和每个函数组件一一对应的?

react 会生成一棵组件树(或Fiber 单链表),树中每个节点对应了一个组件,hooks  的数据就作为组件的一个信息,存储在这些节点上,伴随组件一起出生,一起死亡。

为什么只能在函数最外层调用 Hook?

memoizedState 是按 hook定义的顺序来放置数据的,如果 hook 顺序变化,memoizedState 并不会感知到。

自定义的 Hook 是如何影响使用它的函数组件的?

共享同一个 memoizedState,共享同一个顺序。

“Capture Value” 特性是如何产生的?

每一次 ReRender 的时候,都是重新去执行函数组件了,对于之前已经执行过的函数组件,并不会做任何操作。

react setState 异步更新

setState 实现原理

setState 通过一个队列机制来实现 state 更新,当执行 setState() 时,会将需要更新的 state 浅合并后放入  状态队列,而不会立即更新 state,队列机制可以高效的批量更新 state。如果不通过setState,直接修改this.state  的值,则不会放入状态队列,当下一次调用 setState 对状态队列进行合并时,之前对 this.state 的修改将会被忽略,造成无法预知的错误。

setState()有的同步有的异步?

在React中,  如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state  。

所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。

原因:  在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。

调用风险

当调用 setState 时,实际上是会执行 enqueueSetState 方法,并会对 partialState 及  _pendingStateQueue 队列进行合并操作,最终通过 enqueueUpdate 执行 state 更新。

而 performUpdateIfNecessary 获取 _pendingElement、  _pendingStateQueue、_pendingForceUpdate,并调用 reaciveComponent 和 updateComponent  来进行组件更新。

但,如果在 shouldComponentUpdate 或 componentWillUpdate 方法里调用 this.setState  方法,就会造成崩溃。

这是因为在 shouldComponentUpdate 或 componentWillUpdate 方法里调用 this.setState  时,this._pendingStateQueue!=null,则 performUpdateIfNecessary 方法就会调用  updateComponent 方法进行组件更新,而 updateComponent 方法又会调用  shouldComponentUpdate和componentWillUpdate 方法,因此造成循环调用,使得浏览器内存占满后崩溃。

React Fiber

掉帧:在页面元素很多,且需要频繁刷新的场景下,React 15 会出现掉帧的现象,其根本原因,是大量的同步计算任务阻塞了浏览器的 UI 渲染。

默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。

如果 JS 运算持续占用主线程,页面就没法得到及时的更新。

当我们调用setState更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新 UI,整个过程不能被打断。

如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象。

如何解决主线程长时间被 JS  运算?将JS运算切割为多个步骤,分批完成。在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间进行页面的渲染。等浏览器忙完之后,再继续之前未完成的任务。

React 15 及以下版本通过递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。

而Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的requestIdleCallback

React 框架内部的运作可以分为 3 层:

Fiber 表征reconciliation阶段所能拆分的最小工作单元,其实指的是一种链表树,它可以用一个纯 JS 对象来表示:

const fiber = {   stateNode: {},    // 节点实例   child: {},        // 子节点   sibling: {},      // 兄弟节点   return: {},       // 表示处理完成后返回结果所要合并的目标,通常指向父节点 };

Reconciler区别

从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情。

scheduling(调度)

scheduling(调度)是fiber reconciliation的一个过程,主要是进行任务分配,达到分段执行。任务的优先级有六种:

优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。

Fiber Reconciler 在执行过程中,会分为 2 个阶段:

阶段一可被打断的特性,让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率。

参考:

React Fiber 原理介绍

React Fiber

HOC 与render props区别

Render Props: 把将要包裹的组件作为props属性传入,然后容器组件调用这个属性,并向其传参。

实现方式:

1.通过props.children(props),props.children返回的是UI元素。 JSX 标签中的所有内容都会作为一个 children  prop 传递给 RenderProps组件。因为 RenderProps 将 {props.children} 渲染在一个

中,被传递的这些子组件最终都会出现在输出结果中。

// 定义 const RenderProps = props => <div>    {props.children(props)} </div>  // 调用 <RenderProps>     {() => <>Hello RenderProps</>} </RenderProps>

2.通过props中的任何函数, 自行定义传入内容

// 定义 const LoginForm = props => {   const flag = false;   const allProps = { flag, ...props };    if (flag) {     return <>{props.login(allProps)}</>   } else {     return <>{props.notLogin(allProps)}</>   } }  // 调用 <LoginForm   login={() => <h2>LOGIN</h2>}   noLogin={() => <h2>NOT LOGIN</h2>} />

优点:

1、支持ES6

2、不用担心props命名问题,在render函数中只取需要的state

3、不会产生无用的组件加深层级

4、render props模式的构建都是动态的,所有的改变都在render中触发,可以更好的利用组件内的生命周期。

HOC: 接受一个组件作为参数,返回一个新的组件的函数。

class Home extends React.Component {   // UI }  export default Connect()(Home);

高阶组件由于每次都会返回一个新的组件,对于react来说,这是不利于diff和状态复用的,所以高阶组件的包装不能在render  方法中进行,而只能像上面那样在组件声明时包裹,这样也就不利于动态传参。

优点:

1、支持ES6

2、复用性强,HOC为纯函数且返回值为组件,可以多层嵌套

3、支持传入多个参数,增强了适用范围

缺点:

1、当多个HOC一起使用时,无法直接判断子组件的props是哪个HOC负责传递的

2、多个组件嵌套,容易产生同样名称的props

3、HOC可能会产生许多无用的组件,加深了组件的层级

总的来说,render props其实和高阶组件类似,就是在puru component上增加state,响应react的生命周期。

React 通信

react的数据流是单向的,最常见的就是通过props由父组件向子组件传值。

1、找一个相同的父组件,既可以用props传递数据,也可以用context的方式来传递数据。

2、用一些全局机制去实现通信,比如redux等

3、发布订阅模式

react合成事件

React 合成事件(SyntheticEvent)是 React 模拟原生 DOM  事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。

为什么要使用合成事件?

1.进行浏览器兼容,实现更好的跨平台

React 采用的是顶层事件代理机制,能够保证冒泡一致性,可以跨浏览器执行。React  提供的合成事件用来抹平不同浏览器事件对象之间的差异,将不同平台事件模拟合成事件。

2.避免垃圾回收

事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象。即 React  事件对象不会被释放掉,而是存放进一个数组中,当事件触发,就从这个数组中弹出,避免频繁地去创建和销毁(垃圾回收)。

3.方便事件统一管理和事务机制

实现原理

在 React 中,“合成事件”会以事件委托方式绑定在 document 对象上,并在组件卸载(unmount)阶段自动销毁绑定的事件。

合成事件和原生事件

当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件;所以会先执行原生事件,然后处理 React 事件;最后真正执行  document 上挂载的事件。

合成事件和原生事件最好不要混用。原生事件中如果执行了stopPropagation方法,则会导致其他React事件失效。因为所有元素的事件将无法冒泡到document上,所有的  React 事件都将无法被注册。

合成事件的事件池

合成事件对象池,是 React 事件系统提供的一种性能优化方式。合成事件对象在事件池统一管理,不同类型的合成事件具有不同的事件池。

react 虚拟dom

什么是虚拟dom?

在 React 中,render 执行的结果得到的并不是真正的 DOM 节点,而是轻量级的 JavaScript 对象,我们称之为 virtual  DOM。它通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM节点。

虚拟 DOM 是 React 的一大亮点,具有batching(批处理) 和高效的 Diff 算法。batching 把所有的 DOM  操作搜集起来,一次性提交给真实的 DOM。diff 算法时间复杂度也从标准的的 Diff 算法的 O(n^3) 降到了 O(n)。

batching(批处理)

主要思想是,无论setState您在React事件处理程序或同步生命周期方法中进行多少次调用,它都将被批处理成一个更新, 最终只有一次重新渲染。

虚拟 DOM 与 原生 DOM

如果没有 Virtual DOM,就需要直接操作原生 DOM。在一个大型列表所有数据都变了的情况下,直接重置  innerHTML还算合理,但是,只有一行数据发生变化时,它也需要重置整个 innerHTML,这就造成了大量浪费。

innerHTML 和 Virtual DOM 的重绘性能消耗:

innerHTML: render html string + 重新创建所有 DOM 元素

Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新

Virtual DOM render + diff 显然比渲染 html 字符串要慢,但是它依然是纯 js 层面的计算,比起后面的 DOM  操作来说,依然便宜了太多。innerHTML 的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小相关,但 Virtual DOM 的计算量只有  js 计算和界面大小相关,DOM 操作是和数据的变动量相关。

一些前端基础知识整理汇总

虚拟 DOM 与 MVVM

相比起 React,其他 MVVM 系框架比如 Angular, Knockout , Vue ,Avalon 采用的都是数据绑定。通过  Directive/Binding 对象,观察数据变化并保留对实际 DOM 元素的引用,当有数据变化时进行对应的操作。MVVM 的变化检查是数据层面的,而  React 的检查是 DOM 结构层面的。

MVVM 的性能也根据变动检测的实现原理有所不同:Angular 依赖于脏检查;Knockout/Vue/Avalon 采用了依赖收集。

Angular 最不效率的地方在于任何小变动都有的和 watcher  数量相关的性能代价,当所有数据都变了的时候,Angular更有效。依赖收集在初始化和数据变化的时候都需要重新收集依赖,这个代价在小量更新的时候几乎可以忽略,但在数据量庞大的时候也会产生一定的消耗。

性能比较

在比较性能的时候,要分清楚初始渲染、小量数据更新、大量数据更新这些不同的场合。Virtual DOM、脏检查 MVVM、数据收集 MVVM  在不同场合各有不同的表现和不同的优化需求。

Virtual DOM 为了提升小量数据更新时的性能,也需要针对性的优化,比如 shouldComponentUpdate 或是 immutable  data。

diff 算法

传统 diff 算法通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3),其中 n 是树中节点的总数。O(n^3)  意味着如果要展示1000个节点,就要依次执行上十亿次的比较, 这是无法满足现代前端性能要求的。

diff 算法主要包括几个步骤:

diff 策略

React 通过制定大胆的diff策略,将diff算法复杂度从 O(n^3) 转换成 O(n) 。

tree diff(层级比较)

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

当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会进行进一步的比较。

这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

当出现节点跨层级移动时,并不会出现移动操作,而是以该节点为根节点的树被重新创建,这是一种影响 React 性能的操作,因此 React 官方建议不要进行  DOM 节点跨层级的操作。

注意:在开发组件时,保持稳定的 DOM 结构会有助于性能的提升。例如,可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。

component diff(组件比较)

对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间。因此 React  允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。

对于两个不同类型但结构相似的组件,不会比较二者的结构,而且替换整个组件的所有内容。不同类型的 component 是很少存在相似 DOM tree  的机会,因此这种极端因素很难在实现开发过程中造成重大影响的。

element diff (元素比较)

当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和  REMOVE_NODE(删除)。

<ul>   <li>Duke</li>   <li>Villanova</li> </ul>  <ul>   <li>Connecticut</li>   <li>Duke</li>   <li>Villanova</li> </ul>

React 并不会意识到应该保留<li>Duke</li>和<li>Villanova</li>,而是会重建每一个子元素,不会进行移动 DOM 操作。

key 优化

为了解决上述问题,React 引入了 key 属性, 对同一层级的同组子节点,添加唯一 key 进行区分。

当子元素拥有 key 时,React 使用 key  来匹配原有树上的子元素以及最新树上的子元素。如果有相同的节点,无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置。

react与vue区别

1. 监听数据变化的实现原理不同

Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。

React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。

2. 数据流不同

Vue1.0中可以实现两种双向绑定:父子组件之间props可以双向绑定;组件与DOM之间可以通过v-model双向绑定。

Vue2.x中父子组件之间不能双向绑定了(但是提供了一个语法糖自动帮你通过事件的方式修改)。

React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。

3. HoC和mixins

Vue组合不同功能的方式是通过mixin,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。

React组合不同功能的方式是通过HoC(高阶组件)。

4. 模板渲染方式的不同

模板的语法不同,React是通过JSX渲染模板, Vue是通过一种拓展的HTML语法进行渲染。

模板的原理不同,React通过原生JS实现模板中的常见语法,比如插值,条件,循环等。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如  v-if 。

举个例子,说明React的好处:react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在  this 上进行一次中转,所以我们import 一个组件完了之后,还需要在 components 中再声明下。

5. 渲染过程不同

Vue可以更快地计算出Virtual DOM的差异,这是由于它会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。

React当状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化。

6. 框架本质不同

Vue本质是MVVM框架,由MVC发展而来;

React是前端组件化框架,由后端组件化发展而来。

性能优化

1. 静态资源使用 CDN

CDN是一组分布在多个不同地理位置的 Web 服务器。当服务器离用户越远时,延迟越高。

2. 无阻塞

头部内联的样式和脚本会阻塞页面的渲染,样式放在头部并使用link方式引入,脚本放在尾部并使用异步方式加载

3. 压缩文件

压缩文件可以减少文件下载时间。

1.在 webpack 可以使用如下插件进行压缩:

JavaScript:UglifyPlugin

CSS :MiniCssExtractPlugin

HTML:HtmlWebpackPlugin

2.使用 gzip 压缩。通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能。

4. 图片优化

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 图片懒加载

  3. 响应式图片:浏览器根据屏幕大小自动加载合适的图片。

  4. 降低图片质量:方法有两种,一是通过 webpack 插件 image-webpack-loader,二是通过在线网站进行压缩。

5. 减少重绘重排

6. 使用 requestAnimationFrame 来实现视觉变化

window.requestAnimationFrame()  告诉浏览器&mdash;&mdash;你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

7. webpack 打包, 添加文件缓存

index.html 设置成 no-cache,这样每次请求的时候都会比对一下 index.html 文件有没变化,如果没变化就使用缓存,有变化就使用新的  index.html 文件。

其他所有文件一律使用长缓存,例如设置成缓存一年 maxAge: 1000 * 60 * 60 * 24 * 365。

前端代码使用 webpack 打包,根据文件内容生成对应的文件名,每次重新打包时只有内容发生了变化,文件名才会发生变化。

max-age: 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。在这个时间前,浏览器读取文件不会发出新请求,而是直接使用缓存。

指定 no-cache 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性

输入url后发生了什么

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. DNS域名解析;

  3. 建立TCP连接(三次握手);

  4. 发送HTTP请求;

  5. 服务器处理请求;

  6. 返回响应结果;

  7. 关闭TCP连接(四次握手);

  8. 浏览器解析HTML;

  9. 浏览器布局渲染;

1. DNS域名解析:拿到服务器ip

客户端收到你输入的域名地址后,它首先去找本地的hosts文件,检查在该文件中是否有相应的域名、IP对应关系,如果有,则向其IP地址发送请求,如果没有,再去找DNS服务器。

2. 建立TCP链接:客户端链接服务器

TCP提供了一种可靠、面向连接、字节流、传输层的服务。对于客户端与服务器的TCP链接,必然要说的就是『三次握手』。“3次握手”的作用就是双方都能明确自己和对方的收、发能力是正常的。

客户端发送一个带有SYN标志的数据包给服务端,服务端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息,最后客户端再回传一个带ACK标志的数据包,代表握手结束,连接成功。

SYN &mdash;&mdash; 用于初如化一个连接的序列号。

ACK &mdash;&mdash; 确认,使得确认号有效。

RST &mdash;&mdash; 重置连接。

FIN &mdash;&mdash; 该报文段的发送方已经结束向对方发送数据。

3. 发送HTTP请求

4. 服务器处理请求

5. 返回响应结果

6. 关闭TCP连接(需要4次握手)

为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。

关闭连接时,服务器收到对方的FIN报文时,仅仅表示客户端不再发送数据了但是还能接收数据,而服务器也未必全部数据都发送给客户端,所以服务器可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

7. 浏览器解析HTML

浏览器需要加载解析的不仅仅是HTML,还包括CSS、JS,以及还要加载图片、视频等其他媒体资源。

浏览器通过解析HTML,生成DOM树,解析CSS,生成CSSOM树,然后通过DOM树和CSSPOM树生成渲染树。渲染树与DOM树不同,渲染树中并没有head、display为none等不必显示的节点。

8. 浏览器渲染页面

根据渲染树布局,计算CSS样式,即每个节点在页面中的大小和位置等几何信息。HTML默认是流式布局的,CSS和js会打破这种布局,改变DOM的外观样式以及大小和位置。最后浏览器绘制各个节点,将页面展示给用户。

replaint:屏幕的一部分重画,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变。

reflow:意味着元素的几何尺寸变了,需要重新计算渲染树。

参考:

细说浏览器输入URL后发生了什么

浏览器输入 URL 后发生了什么?

前端路由

什么是路由

路由是用来跟后端服务器进行交互的一种方式,通过不同的路径请求不同的资源。

路由这概念最开始是在后端出现, 在前后端不分离的时期, 由后端来控制路由, 服务器接收客户端的请求,解析对应的url路径,  并返回对应的页面/资源。

前端路由

Ajax,全称 Asynchronous JavaScript And XML,是浏览器用来实现异步加载的一种技术方案。

在Ajax没有出现时期,大多数的网页都是通过直接返回  HTML,用户的每次更新操作都需要重新刷新页面,及其影响交互体验。为了解决这个问题,提出了Ajax(异步加载方案), 有了 Ajax  后,用户交互就不用每次都刷新页面。后来出现SPA单页应用。

SPA 中用户的交互是通过 JS 改变 HTML 内容来实现的,页面本身的 url 并没有变化,这导致了两个问题:

前端路由就是为了解决上述问题而出现的。

前端路由的实现方式

前端路由的实现实际上是检测 url 的变化,截获 url 地址,解析来匹配路由规则。有下面两种实现方式:

1. Hash模式

hash 就是指 url 后的 # 号以及后面的字符。 #后面 hash  值的变化,并不会导致浏览器向服务器发出请求,浏览器不发请求,也就不会刷新页面。

hash 的改变会触发 hashchange 事件,可以用onhashchange事件来监听hash值的改变。

// 监听hash变化,点击浏览器的前进后退会触发 window.onhashchange = function() { ... }  window.addEventListener('hashchange', function(event) { ...}, false);

2.History 模式

在 HTML5 之前,浏览器就已经有了 history 对象。但在早期的 history 中只能用于多页面的跳转:

history.go(-1);       // 后退一页 history.go(2);        // 前进两页 history.forward();    // 前进一页 history.back();       // 后退一页

在 HTML5 的规范中,history 新增了几个 API:

history.pushState();   // 向当前浏览器会话的历史堆栈中添加一个状态 history.replaceState();// 修改了当前的历史记录项(不是新建一个) history.state          // 返回一个表示历史堆栈顶部的状态的值

由于 history.pushState() 和 history.replaceState() 可以改变 url 同时,不会刷新页面,所以在 HTML5  中的 histroy 具备了实现前端路由的能力。

window对象提供了onpopstate事件来监听历史栈的改变,一旦历史栈信息发生改变, 便会触发该事件。

// 历史栈改变 window.onpopstate = function() { ... }

注意:pushState() 不会造成 hashchange 事件调用, 即使新的URL和之前的URL只是锚的数据不同。

两种模式对比

一些前端基础知识整理汇总

前端路由实践

vue-router/react-router 都是基于前端路由的原理实现的~

react-router常用的 history 有三种形式:

Babel Plugin与preset区别

Babel是代码转换器,比如将ES6转成ES5,或者将JSX转成JS等。借助Babel,开发者可以提前用上新的JS特性。

Plugin

实现Babel代码转换功能的核心,就是Babel插件(plugin)。Babel插件一般尽可能拆成小的力度,开发者可以按需引进,  既提高了性能,也提高了扩展性。比如对ES6转ES5的功能,Babel官方拆成了20+个插件。开发者想要体验ES6的箭头函数特性,那只需要引入transform-es2015-arrow-functions插件就可以,而不是加载ES6全家桶。

Preset

可以简单的把Babel Preset视为Babel Plugin的集合。想要将所有ES6的代码转成ES5,逐个插件引入的效率比较低下,  就可以采用Babel Preset。比如babel-preset-es2015就包含了所有跟ES6转换有关的插件。

Plugin与Preset执行顺序

可以同时使用多个Plugin和Preset,此时,它们的执行顺序非常重要。

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 先执行完所有Plugin,再执行Preset。

  3. 多个Plugin,按照声明次序顺序执行。

  4. 多个Preset,按照声明次序逆序执行。

比如.babelrc配置如下,那么执行的顺序为:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. Plugin:transform-react-jsx、transform-async-to-generator

  3. Preset:es2016、es2015

{   "presets": [      "es2015",     "es2016"       ],   "plugins": [      "transform-react-jsx",     "transform-async-to-generator"   ] }

怎样开发和部署前端代码

为了进一步提升网站性能,会把静态资源和动态网页分集群部署,静态资源会被部署到CDN节点上,网页中引用的资源也会变成对应的部署路径。当需要更新静态资源的时候,同时也会更新html中的引用。

如果同时改了页面结构和样式,也更新了静态资源对应的url地址,现在要发布代码上线,是先上线页面,还是先上线静态资源?

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 先部署页面,再部署资源:在二者部署的时间间隔内,如果有用户访问页面,就会在新的页面结构中加载旧的资源,并且把这个旧版本的资源当做新版本缓存起来,其结果就是:用户访问到了一个样式错乱的页面,除非手动刷新,否则在资源缓存过期之前,页面会一直执行错误。

  3. 先部署资源,再部署页面:在部署时间间隔之内,有旧版本资源本地缓存的用户访问网站,由于请求的页面是旧版本的,资源引用没有改变,浏览器将直接使用本地缓存,这种情况下页面展现正常;但没有本地缓存或者缓存过期的用户访问网站,就会出现旧版本页面加载新版本资源的情况,导致页面执行错误,但当页面完成部署,这部分用户再次访问页面又会恢复正常了。

这个奇葩问题,起源于资源的 覆盖式发布,用 待发布资源 覆盖 已发布资源,就有这种问题。

解决它也好办,就是实现  非覆盖式发布。用文件的摘要信息来对资源文件进行重命名,把摘要信息放到资源文件发布路径中,这样,内容有修改的资源就变成了一个新的文件发布到线上,不会覆盖已有的资源文件。

上线过程中,先全量部署静态资源,再灰度部署页面,整个问题就比较完美的解决了。

大公司的静态资源优化方案,基本上要实现这么几个东西:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 配置超长时间的本地缓存 &mdash;&mdash; 节省带宽,提高性能

  3. 采用内容摘要作为缓存更新依据 &mdash;&mdash; 精确的缓存控制

  4. 静态资源CDN部署 &mdash;&mdash; 优化网络请求

  5. 更改资源发布路径实现非覆盖式发布 &mdash;&mdash; 平滑升级

大数相加

function add(a, b){    const maxLength = Math.max(a.length, b.length);    a = a.padStart(maxLength, 0);    b = b.padStart(maxLength, 0);    let t = 0;    let f = 0;     let sum = "";     for (let i = maxLength - 1; i >= 0; i--) {       t = parseInt(a[i]) + parseInt(b[i]) + f;       f = Math.floor(t / 10);       sum = `${t % 10}${sum}`;    }    if (f === 1){       sum = "1" + sum;    }    return sum; }

斐波那契数列求和

function fib(n) {     if (n <= 0) {         return 0;     }     let n1 = 1;     let n2 = 1;     let sum = 1;     for(let i = 3; i <= n; i++) {         [n1, n2] = [n2, sum];         sum = n1 + n2;     }     return sum; };

到此,关于“一些前端基础知识整理汇总”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯