面试题整理

面试题整理

1.清除浮动的几种方式,各自的优缺点,推荐使用哪种?

清除浮动的空元素:

​ 方法:在浮动元素的父元素末尾插入一个空的块级元素,并给该元素应用 clear: both; 样式。

​ 优点:简单易行,兼容性好,不会影响其他元素的布局。

​ 缺点:需要额外插入一个空元素,增加了结构层次。

使用伪元素清除浮动(::after伪元素法):

​ 方法:在浮动元素的父元素上使用 ::after 伪元素,并给其应用 clear: both; 样式。

​ 优点:不需要添加额外的空元素,结构更加清晰。

​ 缺点:低版本浏览器(如IE8及以下版本)对伪元素支持不完全。

使用overflow属性清除浮动:

​ 方法:在浮动元素的父元素上设置 overflow: auto 或 overflow: hidden。

​ 优点:简单易行,不需要添加额外的元素。

​ 缺点:可能会产生滚动条,隐藏超出容器范围的内容。

推荐

推荐使用的清除浮动方式取决于具体的需求和兼容性要求。通常情况下,我会首选使用清除浮动的空元素方法或伪元素法,因为它们简单、兼容性较好,并且不会产生额外的副作用。如果对兼容性要求较高,可以考虑使用overflow属性清除浮动。

2.redux中同步action与异步action最大的区别是什么?

​ 同步action用于表示简单的状态变化,立即触发执行;而异步action用于处理含有异步操作的情况,通过中间件的支持,可以在异步操作完成后触发对应的同步action,从而更新应用的状态。

3.如何通过JS判断一个数组?

​ Array.isArray()、object.definedpropoty.toString().call()、instanceof()、forEach()、Array.isPrototypeof()

4.如何监听div的尺寸变化?

​ 要监听div元素的尺寸变化,可以使用ResizeObserver API 或者 MutationObserver API。

使用ResizeObserver API:

​ 步骤:

​ 创建一个ResizeObserver实例,将其绑定到要监听尺寸变化的div元素上。

在回调函数中处理尺寸变化事件。

使用MutationObserver API:

​ 步骤:

​ 创建一个MutationObserver实例,将其绑定到要监听尺寸变化的div元素上

​ 在回调函数中检查具体的属性变化类型,并处理尺寸变化事件。

Tip

​ 无论是使用ResizeObserver还是MutationObserver,都能捕获到div元素尺寸的变化,并在回调函数中执行相应的操作。其中,ResizeObserver会更专注于监测元素尺寸的变化,而MutationObserver可用于监测更多种类型的 DOM 变化,如属性变化、添加/删除子节点等。根据具体需求,选择适当的 API。

5.React的props.children使用map函数来遍历会收到异常显示,为什么?应该 如何遍历?

​ Props.children在接收参数的时候不一定是一个数组

​ 有三种可能 :
​ 当前组件没有子节点数据类型就是undefined,
​ 有一个子节点数据类型就是object 。
​ 有多个子节点的时候才会是array ,只有在多个节点的时候才可以直接调用map
​ react资深提供了一个react.children.map()方法,可以安全遍历子节点对象。

6.React组件之间如何通信?

父传子:

​ 可以通过定一个状态,通过动态传递的方法,将状态传递给子组件,子组件通过props来接收这个状态

子传父:

​ 父组件通过创建一个带有参数的事件对象,然后传递给子组件,子组件通过props接收,调用这个事件,将需要传递的数据放到参数中,子组件就可以接收数据

兄弟通信:

​ 按照上述两个方法,让父组件在中间做一个组件通信的桥梁,先进行子传父的操作,在进行父传子

隔代传递

可以通过创建一个context对象,通过Provider进行传递,其中的value值就是传递的数据,通过consumer进行接收

Redux

还可以使用redux进行一个数据的集中式管理,通过改变一个组件上的状态,store就会进行相应的改变,从而所有的组件都会改变数据

7.说说你对受控组件和非受控组件的理解?应用场景?

受控组件

就是dom元素的值都存储在组件的state中,受到组件的约束

非受控组件

状态值通过dom节点来获取,例如通过ref来获取input输入框中的数据,一般在进行表单提交的时候使用

8.说说你对fiber架构的理解?解决了什么问题?

​ js引擎和页面渲染引擎是互斥的,一个被执行,另一个就会被高高挂起,如果组件过大,那么js引擎会一直占用主线程,而渲染引擎会一直被挂起,所以对于一些用户互动、动画不能及时得到处理,可能会出现卡顿情况,因此就有了fiber架构,主要做以下事情:

​ (1)为任务提供了优先级,优先级高的可以中断优先级低的,然后等优先级高的执行完,继续执行优先级低的。

​ (2)增添了异步任务,在浏览器空闲的时候执行。

​ (3)dom diff形成了链表,一个dom对应两个队列,两个fiber,目的是为了找到被中断的任务然后继续执行。

9.说说react diff的原理是什么?

​ 总共分为三个层级:
​ tree层级:不对跨层级操作进行优化,只对相同层级进行比较
​ component层级:如果是同一个类的组件,那么继续执行dom diff算法,如果不是,那么就删除该组件下的所有子节点。
​ element层级:为每一个节点提供了唯一标识key,可以进行增删改操作

10.说说你对redux中间件的理解?常用的中间件有哪些?实现原理?

​ redux中间件是用于在dispatch过程,分发action进行拦截处理

​ 常见的中间件有:redux-thunk、redux-logger

​ 实现原理:将所有的redux中间件放到一个数组中然后嵌套执行,最后执行 dispatch,在执行到内部的时候,会有两个方法分别是setState和dispatch 根据一个判断然后执行对应的操作。

11.路由原理 history 和 hash 两种路由方式的特点?

history 路由方式:

​ 特点:

​ 基于浏览器的history API。

​ 使用pushState()和replaceState()方法实现路由导航。

​ 路由被表示为真实的URL路径。

​ 可以在不刷新整个页面的情况下更新路由。

hash 路由方式:

​ 特点:

