前端面试题总结【持续更新···】

前端面试题总结

    • 1、说说你对react的理解?有哪些特性?
    • 2、说说React diff算法是怎么运作的?
    • 3、说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
    • 4、说说你对React中虚拟dom的理解?
    • 5、 说说你对react hook的理解?
    • 6、React组件之间如何通信?
    • 7、说说你对受控组件和非受控组件的理解?应用场景?
    • 8、说说Connect组件的原理是什么?
    • 9、说说react 中jsx语法糖的本质?
    • 10、说说你对redux中间件的理解?常用的中间件有哪些?实现原理?
    • 11、说说AMD、CMD、commonJS模块化规范的区别?
    • 12、说说package.json中版本号的规则?
    • 13、说说React jsx转换成真实DOM的过程?
    • 14、说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?
    • 15、React render方法的原理,在什么时候会触发?
    • 16、React性能优化的手段有哪些?
    • 17、如何通过原生js实现一个节流函数和防抖函数?
    • 18、说说你对koa中洋葱模型的理解?
    • 19、说说如何借助webpack来优化前端性能?
    • 20、说说你对webSocket的理解?
    • 21、说说Real DOM和Virtual DOM的区别?优缺点?
    • 22、说说React中setState和replaceState的区别?
    • 23、说说react中onClick绑定后的工作原理?
    • 24、Redux的实现原理,写出其核心实现代码?
    • 25、说说你对fiber架构的理解?解决了什么问题?
    • 26、什么是垂直外边距合并?说说合并后的几种情况?
    • 27、什么是强缓存和协商缓存?
    • 28、useEffect的依赖为引用类型如何处理?
    • 29、知道react里面的createPortal么,说说其使用场景?
    • 30、Provider和connect的底层原理实现,写出其核心代码?
    • 31、说说webpack中常见的loader?解决了什么问题?
    • 32、说说javascript内存泄漏的几种情况?
    • 33、说说React中setState执行机制?
    • 34、说说react的事件机制?
    • 35、如何使用css实现一个三角形?
    • 36、说说React生命周期中有哪些坑?如何避免?
    • 37、调和阶段setState干了什么?
    • 38、React合成事件的原理?
    • 39、为什么react元素有一个$$type属性 ?
    • 40、说说你对事件循环event loop的理解?
    • 41、前端跨域的解决方案?
    • 42、数组常用方法及作用
    • 43、for...in循环和for...of循环的区别?
    • 44、Js数据类型判断都有哪几种方式?至少说出5种?它们的区别是什么?
    • 45、说说你对Object.defineProperty()的理解?
    • 45、webpack中代码分割如何实现?
    • 46、props和state相同点和不同点?render方法在哪些情况下会执行?
    • 47、shouldComponentUpdate有什么作用?
    • 48、说说React中的虚拟dom?在虚拟dom计算的时候diff和key之间有什么关系?
    • 49、react新出来两个钩子函数是什么?和删掉的will系列有什么区别?
    • 50、React的props.children使用map函数来遍历会收到异常显示,为什么?应该 如何遍历?
    • 51、Vue组件之间通信的方式有哪些,能够说出七种及其以上的得满分,必须写出 具体的实现方式才可以?
    • 52、redux本来是同步的,为什么它能执行异步代码?实现原理是什么?中间件的 实现原理是什么?
    • 53、redux中同步action与异步action最大的区别是什么?
    • 54、redux-saga和redux-thunk的区别与使用场景?
    • 55、 在使用redux过程中,如何防止定义的action-type的常量重复?
    • 56、Vuex的实现原理是什么,写出其实现的核心代码?
    • 57、为什么for循环比forEach性能高?
    • 58 、React的路由的原理是什么,写出其实现的核心代码?
    • 59、说说reduce方法的作用?自己手动封装一个reduce,写出其核心代码?
    • 60、什么是发布订阅模式,写出其核心实现代码?
    • 61、移动端1像素的解决方案,写出其核心代码?
    • 62、说一说你对视口的理解?
    • 63、谈谈你对immutable.js的理解?
    • 64、CDN的特点及意义?
    • 65、![] == ![],![] == [],结果是什么?为什么?
    • 66、什么是闭包,应用场景是什么?
    • 67、谈谈你是如何做移动端适配的?
    • 68、弹性盒中的缩放机制是怎样的?
    • 69、说说你对TypeScript中泛型的理解及其应用场景?
    • 70、ReactDOM.render 是如何串连链接的?
    • 71、说说你对TypeScript装饰器的理解及其应用场景?
    • 72、React中”栈调和”Stack Reconciler过程是怎样的?
    • 73、说说你对nodejs 中间件的理解,如何封装一个node 中间件?
    • 74、说说Tcp为什么需要三次握手和四次挥手?
    • 75、说说webpack proxy的工作原理?为什么能够解决跨域?

1、说说你对react的理解?有哪些特性?

React是一个用于构建用户界面的JavaScript库,它由Facebook开发并开源。React采用声明式编程模型,将界面抽象为组件,可以方便地进行组件的组合和复用。React采用虚拟DOM技术,将数据和界面进行分离,通过高效的DOM Diff算法进行渲染,提高了渲染性能,同时也方便了开发和维护。
React的主要特性包括:
1、声明式编程模型:React采用声明式编程模型,将界面抽象为组件,通过组合不同的组件来构建复杂的界面,使得代码更加简洁、易于理解和维护。
2、虚拟DOM:React采用虚拟DOM技术,将数据和界面进行分离,通过高效的DOM Diff算法进行渲染,提高了渲染性能,同时也方便了开发和维护。
3、组件化:React将界面抽象为组件,每个组件封装了自己的状态和行为,可以方便地进行组合和复用,使得代码更加模块化、可扩展性更强。
单向数据流:React采用单向数据流的模型,将数据从父组件传递到子组件,确保了数据的一致性和可控性。
4、生态系统丰富:React拥有庞大的社区和丰富的生态系统,开发者可以方便地使用各种插件和库来扩展React的功能,如React-Router、React-Redux、Ant Design等。
总的来说,React是一个功能强大、易于使用、高效的JavaScript库,它的特性包括声明式编程模型、虚拟DOM、组件化、单向数据流等,使得开发者可以更加方便地构建高质量的用户界面。

2、说说React diff算法是怎么运作的?

React diff是React框架中用于比较前后两个虚拟DOM树的算法,它的原理是通过对比新旧虚拟DOM树的结构,找出需要更新的部分并进行最小化的更新,从而实现高效的更新UI。
React diff算法的具体流程如下:
首先比较两个根节点,如果节点类型不同,则直接替换整个节点及其子节点。
如果节点类型相同,则比较节点的属性,如果属性不同,则更新该节点的属性。
如果节点类型和属性都相同,则比较节点的子节点。
对于子节点,React diff算法采用了三种策略:
a. 如果子节点数量不同,则直接替换整个子节点列表。
b. 如果子节点类型不同,则直接替换该子节点及其子节点。
c. 如果子节点类型相同,则递归地对比子节点。
在比较子节点时,React diff算法会采用一些优化措施,例如给每个子节点添加唯一的key属性,以方便React diff算法进行快速查找和比较,从而减少不必要的更新。
总之,React diff算法通过对比虚拟DOM树的结构和属性,以及采用一些优化策略,实现了高效的更新UI,从而提高了React应用的性能和用户体验

3、说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?

React组件生命周期分为三个阶段:Mounting、Updating和Unmounting。下面分别介绍每个阶段对应的方法和功能。
Mounting(挂载阶段):组件第一次被创建和插入到DOM中。在这个阶段中,组件可以访问props和state,并可以在组件中进行一些初始化操作。Mounting阶段包括以下方法:
constructor:组件构造函数,在组件创建时调用,用于初始化组件的状态和属性。
getDerivedStateFromProps:在组件创建时和更新时都会被调用,用于根据新的props计算state的值,返回一个新的state对象。
render:用于渲染组件的UI,这是Mounting阶段最重要的方法。
componentDidMount:在组件挂载后立即调用,常用于进行异步数据请求或DOM操作等副作用操作。
Updating(更新阶段):组件在接收到新的props或state时被重新渲染。在这个阶段中,组件可以根据新的props或state进行重新渲染,并可以执行一些副作用操作。Updating阶段包括以下方法:
getDerivedStateFromProps:在组件创建时和更新时都会被调用,用于根据新的props计算state的值,返回一个新的state对象。
shouldComponentUpdate:在组件即将更新前被调用,用于判断组件是否需要重新渲染。默认情况下,组件每次更新都会重新渲染,但可以通过该方法实现性能优化。
render:用于渲染组件的UI。
getSnapshotBeforeUpdate:在组件更新前被调用,用于获取当前DOM状态,返回一个值作为componentDidUpdate的第三个参数。
componentDidUpdate:在组件更新后被调用,常用于执行一些更新后的操作。
Unmounting(卸载阶段):组件从DOM中被移除。在这个阶段中,组件可以进行一些清理操作,如清除定时器、取消订阅等。Unmounting阶段包括以下方法:
componentWillUnmount:在组件卸载前被调用,用于清理组件的副作用操作,如定时器、订阅等。
React还提供了一些其他的生命周期方法,如错误处理相关的生命周期方法和新的生命周期方法(如getDerivedStateFromError、getSnapshotBeforeUpdate等),这些方法都有特定的用途,可以根据需要进行使用。

4、说说你对React中虚拟dom的理解?

虚拟DOM(Virtual DOM)是React中的一种重要的概念,它是用JavaScript对象来描述真实DOM的一种数据结构。在React中,当状态发生改变时,React会先通过虚拟DOM算法计算出新的虚拟DOM树,然后通过比较新的虚拟DOM树和旧的虚拟DOM树之间的差异,最终只更新差异部分的真实DOM,从而实现高效的DOM更新。
在虚拟DOM计算的时候,diff和key是密切相关的概念。diff是指比较新旧虚拟DOM树之间的差异,找出需要更新的部分。而key是在虚拟DOM更新中用来辨识DOM节点的标识符,它可以帮助React准确地找到需要更新的DOM节点,从而提高DOM更新的效率。
在虚拟DOM计算过程中,如果没有给每个组件节点提供唯一的key,那么React会默认使用每个节点在虚拟DOM树中的索引作为key。这种情况下,如果新旧虚拟DOM树中的节点顺序发生了变化,那么React会认为这些节点都是不同的节点,从而重新创建和渲染这些节点,这样就会带来一定的性能问题。
因此,在虚拟DOM计算过程中,正确使用key是非常重要的。正确使用key可以帮助React准确地找到需要更新的DOM节点,避免不必要的DOM更新,提高DOM更新的效率。在使用key时,需要保证每个节点的key是唯一的,并且在同一层级的兄弟节点之间保持稳定,不要随意更改

5、 说说你对react hook的理解?

React Hooks是React 16.8版本引入的新特性,它可以让我们在不编写class组件的情况下,使用state和其他React特性。
React Hooks的引入解决了以下几个问题:

解决了组件逻辑复用的问题
在React之前,组件逻辑的复用通常是通过高阶组件、render props等方式来实现。而使用Hooks后,可以将组件逻辑封装成自定义Hook,然后在多个组件中进行复用,从而避免了高阶组件和render props等方式带来的代码冗余和复杂性。
解决了类组件的问题
类组件在处理复杂逻辑时,往往需要使用生命周期方法和state等特性,而这些特性在使用时需要考虑作用域、绑定this等问题。而使用Hooks后,可以在函数组件中使用state、生命周期方法等特性,避免了类组件的一些问题。
解决了代码复杂度的问题
在React之前,组件的业务逻辑通常被拆分成多个生命周期方法和方法之间的调用,从而导致代码复杂度的增加。而使用Hooks后,可以将组件的逻辑拆分成多个自定义Hook,从而避免了组件之间的耦合和代码冗余。
总的来说,React Hooks的引入可以让我们更方便地编写React应用程序,提高代码的复用性和可维护性。同时,Hooks还可以提高代码的可测试性,从而让我们更容易地编写高质量的React代码。

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