​ 基于URL的哈希部分(#)实现路由。

​ 路由被表示为URL的一部分,位于#后面。

​ 可以在不刷新整个页面的情况下更新路由。

12.什么是强缓存和协商缓存?

强缓存:

​ 强缓存通过在响应头中设置有效的缓存策略,使得浏览器在一定时间内直接从缓存中获取资源,而不发送请求到服务器。

​ 特点:

​ 浏览器不向服务器发送请求,直接从本地缓存中获取资源。

​ 缓存时间由响应头中的Cache-Control和Expires字段设置。

​ 如果缓存仍然有效,浏览器将直接使用缓存的资源。

​ 常见的Cache-Control值有max-age(指定秒数的最大缓存时间)和public(允许代理服务器缓存)。

协商缓存:

​ 协商缓存通过在请求头中提供上一次获取资源时的相关信息,与服务器进行验证来判断资源是否需要更新。

​ 特点:

​ 浏览器发送GET请求到服务器,并携带缓存验证信息。

​ 服务器通过验证信息判断资源是否有更新。

​ 如果资源没有更新,服务器返回304 Not Modified响应,浏览器从缓存中获取资源。

​ 如果资源有更新,服务器返回新的资源,并且带有新的缓存验证信息。

​ 常见的缓存验证字段有If-Modified-Since(上次修改时间)和If-None-Match(资源的唯一标识,例如 ETag)。

13.什么是面向对象编程及面向过程编程,它们的异同和优缺点?

概念

面向对象编程是一种以对象为基础的编程方式,将数据和相关的操作封装在一起形成对象。在面向对象编程中,程序的主要构建单位是类,通过创建类的实例对象来完成具体的功能,通过对对象的封装、继承和多态等特性来实现程序的灵活性和复用性。

面向过程编程是一种以过程为基础的编程方式,将程序分解为一系列的步骤或函数,通过顺序执行这些步骤来完成任务。在面向过程编程中,数据和函数是分离的,函数根据数据进行处理。面向过程编程主要关注解决问题所需要的步骤和操作,侧重于算法和数据结构。

异同:

抽象层次不同

数据和功能的封装不同

面向对象编程支持继承和多态,可以通过继承扩展和重用代码

而面向过程编程没有继承的概念,需要通过拷贝和粘贴来实现代码复用

面向对象编程的优点包括:

可重用性和可维护性高、抽象能力强、更好的模块化和组织能力

面向过程编程的优点包括:

性能较好、学习和理解成本较低、结构简单

面向对象编程的缺点包括:

学习曲线较陡、运行时开销较大

面向过程编程的缺点包括:

可维护性较差、不易于扩展和重用

14.说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?

​ 与 @reduxjs/toolkit 相对应的是 react-redux,它是一个用于将 Redux 与 React 应用集成的库。react-redux 提供了一些 React 组件和 hooks,例如 Provider 和 connect,用于将 Redux store 传递给应用并使组件能够访问 store 中的状态和触发 actions。

​ @reduxjs/toolkit 是一个与 Redux 配套使用的工具包,用于简化 Redux 的开发,而 react-redux 则是将 Redux 和 React 进行集成的库,提供了一些用于连接 Redux 和 React 组件的工具和组件。

15.window.onload和$(document).ready?

​ 如果代码仅依赖于DOM结构的完成而不需要等待外部资源加载完成,可以选择使用 $(document).ready();如果需要等待全部内容加载完毕后再执行,可以使用 window.onload

16.React性能优化的手段有哪些?

​ 使用生产环境构建

​ 使用 React.memo 或 PureComponent

​ 避免不必要的组件渲染

​ 使用 React.lazy 和 Suspense

​ 分割大型组件

​ 使用 Keys

​ 使用虚拟化技术

​ 避免在 render 方法中绑定函数

​ 使用 memoize 库进行数据缓存

​ 使用性能分析工具

17. 防抖节流的理解及其应用场景?如何通过原生js实现一个节流函数和防抖函数?

概念

防抖和节流是两种常用的函数优化技术,用于限制函数的触发频率,提高性能和用户体验。

节流:

在节流技术中,当一个事件触发后,会立即执行相应的操作,并且在指定的时间间隔内对后续的事件触发进行忽略。只有当下一个事件触发的时间超过指定的时间间隔后,才会再次执行相应的操作。

节流的应用场景:

滚动事件:在页面滚动过程中,使用节流可以控制回调函数的执行频率,避免频繁触发导致性能问题。
频繁点击事件:在按钮点击等频繁触发的事件中,使用节流可以限制回调函数的执行次数,提高用户体验。

节流函数的实现:

function throttle(fn, delay) {
	let timer = null;
	let lastTime = 0;
	return function (...args) {
		const currentTime = new Date().getTime();
  		if (currentTime - lastTime < delay) {
			clearTimeout(timer);
			timer = setTimeout(() => {
				lastTime = currentTime;
				fn.apply(this, args);
			}, delay);
		} else {
			lastTime = currentTime;
			fn.apply(this, args);
		}
 	};
}

防抖:

在防抖技术中,当一个事件触发后,如果在指定的时间间隔内没有再次触发该事件,才会执行相应的操作。如果在指定的时间间隔内再次触发了事件,则重新开始计时,等待一定时间后执行相应的操作。

防抖的应用场景:

输入框搜索:在用户输入搜索关键词时,使用防抖可以避免频繁触发搜索请求,等待用户停止输入后再执行搜索操作。
窗口调整:在窗口调整大小时,使用防抖可以减少频繁触发调整窗口大小所引起的回调函数执行次数。

防抖函数的实现:

function debounce(fn, delay) {
	let timer = null;
	return function (...args) {
		clearTimeout(timer);
		timer = setTimeout(() => {
			fn.apply(this, args);
		}, delay);
	};
}

Tip

防抖和节流的选择要根据具体的业务需求和场景来确定。如果希望在事件触发后立即执行一次,但在指定的时间范围内不再重复执行,则可以选择防抖技术;如果希望在一定的时间间隔内,以固定的频率执行操作,则可以选择节流技术。根据实际情况选择适合的方案,可以提升用户体验和性能。

18.说说webpack中常见的loader?解决了什么问题?

​ Style-loader:将css添加到内联样式标签style里

​ css-loader:允许通过require引入css返回css代码

​ less-loader:处理less

​ Sass-loader:处理sass

​ postcss-loader:使用postcss处理css

​ babel-loader:用babel来转换es6文件到ES

19.现在要你完成一个Dialog组件,说说你设计的思路?它应该有什么功能?

组件结构设计:Dialog 组件应该包含标题、内容、按钮等元素,可以使用一个包裹容器来包含这些元素。可以将标题和内容作为组件的子元素进行传递,按钮可以作为组件的 props 进行配置。

可定制化功能:Dialog 组件应该支持多种配置选项,以满足不同的需求。例如,可以通过 props 设置 Dialog 的标题、内容、样式、显示/隐藏状态等。另外,可以允许用户自定义按钮的数量、文本和点击事件。

动画效果:为了提升用户体验,可以为 Dialog 组件添加一些动画效果,例如淡入淡出、滑动等。可以使用 CSS 过渡或动画库(如 react-transition-group)来实现这些效果。

键盘操作:Dialog 组件应该支持键盘操作,例如按下 esc 键可以关闭 Dialog。

弹窗层级和遮罩:为了确保 Dialog 组件在页面中的层级,可以通过使用 CSS 的 z-index 属性设置 Dialog 的层级,使其覆盖在其他元素之上。另外,可以为 Dialog 添加遮罩层,在 Dialog 弹出时阻止用户对其他元素的操作。

可拖拽功能(可选):如果需要更灵活的交互方式,可以添加可拖拽功能,允许用户通过鼠标拖拽 Dialog 在页面中的位置。

响应式设计:Dialog 组件应该具备响应式特性,以适应不同屏幕尺寸,可以通过 CSS 媒体查询或响应式布局库(如 react-responsive)来实现。

事件回调:Dialog 组件应该提供一些事件回调函数,例如关闭事件、按钮点击事件等,以便于父组件在发生这些事件时执行相应的操作。

20.闭包的理解,优点缺点,应用场景

概念

闭包是在编程中一种重要的概念,它是指一个函数可以记住并访问其所在的词法环境中变量的能力。更具体地说,闭包是由一个函数以及与其相关的引用环境组成的包裹(closure)。

优点:

  1. 保护数据:闭包可以提供一个封装的环境,内部数据不容易被外部访问和修改,从而增加数据的安全性。
  2. 记忆状态:闭包可以在函数执行时记住上次执行的状态,这对于需要持久化状态的任务很有用。
  3. 实现私有变量:通过闭包,可以创建私有变量,只能在闭包内部访问,不暴露给外部。

缺点:

  1. 内存占用:闭包会将其所引用的外部变量保存在内存中,如果闭包持有大量数据或者被多次引用,可能会导致内存占用问题。
  2. 资源管理:如果不适当地使用闭包,可能会导致资源泄露,例如循环引用的情况下,闭包无法释放相关资源。

应用场景:

  1. 缓存实现:闭包可以被用于实现一个简单的缓存机制,通过记住先前的计算结果来提高函数的执行性能。
  2. 模块化开发:闭包可以用于实现模块化的代码结构,通过将一些私有的变量和函数封装在闭包内,提供对外的公共接口。
  3. 私有变量和方法:通过闭包,可以创建一些只在特定函数内部可访问的私有变量和方法,增强了代码的封装性和安全性。

Tip

需要注意的是,闭包可能导致一些隐蔽的问题,如内存泄漏和性能问题,因此在使用闭包时需谨慎并合理权衡利弊。

21.事件循环的理解,以及应用理解

概念

事件循环是一种用于管理异步操作的机制,它在编程中广泛应用于事件驱动的系统或框架中。它基于事件和回调机制,用于处理非阻塞的、异步的操作。

原理

事件循环的工作原理如下:

  1. 事件循环会不断地从事件队列中获取事件,并按照一定的顺序进行处理。
  2. 当某个事件被获取到后,事件循环会查找该事件对应的回调函数,并执行该回调函数。
  3. 在执行回调函数的过程中,可能会发起新的异步操作,这些操作会被添加到事件队列中等待处理。
  4. 当事件循环中没有待处理的事件时,它将会阻塞或者退出。

应用理解:

  1. 浏览器中的异步操作:事件循环在浏览器中被广泛应用于处理异步操作,如ajax请求、DOM事件等。当有异步操作完成时,触发相应的事件,事件循环会接收并处理这些事件。
  2. Node.js中的事件驱动编程:Node.js采用了事件驱动的编程方式,基于事件循环机制。它使用事件循环来处理I/O操作、网络请求和其他异步事件,提供高性能的非阻塞执行环境。
  3. UI框架的响应式更新:在前端开发中,一些流行的UI框架如React和Vue也借鉴了事件循环的思想。它们通过虚拟DOM和组件的生命周期管理,实现了高效的响应式更新机制。

Tip

总的来说,事件循环是一种处理异步操作的机制,通过轮询、事件和回调函数来实现非阻塞的执行流程。它在各种异步编程场景中发挥着重要的作用,帮助提高程序的性能和可维护性。

22.js类型检验的方式

  1. typeof运算符
    typeof运算符用于检查一个值的数据类型,返回一个表示数据类型的字符串。例如,typeof ‘hello’会返回’string’,typeof 10会返回’number’,typeof true会返回’boolean’。但是,typeof对于一些引用类型如数组和null时,返回的结果可能不够准确。

  2. instanceof运算符
    instanceof运算符用于检查一个对象是否属于某个特定的类型。例如,‘hello’ instanceof String会返回false,而new String(‘hello’) instanceof String会返回true。这种方式适用于对于对象类型的检验。

  3. constructor属性
    constructor属性是一个指向创建对象的构造函数的引用。通过判断一个对象的constructor属性,可以得知该对象的构造函数。例如,‘hello’.constructor === String会返回true。但是需要注意的是,constructor属性并非是完全可靠的方式,因为它可以被修改。

  4. Object.prototype.toString方法
    通过调用Object.prototype.toString方法,可以获得一个对象的内部[[Class]]属性的值,从而得知该对象的类型。例如,Object.prototype.toString.call(‘hello’)会返回’[object String]'。这种方式适用于对于各种类型(包括内置类型和自定义类型)的检验。

  5. typeof和instanceof的组合使用
    通过先使用typeof判断基本类型,再使用instanceof判断引用类型,可以进行更准确的类型检验。例如,typeof ‘hello’ === 'string’可以用来判断字符串类型,arr instanceof Array可以用来判断数组类型。

Tip

需要根据实际需求选择合适的类型检验方式。在开发中,也可以结合使用多种方式来进行类型检查,以增强代码的健壮性和可读性。此外,还可以使用第三方库如TypeScript等提供的更强大的类型检验功能。

23.说说React生命周期中有哪些坑?为什么要溢出will相关生命周期?

getDerivedStateFromProps容易编写反模式代码,使受控组件和非受控组件区分模糊
componentWillMount在react中已被标记弃用,主要是因为新的异步架构会导致它被多次调用,所以网络请求以及事件绑定都应该放到componentDidMount中
componentWillReceiveProps因为性能问题被getDerivedFromProps取代
shouldComponentUpdate通过返回true或false来确定是否被触发新的渲染,可用于性能优化
componentWillUpdate同样是由于新的异步渲染机制,而被标记废弃,可结合getSnapshotBeforeUpdate与componentDidUpdate使用
在componentWillUNmount中忘记解除事件绑定,取消定时器等操作,容易引发bug

24.说说Real diff算法是怎么运作的,从tree层到component层到element层分别讲解?

从tree层开始:

React使用Fiber Reconciliation来构建Virtual DOM树的表示形式。这是一个树状结构,其中每个节点都表示一个虚拟DOM元素。
在进行更新操作时,React会首先对比前后两棵树的不同之处。
React使用diff算法从根节点开始递归比较两棵树的每个节点,找出需要进行更新的节点。

到component层:

当React找到需要更新的节点时,它会比较节点所对应的组件类型。
如果组件类型不同,React会将旧的组件卸载,并创建新的组件进行替换,这被称为“unmount and mount”。
如果组件类型相同,React会比较组件的props和state是否发生了变化。
如果props或state发生了变化,React会触发组件的更新流程,重新渲染组件,并执行相应的生命周期方法。

到element层:

当React比较两个组件时,它会进一步比较组件的子元素,即虚拟DOM的嵌套结构。
React会逐级比较每个元素的类型、key值和属性等,并决定是否需要对其进行更新。
如果两个元素类型不同,React会将旧的元素替换为新的元素。
如果两个元素类型相同,React会继续比较它们的子元素,递归地进行类型和属性的比较。

25.调和阶段setState干了什么?

构建更新任务队列:

当调用setState方法时,React会将更新任务添加到待处理的更新队列中。
React使用一种称为Fiber的数据结构来表示组件树,每个更新任务都被封装为一个Fiber节点。

执行调和过程:

在调和过程中,React会遍历更新任务队列,对每个需要更新的组件执行调和操作。
调和操作是指比较当前组件的Virtual DOM与上一次渲染时的Virtual DOM,找出需要更新的部分。

生成更新:

在调和过程中,React会通过比较前后两次的Virtual DOM树,找到需要进行更新的部分,并生成一系列更新操作。
这些更新操作被封装为Fiber节点,并形成一个更新链表。

应用更新:

在调和过程完成后,React会将更新链表中的更新操作应用到真实的DOM中。
React使用一种称为Reconciler的机制来逐个处理更新链表中的更新操作。
根据每个更新操作的类型(插入、更新、删除等),React将相应地更新真实的DOM节点。

26.CSS3的新特性都有哪些?

过渡(Transitions):允许元素在一种样式到另一种样式之间平滑过渡,可以定义过渡的持续时间、延迟和缓动函数。

动画(Animations):提供了更强大的动画效果控制,可以定义关键帧、持续时间、延迟和缓动函数,实现复杂的动画效果。

2D转换(Transforms):包括平移、缩放、旋转和倾斜等变换效果,可以通过CSS属性实现。

3D转换(3D Transforms):通过CSS属性在三维空间内实现元素的旋转、平移、缩放和倾斜等效果。

边框圆角(Border-radius):可以使元素的边框产生圆角效果,可以分别设置每个角的圆角半径。

阴影(Box-shadow):用于给元素添加投影效果,包括水平偏移、垂直偏移、模糊半径和颜色等属性。

渐变(Gradients):包括线性渐变(Linear Gradients)和径向渐变(Radial Gradients),能够创建平滑的颜色渐变效果。

媒体查询(Media Queries):可以根据设备的特性(如宽度、高度、分辨率等)来应用不同的样式规则,实现响应式设计。

伸缩布局(Flexbox):用于创建灵活的布局结构,可以对子元素进行水平和垂直方向的分布和对齐等操作。

多列布局(Multiple Columns):使文本内容能够在多列中呈现,可以控制列数、间距和规则等。

网格布局(Grid):提供了一种更强大的布局系统,可以将网页分割成多个网格,方便进行自适应布局。

文字溢出处理(Text-overflow):用于控制文本在容器内溢出时的显示方式,可实现省略号显示或渐变效果。

27.说说redux的工作流程?

创建一个Redux存储(Store):

Redux的核心是Store,用于存储应用程序的状态。
Store是通过Redux的createStore函数来创建的,需要传入一个Reducer函数作为参数。Reducer函数用于定义应用程序状态的更新逻辑。

定义应用程序的状态(State):

应用程序的状态是以一个JavaScript对象的形式存在于Store中。
开发者需要定义应用程序状态的结构和初始值,并将其传递给Reducer函数。

创建Action:

Action是一个用于描述状态变化的纯JavaScript对象。
开发者需要定义和创建Action,以描述应用程序中发生的事件或用户操作。

分发Action:

要更新应用程序的状态,需要通过调用Redux的dispatch方法,并传递一个Action作为参数。
dispatch会将Action发送给Reducer函数,触发状态的更新过程。
Reducer函数处理Action:

Reducer函数根据接收到的Action描述,执行相应的逻辑来更新状态。
Reducer函数需要根据Action的类型来判断要进行的操作,并返回一个新的状态对象。

更新状态:

当Reducer函数返回一个新的状态对象后,Redux会将这个状态更新到Store中。
Redux会使用浅比较来对新旧状态进行比较,只有在状态发生实际变化时才会更新Store。

订阅状态变化:

开发者可以通过Redux的subscribe方法来订阅状态的变化。
subscribe方法接收一个回调函数,每当状态变化时,回调函数将被调用。

28.React合成事件的原理,有什么好处和优势?

概念

React的合成事件是指React在DOM事件系统之上构建的一层事件抽象,它的原理是将所有浏览器原生事件统一封装成React合成事件对象,并通过事件委托的方式处理事件。

原理

事件注册:React在组件渲染时为需要绑定事件的元素注册事件监听器。
事件分发:当元素上触发某个事件时,React会将该事件统一封装成一个合成事件对象。
事件处理:React会根据组件的声明式事件处理函数,调用对应的处理函数,并传递合成事件对象作为参数。
事件委托:React使用事件委托的方式将事件绑定到最外层的祖先元素上,而不是在每个子元素上绑定事件监听器,以提高性能。

合成事件的好处和优势包括:

跨浏览器兼容性:React合成事件抽象了底层的浏览器事件系统,使得开发者不需要关心不同浏览器之间的差异,提高了跨浏览器兼容性。

性能优化:React使用事件委托的方式将事件绑定在最外层的祖先元素上,减少了绑定事件监听器的数量,提高了性能。

统一的事件接口:React将浏览器原生事件封装成合成事件对象,提供了一致的、跨平台的事件接口,并丰富了事件对象的属性和方法,方便开发者使用。

事件扩展和优化:React的合成事件对象可以根据不同的事件类型进行优化,例如,在滚动事件中使用了节流和防抖来优化性能,以减少事件的触发次数。

29.为什么react元素有一个$$type属性?

是因为 JSON 不支持Symbol。因此即使是服务端有风险漏洞并且返回一个 JSON,这个 JSON 也不会包含Symbol.for(‘react.element’).,在 Reconcile 阶段,React 会检查element.$$typeof标志是否合法。不合法的话直接报错,React 不能接受对象作为 children

专门使用 Symbol.for() 的好处是, Symbols 在 iframe 和 worker 等环境之间是全局的。因此,即使在更奇特的条件下,Symbols 也能在不同的应用程序之间传递受信任的元素。同样,即使页面上有多个 React 副本,它们仍然可以“同意”有效的 $$typeof 值
如果浏览器不支持Symbols,React 使用0xeac7代替,有效的防止了XSS的攻击

30.封装一个使用递归方式的深拷贝方法deepClone

function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  let clone;
  if (Array.isArray(obj)) {
    clone = [];
    for (let i = 0; i < obj.length; i++) {
      clone[i] = deepClone(obj[i]);
    }
  } else {
    clone = {};
    for (let key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        clone[key] = deepClone(obj[key]);
      }
    }
  }

  return clone;
}

此封装方法首先判断传入的参数obj是否是原始数据类型或者null,若是则直接返回。否则,创建一个空对象clone,如果obj是数组,则遍历数组并递归地对每个元素进行深拷贝,并添加到clone中。如果obj是普通对象,则遍历对象的属性,并递归地对每个属性的值进行深拷贝,并添加到clone中。

31.说说你对fiber架构的理解?解决了什么问题?

js引擎和页面渲染引擎是互斥的,一个被执行,另一个就会被高高挂起,如果组件过大,那么js引擎会一直占用主线程,而渲染引擎会一直被挂起,所以对于一些用户互动、动画不能及时得到处理,可能会出现卡顿情况。
而Fiber模型采用了用户态线程,可以在应用程序中自行创建、销毁和调度,避免了操作系统内核的干预。这使得Fiber可以更加轻量级和高效。

Fiber架构解决了传统线程模型中的一些问题

高并发性:传统线程模型面临创建大量线程的开销和线程切换的代价,而Fiber可以更轻量地创建和销毁,实现更高的并发性能。

资源利用率:Fiber模型不需要依赖操作系统内核进行线程切换,因此减少了上下文切换、系统调用等开销,提高了资源利用率。

响应性能:Fiber可以通过协作式调度,避免了传统线程模型中的抢占式调度带来的延迟。应用程序可以在合适的时机主动让出CPU控制权,提高了响应性能。