React 组件之间可以通过以下几种方式进行通信:
props:父组件可以通过 props 将数据传递给子组件。子组件可以通过 props 获取数据并进行渲染。如果需要改变传递的数据,可以在父组件中修改并重新传递给子组件。
回调函数:父组件可以将函数作为 props 传递给子组件,子组件可以在需要的时候调用该函数,并将需要传递的数据作为参数传递回父组件。
Context:Context 是 React 提供的一种跨组件层级传递数据的方法。父组件可以通过创建 Context 对象并传递给子组件,子组件可以通过该对象获取数据,无需通过 props 逐层传递。
共享状态:如果多个组件需要共享同一个状态,可以将该状态提升到共同的父组件中,并将其作为 props 传递给子组件。当状态发生改变时,父组件可以重新渲染,从而触发子组件的重新渲染。
Redux:Redux 是一种状态管理库,可以在整个应用程序中共享状态。通过将 Redux 状态与 React 组件结合使用,可以实现组件之间的高效通信和数据共享。
总的来说,React 组件之间的通信可以通过 props、回调函数、Context、共享状态和 Redux 等方式来实现。开发人员可以根据具体情况选择最适合的方式来实现组件之间的通信。

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

在HTML中,表单元素的标签的值改变通常是根据用户输入进行更新。
在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 进行更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为受控组件。
比如,给表单元素input绑定一个onChange事件,当input状态发生变化时就会触发onChange事件,从而更新组件的state
非受控组件指的是,表单数据由DOM本身处理。即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值。
在非受控组件中,可以使用一个ref来从DOM获得表单值
应用场景
受控组件使用场景:一般用在需要动态设置其初始值的情况。例如:某些form表单信息编辑时,input表单元素需要初始显示服务器返回的某个值然后进行编辑。
非受控组件使用场景:一般用于无任何动态初始值信息的情况。例如:form表单创建信息时,input表单元素都没有初始值,需要用户输入的情况

8、说说Connect组件的原理是什么?

Connect组件是React-Redux库中用于将React组件与Redux store连接的高阶组件。Connect组件的原理如下:
Connect组件通过React的上下文机制获取到Redux store,然后将store传递给子组件。
Connect组件根据传入的配置参数(如mapStateToProps、mapDispatchToProps等)从store中获取所需要的数据,并将这些数据作为props传递给子组件。
Connect组件会监听store的变化,当store发生变化时,会重新计算需要传递给子组件的props,并将这些新的props传递给子组件进行渲染。
Connect组件还会封装子组件的生命周期方法,以便在需要时触发store的更新操作。
总的来说,Connect组件的原理是通过获取Redux store并将其传递给子组件,从store中获取所需要的数据,并将这些数据作为props传递给子组件。同时,Connect组件还会监听store的变化,重新计算需要传递给子组件的props,并将这些新的props传递给子组件进行渲染。

9、说说react 中jsx语法糖的本质?

JSX(JavaScript XML)是一种语法糖,它是JavaScript的扩展,允许我们在JavaScript代码中编写类似于HTML的代码,以更加直观的方式描述页面的结构和内容。
JSX的本质是一个语法糖,它并不是一种新的语言或技术。它将HTML标签和JavaScript代码结合在一起,使用一种更加直观的方式来描述页面的结构和内容,从而提高了代码的可读性和可维护性。
JSX本质上是一种JavaScript的扩展,它可以被编译成常规的JavaScript代码,并被解释器执行。在编译过程中,JSX会被转换成React元素,这些元素用于描述页面的结构和内容。React会将这些元素转换成虚拟DOM树,并在需要更新页面时使用Diff算法来计算出哪些地方需要更新。
总的来说,JSX的本质是一种语法糖,它提供了一种更加直观和易于理解的方式来描述页面的结构和内容,同时也提高了代码的可读性和可维护性。

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

Redux中间件是Redux框架中用于处理异步操作和副作用的一种机制。它是一个函数,可以在Redux的dispatch过程中对action进行拦截和处理,从而实现对数据流的控制。
常用的Redux中间件包括:
redux-thunk:用于处理异步操作,允许action返回一个函数,该函数接收dispatch和getState两个参数,可以进行异步操作并调用dispatch更新状态。
redux-saga:用于处理复杂的异步操作,基于ES6的Generator函数实现,可以通过yield关键字实现异步操作的同步化。
redux-logger:用于记录Redux应用中的action和状态变化,方便开发调试。
redux-promise:用于处理异步操作,允许action返回一个Promise对象,可以等待Promise对象完成后再进行状态更新。
Redux中间件的实现原理是基于函数式编程的思想,即将多个函数组合在一起,形成新的函数。在Redux中,中间件函数可以接收store的dispatch和getState两个参数,同时返回一个新的函数,这个新的函数可以对action进行处理,然后调用原始的dispatch函数进行状态更新。
可以通过compose函数将多个中间件函数进行组合,形成一个中间件链,将每个中间件函数串联起来,形成一个新的dispatch函数。当调用这个新的dispatch函数时,中间件函数会按照顺序依次执行,从而完成对action的拦截和处理。
总之,Redux中间件是一种处理异步操作和副作用的机制,可以通过函数式编程的方式实现。常用的中间件包括redux-thunk、redux-saga、redux-logger和redux-promise等

11、说说AMD、CMD、commonJS模块化规范的区别?

AMD(Asynchronous Module Definition)、CMD(Common Module Definition)以及CommonJS都是JavaScript中常用的模块化规范,它们之间存在以下几个区别:
加载方式不同
AMD和CMD都是异步加载模块,它们使用define()函数来定义模块并且通过require()函数来加载模块。而CommonJS是同步加载模块,它使用require()函数来加载模块。
模块定义方式不同
AMD和CMD的模块定义方式比较类似,它们都是通过define()函数来定义模块,但是它们的依赖管理方式不同。AMD在define()函数中声明依赖关系,而CMD则是在函数体中通过require()函数来声明依赖关系。而CommonJS的模块定义方式则是通过module.exports来导出模块,通过require()函数来导入模块。
应用场景不同
AMD和CMD主要用于浏览器端的模块化开发,因为浏览器端需要异步加载模块,以避免阻塞页面加载。而CommonJS主要用于服务端的模块化开发,因为服务端不需要异步加载模块。

12、说说package.json中版本号的规则?

在package.json中,版本号是一个非常重要的属性,它用于指定当前项目的版本号,以便其他开发者和工具可以识别并使用该版本号。在npm中,版本号的规则如下:
版本号由三个数字组成:主版本号、次版本号和修订号,格式为x.y.z,例如1.2.3。
每次发布新版本时,必须更新版本号。如果只是修复了一些bug,可以将修订号加1。如果添加了一些新功能,但是向后兼容,可以将次版本号加1。如果添加了一些不向后兼容的改变,必须将主版本号加1,并将次版本号和修订号都重置为0。
版本号还可以包含一个预发布标识符和一个构建标识符,用于表示版本号的预发布状态和构建状态。预发布标识符以“-”开头,例如1.0.0-beta.1。构建标识符以“+”开头,例如1.0.0+build.1234。

^:只会执行不更改左边非零数字的更新
:如果写入的是0.13.0,则当运行npm update时i,会更新到补丁版本

:接收高于指定版本的任何版本
=:接受等于或者高于指定版本的任何版本
<=:接受等于或者低于指定版本的任何版本
<:接受低于指定版本的任何版本
=:接收确切的版本
-:接受一定范围的版本
||:组合集合
无符号:接收指定的特定版本
Latest:使用可以用的最高版本

13、说说React jsx转换成真实DOM的过程?

React 中的 JSX 语法是一种语法糖,在编译时会被转换成真实 DOM 元素。
下面是 JSX 转换成真实 DOM 的过程:
编写 JSX 代码:在 React 中通过编写 JSX 代码来描述组件的结构和样式。
解析 JSX 代码:使用 Babel 等工具将 JSX 代码转换为 JavaScript 代码。
创建 React 元素:将解析后的 JS 代码转化为 React 元素,React 元素是一个 JavaScript 对象,包含了组件的类型、属性和子元素。
创建虚拟 DOM:React 使用虚拟 DOM 来描述真实 DOM,将 React 元素转化为虚拟 DOM,虚拟 DOM 是一个纯 JavaScript 对象。
更新虚拟 DOM:当组件的状态发生变化时,React 会更新虚拟 DOM。
比较虚拟 DOM:React 会将新的虚拟 DOM 和旧的虚拟 DOM 进行比较,找出需要更新的部分。
渲染真实 DOM:将需要更新的部分渲染到真实 DOM 上,更新页面的显示。
需要注意的是,React 会尽可能地将需要更新的部分合并在一起,然后一次性更新,从而提高页面渲染的性能。同时,React 还使用了一些优化策略,例如 shouldComponentUpdate 和 PureComponent 等,来尽可能地减少不必要的 DOM 操作,提高应用的性能。

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

Redux Toolkit 是一个官方推出的工具集,它旨在简化 Redux 开发流程并提供一些常见用例的最佳实践。它提供了许多实用的函数和工具,如 createSlice、createAsyncThunk、createEntityAdapter 等,这些工具可以帮助开发者更快地编写 Redux 应用,并减少样板代码的量。
React-Redux 是将 Redux 和 React 应用程序结合起来的官方库。它提供了一个组件和一个钩子函数,以便将 Redux 存储中的状态映射到 React 组件的 props 中,并将 Redux 动作分派给对应的 Redux 存储。React-Redux 通过提供一个容器组件(connect 函数)和一个钩子函数(useSelector)来实现这一点。
Redux Toolkit 和 React-Redux 并不是互斥的,它们可以一起使用。Redux Toolkit 提供了一些工具和函数,可以更容易地编写和管理 Redux 存储,而 React-Redux 则提供了一些工具和函数,可以更容易地将状态映射到 React 组件中。两者的主要区别在于 Redux Toolkit 是一个提高 Redux 应用程序开发效率的工具集,而 React-Redux 是一个将 Redux 和 React 结合在一起的库。

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

React的render方法是用于渲染组件的方法,它的原理如下:
当组件的props或state发生变化时,React会调用组件的render方法,生成新的虚拟DOM树。
React会将新的虚拟DOM树与旧的虚拟DOM树进行比较,找出需要更新的部分。
React会将需要更新的部分转换为真实的DOM节点,并更新到页面上。
如果组件的props或state没有发生变化,则不会触发render方法,而是使用之前生成的虚拟DOM树进行渲染。
在React中,render方法是一个必须实现的方法,它负责生成组件的虚拟DOM树,并将其返回给React进行后续处理。React会根据虚拟DOM树的变化来更新页面上的内容,从而实现组件的动态渲染。
需要注意的是,当组件的props或state发生变化时,并不会立即触发render方法,而是会先进行一些优化,例如批量更新、异步更新等,以提高渲染性能。只有在必要时才会触发render方法,生成新的虚拟DOM树并进行渲染。

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

React性能优化主要分为以下几个方面:
1、使用PureComponent或React.memo:PureComponent是React内置的一个类组件,它会自动判断组件是否需要重新渲染,从而避免不必要的重复渲染。React.memo是一个高阶组件,用于优化函数组件的渲染性能。
2、使用shouldComponentUpdate:shouldComponentUpdate是React生命周期中的一个钩子函数,用于手动判断组件是否需要重新渲染。通过对组件的props和state进行比较,可以避免不必要的渲染。
3、避免不必要的渲染:在组件中尽量避免使用匿名函数或在render函数中创建新的对象,这会导致组件在每次渲染时都重新创建新的函数和对象,从而影响性能。
4、使用React.lazy和React.Suspense:React.lazy和React.Suspense是React16.6版本新增的特性,用于实现组件的懒加载,从而提高应用的初始加载速度。
5、使用分页、虚拟滚动等技术:对于大量数据的渲染,可以使用分页、虚拟滚动等技术,将数据按需加载,从而减少渲染的数量和时间。
6、使用Immutable.js或Immer.js:Immutable.js和Immer.js是两个流行的JavaScript库,用于处理不可变数据和简化状态管理,从而提高React应用的性能和可维护性。
总之,React性能优化是一个持续不断的过程,需要从多个方面入手,不断改进和优化应用的性能。

17、如何通过原生js实现一个节流函数和防抖函数?

节流函数

节流函数的实现思路是在一定时间内只执行一次函数,可以通过setTimeout来实现

function throttle(fn, delay) {
  let timer = null;
  return function() {
    const context = this;
    const args = arguments;
    if (!timer) {
      timer = setTimeout(function() {
        fn.apply(context, args);
        timer = null;
      }, delay);
    }
  }
}

防抖函数

防抖函数的实现思路是在一定时间内只执行最后一次函数,可以通过clearTimeout和setTimeout来实现。

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

18、说说你对koa中洋葱模型的理解?

Koa是一个基于Node.js的Web开发框架,它采用了一种称为“洋葱模型”的中间件执行方式,用于处理HTTP请求和响应。
洋葱模型的执行方式类似于一颗洋葱,每个中间件都包裹在整个请求/响应周期的外层,当请求进入时,会先经过第一个中间件的处理,然后再逐层向内执行,直到最后一个中间件处理完毕后,再从内层向外返回响应。在执行的过程中,每个中间件都可以对请求和响应进行修改,并将控制权交给下一个中间件
优点
简化了开发流程:使用中间件可以将复杂的业务逻辑分解成多个独立的模块,每个中间件只需要关注自己的任务,从而简化了开发流程。
可以灵活定制流程:由于中间件之间是独立的,因此可以根据实际需求灵活定制请求/响应的处理流程,从而适应不同的业务场景。
可以方便地扩展功能:由于中间件可以对请求和响应进行修改,因此可以方便地扩展框架的功能,例如添加日志、调试信息等。

19、说说如何借助webpack来优化前端性能?

Webpack是一个强大的前端打包工具,可以通过各种配置来优化前端性能,具体的优化方式如下:
代码压缩:使用UglifyJSPlugin对JS代码进行压缩,使用optimize-css-assets-webpack-plugin对CSS代码进行压缩,减小代码体积,提高网页加载速度。
代码分离:使用webpack的代码分离功能(如SplitChunksPlugin和dynamic import)将代码分为多个小块,实现按需加载,减少首屏加载时间。
按需加载:使用react-router的按需加载功能(如React.lazy和React.Suspense)对组件进行按需加载,减少首屏加载时间。
静态资源优化:使用file-loader和url-loader对图片和其他静态资源进行打包和优化,减少HTTP请求。
缓存优化:使用webpack的chunkhash和contenthash功能对文件进行版本控制,从而实现缓存优化,减少不必要的文件下载。
代码检查:使用eslint-loader对JS代码进行检查,避免低级错误和代码冗余,提高代码质量和性能。
预编译器优化:使用sass-loader和less-loader对CSS进行预编译,提高编写效率和代码复用性。
通过以上优化方式,可以有效地提高前端性能,减少加载时间,提高用户体验。同时,这些优化也可以提高开发效率和代码质量,减少维护成本

20、说说你对webSocket的理解?

WebSocket是一种基于TCP协议的全双工通信协议,可以在客户端和服务器之间建立实时的双向数据通信。与HTTP协议不同的是,WebSocket协议不需要像HTTP一样每次都建立新的连接,而是可以在客户端和服务器之间建立一次连接,然后保持连接状态,实现实时数据的双向传输。
WebSocket的特点包括以下几点:
双向通信:WebSocket可以实现实时的双向数据通信,客户端和服务器可以随时发送和接收数据。
长连接:WebSocket建立连接后可以保持连接状态,避免了HTTP协议中每次请求都需要建立新的连接的缺点。
实时性:WebSocket可以实时地传输数据,能够满足实时性要求的应用场景。
轻量级:WebSocket协议是一种轻量级的通信协议,数据传输的开销相对较小,对服务器的负载也较小。
WebSocket的应用场景比较广泛,例如在线聊天、实时游戏、股票行情等需要实时通信的应用。在实际开发中,可以使用WebSocket技术来实现这些应用的实时通信功能,提高用户体验和应用的实用性。

21、说说Real DOM和Virtual DOM的区别?优缺点?

Real DOM和Virtual DOM是两种不同的Dom处理方案,Real DOM是指浏览器中的真实Dom树由浏览器引擎解析HTML文档生成的每次更新都会重新生成整个DOM树在与旧的DOM树进行比较找出差异最后在进行渲染这种处理方式比较消耗性能因为每次更新都要重新渲染整个dom树渲染耗费的时间也比较多,就会导致页面加载过慢

Virtual DOM是一种轻量级的、存在于内存中的虚拟DOM树,由框架或库通过JS对象模拟真实的DOM,每次更新只需要比较新旧虚拟DOM树的差异,然后只对差异进行操作,最后再将差异渲染到真实的DOM树上。这种处理方式可以减少比较和渲染的次数,提高页面的加载速度和性能。

Virtual DOM的优点包括:

提高页面性能,减少DOM操作次数

提高开发效率,可以通过JS对象来操作DOM,而不需要直接操作真实的DOM

跨平台,可以在不同的平台上运行,如浏览器、移动端等

Virtual DOM的缺点包括:

需要额外的内存开销,因为需要在内存中维护一个虚拟DOM树

初次渲染需要较多的时间,因为需要将虚拟DOM树转换为真实的DOM树

可能会导致一些性能问题,因为虚拟DOM的比较算法可能会比真实DOM的操作更复杂

22、说说React中setState和replaceState的区别?

setState用于设置状态对象。nextState,将要设置的新状态,该状态会和当前的state合并
callback,可选参数,回调函数。该函数会在setState设置成功,且组件重新渲染后调用
合并nextState和当前state,并重新渲染组件。setState是react事件处理函数中和请求
回调函数中触发UI更新的主要方法。

注意: 不能在组件内部通过this.state修改状态,因为状态会在调用setState()后被代替。

replaceState()方法与setState()类似,但是方法只会保留nextState中状态,原state不在nextState中的状态都会被删除。

  • nextState,将要设置的新状态,该状态会替换当前的state。
  • callback,可选参数,回调函数。该函数会在replaceState设置成功,且组件重新渲染后调用。

23、说说react中onClick绑定后的工作原理?

当给组件(元素)绑定onClick事件之后

react会对事件先进行注册,将事件统一注册到document上
根据组件唯一的标识key来对事件函数进行存储

统一的指定dispatchEvent回调函数

储存事件回调

react会将click这个事件统一存到一个对象中,回调函数的存储采用键值对(key/value)的方式存储在对象中,key 是组件的唯一标识 id,value 对应的就是事件的回调函数,通过组件的key就能回调到相应的函数了

24、Redux的实现原理,写出其核心实现代码?

Redux 的实现原理可以分为三个部分:store、action 和 reducer。
Store:Store 是应用程序中存储状态的地方。它是一个 JavaScript 对象,包含所有应用程序状态的唯一来源。Redux 中的 Store 可以通过 createStore 方法创建。
Action:Action 是一个普通的 JavaScript 对象,用于描述发生了什么事件。它包含一个 type 属性和一些其他数据,用于更新应用程序状态。在 Redux 中,Action 可以通过 createAction 方法创建。
Reducer:Reducer 是一个纯函数,它接收当前状态和 Action 作为参数,并返回新的状态。它描述了如何处理 Action 并更新应用程序状态。在 Redux 中,Reducer 可以通过 createReducer 方法创建。