易于管理:Fiber模型允许应用程序自行管理线程的创建和调度,简化了并发编程模型,提高了可维护性和可扩展性。

32.如何避免ajax数据请求重新获取?

数据缓存

在将Ajax请求结果展示到页面之前,将数据存储在缓存中。下次需要相同数据时,先检查缓存是否存在该数据,如果存在直接使用缓存数据,避免发送重复的Ajax请求。

状态管理

使用状态管理工具(如Redux、Vuex)来管理数据的生命周期和状态。在发起Ajax请求之前,先检查状态管理中是否已经存在所需的数据,如果存在,则直接使用。只有在数据没有被获取或者过期时,才发送Ajax请求。

请求拦截

在发送Ajax请求之前,通过请求拦截器进行判断。判断数据是否已经存在于本地或者全局变量中,如果存在,则直接返回缓存数据,而不发送实际的Ajax请求。

接口设计优化

在后端接口设计时,考虑将一些相对稳定的数据进行缓存,并设置合理的缓存时间。这样可以避免频繁的Ajax请求,提高接口的响应速度和性能。

前端数据共享

如果多个组件需要获取相同的数据,可以将数据请求和共享逻辑抽离出来,通过高阶组件或者上下文等方式共享数据。这样在多个组件中只需要发送一次Ajax请求,避免了重复请求。

通过合理使用数据缓存、状态管理、请求拦截和接口设计优化等方法,可以避免Ajax数据请求的重复获取,提升应用程序的性能和用户体验。

33.短轮询、长轮询和 WebSocket 间的区别?

短轮询、长轮询和WebSocket是三种不同的客户端和服务器之间进行实时通信的方式,它们在工作原理和应用场景上存在一些区别。

短轮询

短轮询是最简单和最传统的实时通信方式。在短轮询中,客户端发送一个HTTP请求到服务器,然后服务器立即返回响应,不管有没有新的数据更新。客户端收到响应后,再根据需要重新发送请求。这样,客户端需要定期发送请求来获取最新的数据,并且可能会导致频繁的网络请求。短轮询适用于对实时性要求不高,而且服务器资源有限的场景。

长轮询

长轮询是一种改进的实时通信方式,旨在减少频繁的网络请求。在长轮询中,客户端发送一个HTTP请求到服务器,但服务器不会立即返回响应。相反,服务器会将请求挂起,直到有新的数据更新或者超时。当有新的数据时,服务器会立即返回响应给客户端,客户端收到响应后再重新发起新的请求。这样,客户端只会在有新数据时才接收到响应,减少了频繁的轮询请求。长轮询适用于对实时性要求较高,但服务器资源有限的场景。

WebSocket

WebSocket是HTML5提供的一种全双工的通信协议,能够在客户端和服务器之间建立持久的连接,实现实时的双向通信。相对于短轮询和长轮询,WebSocket采用与HTTP不同的协议,并在客户端和服务器之间建立一个长期的连接通道。这样,双方可以随时通过该连接通道发送消息和接收消息,无需频繁发起新的请求。WebSocket适用于对实时性要求高,并且需要实现双向通信的场景,如聊天应用、实时游戏等。

总结:

  • 短轮询适用于对实时性要求不高的场景,但会导致频繁的网络请求。
  • 长轮询适用于对实时性要求较高,但服务器资源有限的场景,能够减少轮询请求。
  • WebSocket适用于对实时性要求高,并且需要实现双向通信的场景,可以建立持久的连接通道。

34.前端跨域的解决方案?

JSONP

JSONP是一种跨域请求的方法,它利用了HTML中

CORS

CORS是一种基于HTTP头部的机制,允许服务器在响应中设置一些头部信息,告诉浏览器是否允许跨域请求。通过在服务器端设置相应的响应头,可以指定允许访问的域名、方法等,并且可以支持各种类型的请求。在前端代码中,只需正常发起跨域请求即可。

代理服务器

使用代理服务器是一种常见的跨域解决方案。前端应用程序通过向自己的服务器发起请求,然后服务器再代为转发请求到目标服务器,最后将响应返回给前端。这样,由于请求是发送到同一域名下的服务器,就不会受到同源策略的限制。

WebSocket

WebSocket是一种支持双向通信的协议,它不受同源策略的限制,可以在浏览器中跨域进行实时通信。通过建立WebSocket连接,前端可以与不同域名的服务器进行双向数据传输。

Nginx反向代理

使用Nginx等反向代理服务器,将前端的跨域请求代理到后端服务器上。在Nginx的配置中,可以设置反向代理规则,将跨域请求转发到目标服务器上,然后将响应返回给前端。

35.你对自定义hook的理解,模拟简易版的useState?

自定义 Hook 是 React 发布的一项功能,用于在函数组件中重用状态逻辑。它是一种函数,以 “use” 开头并调用其他 Hook。使用自定义 Hook 可以将组件逻辑提取到可独立复用的函数中。

要模拟简易版的 useState,我们可以创建一个名为 useMyState 的自定义 Hook。下面是一个实现的示例:

import { useState } from 'react';

function useMyState(initialValue) {
  const [state, setState] = useState(initialValue);
  function updateState(newValue) {
    setState(newValue);
  }
  return [state, updateState];
}

在这个示例中,useMyState 接收一个初始值,使用内置的 useState Hook 创建状态和状态更新函数。然后,我们定义了一个 updateState 函数,它用于更新状态。最后,我们返回一个数组,其中包含当前状态和状态更新函数。

36.介绍一下 Tree Shaking?

概念

Tree Shaking是一种在JavaScript中进行代码优化的技术,旨在减少最终打包后的代码大小。它通过静态代码分析的方式,识别并移除未被使用的代码片段,以便在最终的构建过程中只包含实际使用到的代码。

核心思想

Tree Shaking的核心思想是基于ES6模块的静态特性。在模块系统中,模块的依赖关系是明确地声明的,这使得编译器可以在构建过程中进行静态分析。通过这种方式,编译器能够确定哪些代码被引用,并进一步确定哪些代码是不可达的。

原理

在实际应用中,Tree Shaking通常与工具链(例如Webpack、Rollup等)配合使用。这些工具会扫描整个项目的代码,并根据依赖关系构建一个代码依赖图。然后通过分析模块导入和导出的关系,扫描并标记未被引用的代码片段。最后,在打包过程中,只有被标记为被引用的代码会被包含进最终的构建结果中,而未被引用的代码将被剔除。

优点

Tree Shaking的优点是可以显著减少最终打包后的代码体积,提高应用程序的加载速度。它对于代码库的维护和优化也非常有帮助,因为开发者可以更加自由地组织代码,而不必担心未使用的代码的影响。然而,值得注意的是,Tree Shaking只能移除掉在编译时不可达的代码,而对于在运行时动态导入的代码则无能为力。

37.说说你对Object.defineProperty()的理解?

Object.defineProperty()是JavaScript中的一个方法,用于在一个对象上定义一个新属性,或者修改已有属性的特性。

该方法接受三个参数:对象、属性名和属性描述符对象。属性描述符对象包含了一系列用于定义属性特性的属性,例如可写性(writable)、可枚举性(enumerable)、可配置性(configurable)和值(value)等。

使用Object.defineProperty()可以实现对属性的精细控制。下面是对属性描述符对象的属性的解释:

  • value:属性的值。
  • enumerable:属性是否可枚举,默认为false。如果设置为true,该属性可以被for…in循环枚举到。
  • writable:属性是否可写,默认为false。如果设置为true,该属性的值可以被修改。
  • configurable:属性是否可配置,默认为false。如果设置为true,该属性的特性(除了writable)可以被修改,同时该属性也可以被删除。

例子:

const obj = {};

Object.defineProperty(obj, 'name', {
  value: 'John',
  writable: false,
  enumerable: true,
  configurable: false
});

console.log(obj.name); // 输出 'John'
obj.name = 'Mark'; // 不会修改成功,因为writable被设置为false
console.log(obj.name); // 依然输出 'John'

for (let key in obj) {
  console.log(key); // 输出 'name'
}

delete obj.name; // 不会删除成功,因为configurable被设置为false
console.log(obj.name); // 输出 'John'

通过使用Object.defineProperty(),我们可以在对象中创建具有特定特性的属性,并对其进行更精确的控制。这在一些特定的场景中非常有用,例如创建只读属性、隐藏属性或者控制对象的可枚举性等。

38.for…in循环和for…of循环的区别?

for…in循环和for…of循环是两种不同的循环语法,用于迭代遍历数据结构中的元素。它们的区别如下:

  1. 适用对象类型不同:for…in适用于遍历对象中的可枚举属性(包括继承得到的属性),而for…of适用于遍历可迭代对象(例如数组、字符串、Set、Map等)中的值。

  2. 迭代的顺序:for…in以任意的顺序遍历对象的属性,因为对象的属性在内部存储中没有特定的顺序。而for…of则按照可迭代对象的元素顺序进行迭代。

  3. 迭代的目标:for…in迭代的是对象的属性名,而for…of迭代的是对象的属性值。

  4. 遍历方式:for…in使用的是对象的键来进行遍历,而for…of使用的是对象的值进行迭代。