// createStore 方法用于创建 Store
function createStore(reducer, initialState) {
  let state = initialState;
  let listeners = [];

  function getState() {
    return state;
  }

  function subscribe(listener) {
    listeners.push(listener);
    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  function dispatch(action) {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
    return action;
  }

  dispatch({ type: '@@redux/INIT' });

  return {
    getState,
    subscribe,
    dispatch,
  };
}

// createAction 方法用于创建 Action
function createAction(type, payload) {
  return { type, payload };
}

// createReducer 方法用于创建 Reducer
function createReducer(initialState, handlers) {
  return function reducer(state = initialState, action) {
    if (handlers.hasOwnProperty(action.type)) {
      return handlers[action.type](state, action);
    } else {
      return state;
    }
  };
}

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

Fiber架构是一种基于协程的异步编程框架,它可以在单个线程中实现高并发,避免了传统多线程编程中的上下文切换开销和锁竞争问题,从而提高了程序的性能和吞吐量。
Fiber架构解决了传统多线程编程中的一些问题,例如:
上下文切换开销:在传统多线程编程中,线程之间的切换需要保存和恢复线程的上下文,这会带来较大的开销。而Fiber架构中,协程的切换只需要保存和恢复协程的上下文,开销较小。
锁竞争问题:在传统多线程编程中,多个线程对共享资源的访问可能会产生锁竞争问题,降低程序的性能。而Fiber架构中,协程之间的访问是非抢占式的,不会产生锁竞争问题。
线程资源利用率:在传统多线程编程中,为了充分利用多核CPU的性能,需要创建大量的线程,但线程的创建和销毁也会带来额外的开销。而Fiber架构中,只需要一个线程就可以实现高并发,避免了线程创建和销毁的开销。
总之,Fiber架构通过协程的方式解决了传统多线程编程中的一些问题,提高了程序的性能和吞吐量,使得编写高效且可维护的异步程序变得更加容易。

26、什么是垂直外边距合并?说说合并后的几种情况?

外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距。
合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。
实际工作中,垂直外边距合并问题常见于第一个子元素的margin-top会顶开父元素与父元素相邻元素的间距,而且只在标准浏览器下产生问题,IE下反而表现良好。

相邻块元素垂直外边距的合并

嵌套块元素垂直外边距的合并

解决方法:

父盒子可以加个边框
用overflow(加了这句话在浏览器中可以看到也是自动加了1px的边框)。
可以为父元素定义上内边距

27、什么是强缓存和协商缓存?

强缓存和协商缓存都是浏览器缓存机制中的一种。
强缓存是指在缓存期间,浏览器直接使用本地缓存,不向服务器发送请求。可以通过设置HTTP响应头中的Cache-Control和Expires字段来控制强缓存的过期时间。当缓存未过期时,浏览器直接从本地缓存中获取资源,这样能够有效减少网络请求,提升页面的加载速度。
协商缓存是指在缓存期间,浏览器向服务器发送请求,服务器根据请求头中的If-Modified-Since和If-None-Match字段来判断资源是否需要更新。如果资源没有被更新,则服务器返回304 Not Modified状态码,浏览器继续使用本地缓存。如果资源已经被更新,则服务器返回最新的资源,浏览器更新本地缓存并使用最新的资源。
相比较而言,强缓存的效率更高,因为浏览器直接从本地缓存中获取资源,不需要向服务器发送请求。但是它的缺点在于,如果资源更新了,客户端也无法及时得到更新后的版本,需要等到缓存过期才能获取最新的资源。
协商缓存虽然需要向服务器发送请求,但是它可以更加精确地判断资源是否需要更新,如果资源没有更新,客户端可以直接使用本地缓存,否则只需要更新修改的部分。这样能够有效地减少资源的传输量,提高网站的性能和用户体验。

28、useEffect的依赖为引用类型如何处理?

在React中,useEffect的依赖项是一个数组,用于指定哪些状态变量发生变化时会触发useEffect中的回调函数。当依赖项是引用类型时,需要注意以下几点:
不要直接修改引用类型。因为直接修改引用类型,即使值没有发生变化,也会被视为发生了变化。正确的做法是使用不可变性的方式来更新引用类型,例如使用扩展运算符或concat方法来创建新的数组或对象。
如果引用类型的值会发生变化,可以使用useState等Hook来创建新的引用类型来触发useEffect的回调函数。例如,可以使用useState来创建一个标记变量,当引用类型的值发生变化时,修改标记变量的值来触发useEffect的回调函数。
如果引用类型的值是不可变的,可以使用JSON.stringify方法将其转换为字符串,然后将字符串作为依赖项。这样可以确保只有值发生变化时才会触发useEffect的回调函数。但需要注意,这种方法对于大型数据结构或频繁发生变化的数据结构可能会影响性能。
综上所述,处理引用类型的依赖项需要注意避免直接修改引用类型,使用不可变性的方式更新引用类型,并选择合适的依赖项来触发useEffect的回调函数。

29、知道react里面的createPortal么,说说其使用场景?

createPortal是React提供的一个API,用于将组件渲染到指定的DOM节点上。在React中,组件的渲染通常是从父组件逐级传递到子组件,最终渲染到DOM树上。而使用createPortal可以实现跨组件层级渲染,将组件渲染到DOM树上的某个指定节点上,而不是从父组件逐级传递到子组件。
createPortal的使用场景比较广泛,常见的如:
模态框:将模态框组件渲染到body节点上,避免被其他组件的样式影响。
悬浮组件:将悬浮组件渲染到特定的节点上,如固定在页面右下角的悬浮按钮。
通知组件:将通知组件渲染到特定的节点上,如右上角的消息提示框。
画布组件:将画布组件渲染到特定的节点上,如游戏画布或音视频播放器。
总之,createPortal的使用场景主要是在需要跨组件层级渲染组件时使用,能够提高组件的灵活性和可复用性。

30、Provider和connect的底层原理实现,写出其核心代码?

React-Redux提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。

Provider组件为了定义业务逻辑,需要给出下面两方面的信息。

输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。

connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。

一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。

React-Redux 提供Provider组件,可以让容器组件拿到state。

Provider的核心代码如下:

class Provider extends Component {
  getChildContext() {
    return { store: this.props.store };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: PropTypes.object.isRequired
};

connect的核心代码如下:

function connect(mapStateToProps, mapDispatchToProps) {
  return function(WrappedComponent) {
    return class extends Component {
      static contextTypes = {
        store: PropTypes.object.isRequired
      };
      constructor(props, context) {
        super(props, context);
        this.state = mapStateToProps(context.store.getState(), props);
      }
      componentDidMount() {
        this.unsubscribe = context.store.subscribe(() => {
          this.setState(mapStateToProps(context.store.getState(), this.props));
        });
      }
      componentWillUnmount() {
        this.unsubscribe();
      }
      render() {
        return (
          <WrappedComponent
            {...this.props}
            {...mapStateToProps(context.store.getState(), this.props)}
            {...mapDispatchToProps(context.store.dispatch, this.props)}
          />
        );
      }
    }
  }
}

31、说说webpack中常见的loader?解决了什么问题?

Webpack中常见的Loader主要包括以下几个:

Babel-loader:将ES6、TypeScript等高级语言转换为浏览器可以识别的JavaScript代码,解决了不同浏览器对JavaScript语言支持不同的问题。

CSS-loader:将CSS文件中的样式转换为JavaScript模块,方便在JavaScript代码中引用,并解决了浏览器对不同CSS特性的兼容性问题。

Style-loader:将CSS模块的样式插入到HTML页面的标签中,使样式生效。

File-loader/url-loader:将图片、字体等文件打包成单个文件,方便在浏览器中加载和使用。

Vue-loader:将Vue单文件组件转换为JavaScript模块,方便在应用程序中使用Vue框架。

这些Loader主要解决了前端开发中代码转换、资源管理等问题。通过使用Loader,我们可以将不同类型的文件转换为可执行的JavaScript、CSS等代码,并将所有资源打包成单个文件,方便在浏览器中加载和使用。这样可以大大提高前端开发的效率和代码质量。

32、说说javascript内存泄漏的几种情况?

avaScript内存泄漏是指不再使用的内存没有被及时释放,导致内存占用过高,最终导致程序运行缓慢或崩溃。常见的几种情况如下:
全局变量:在全局作用域中声明的变量没有及时释放,导致占用内存过高。解决方法是使用let或const关键字声明变量,避免变量污染全局作用域。
闭包:在函数内部使用闭包时,如果没有及时释放引用,会导致内存泄漏。解决方法是在函数执行完毕后,手动解除引用,或使用let关键字声明变量。
定时器:在使用setTimeout或setInterval等定时器时,如果没有及时清除定时器,会导致内存泄漏。解决方法是在定时器执行完毕后,手动清除定时器。
DOM元素:在使用document.createElement等DOM操作时,如果没有及时清除DOM元素,会导致内存泄漏。解决方法是在不需要使用DOM元素时,手动清除DOM元素。
循环引用:在对象之间存在循环引用时,如果没有及时解除引用,会导致内存泄漏。解决方法是使用WeakMap等内置对象来避免循环引用。
总之,要避免JavaScript内存泄漏,需要注意内存管理和垃圾回收机制,及时释放不再使用的内存,避免占用过高的内存空间。

33、说说React中setState执行机制?

在 React 中,setState 是用于更新组件状态的方法。它是异步执行的,因为 React 希望在进行多次状态更新时,将其批量执行以提高性能。

当我们调用 setState 时,React 会将新的状态合并到当前状态中,然后计划一次更新操作。更新操作将在当前代码执行结束后异步执行。这意味着调用 setState 后,如果我们立即打印状态,可能看到的是当前状态而不是新的状态。

在处理更新时,React 会首先比较新旧状态,检查它们是否相同。如果新旧状态相同,则不会执行任何操作。否则,React 将计划重新渲染组件并将新状态应用于组件。

值得注意的是,在某些情况下,React 可能会在 setState 中同步执行更新,而不是异步执行。这通常发生在使用 refs 或在生命周期方法中调用 setState 时。在这种情况下,我们需要小心处理更新,以避免出现问题

34、说说react的事件机制?

React 的事件机制其实使用了 事件委托 的方式,React 在内部自己实现了浏览器中对应事件的合成事件,web 浏览器中,浏览器在生成虚拟 dom 树的时候,
解析出的合成事件挂载到 document 上,部分事件仍然在 dom 元素上,事件的实现是插件的机制,方便后续拓展事件。

35、如何使用css实现一个三角形?

可以使用CSS的border属性和transparent属性实现三角形效果。具体步骤如下:

<div class="triangle"></div>
使用CSS设置该元素的样式,设置宽高为0,同时设置border属性,其中border-width为0,其他三个方向的border-width不为0,例如:

.triangle {
  width: 0;
  height: 0;
  border-width: 50px;
  border-style: solid;
  border-color: transparent transparent red transparent;
}

在上面的代码中,设置了三角形的宽高为0,同时设置了border-width为50px,这将会产生一个大小为50px的三角形。然后设置了三角形的边框样式为实线(solid),并设置了颜色为透明、透明、红色、透明,这样就可以产生一个红色的三角形。

最后,通过设置CSS的transform属性来调整三角形的位置和方向,例如:

.triangle {
  width: 0;
  height: 0;
  border-width: 50px;
  border-style: solid;
  border-color: transparent transparent red transparent;
  transform: rotate(45deg);
}

在上面的代码中,通过设置transform: rotate(45deg)来将三角形旋转45度,使其成为一个等边三角形。
通过上述步骤,就可以使用CSS实现一个三角形效果。需要注意的是,产生的三角形只是一个视觉上的效果,并不是一个真正的三角形元素。

36、说说React生命周期中有哪些坑?如何避免?

避免生命周期中的坑需要做好两件事:不在恰当的时候调用了不该调用的代码;2、在需要调用时,不要忘了调用

getDerivedStateFromProps 容易编写反模式代码,使受控组件和非受控组件区分模糊
componentWillMount 在 React 中已被标记弃用,不推荐使用,主要的原因是因为新的异步架构会导致它被多次调用,所以网络请求以及事件绑定应该放到 componentDidMount 中
componentWillReceiveProps 同样也被标记弃用,被 getDerivedStateFromProps 所取代,主要原因是性能问题。
shouldComponentUpdate 通过返回 true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。
componentWillUpdate 同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合 getSnapshotBeforeUpdate 与 componentDidUpdate 改造使用。
如果在 componentWillUnmount 函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug。
如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加

37、调和阶段setState干了什么?

代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。
经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面;
在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染;
在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染

38、React合成事件的原理?

React合成事件是React中处理原生DOM事件的一种方式,它是基于浏览器的事件模型封装的一层抽象,提供了一种跨浏览器的事件处理机制。
React合成事件的原理如下:
React在顶层上注册了事件处理程序,处理所有的事件,并且在事件处理程序中处理了所有的异常,防止事件的异常中断了整个应用程序的运行。
当浏览器触发一个事件时,React会在事件池中创建一个合成事件对象,并将原生事件封装到合成事件对象中。
合成事件对象中包含了原生事件对象的所有信息,例如事件类型、目标元素、鼠标位置、按键状态等。
React会将合成事件对象传递给事件处理程序,而不是直接将原生事件对象传递给事件处理程序。
在事件处理程序中,可以通过访问合成事件对象来获取事件的信息,而不需要关心浏览器的差异性。
React使用事件委托的方式来处理事件,即将事件绑定在顶层元素上,然后通过事件冒泡机制在组件树中传递,这样可以减少绑定事件的数量,提高性能。
总的来说,React合成事件的原理是通过封装原生事件对象来提供一种跨浏览器的事件处理机制,并使用事件委托的方式来处理事件,提高性能。

39、为什么react元素有一个$$type属性 ?

React元素有一个 t y p e 属性,这是 R e a c t 内部用来标识组件类型的属性,它与 t y p e 属性不同。元素的 t y p e 属性是一个字符串,用于表示元素的类型,可以是原生 D O M 元素的标签名,也可以是自定义组件的类型,例如“ d i v ”、“ s p a n ”、“ M y C o m p o n e n t ”等。而 type属性,这是React内部用来标识组件类型的属性,它与type属性不同。 元素的type属性是一个字符串,用于表示元素的类型,可以是原生DOM元素的标签名,也可以是自定义组件的类型,例如“div”、“span”、“MyComponent”等。 而 type属性,这是React内部用来标识组件类型的属性,它与type属性不同。元素的type属性是一个字符串,用于表示元素的类型,可以是原生DOM元素的标签名,也可以是自定义组件的类型,例如divspanMyComponent等。而type属性是React内部用来区分不同类型组件的属性,它是一个内部属性,用户通常不需要使用它。 t y p e 属性的值是一个 S y m b o l 类型的值,它可以确保在不同的 J a v a S c r i p t 环境中具有唯一性。 R e a c t 使用 type属性的值是一个Symbol类型的值,它可以确保在不同的JavaScript环境中具有唯一性。 React使用 type属性的值是一个Symbol类型的值,它可以确保在不同的JavaScript环境中具有唯一性。React使用type属性来进行组件类型比较和优化,例如在shouldComponentUpdate生命周期方法中,React会比较新旧组件的 t y p e 属性,来确定是否需要进行更新操作。在一些高级应用场景下,用户也可以使用 type属性,来确定是否需要进行更新操作。在一些高级应用场景下,用户也可以使用 type属性,来确定是否需要进行更新操作。在一些高级应用场景下,用户也可以使用type属性来自定义组件的类型,以实现更高级的功能。
需要注意的是,由于$$type属性是React内部使用的属性,用户不应该直接使用它来判断组件类型或进行其他操作,而应该使用React提供的API来进行操作。

40、说说你对事件循环event loop的理解?

事件循环(Event Loop)是JavaScript运行时(runtime)中的一种执行模型,用于处理异步任务和事件回调。JavaScript是单线程语言,只能在一个事件循环中执行一个任务,所以事件循环的机制非常重要。
事件循环的流程如下:
JavaScript引擎首先从执行栈中取出当前要执行的任务,执行该任务。
如果当前任务中包含异步任务(如setTimeout、setInterval、Promise等),则将这些任务挂起,并注册对应的回调函数,然后继续执行后续的任务。
当异步任务完成时,将该任务对应的回调函数放入任务队列(Task Queue)中。
当执行栈中所有的任务都执行完毕后,JavaScript引擎会检查任务队列是否有任务,如果有,则将队列中的第一个任务取出,并放入执行栈中执行。
重复上述步骤,不断循环执行,直到任务队列为空或程序被终止。
需要注意的是,任务队列分为宏任务(Macrotask)和微任务(Microtask)两种类型。宏任务包括所有的异步任务、定时器任务和事件回调任务等,而微任务则包括Promise的resolve、reject、finally等回调函数。
在事件循环中,微任务具有高优先级,会优先于宏任务执行。也就是说,当执行栈中的任务执行完毕后,JavaScript引擎会先执行所有的微任务,然后再执行宏任务。这种机制保证了JavaScript代码的执行顺序和可靠性,避免了异步任务执行的不确定性。

41、前端跨域的解决方案?

通过jsonp跨域 document.domain + iframe跨域 location.hash + iframe window.name + iframe跨域 postMessage跨域 跨域资源共享(CORS) nginx代理跨域 nodejs中间件代理跨域 WebSocket协议跨域

42、数组常用方法及作用

JavaScript中数组是一种非常常用的数据结构,提供了丰富的方法来操作和处理数组元素。以下是15个常用的数组方法及其作用:
push():向数组末尾添加一个或多个元素,并返回新的数组长度。
pop():从数组末尾移除并返回一个元素。
unshift():向数组开头添加一个或多个元素,并返回新的数组长度。
shift():从数组开头移除并返回一个元素。
slice():返回一个数组的一部分,不会改变原数组。
splice():向或从数组中添加或移除元素,会改变原数组。
concat():将两个或多个数组合并成一个新数组,不会改变原数组。
join():将数组中的所有元素转换为字符串,并连接在一起。
reverse():将数组中的元素顺序颠倒,会改变原数组。
sort():将数组中的元素按照指定的顺序排序,会改变原数组。
indexOf():返回数组中某个元素的索引,如果不存在则返回-1。
lastIndexOf():返回数组中某个元素最后出现的位置的索引,如果不存在则返回-1。
map():对数组中的每个元素进行操作,并返回一个新的数组。
filter():从数组中筛选出符合条件的元素,并返回一个新的数组。
reduce():将数组中的元素累加或合并成一个值,并返回该值。
需要注意的是,数组的方法有些会改变原数组,有些则会返回新的数组或其他值,使用时需要根据实际情况选择适合的方法。

43、for…in循环和for…of循环的区别?

for…in 循环:只能获得对象的键名,不能获得键值
for…in循环有几个缺点
  ①数组的键名是数字,但是for…in循环是以字符串作为键名“0”、“1”、“2”等等。
  ②for…in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
  ③某些情况下,for…in循环会以任意顺序遍历键名。
  for…in循环主要是为遍历对象而设计的,不适用于遍历数组。
  
for…of 循环:允许遍历获得键值
for…of循环
  ①有着同for…in一样的简洁语法,但是没有for…in那些缺点。
  ②不同于forEach方法,它可以与break、continue和return配合使用。
  ③提供了遍历所有数据结构的统一操作接口

44、Js数据类型判断都有哪几种方式?至少说出5种?它们的区别是什么?

  1. typeof判断
    typeof返回的类型都是字符串形式
  2. Constructor
    实例constructor属性指向构造函数本身
    constructor 判断方法跟instanceof相似,但是constructor检测Object与instanceof不一样,constructor还可以处理基本数据类型的检测,不仅仅是对象类型
  3. Instanceof
    instanceof可以判类型是否是实例的构造函数
    instanceof 后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。
  4. Object.prototype.toString.call()
    判断类型的原型对象是否在某个对象的原型链上
  5. 通过object原型上的方法判断
    比如array.isArray()来判断是不是一个数组
  6. ===(严格运算符)
    通常出现在我们的条件判断中,用来判断数据类型的话就会非常的有局限性,比如判断一个变量是否为空,变量是否为数据等

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

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

该方法接受三个参数

第一个参数是 obj:要定义属性的对象,
第二个参数是 prop:要定义或修改的属性的名称或 Symbol,
第三个参数是 descriptor:要定义或修改的属性描述符
函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符。

数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。
存取描述符是由 getter 函数和 setter 函数所描述的属性。
一个描述符只能是这两者其中之一;不能同时是两者。

这两种同时拥有下列两种键值:

configurable: 是否可以删除目标属性或是否可以再次修改属性的特性(writable, configurable, enumerable)。设置为true可以被删除或可以重新设置特性;设置为false,不能被可以被删除或不可以重新设置特性。默认为false。

enumerable: 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false。

45、webpack中代码分割如何实现?

Webpack中的代码分割是通过将代码拆分成更小的块,以便在运行时按需加载。这可以减少初始加载时间并提高应用程序的性能。Webpack支持两种类型的代码分割:同步和异步。

同步代码分割
同步代码分割是指在构建时将代码拆分成更小的块。这样可以将应用程序初始加载时间分散到多个块中。在Webpack中,可以使用SplitChunksPlugin插件来实现同步代码分割。该插件将重复模块拆分成一个单独的文件,使得多个模块之间可以共享这个文件,从而减少了初始加载时间。

异步代码分割
异步代码分割是指在运行时将代码拆分成更小的块。这样可以在需要时按需加载块,而不是在初始加载时一次性加载所有代码。在Webpack中,可以使用import()函数或dynamic import语法来实现异步代码分割。这些函数和语法允许你在需要时异步加载代码块。

例如,下面的代码将使用import()函数来异步加载一个模块:

import("./module").then(module => {
  // 使用模块
});

Webpack将根据需要将模块打包到单独的文件中,并在需要时异步加载它们。

异步代码分割还可以使用Webpack的optimization.splitChunks选项来进一步配置。该选项允许你指定哪些模块应该被拆分成单独的块,以及如何命名这些块。

总之,Webpack中的代码分割可以通过同步和异步两种方式实现,它可以减少初始加载时间并提高应用程序的性能。

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

props和state是React中两个非常重要的概念,它们用于管理组件的数据和状态
相同点
都是用于管理组件的数据和状态。
都可以触发组件的重新渲染。
都是React组件的内部状态,外部无法直接访问和修改。
不同点
数据来源不同:props通常是由父组件传递给子组件的,而state通常是组件内部自己维护的。
可修改性不同:props是只读的,不能在组件内部直接修改,而state是可读写的,可以在组件内部修改。
更新方式不同:props的更新通常是由父组件触发的,而state的更新通常是由组件自身触发的。
render执行时机
组件第一次挂载时会执行render方法。
组件的props或state发生变化时会执行render方法。
父组件重新渲染时,子组件也会重新渲染,因此render方法也会执行。
强制更新组件时,render方法也会执行。
总之,render方法是React组件中非常重要的一个方法,它用于根据当前组件的props和state生成对应的UI。在组件的生命周期中,render方法会在多个时机被执行,以保证组件的渲染和更新。

47、shouldComponentUpdate有什么作用?

shouldComponentUpdate是React中的一个生命周期方法,它的作用是控制组件是否需要重新渲染。
当组件的props或state发生变化时,React会调用shouldComponentUpdate方法,该方法默认返回true,表示组件需要重新渲染。但是,在某些情况下,组件并不需要重新渲染,这时可以在shouldComponentUpdate方法中进行一些判断,返回false表示组件不需要重新渲染,从而提高程序的性能和效率。
shouldComponentUpdate方法可以接收两个参数:nextProps和nextState,分别表示组件即将接收到的props和state。在shouldComponentUpdate方法中,开发者可以根据当前的props和state以及即将接收到的nextProps和nextState进行一些比较和判断,以决定是否需要重新渲染组件。
shouldComponentUpdate方法的作用包括:
提高程序的性能和效率:通过控制组件是否需要重新渲染,可以避免不必要的渲染,从而提高程序的性能和效率。
避免不必要的数据传递:如果组件不需要重新渲染,就意味着不需要重新计算和传递数据,从而避免了不必要的数据传递和计算。
优化用户体验:通过避免不必要的渲染和数据传递,可以减少页面的闪烁和卡顿,从而提高用户的体验和满意度。
总之,shouldComponentUpdate是React中一个非常重要的生命周期方法,它可以用于控制组件是否需要重新渲染,提高程序的性能和效率,避免不必要的数据传递,优化用户体验

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

虚拟DOM(Virtual DOM)是React中的一种重要的概念,它是用JavaScript对象来描述真实DOM的一种数据结构。在React中,当状态发生改变时,React会先通过虚拟DOM算法计算出新的虚拟DOM树,然后通过比较新的虚拟DOM树和旧的虚拟DOM树之间的差异,最终只更新差异部分的真实DOM,从而实现高效的DOM更新。
在虚拟DOM计算的时候,diff和key是密切相关的概念。diff是指比较新旧虚拟DOM树之间的差异,找出需要更新的部分。而key是在虚拟DOM更新中用来辨识DOM节点的标识符,它可以帮助React准确地找到需要更新的DOM节点,从而提高DOM更新的效率。
在虚拟DOM计算过程中,如果没有给每个组件节点提供唯一的key,那么React会默认使用每个节点在虚拟DOM树中的索引作为key。这种情况下,如果新旧虚拟DOM树中的节点顺序发生了变化,那么React会认为这些节点都是不同的节点,从而重新创建和渲染这些节点,这样就会带来一定的性能问题。
因此,在虚拟DOM计算过程中,正确使用key是非常重要的。正确使用key可以帮助React准确地找到需要更新的DOM节点,避免不必要的DOM更新,提高DOM更新的效率。在使用key时,需要保证每个节点的key是唯一的,并且在同一层级的兄弟节点之间保持稳定,不要随意更改。

49、react新出来两个钩子函数是什么?和删掉的will系列有什么区别?

React类组件中新出来的两个生命周期函数是getDerivedStateFromProps和getSnapshotBeforeUpdate。
区别:
在组件更新阶段,getDerivedStateFromProps是在render方法之前调用,用于更新组件的state状态。而getSnapshotBeforeUpdate是在组件的DOM更新之前调用,可以获取到组件更新前的状态。
getDerivedStateFromProps钩子函数是一个静态方法,不可以访问组件实例的this对象,只能通过传入的参数获取当前组件的状态和属性。而getSnapshotBeforeUpdate方法可以访问组件实例的this对象。
will系列的生命周期函数已经被标记为过时,不建议再使用,而getDerivedStateFromProps和getSnapshotBeforeUpdate是React16.3以后新增的生命周期函数,可以在类组件中使用
总的来说,getDerivedStateFromProps和getSnapshotBeforeUpdate的使用场景比will系列更加明确和安全,但需要注意使用时的细节。

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

在React中,使用map函数来遍历props.children时,可能会收到异常显示。这是因为props.children不一定是数组类型,有可能是字符串、数字、布尔值、null或undefined等非数组类型,如果尝试对非数组类型使用map函数来遍历,就会出现异常。
为了遍历props.children,可以使用React.Children.map函数来遍历。React.Children.map函数可以遍历props.children,并对每个子元素执行指定的回调函数。该函数的语法如下:
React.Children.map(children, function[(thisArg)])
其中,children是要遍历的子元素,function是要执行的回调函数,thisArg是可选的回调函数中的this对象。

import React from "react";

function Example(props) {
  return (
    <div>
      {React.Children.map(props.children, (child, index) => {
        return <li key={index}>{child}</li>;
      })}
    </div>
  );
}

export default Example;

在上面的代码中,React.Children.map函数遍历props.children,并将每个子元素都包装在一个li标签中返回。注意,要为每个子元素添加唯一的key属性,以便React能够正确地识别每个子元素的身份。
总的来说,使用React.Children.map函数可以遍历props.children,并对每个子元素执行指定的回调函数,避免了使用map函数遍历props.children时出现的异常显示问题。

51、Vue组件之间通信的方式有哪些,能够说出七种及其以上的得满分,必须写出 具体的实现方式才可以?

1、Props
2、Event/EventBus
3、Vuex(状态管理模式)
4、 e m i t / o n 5 、 o n 5 、 o n 5 、 r e f s 6 、 P r o v i d e / I n j e c t 7 、 emit / o n 5 、 on 5、on5、refs 6、Provide / Inject 7、 emit/on5on5on5refs6Provide/Inject7parent / c h i l d r e n 其他可能的方式还包括: 8 、 children 其他可能的方式还包括: 8、 children其他可能的方式还包括:8attrs / l i s t e n e r s 9 、 W a t c h e r s / C o m p u t e d p r o p e r t i e s 10 、 listeners 9、Watchers / Computed properties 10、listeners9、Watchers/Computedproperties10、root
11、LocalStorage / SessionStorage
12、Third-party libraries (e.g. PubSubJS)

52、redux本来是同步的,为什么它能执行异步代码?实现原理是什么?中间件的 实现原理是什么?

Redux 是一个状态管理库,它提供了一种将应用程序的状态和行为分离的方法。在 Redux 中,状态被保存在一个中央存储库中,并且只能通过派发 action 来修改。action 是一个包含 type 和 payload 的简单对象,它描述了应用程序中发生的事件。Reducer 函数被用来处理 action 并更新状态,从而改变应用程序的行为。
在 Redux 中,所有的 action 和 reducer 都是同步的,这意味着它们会立即执行,不会阻塞应用程序的运行。但是,在实际开发中,有些操作需要异步地执行,比如从服务器获取数据或执行动画效果。为了支持这些异步操作,Redux 提供了一种叫做中间件(Middleware)的机制。
中间件是一种拦截和处理 action 的函数,它可以在 action 到达 reducer 之前或之后执行一些额外的操作。中间件能够实现异步操作的原因是因为它们可以返回一个函数而不是一个 action 对象。这个函数可以在某些条件下(比如在服务器响应返回后)再次派发一个新的 action,从而更新应用程序的状态。
中间件的实现原理是基于函数的柯里化和高阶函数的概念。中间件本质上是一个接收 store 对象和 next 函数作为参数的函数,它返回一个接收 action 对象并返回结果的函数。在这个过程中,中间件可以修改 action 对象,调用 next 函数并传递修改后的 action 对象,或者直接返回一个新的 action 对象。通过这种方式,中间件能够拦截和处理 action,并将它们转换成不同的形式。
Redux 的中间件机制是一个非常强大的功能,它可以让开发者在不修改应用程序原有代码的情况下,实现异步操作、日志记录、错误处理、性能优化等各种功能。同时,Redux 的中间件机制也是一个非常灵活和可扩展的机制,开发者可以根据自己的需求自定义中间件,并将它们串联起来,以实现复杂的应用程序逻辑。

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

Redux 中同步 action 和异步 action 最大的区别在于它们返回的对象不同。
同步 action 返回一个包含 type 和 payload 的简单对象,用于描述发生的事件和需要更新的状态,例如

const incrementCounter = () => ({
  type: 'INCREMENT_COUNTER',
  payload: 1
})

异步 action 返回一个函数,函数可以在某些条件下(例如服务器响应返回后)再次派发一个新的 action,从而更新应用程序的状态,例如:

const fetchUsers = () => {
  return (dispatch) => {
    dispatch({ type: 'FETCH_USERS_REQUEST' })
    fetch('https://api.example.com/users')
      .then(response => response.json())
      .then(data => dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data }))
      .catch(error => dispatch({ type: 'FETCH_USERS_FAILURE', payload: error }))
  }
}

在这个例子中,异步 action fetchUsers 返回了一个函数,这个函数接收一个 dispatch 函数作为参数。这个函数在内部发起了一个 API 请求,并在请求成功或失败后分别派发了一个新的 action 来更新应用程序的状态。

总之,同步 action 和异步 action 的最大区别在于它们返回的对象不同,前者是一个简单对象,后者是一个函数。异步 action 可以在函数内部进行异步操作,等待异步操作完成后再发起新的 action 来更新状态。这个特性是 Redux 可以用来处理异步操作的重要机制。

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

redux-saga和redux-thunk都是Redux中常用的中间件,用于处理异步操作和副作用。它们的区别和使用场景主要包括以下几点:
工作原理
redux-thunk是Redux官方提供的中间件,它通过在action中使用函数来处理异步操作。当dispatch一个函数类型的action时,redux-thunk会将这个函数执行,并将dispatch和getState作为参数传递给这个函数,从而实现异步操作。
redux-saga是一个基于Generator函数的中间件,它通过在saga文件中编写Generator函数来处理异步操作。saga文件包含了一系列的Generator函数,每个Generator函数可以监听一个或多个action,并在监听到相应的action时执行相应的操作。
处理异步操作的能力
redux-thunk只能处理简单的异步操作,例如发送AJAX请求等。当异步操作比较复杂时,redux-thunk的代码可能会变得非常冗长。
redux-saga具有更强大的异步操作处理能力,它可以处理复杂的异步操作,例如异步的流程控制、错误处理、取消操作等。使用redux-saga可以将异步操作的逻辑与组件的逻辑分离开来,使代码更加清晰易于维护。
使用场景
redux-thunk适用于处理简单的异步操作,例如发送AJAX请求、获取数据等。当异步操作比较复杂时,redux-thunk的代码可能会变得非常冗长,不易于维护。
redux-saga适用于处理复杂的异步操作,例如流程控制、错误处理、取消操作等。使用redux-saga可以将异步操作的逻辑与组件的逻辑分离开来,使代码更加清晰易于维护。