下面是使用for…in和for…of遍历数组的例子:

const arr = [1, 2, 3];

for (let index in arr) {
  console.log(index); // 输出 0, 1, 2
}

for (let value of arr) {
  console.log(value); // 输出 1, 2, 3
}

在这个例子中,for…in循环通过迭代数组的索引来进行遍历,而for…of循环通过迭代数组的值来进行遍历。

需要注意的是,for…in在遍历数组时可能会遍历到原型链上的可枚举属性,因此在遍历数组时要注意使用hasOwnProperty()方法或者进行属性过滤,以避免遍历到不希望的属性。

总结起来,for…in适用于遍历对象属性,而for…of适用于遍历可迭代对象的值。使用时根据需求选择适合的循环语法。

39.说说你对vue中mixin的理解?

Mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类。
Mixin类通常作为功能模块使用,在需要该功能时“混入”,有利于代码复用又避免了多继承的复杂。

Vue中的mixin官方定义

mixin,提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。

本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等
我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来。
在Vue中我们可以局部混入跟全局混入。

40.React render方法的原理,在什么时候会触发?

概念

React中的render()方法是类组件中的一个生命周期方法,用于定义组件的渲染逻辑和生成虚拟DOM。

原理

render()方法的原理是根据组件的状态(state)和属性(props)生成虚拟DOM(Virtual DOM)。虚拟DOM是一个轻量级的JavaScript对象,它描述了组件在特定时刻应该渲染出的DOM结构。

当组件的状态或属性发生变化时,React会比较前后两个虚拟DOM的差异,并在实际的DOM中通过最小化的操作来更新变化的部分,实现高效的页面更新。

触发render()方法的时机有以下几种情况:

  1. 组件初始化:在组件创建时,第一次渲染会触发render()方法。

  2. 组件状态变化:重要的是,当组件的状态(调用setState()方法)发生变化时,React会自动调用render()方法来重新渲染组件。

  3. 父组件重新渲染:当父组件进行重新渲染时,它的子组件也会随之重新渲染,进而触发子组件的render()方法。

  4. 组件接收新属性:当组件接收到新的属性(props)时,会触发render()方法。

Tip

需要注意的是,render()方法是一个纯粹的函数,它不应该进行任何副作用操作,例如发送网络请求或者更改组件状态。它只负责根据组件的状态和属性来生成虚拟DOM并返回。如果需要进行副作用操作,应该放在其他生命周期方法或者组件钩子中。

总而言之,React的render()方法根据组件的状态和属性生成虚拟DOM,并在页面上更新对应的变化。它会在初始化、状态变化、父组件重新渲染和接收新属性等情况下触发。

41.扩展运算符都有哪些作用,详细介绍一下?

扩展运算符是一个三个连续点(…)的语法,可以在多种上下文中使用,并具有以下几个作用:

  1. 展开数组:扩展运算符可以将一个数组展开为独立的元素。这个特性可以用于创建新的数组、合并数组或在函数调用时传递参数。

    const arr1 = [1, 2, 3];
    const arr2 = [4, 5, 6];
    
    const combinedArr = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
    
  2. 展开对象:类似于展开数组,扩展运算符还可以将一个对象的属性展开为独立的键值对。这使得在创建新对象时可以轻松地引入已有对象的属性。

    const obj1 = { name: 'John', age: 30 };
    const obj2 = { city: 'New York' };
    
    const combinedObj = { ...obj1, ...obj2 }; // { name: 'John', age: 30, city: 'New York' }
    
  3. 复制数组和对象:使用扩展运算符还可以创建数组和对象的浅拷贝副本。这样的拷贝是对原始数据的引用,因此对复制的数组或对象的修改不会影响到原始数据。

    const arr = [1, 2, 3];
    const copyArr = [...arr]; // [1, 2, 3]
    
    const obj = { name: 'John', age: 30 };
    const copyObj = { ...obj }; // { name: 'John', age: 30 }
    
  4. 函数调用时传递参数:扩展运算符可以在函数调用时将数组或对象的元素作为独立的参数传递给函数。

    function sum(a, b, c) {
      return a + b + c;
    }
    
    const numbers = [1, 2, 3];
    const result = sum(...numbers); // 6
    
  5. 剩余参数:扩展运算符还可以用作函数的剩余参数(rest parameter),将传递给函数的多余参数收集为一个数组。

    function sum(...args) {
      return args.reduce((total, num) => total + num, 0);
    }
    
    const result = sum(1, 2, 3, 4, 5); // 15
    

扩展运算符非常方便且易读,使得在处理数组和对象时更加灵活和简洁。它对于数组、对象的合并、复制和函数参数的收集等场景都提供了便利的语法。

42.js找出数组中出现次数最多的数,并统计出现多少次,编写个函数?

可以使用 JavaScript 来编写一个函数来找出数组中出现次数最多的数,并统计它出现的次数。下面是一个示例实现:

function findMostFrequentNumber(arr) {
  // 创建一个空对象来存储每个数字出现的次数
  let counts = {};
  // 找出出现次数最多的数字和它的出现次数
  let maxCount = 0;
  let mostFrequentNum = null;
  
  // 遍历数组,统计每个数字出现的次数
  for (let i = 0; i < arr.length; i++) {
    let num = arr[i];
    // counts中的这一项是否存在,存在加一,否则为一
    counts[num] = counts[num] ? counts[num] + 1 : 1;
  }
  
  // 使用for in遍历存储数量的对象,找出最多的哪一项以及它的频率
  for (let num in counts) {
    if (counts[num] > maxCount) {
      maxCount = counts[num];
      mostFrequentNum = num;
    }
  }

  // 返回结果
  return {
    number: mostFrequentNum,
    count: maxCount
  };
}

let arr = [1, 2, 3, 4, 5, 3, 2, 2, 4, 2, 2, 3, 1, 3, 5];
let result = findMostFrequentNumber(arr);

console.log(`出现最多的数字: ${result.number}`);
console.log(`出现次数: ${result.count}`);

运行上面的代码,输出的结果将会是:

出现最多的数字: 2
出现次数: 5

在给定的数组中,数字 2 出现了最多的次数,共出现了 5 次。

43.说说React中setState执行机制?

setState并不会立即更新组件的状态,而是将状态更新请求添加到一个队列中。
React会根据当前执行环境,在合适的时机进行状态更新批处理。
多个setState调用会进行批处理,以提高性能。
可以使用setState的回调函数,在状态更新完成后执行其他操作。

44.如何使用css实现一个三角形?

创建一个无内容的 div 元素,并添加了 triangle 类名来应用样式。接下来的 CSS 代码用于创建三角形的效果。

<div class="triangle"></div>

首先,我们将宽度和高度设为 0,这样元素的可见区域就没有大小了。接着,我们使用 border 属性创建三角形的边框。border-left 和 border-right 属性将创建顶点相接的两条斜边,而 border-bottom 属性将创建底边。

.triangle {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-bottom: 100px solid #000;
}

通过添加 transform: rotate(45deg); 来旋转元素,使得其变为一个三角形。
透明的边框(border-left: 50px solid transparent; 和 border-right: 50px solid transparent;)创建了一个等腰直角三角形。然后,我们再通过 border-bottom 属性设置底边的大小和颜色。

.triangle {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-bottom: 100px solid #000;
  transform: rotate(45deg);
}