总的来说,redux-thunk和redux-saga都是Redux中常用的中间件,用于处理异步操作和副作用。它们的区别在于工作原理、处理异步操作的能力和使用场景。使用redux-thunk适合处理简单的异步操作,而使用redux-saga适合处理复杂的异步操作。

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

在Redux中,定义Action-Type的常量是一种良好的编程习惯,可以提高代码的可读性和可维护性。为了防止定义的Action-Type常量重复,可以使用一些工具来帮助自动生成唯一的常量,例如使用uuid或者shortid等库。
具体来说,可以按照以下步骤来防止定义的Action-Type常量重复:
安装uuid或者shortid等库:可以使用npm或者yarn等包管理工具进行安装,例如:npm install uuid。
生成唯一的Action-Type常量:可以使用uuid或者shortid等库生成唯一的Action-Type常量,例如:
import uuid from ‘uuid’;
const ADD_TODO = ADD_TODO_${uuid.v4()};
这样生成的ADD_TODO常量就是唯一的,可以避免与其他常量重复。
统一管理Action-Type常量:可以将所有的Action-Type常量都统一放在一个文件中进行管理,例如:

// ActionTypes.js
import uuid from 'uuid';

export const ADD_TODO = `ADD_TODO_${uuid.v4()}`;
export const REMOVE_TODO = `REMOVE_TODO_${uuid.v4()}`;
export const EDIT_TODO = `EDIT_TODO_${uuid.v4()}`;

// 其他常量…
这样可以方便地进行管理和维护,同时也避免了常量重复的问题。

总之,为了防止定义的Action-Type常量重复,可以使用一些工具来帮助自动生成唯一的常量,例如使用uuid或者shortid等库。同时,将所有的Action-Type常量统一放在一个文件中进行管理也是一个良好的编程习惯。

56、Vuex的实现原理是什么,写出其实现的核心代码?

Vuex 是 Vue.js 官方的状态管理工具,它基于 Flux 和 Redux 架构思想,用于管理应用中的所有组件的状态。
Vuex 的实现原理可以简单概括为:通过一个全局的 Store 对象来存储整个应用的状态,同时定义一些 Mutation 和 Action 函数,用于修改状态和触发异步操作。组件可以通过 Getter 函数来获取状态,并通过 Mutation 和 Action 函数来修改状态。

下面是一个简单的 Vuex 实现的核心代码:

// 定义一个 Store 类
class Store {
  constructor(options) {
    // 初始化 state
    this.state = options.state || {};

    // 初始化 mutations 和 actions
    this.mutations = options.mutations || {};
    this.actions = options.actions || {};

    // 绑定 this
    this.commit = this.commit.bind(this);
    this.dispatch = this.dispatch.bind(this);
  }

  // 提交 mutation
  commit(type, payload) {
    const mutation = this.mutations[type];
    if (mutation) {
      mutation(this.state, payload);
    }
  }

  // 分发 action
  dispatch(type, payload) {
    const action = this.actions[type];
    if (action) {
      action(this, payload);
    }
  }
}

// 定义一个 install 方法,用于在 Vue 中注册 Store 实例
function install(Vue, store) {
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        this.$store = this.$options.store;
      } else if (this.$parent && this.$parent.$store) {
        this.$store = this.$parent.$store;
      }
    },
  });
}

// 导出 Store 和 install
export default Store;
export { install };

在上面的代码中,我们定义了一个 Store 类,用于存储整个应用的状态,并提供了 commit 和 dispatch 方法,用于修改状态和触发异步操作。在 commit 方法中,我们通过 Mutation 函数来修改状态;在 dispatch 方法中,我们通过 Action 函数来触发异步操作。
同时,我们还定义了一个 install 方法,用于在 Vue 中注册 Store 实例,这样就可以在组件中通过 this.$store 来访问全局的状态和操作。

总之,Vuex 的实现原理是非常简单的,它通过一个全局的 Store 对象来存储整个应用的状态,并通过 Mutation 和 Action 函数来修改状态和触发异步操作,从而实现了应用中状态的统一管理。

57、为什么for循环比forEach性能高?

在JavaScript中,for循环比forEach性能高的原因主要有以下几点:
作用域链
在JavaScript中,每个函数都有一个作用域链,当访问变量时,JavaScript引擎会沿着作用域链逐层查找,直到找到该变量为止。而forEach方法中的回调函数是一个闭包,会在每次循环迭代中创建一个新的作用域,从而影响性能。
函数调用
forEach方法中的回调函数会对每个元素都执行一次,这意味着会进行多次函数调用。而for循环中的代码只需要在循环内部执行一次,从而减少了函数调用的次数,提高了性能。
长度计算
在forEach方法中,需要通过数组的length属性来计算需要迭代的次数。而在for循环中,可以通过将数组的length属性存储在一个变量中来避免重复计算,从而提高性能。
综上所述,for循环比forEach性能高的原因主要是因为for循环避免了闭包、函数调用和重复计算长度等问题。但是,在使用for循环时,需要注意避免出现死循环和无限循环的情况,以避免造成性能问题。

58 、React的路由的原理是什么,写出其实现的核心代码?

React的路由基于浏览器自带的history API,可以使用pushState和replaceState方法来改变URL并更新浏览器的历史记录。React中常用的路由库是react-router,它提供了BrowserRouter、HashRouter和MemoryRouter三种路由器组件,分别基于HTML5 History API、Hash 和内存实现路由。下面是一个使用BrowserRouter的简单示例:


```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;

ReactDOM.render(
  <Router>
    <div>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
      </ul>
      <hr />
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
    </div>
  </Router>,
  document.getElementById('root')
);

上述代码中,我们使用了BrowserRouter路由器组件和Route路由匹配组件来实现路由。BrowserRouter接受一个basename属性,可以指定根路径,如果不指定,则默认为/。Route接受一个path属性和一个component属性,表示当当前路径与path属性匹配时,渲染对应的组件。在Link组件中使用to属性来指定链接的路径。
当用户点击链接时,Link组件会生成一个新的URL并使用history.pushState方法改变浏览器的历史记录。BrowserRouter组件会监听popstate事件,并根据当前的URL匹配Route组件,渲染对应的组件。

59、说说reduce方法的作用?自己手动封装一个reduce,写出其核心代码?

reduce() 方法是JavaScript中数组的方法之一,它可以用来迭代数组并执行指定的操作,将结果汇总为单个值。该方法接收一个回调函数作为参数,该回调函数可以自定义对每个数组元素的操作,并将结果累积到一个累加器变量中,最后返回累加器的值。

reduce() 方法的基本语法如下:

arr.reduce(callback[, initialValue])

其中,callback函数可以接收4个参数:

accumulator:累加器变量,存储执行结果的累积值。
currentValue:当前元素的值。
currentIndex:当前元素的索引。
array:原数组。
initialValue参数是可选的,用于指定累加器变量的初始值。如果不提供该参数,reduce() 方法将使用数组的第一个元素作为累加器的初始值。

下面是一个使用reduce()方法计算数组元素总和的示例代码:

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}, 0);
console.log(sum); // Output: 15

在这个例子中,我们首先定义了一个包含5个数字的数组,然后使用reduce()方法对该数组进行迭代,累加所有元素的值,最后得到它们的总和。

接下来,我们手动封装一个reduce()方法,以更好地理解它的实现原理。

Array.prototype.myReduce = function(callback, initialValue) {
  let accumulator = initialValue === undefined ? undefined : initialValue;
  for (let i = 0; i < this.length; i++) {
    if (accumulator !== undefined) {
      accumulator = callback.call(undefined, accumulator, this[i], i, this);
    } else {
      accumulator = this[i];
    }
  }
  return accumulator;
}

在这个封装的方法中,我们使用了相似的迭代循环来遍历数组元素,并根据传入的回调函数和累加器变量来计算结果。需要注意的是,在每次迭代时,我们使用了callback.call()方法来调用回调函数,以确保回调函数中的this指向undefined。

最后,我们返回累加器变量的值,即为reduce()方法的计算结果。

总之,reduce()方法是一种十分有用的数组方法,它可以对数组元素进行累加、计算、过滤等操作,并将结果汇总为单个值。通过手动封装该方法,我们能够更好地理解它的实现原理,并加深对JavaScript语言的理解。

60、什么是发布订阅模式,写出其核心实现代码?

发布订阅模式(Publish-Subscribe Pattern)是一种常见的软件设计模式,用于解耦事件的发布者和订阅者,使它们能够独立地演化。在该模式中,发布者和订阅者不直接相互作用,而是通过一个中介对象(通常称为消息队列或事件总线)来进行通信。当发布者触发一个事件时,中介对象会将该事件通知所有订阅者,订阅者则可以选择接收或忽略该事件。

// 定义一个事件中心(或消息总线)对象
const eventBus = {
  topics: {},
  subscribe: function(topic, callback) {
    if (!this.topics[topic]) {
      this.topics[topic] = [];
    }
    this.topics[topic].push(callback);
  },
  publish: function(topic, data) {
    if (!this.topics[topic]) {
      return;
    }
    this.topics[topic].forEach(callback => callback(data));
  }
};

// 定义一个订阅者函数
function subscriber(data) {
  console.log(`Received data: ${data}`);
}

// 订阅事件
eventBus.subscribe('topic1', subscriber);

// 发布事件
eventBus.publish('topic1', 'Hello world!');

在这个例子中,我们首先定义了一个事件中心对象(eventBus),它包含了一个topics对象用于存储所有事件的回调函数。事件中心对象还提供了subscribe()和publish()方法,用于订阅和发布事件。
接下来,我们定义了一个订阅者函数(subscriber),它用于处理收到的数据。
最后,我们订阅了一个名为"topic1"的事件,并发布了一个数据为"Hello world!"的事件。当事件被发布时,事件中心对象会将该事件通知所有订阅者,订阅者会自动接收到该事件并执行相应的回调函数。
这就是一个简单的发布订阅模式的实现。该模式的核心思想是将事件的发布者和订阅者解耦,使它们能够独立演化,从而提高了程序的灵活性和可维护性。

61、移动端1像素的解决方案,写出其核心代码?

在移动端开发中,1像素的问题是一个比较普遍的问题,由于不同设备的像素密度不同,所以1像素的线在高像素密度的设备上会显得比较粗,影响页面的美观性和用户体验。为了解决1像素的问题,可以采用以下几种解决方案:

使用CSS3的scale属性:可以通过CSS3的scale属性将1像素的线条缩小到0.5像素,从而达到更细的效果,例如:

.line {
  position: relative;
  border-top: 1px solid #000;
  transform: scaleY(0.5);
  transform-origin: 0 0;
}

这样可以将1像素的线条缩小到0.5像素,从而达到更细的效果。

使用伪元素:可以通过在元素的before或after伪元素中添加1像素的线条,并将伪元素的高度设置为0.5像素,从而达到更细的效果,例如:

.line::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 100%!;(MISSING)
  height: 1px;
  background-color: #000;
  transform: scaleY(0.5);
  transform-origin: 0 0;
}

这样可以将1像素的线条缩小到0.5像素,并将伪元素的高度设置为0.5像素,从而达到更细的效果。

使用viewport单位:可以通过使用viewport单位来实现1像素效果,例如:

.line {
  position: relative;
  border-top: 1px solid #000;
  border-top-width: 0.01rem; /* 1px / 100 = 0.01rem */
}

这样可以将1像素的线条的宽度设置为0.01rem,从而达到更细的效果。

总之,在移动端开发中,1像素的问题是一个比较普遍的问题,可以通过使用CSS3的scale属性、伪元素和viewport单位等方法来解决。开发者在使用这些方法时,需要考虑不同设备的像素密度和适配方法,以便更好地实现1像素效果。

62、说一说你对视口的理解?

视口可以分为布局视口,视觉视口,理想视口
布局视口
也是理解成一个盒子,但和pc端不同的是,它是固定的,即不会变大变小。 浏览器厂商针对移动端设备设计了一个容器,
没有设置 的情况下
会先用这个容器去承装pc网页,这个容器一般是980px。
视觉视口
用户可见的区域,绝对宽度永远和设备屏幕一样宽怎么理解呢,打个比喻:布局视口就像是在显微镜下的观察器皿,里面的元素就是各种被观察物,而视觉视口正是显微镜,一直只有那么点地方给我们看,但是我们可以放大缩小,正因为观察器皿没有变化,所以观察物不会被移动,这就是移动端元素不会因为放大缩小错位的原因。
理想视口
没有设置为非标准理想视口优点:不同设备呈现效果几乎一样,因为是通过布局容器等比缩放的缺点:元素太小,页面文字不清楚,用户体验不好就是上面介绍的等比缩放过来的页面
设置了符合理想视口标准优点:页面清晰展现,内容不再小到难以观察,用户体验较好缺点:同一个元素不同屏幕(设备)呈现效果不一样解决:做适配
主要通过 来设置
该meta标签的作用是让当前布局视口的宽度等于设备的宽度,同时不允许用户手动缩放。

63、谈谈你对immutable.js的理解?

Immutable.js是Facebook开源的一个JavaScript库,它提供了一组不可变数据结构和操作方法,用于管理和操作不可变的数据。它的主要特点包括:
不可变性:Immutable.js中的数据结构和操作都是不可变的,即一旦创建就无法修改,任何修改操作都会返回一个新的数据结构。
函数式编程:Immutable.js中的操作方法都是纯函数,不会修改原始数据,而是返回一个新的数据结构。
高效性能:Immutable.js中的数据结构和操作都是高效的,可以减少不必要的内存分配和GC。
Immutable.js的主要应用场景包括:
React应用:由于React中推崇不可变数据的概念,因此Immutable.js可以作为React应用的数据管理工具,避免因可变数据引起的问题。
数据缓存:由于Immutable.js中的数据结构是不可变的,因此可以方便地进行数据缓存,避免重复计算和数据传递。
函数式编程:由于Immutable.js中的操作方法都是纯函数,因此可以方便地进行函数式编程和函数组合。

总之,Immutable.js是一个非常有价值的JavaScript库,它提供了一组不可变数据结构和操作方法,可以用于管理和操作不可变的数据,避免因可变数据引起的问题,同时也可以方便地进行数据缓存、函数式编程等操作。但是,由于Immutable.js的学习曲线较陡峭,开发者在使用时需要花费一些时间去理解和掌握。

64、CDN的特点及意义?

CDN(Content Delivery Network)即内容分发网络,是一种通过在全球范围内部署节点服务器来加速内容传输的技术。CDN的特点和意义主要包括以下几点:
高速访问
CDN可以将网站的静态资源如图片、音视频、JavaScript和CSS等文件缓存在全球各地的节点服务器上,当用户请求访问这些资源时,可以就近从最优的缓存服务器上获取,从而实现更快的访问速度。
提高网站稳定性
CDN可以将网站的访问流量分散到不同的缓存服务器上,从而分散了网站的访问压力。当某个节点服务器因故障或访问量过大时,CDN可以自动切换到其他可用的节点服务器上,从而提高网站的稳定性。
节省带宽成本
CDN可以将网站的内容缓存在全球各地的节点服务器上,当用户请求访问这些资源时,可以就近从最优的缓存服务器上获取,从而减少了网站的带宽消耗,节省了带宽成本。
提高用户体验
CDN可以提高网站的访问速度和稳定性,从而提高用户的访问体验,降低了用户的等待时间,增加了用户的满意度。

总的来说,CDN的特点和意义在于通过在全球范围内部署节点服务器来加速内容传输,提高网站的访问速度和稳定性,节省带宽成本,提高用户体验,从而实现更好的网站性能和更高的用户满意度。

65、![] == ![],![] == [],结果是什么?为什么?

表达式![]会将空数组[]转换为布尔值,并取它的相反值,即![]的值为false。同理,![]的值也是false。 因此,![] == ![]的结果为true。

这是因为在JavaScript中,空数组[]被当作一个对象来处理,对象在被转换为布尔值时总是为true。因此,![]的值为false。

另外,由于!运算符的优先级低于==运算符,因此![] == []会先将![]的值计算出来,然后再进行比较。因为两个false是相等的,所以![] == ![]的结果为true。

66、什么是闭包,应用场景是什么?

闭包是指在函数内部创建一个新的作用域,并且该作用域可以访问函数外部的变量。简单来说,闭包就是函数和函数内部能访问到的变量的组合。
闭包的应用场景有很多,其中一些比较常见的包括:
封装变量:由于闭包可以访问函数外部的变量,因此可以使用闭包来封装私有变量,避免变量被外部访问和修改,从而保证程序的安全性和稳定性。
实现模块化:由于闭包可以创建独立的作用域,因此可以用闭包来实现模块化的开发方式,将变量和方法封装在一个闭包中,从而避免命名冲突和变量污染。
延迟执行:由于闭包可以访问函数外部的变量,因此可以用闭包来实现某些需要延迟执行的操作,例如setTimeout等。
缓存变量:由于闭包可以访问函数外部的变量,因此可以用闭包来缓存一些计算结果,避免重复计算,提高程序的性能。
实现回调函数:由于闭包可以访问函数外部的变量,因此可以用闭包来实现一些回调函数的功能,例如事件处理函数等。

总之,闭包是一种非常重要的JavaScript特性,它可以用于实现很多常见的编程需求,例如封装变量、实现模块化、实现回调函数等。但是,由于闭包会占用内存和资源,因此开发者在使用闭包时需要注意内存管理和性能优化。

67、谈谈你是如何做移动端适配的?

移动端适配是Web开发中非常重要的一环,因为不同的移动设备具有不同的屏幕尺寸、分辨率和像素密度,如果不进行适配,页面可能会出现布局错乱、字体过小、图片模糊等问题。在移动端适配中,我通常采用以下几种方法:
使用Viewport:Viewport是一种显示网页内容的区域,它的大小可以随着设备的屏幕尺寸和方向而变化。在移动端适配中,可以通过设置Viewport的meta标签来控制页面的缩放比例,例如:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

这样可以让页面自适应设备的屏幕尺寸,并保持页面的比例不变。
使用媒体查询:媒体查询是CSS3中的一个功能,它可以根据设备的屏幕尺寸和方向来动态改变页面的样式,例如:
@media screen and (max-width: 768px) { /* 在屏幕宽度小于等于768px时应用的样式 */ }
这样可以针对不同的设备尺寸和方向设置不同的样式,从而实现移动端适配。
使用rem布局:rem是相对于根元素(即html元素)的字体大小来计算的单位,它的大小不会受到设备像素密度的影响。在移动端适配中,可以将根元素的字体大小设置为设备宽度的一定比例,例如:

html { font-size: calc(100vw / 7.5); /* 以7.5rem为基准 */ }

这样可以根据设备的屏幕宽度来动态调整字体大小和布局,从而实现移动端适配。

总之,移动端适配是Web开发中非常重要的一环,可以通过使用Viewport、媒体查询和rem布局等方法来实现适配。开发者在使用这些方法时,需要充分了解不同设备的特点和适配方法,以便更好地实现移动端适配。

68、弹性盒中的缩放机制是怎样的?

弹性盒(Flexbox)是一种用于布局的CSS模块,它提供了一种强大的缩放机制,使得容器和项目可以根据可用空间进行缩放和分配。弹性盒中的缩放机制主要包括以下几个方面:
容器的缩放
在弹性盒中,容器可以根据可用空间进行缩放。如果子元素的宽度或高度总和小于容器的宽度或高度,则容器会根据子元素的大小进行缩放,并将子元素按比例分配剩余空间。如果子元素的宽度或高度总和大于容器的宽度或高度,则容器会按比例缩小子元素的大小,使它们可以适应容器的大小。
项目的缩放
在弹性盒中,项目可以根据容器的大小进行缩放。如果项目的flex属性的值为1,则项目会根据剩余空间进行缩放,并按比例分配剩余空间。如果项目的flex属性的值为2,则项目会根据剩余空间进行缩放,并分配的空间是flex属性值之间的比例关系。
最小宽度和最大宽度
在弹性盒中,可以通过设置min-width和max-width属性来限制项目的宽度。如果项目的宽度小于min-width,则项目会根据容器的大小进行缩放,直到达到min-width为止。如果项目的宽度大于max-width,则项目会按比例缩小,直到达到max-width为止。
对齐方式
在弹性盒中,可以通过设置justify-content和align-items属性来控制项目的对齐方式。justify-content属性用于控制项目在主轴上的对齐方式,align-items属性用于控制项目在交叉轴上的对齐方式。

总的来说,弹性盒提供了一种强大的缩放机制,可以根据容器的大小和子元素的大小进行缩放和分配。在弹性盒中,可以通过设置flex属性、min-width和max-width属性、对齐方式等来控制项目的大小和对齐方式。

69、说说你对TypeScript中泛型的理解及其应用场景?

泛型是 TypeScript 中非常重要的一部分,它可以让我们在编写代码时使用一些通用的类型,从而提高代码的复用性和可读性。
泛型的本质是参数化类型,它允许我们在定义函数、类或接口时使用一个占位符类型,这个占位符类型在使用时才会被具体化。这样,我们就可以在一些通用的场景下使用相同的代码,而不需要针对不同的类型重复编写不同的代码。

比如,我们可以定义一个通用的 Identity 函数,它可以接收一个任意类型的参数,
并返回相同类型的结果:

function identity<T>(arg: T): T {
  return arg;
}

let output1 = identity<string>("hello"); // output1 类型为 string
let output2 = identity<number>(42); // output2 类型为 number

这里的就是泛型占位符,表示我们可以使用任何类型作为函数的参数类型,并返回相同类型的结果。在调用 identity 函数时,我们可以使用不同的类型参数,例如 string 或 number。

泛型在 TypeScript 中的应用场景非常广泛,例如:

容器类(如数组、栈、队列等)的实现;
对象映射(如字典)的实现;
函数式编程中的函数组合和柯里化;
可以用于扩展其他类或接口,提高代码的可扩展性;
可以用于编写通用的类型,提高代码的复用性。
总之,泛型是 TypeScript 中非常重要的一部分,它可以让我们编写更加通用、灵活和可读性更高的代码,使我们的代码更加健壮和可扩展。

70、ReactDOM.render 是如何串连链接的?

ReactDOM.render() 方法是将组件渲染到页面上的核心方法。当执行 ReactDOM.render() 方法时,React 会创建一个根节点,然后将根组件渲染到这个根节点上。在渲染的过程中,React 会使用一种叫做 Fiber 的数据结构来描述组件树,然后使用一种叫做协调(reconciliation)的算法来决定如何更新 DOM。
协调算法的核心思想是将渲染过程分为两个阶段:协调阶段和提交阶段。在协调阶段,React 会创建一棵 Fiber 树,并将它与前一次的 Fiber 树进行比较,以确定哪些组件需要进行更新,哪些组件需要被删除,以及哪些组件需要被插入。在生成 Fiber 树的过程中,React 还会为每个 Fiber 节点打上标记,用来指示这个节点需要进行何种操作。
在提交阶段,React 会执行一系列 DOM 操作,来将组件渲染到页面上。具体来说,React 会将所有需要更新的组件分成若干个批次(batch),并将每个批次的组件放在一个单独的链表中。然后,React 会按照批次的顺序依次处理每个组件,以完成页面的渲染。在处理每个组件时,React 会执行以下三种操作之一:
插入操作:将新的组件插入到页面上。
更新操作:更新已有的组件。
删除操作:从页面上删除已有的组件。
在执行每个操作时,React 还会执行一些优化策略,来减少 DOM 操作的次数和影响。比如,React 会使用 Diff 算法来比较前后两个组件的状态,并只更新发生变化的部分。另外,React 还会使用一些技术,比如批处理和分时处理,来优化渲染过程的性能。
通过使用 Fiber 数据结构和协调算法,React 能够高效地处理大量组件的渲染,并在渲染过程中保证 DOM 操作的正确性。同时,React 还能够根据不同的场景调整批次的大小和优先级,从而在不同的环境中获得最佳的性能和用户体验。

71、说说你对TypeScript装饰器的理解及其应用场景?

TypeScript装饰器是一种特殊的声明,它可以被附加到类声明、方法、属性或参数上,用来改变类的行为。在TypeScript中,装饰器是一种实验性的特性,可以在开启experimentalDecorators选项的情况下使用。
装饰器可以被用来实现很多功能,比如日志记录、性能统计、错误报告、权限控制、代码注入等等。以下是几个常见的应用场景:
类型检查和类型推断:装饰器可以通过注解的方式提供类型信息,帮助TypeScript进行类型检查和类型推断。比如,@Component装饰器可以提供组件的元数据,从而让TypeScript能够对组件进行类型检查和类型推断。
日志记录和性能统计:装饰器可以被用来在方法调用前后记录日志信息和统计性能信息。比如,@Log装饰器可以在方法调用前后输出日志信息,@Performance装饰器可以在方法调用前后计时并输出性能信息。
权限控制和验证:装饰器可以被用来对方法进行权限控制和验证。比如,@Role(‘admin’)装饰器可以限制只有管理员才能调用某个方法,@Validate装饰器可以对方法参数进行验证。

下面是一个简单的例子,展示了如何使用装饰器实现一个日志记录的功能:

function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const method = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Entering ${key}`);
    const result = method.apply(this, args);
    console.log(`Exiting ${key}`);
    return result;
  };
  return descriptor;
}