45.说说React jsx转换成真实DOM的过程?

JSX是语法糖,通过babel转成 React,createElement 函数
首先解析出来的话,就是一个 createElement 函数
然后这个函数执行完后,会返回一个 vnode
通过vdom的patch或者是其他的一个方法,最后渲染一个页面

46.说说webpack中代码分割如何实现?

在Webpack中,代码分割(Code Splitting)是一种将代码拆分成多个小块(chunks)并按需加载的技术,可以提高应用程序的性能和加载速度。Webpack提供了多种方式来实现代码分割。

使用动态导入语法:使用ES6的动态import()语法,可以在代码中指定要分割的模块。

import("./module")
  .then(module => {
    // 使用模块
  })
  .catch(error => {
    // 处理错误
  });

这样做能够将module模块分割出去,使得它能够在需要时进行按需加载。

使用Webpack的内置函数:Webpack提供了一些内置函数来帮助完成代码分割。其中最常用的是import()函数。

import(/* webpackChunkName: "module" */ "./module")
  .then(module => {
    // 使用模块
  })
  .catch(error => {
    // 处理错误
  });

在这个例子中,我们通过注释webpackChunkName来为生成的分割块指定名称,使得加载的代码更易于理解和管理。

使用Webpack配置:你还可以在Webpack配置文件中配置代码分割的行为。

module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: "all"
    }
  }
};

这个配置告诉Webpack在打包过程中自动进行代码分割,并根据需要生成更小的块。

47.说说如何借助webpack来优化前端性能?

Webpack可以帮助优化前端性能的方式有很多,下面是一些常用的优化技巧:

  1. 代码分割(Code Splitting):使用Webpack的代码分割功能,将代码拆分成多个小块(chunks),并按需加载。这可以减小初始加载的文件大小,提升页面的加载速度。

  2. 按需加载(Lazy Loading):通过使用动态导入(例如使用import()语法)来实现按需加载,只在需要时加载所需的模块。这可以减少初始加载时间,避免不必要的资源下载。

  3. 压缩代码(Code Minification):Webpack可以对JavaScript和CSS进行压缩,减小文件的大小,减少网络传输时间,提升加载速度。

  4. Tree Shaking:通过Webpack的Tree Shaking功能,可以消除没有使用到的代码,减小打包后的文件大小。这可以优化代码的运行时性能。

  5. 图片优化:Webpack提供了各种插件和加载器(Loader)来优化图片的加载和处理。例如,使用url-loader加载器可以将小图片转换成Base64编码,减少HTTP请求的数量。

  6. 缓存策略:Webpack可以通过生成带有哈希值的文件名来实现缓存策略。当文件内容发生变化时,Webpack会生成一个新的哈希值,使得浏览器可以从服务器上获取最新的文件版本。

  7. 条件编译:Webpack支持使用预处理器(Preprocessor)和条件注释(Conditional Comment)来根据不同的环境和构建配置,选择性地包含或排除某些部分的代码。

  8. 使用Webpack插件:Webpack有许多优秀的插件可用于进一步优化前端性能,例如terser-webpack-plugin用于最小化JavaScript代码,optimize-css-assets-webpack-plugin用于最小化CSS代码等。

这些只是一些常见的优化技巧,实际上Webpack提供了很多功能和配置选项,可以根据具体需求进行更精细化的性能优化。

需要注意的是,优化前端性能是一个综合性的过程,不仅仅依赖于Webpack的配置和功能,还需要考虑其他方面的因素,例如网络请求的优化、缓存策略的设置等。

48.TypeScript中的interface和type的区别?

在TypeScript中,interface和type都用于定义类型,但它们在某些方面有一些区别。

  1. 语法:interface使用关键字interface进行定义,而type使用关键字type进行定义。
// interface定义
interface Dog {
  name: string;
  age: number;
}

// type定义
type Cat = {
  name: string;
  age: number;
};
  1. 合并声明:当定义相同名称的interface时,它们会合并为一个声明,而type则会报错。
// interface合并声明
interface Animal {
  name: string;
}

interface Animal {
  age: number;
}

// type报错
type Animal = {
  name: string;
};

type Animal = {
  age: number;
};
  1. 实现/继承:interface可以通过implements关键字来实现,而type不能。
interface Printable {
  print(): void;
}

class Book implements Printable {
  print() {
    console.log("Printing book...");
  }
}
  1. 可以在interface中声明多次同名的属性,但在type中会报错。
interface Person {
  name: string;
  age: number;
  name: string; // 合法,在编译时会被合并为一个属性声明
}

type Person = {
  name: string;
  age: number;
  name: string; // 报错,重复的属性声明
};
  1. 对于拓展(extends)其他类型,interface使用extends关键字,type使用&操作符。
interface A {
  name: string;
}

interface B extends A {
  age: number;
}

type C = {
  name: string;
} & {
  age: number;
};

综上所述,interface和type在大多数情况下可以互换使用,但在一些语法和用法上有所区别。在选择使用interface还是type时,可以根据具体情况和个人喜好进行选择。

49.对typescript中函数重载的理解?

函数重载是指在同一个作用域内声明多个具有相同名称但参数类型或参数个数不同的函数。
在TypeScript中,函数重载主要用于为同一函数提供多个不同的函数参数,并根据不同的参数类型或参数个数来确定函数的行为。

50.TypeScript支持的访问修饰符有哪些?

public:默认访问修饰符,表示公开的,可以在任何地方访问。
private:表示私有的,只能在类内部访问。
protected:表示受保护的,可以在类内部和派生类中访问,不能在实例中访问
readonly:表示只读的,只能在声明时或构造函数中初始化,之后不能修改。
static:表示静态的,属于类而不是实例,可以通过类名直接访问。
abstract:表示抽象的,只能用于抽象类和抽象方法,不能被实例化,需要在派生类中实现。
export:表示可导出的,用于将类、函数、变量等导出,供其他模块使用。

51.props和state相同点和不同点?render方法在哪些情况下会执行?

相同点:

  1. 都是用于存储和管理组件数据的机制。
  2. 都可以影响组件的渲染结果。

不同点:
3. props是父组件传递给子组件的数据,是只读的,子组件不能直接修改props的值。而state是组件自身内部管理的数据,可以在组件内部进行修改。
4. props是通过组件的属性传递的,即props由上层组件决定。而state是组件自身的状态,由组件内部初始化和修改。
5. 当父组件的props发生变化时,子组件将会重新渲染,而当组件的state发生变化时,组件自身会重新渲染。
6. 在函数组件中,props通过函数参数传递,state通过React的useState钩子函数来声明和使用。在类组件中,props通过this.props访问,state通过this.state访问。

render方法在以下情况下会执行:
7. 组件初始化渲染时,即在组件的生命周期方法componentDidMount、componentDidUpdate中会触发render方法。
8. 当组件的props或state发生变化时,render方法会重新执行,重新渲染组件。
9. 如果shouldComponentUpdate生命周期方法返回true,那么render方法将被触发重新渲染组件。
10. 当组件的forceUpdate方法被调用时,无论props和state是否有变化,render方法都会执行重新渲染组件。

52.说说React中的虚拟dom?在虚拟dom计算的时候diff和key之间有什么关系?

React中的虚拟DOM是一种用JavaScript对象来表示真实DOM结构的轻量级副本。它可以作为React框架的中间层,用来管理和优化DOM操作,提高性能。

在React中,当组件的状态(State)发生变化时,React会通过调用render方法生成一个新的虚拟DOM树。然后,React会将新的虚拟DOM树与之前的虚拟DOM树进行比较,并计算出需要更新的最小操作集,然后将这个操作集应用到真实DOM树上,实现页面的局部更新。

在虚拟DOM计算的过程中,diff算法和key属性之间有一定的关系。

diff算法是指React在比较新旧虚拟DOM树时使用的一种策略,用于找出哪些部分需要更新。React采用的是一种称为"双端比较"的方式进行差异比较。该算法会同时从虚拟DOM树的根节点和叶节点开始进行比较,通过递归和循环遍历的方式找出差异。

key属性在React中用于在虚拟DOM的列表渲染中给每个子元素提供一个稳定的唯一标识。当列表的顺序或数量发生变化时,React会根据元素的key属性来判断哪些元素被新增、删除或移动,以尽量减少DOM操作的次数。在diff算法中,key属性是非常重要的一部分,它能够帮助React快速判断哪些元素需要进行更新。

diff算法和key属性是相辅相成的,key属性可以帮助React在使用diff算法时更快速地识别出真正需要更新的元素,从而提高性能。

53.谈谈你对immutable.js的理解?

Immutable.js是一个JavaScript库,用于操作不可变数据结构。它的设计思想是基于函数式编程的理念,通过保持数据的不可变性来简化复杂的数据操作。

不可变数据是指一旦创建,就不能被修改的数据。每当对不可变数据做出修改时,都会创建一个全新的数据副本,而不是直接修改原来的数据。这种通过复制和共享数据来实现的不可变性,在处理大型、复杂对象时可以提供更高的性能和可靠性。

Immutable.js提供了一组丰富而强大的数据结构,如List、Map、Set、Stack等,这些数据结构都是不可变的。它们可以用于存储和操作数据,而不会改变原始数据。

总体来说,Immutable.js提供了一种强大的不可变数据管理方式,可以简化复杂数据操作,并提供更高的性能和可靠性。然而,是否使用Immutable.js还需要根据具体的应用场景和使用需求来权衡利弊。

54.redux-saga和redux-thunk的区别与使用场景?

Redux-saga和Redux-thunk是两个常用的Redux中间件,用于处理异步操作。

区别

  1. 方式不同:Redux-thunk是一个函数,允许action创建函数返回一个函数,这个函数可以在其中进行异步操作。而Redux-saga使用Generator函数来处理异步操作,通过监听Redux中的action,并在满足条件时执行相应的异步操作。
  2. 控制流程不同:Redux-thunk允许将异步操作放在action中执行,使得异步操作与状态改变能够关联在一个地方。而Redux-saga将控制流程从组件中抽离出来,使用Generator函数编写复杂的异步逻辑,并使用effect来管理副作用操作的执行。
  3. API复杂度不同:Redux-thunk相对较简单,只需要编写一个包含async操作的函数即可。而Redux-saga提供了一系列复杂的Effect用于管理异步操作,如take, put, call, fork等,需要更深的理解和学习成本。

使用场景

  1. Redux-thunk适用于简单的异步操作,如发送API请求、延迟执行等。它比较适合处理简单的请求响应和副作用操作,且学习成本相对较低。
  2. Redux-saga适用于处理复杂的异步场景,例如需要实现多个并行或串行的异步操作、操作之间需要有依赖关系或条件判断的情况。它可以帮助你管理复杂的异步流程,并提供了更高的可扩展性和灵活性,但学习成本相对较高。

综上所述,Redux-thunk适用于简单的异步场景,具有简单的API和低学习成本。而Redux-saga适用于复杂的异步场景,提供更强大的控制流程和复杂的Effect来处理异步操作,但学习成本相对较高。选择使用哪个中间件取决于项目的需求和开发团队的经验水平。

55.在使用redux过程中,如何防止定义的action-type的常量重复?

在Redux中,可以通过遵循一些约定和使用辅助工具来避免定义重复的action-type常量。

以下是一些防止定义重复action-type常量的方法:

  1. 命名空间(Namespace):为每个模块或功能定义一个唯一的命名空间,将相关的action-type常量放置在该命名空间下。例如,可以使用模块名或功能名作为命名空间前缀,例如"USER_"、"CART_"等。

  2. 常量文件:创建一个独立的常量文件,用于存放所有的action-type常量。将每个模块或功能的常量按照命名空间进行分组,可以更好地组织和管理常量。

  3. 唯一性检查:使用辅助工具来检查action-type常量的唯一性。例如,可以使用工具库像redux-actredux-actions或自定义的工具函数来检查常量是否重复。

  4. 命名规则:定义一套清晰的命名规则,遵循一致的命名约定。例如,使用大写字母和下划线来表示常量,避免使用相似的名称或拼写错误。

  5. 统一管理:将所有的action-type常量集中在一个地方进行管理,可以使用统一的文件或模块来存放所有的常量定义,以便于查找和维护。

通过以上的方法,可以减少定义重复action-type常量的可能性,并提高代码的可读性和维护性。此外,团队内部也可进行代码审查或进行讨论,以确保常量的唯一性和一致性。

56.CDN的特点及意义?

CDN(Content Delivery Network)即内容分发网络,是一种通过将内容分发到全球分布的边缘节点服务器来提供高性能、可扩展性和可靠性的网络服务。

CDN的特点包括:

  1. 高效性:CDN通过就近选择最佳服务器节点,将内容分发到用户附近的边缘节点,减少网络延迟和传输时间,提供更快的响应速度和更好的用户体验。

  2. 可扩展性:CDN采用分布式的架构,具有弹性和可伸缩性,能够处理大规模的并发请求和海量的数据流量。

  3. 可靠性:CDN通过在全球范围内部署多个服务器节点,提供冗余和负载均衡机制,降低因单一服务器故障而导致的服务中断或性能下降的风险。

  4. 优化传输:CDN通过使用多种优化技术,例如压缩、缓存、去重、预取等,减少网络带宽的使用和数据传输的成本,提高传输效率。

CDN的意义包括:

  1. 提升用户体验:CDN能够将内容就近分发到用户附近的边缘节点,减少数据传输的时间和网络延迟,提供更快速的页面加载和流畅的视频播放,提升用户的体验。

  2. 节约带宽成本:CDN通过利用边缘节点提供本地缓存和就近分发,减少原始服务器的负载压力和带宽消耗,降低数据传输的成本。

  3. 保障可靠性和高可用性:CDN通过在全球范围内部署多个服务器节点,在某一节点故障时可以自动切换到其他可用的节点,保障服务的可靠性和高可用性。

  4. 支持全球化业务扩展:对于全球化的企业和应用程序,CDN能够将内容分发到全球不同地区,提供低延迟、高可用性的服务,支持业务的扩展和用户的增长。

综上所述,CDN通过就近分发内容、优化传输、提升用户体验和节约带宽成本,对于提供高性能、可靠性和可扩展性的网络服务具有重要的意义。它在互联网应用、网站加速、流媒体传输等领域得到广泛应用。

57.移动端1像素的解决方案?

使用视口单位:使用视口单位(如vw和vh)来代替传统的像素单位。视口单位会根据视口的大小进行相对的计算,而不是根据设备的像素密度

使用CSS媒体查询:针对不同的设备像素密度使用不同的样式。你可以使用CSS媒体查询来检测设备的像素密度,并分别应用不同的样式。

使用JavaScript动态计算:通过JavaScript动态计算元素的像素大小,并根据设备的像素密度进行调整。你可以使用window.devicePixelRatio来获取设备的像素比例,然后根据不同的比例来设置元素的样式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值