class Calculator {
  @log
  add(x: number, y: number) {
    return x + y;
  }
}

const calculator = new Calculator();
console.log(calculator.add(1, 2)); // 输出 "Entering add"、"Exiting add" 和 3

上述代码中,我们定义了一个log装饰器,它可以在方法调用前后输出日志信息。我们将@log装饰器应用到Calculator类的add方法上,从而实现了日志记录的功能。当我们调用add方法时,会输出"Entering add"和"Exiting add"的日志信息。

需要注意的是,装饰器在TypeScript中还是一个实验性的特性,可能会在未来的版本中有所变动。在实际应用中,需要谨慎使用装饰器,并根据需要选择合适的方案。

72、React中”栈调和”Stack Reconciler过程是怎样的?

React的"栈调和"(Stack Reconciler)是指一种基于深度优先遍历的算法,用于比较虚拟DOM(Virtual DOM)的变化并更新实际DOM。下面是该过程的详细步骤:
1、开始遍历虚拟DOM树,比较当前节点的类型和属性是否相同。
2、如果当前节点相同,则继续比较子节点。如果子节点也相同,则不需要做任何操作,直接进入下一个兄弟节点。
3、如果当前节点不同,但是有相同的兄弟节点,继续比较兄弟节点,直到找到相同的节点或者没有相同的兄弟节点。
4、如果找到相同的节点,则将该节点移动到正确的位置,更新属性和子节点。
5、如果没有找到相同的节点,则将该节点插入到正确的位置,并更新属性和子节点。
6、如果当前节点有旧节点但是没有新节点,将旧节点从实际DOM中移除。
7、遍历完所有节点后,更新实际DOM,并触发必要的生命周期方法和事件回调函数。
在这个过程中,React使用了一些优化策略,例如:在比较子节点时,会使用“key”属性来减少不必要的移动操作;在节点比较时,会使用类型判断等技巧来提高效率等等。

总之,"栈调和"是React实现高效的虚拟DOM比较和更新的核心算法之一,通过这种算法,React能够在最短的时间内更新实际DOM,提高了应用程序的性能和用户体验。

73、说说你对nodejs 中间件的理解,如何封装一个node 中间件?

在Node.js中,中间件(middleware)是用于处理HTTP请求和响应的功能模块。它们可以在请求到达路由处理程序之前或之后执行一些特定的操作,例如身份验证、日志记录、错误处理等。
中间件通常由一系列函数组成,每个函数都有访问请求对象(req)、响应对象(res)和下一个中间件函数(next)的参数。它可以修改请求或响应对象,执行某些任务,然后将控制权传递给下一个中间件函数。

以下是一个简单的示例,展示了如何封装一个Node.js中间件:

// loggerMiddleware.js

// 中间件函数
function logger(req, res, next) {
  console.log(`[${new Date().toLocaleString()}] ${req.method} ${req.url}`);
  next(); // 将控制权传递给下一个中间件函数
}

module.exports = logger;

在上面的示例中,我们定义了一个名为logger的中间件函数。它将请求的方法和URL记录到控制台,并将控制权传递给下一个中间件函数。注意,next()函数的调用是必需的,以便将控制权传递给下一个中间件。

要在Node.js应用程序中使用这个中间件,可以将其添加到路由处理程序之前:

// app.js

const express = require('express');
const loggerMiddleware = require('./loggerMiddleware');

const app = express();

// 使用中间件
app.use(loggerMiddleware);

// 定义路由处理程序
app.get('/', (req, res) => {
  res.send('Hello, World!');
});

// 启动服务器
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

在上面的示例中,我们通过调用app.use(loggerMiddleware)将loggerMiddleware中间件添加到应用程序中。这样,在每个请求到达根路径(‘/’)之前,loggerMiddleware中间件都会被调用。

这只是一个简单的示例,实际中间件可以执行更复杂的任务,例如身份验证、错误处理、响应修改等。通过编写自己的中间件函数,你可以根据应用程序的需求封装不同的功能。

74、说说Tcp为什么需要三次握手和四次挥手?

TCP需要进行三次握手来建立连接,主要是为了保证数据传输的可靠性和安全性。具体原因如下:
防止已失效的连接请求报文段重复发送造成服务端的错误处理。
防止客户端发送的连接请求报文段在网络中长时间滞留,等待服务端响应,从而浪费网络资源。
防止服务端发送的连接应答报文段在网络中长时间滞留,导致客户端无法及时确认连接状态。
确认双方的接收能力和发送能力正常,确保连接的可靠性和稳定性。
TCP需要进行四次握手来关闭连接,主要是为了确保数据传输的完整性和可靠性。具体原因如下:
防止已失效的数据报文段在连接关闭后重复传输。
确保客户端和服务端都接收到了对方的关闭请求。
确认双方的接收能力和发送能力正常,确保连接的可靠性和稳定性。
防止服务端重复发送数据,导致客户端接收到重复数据。

75、说说webpack proxy的工作原理?为什么能够解决跨域?

Webpack的代理(proxy)是一种在开发环境下用于解决跨域请求的技术。它可以将前端应用程序中的API请求转发到另一个域名或端口,从而绕过浏览器的同源策略限制。

Webpack代理的工作原理如下:

1、在Webpack配置文件中,我们可以使用devServer配置项来设置代理。通常,我们将代理配置为一个对象,其中键表示要代理的URL路径,值表示目标URL。

// webpack.config.js

module.exports = {
  // ...其他配置
  devServer: {
    proxy: {
      '/api': 'http://api.example.com'
    }
  }
};

2、当前端应用程序发起一个带有/api路径的请求时,Webpack devServer会检查该请求是否匹配代理配置中的任何路径。如果匹配成功,它会将请求转发到目标URL(这里是http://api.example.com)。
3、转发请求后,代理服务器将接收到该请求,并将其发送到目标URL。由于请求是在后端服务器之间进行的,因此不存在浏览器的同源策略限制。
4、目标服务器处理该请求,并将响应返回给代理服务器。
5、代理服务器接收到响应后,将其返回给前端应用程序。
通过这种方式,Webpack代理实现了在开发环境下绕过浏览器的同源策略限制,从而解决了跨域请求的问题。
当开发环境中的前端应用程序需要访问不同域名或端口的API时,浏览器会根据同源策略进行限制,阻止跨域请求。而Webpack代理充当了一个中间层,它将前端应用程序的请求转发到目标服务器,并将响应返回给前端应用程序,使得跨域请求变得可能。
需要注意的是,Webpack代理仅在开发环境下起作用,它并不会影响生产环境。在生产环境中,应该通过其他方式(如CORS配置)来解决跨域请求的问题。

你可能感兴趣的:(前端,javascript,react.js)