2023年面试总结


react hooks

React Hooks 是从 React 16.8 版本推出的新特性,目的是解决 React 的状态共享以及组件生命周期管理混乱的问题,Hooks 是对 React function 组件的一种扩展,通过一些特殊的函数,让无状态组件拥有状态组件才拥有的能力

useState();

useEffect();

useCallback();

useMemo();

useRef();

useContext();

useReducer();

useEffect 用法

useEffect 钩子是 react 钩子 API 的一部分。如果你熟悉 react 生命周期,useEffect 钩子相当于生命周期方法 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合。useEffect 是一个接受个参数的函数。传递给 useEffect 的第一个参数是一个名为 effect 的函数(你可以猜到为什么这个钩子叫 useEffect),第二个参数(是可选的)是一个存储依赖关系的数组。

高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。 HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。 具体而言,高阶组件是参数为组件,返回值为新组件的函数。
高阶组件不是组件,是增强函数,可以输入一个元组件,输出一个新的增强组件
高阶组件的主要作用是代码复用,操作状态和参数

react 性能优化的方法

1.使用 React.Memo 来缓存组件

2.使用 useMemo 缓存大量的计算

3.使用 React.PureComponent , shouldComponentUpdate

4.使用 React.Fragment 避免添加额外的 DOM

事件循环机制

简单的说,事件循环(eventLoop)是单线程的 JavaScript 在处理异步事件时进行的一种循环过程,具体来讲,对于异步事件它会先加入到事件队列中挂起,等主线程空闲时会去执行事件队列中的事件。

主线程任务——>微任务——>宏任务 如果宏任务里还有微任就继续执行宏任务里的微任务,如果宏任务中的微任务中还有宏任务就在依次进行
主线程任务——>微任务——>宏任务——>宏任务里的微任务——>宏任务里的微任务中的宏任务——>直到任务全部完成 我的理解是在同级下,微任务要优先于宏任务执行

在同一轮任务队列中,同一个微任务产生的微任务会放在这一轮微任务的后面,产生的宏任务会放在这一轮的宏任务后面
在同一轮任务队列中,同一个宏任务产生的微任务会马上执行,产生的宏任务会放在这一轮的宏任务后面

它不停检查 Call Stack 中是否有任务(也叫栈帧)需要执行,如果没有,就检查 Event Queue,从中弹出一个任务,放入 Call Stack 中,如此往复循环。

项目是怎么部署的

1.Jenkins

2.webpack

hooks 和类组件的区别

区别:

1、hooks 的写法比 class 简洁;

2、hooks 的业务代码比 class 更加聚合;

3、class 组件的逻辑复用通常用 render props 以及 HOC 两种方式,而 react hooks 提供了自定义 hooks 来复用逻辑。

useeffects 模仿类组件生命周期

useEffect 钩子是 react 钩子 API 的一部分。如果你熟悉 react 生命周期,useEffect 钩子相当于生命周期方法 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。

ts 泛型了解吗

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,使接口,函数或类更加通用

webpack 你常用的 loader plugin ,有做过 webpack 优化吗

常用 loader

(1)babel-loader:把 es6 转成 es5;

(2)css-loader:加载 css,支持模块化,压缩,文件导入等特性;

(3)style-loader:把 css 代码注入到 js 中,通过 dom 操作去加载 css;

(4)eslint-loader:通过 Eslint 检查 js 代码;

(5)image-loader:加载并且压缩图片晚间;

(6)file-loader:文件输出到一个文件夹中,在代码中通过相对 url 去引用输出的文件;

(7)url-loader:和 file-loader 类似,文件很小的时候可以 base64 方式吧文件内容注入到代码中。

(8)source-map-loader:加载额外的 source map 文件,方便调试。

常用 plugin

(1)uglifyjs-webpack-plugin:通过 UglifyJS 去压缩 js 代码;

(2)commons-chunk-plugin:提取公共代码;

(3)define-plugin:定义环境变量。

webpack 原理

webpack 是一个打包模块化 js 的工具,可以通过 loader 转换文件,通过 plugin 扩展功能。

封装过哪些组件,怎么封装的

封装时考虑的因素

1.我们需要考虑到未来的可维护性和复用性,这时就需要抽象出通用的逻辑或者功能,以便在不同的项目中使用

2.在开发组件时,需要将组件内部的数据和展示进行分离,以便组件的复用性和可维护性更高。通过 props 属性将外部的数据传递给组件,以便组件可以根据外部的数据进行展示。

3.抽象组件样式:在进行组件封装时,我们需要考虑到组件的样式问题

路由守卫做过吗,怎么实现的

路由守卫是什么
React Router 路由守卫(Route Guard)可以用来在进入或离开某个路由时进行验证和控制,以实现路由的权限管理、用户登录验证等功能。 React Router 提供了多种路由守卫的实现方式,包括使用高阶组件、使用函数组件和使用 render 属性等方式。

与 vue 不同,vue 直接使用 beforeEach 即可实现全局路由守卫等功能。
react 要实现路由守卫得自己配置。
实现该功能的前提是你需要先掌握 react 的高阶组件的使用

需要配置一张路由表。
需要使用高阶组件。

空值合并运算符(??)与逻辑或运算符(||)区别

空值合并运算符(nullish coalescing operator)的写法为两个问号 ??。

我们可以使用我们已知的运算符重写 result = a ?? b,像这样:

result = a !== null && a !== undefined ? a : b;

例如,在这里,如果 user 的值不为 null/undefined 则显示 user,否则显示 匿名:

let user;

alert(user ?? “匿名”); // 匿名(user 未定义)

?.(可选链操作符)

?.表示:可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每 个引用是否有效。操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空(null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值

?.的个人理解 打个比方就是判断对象的某个属性是否存在,如果存在那么就返回整个属性的值,否则返回 undefined

let a = {
  getName: () => {
    return "123";
  },
};
let result1 = a?.getName?.(); // 123
let result2 = a?.setName?.(); // undefined

与函数调用一起使用时,如果给定的函数不存在,则返回 undefined,不会报错

注意: 如果被当作方法调用的是一个属性,使用?.还是会报错

了解过 fiber 吗 (用于解决渲染复杂组件时产生的卡顿问题)

React 更新视图的原理:通过 setState 改变数据从而触发虚拟 DOM 去进行对比,对比结束后将再进行 DOM 更新。当对比少的节点时使用这种方法时比较合理的,但是当我们一次更新有几百个甚至更多组件需要进行对比时,由于 Diff 是一个同步的方法,在进行对比时,由于 JS 单线程的原因,导致其他的事件都无法响应。
当在这个 Diff 的过程中如果有有通过 Input 输入信息,那么他将得不到响应,显然这样体验会非常不好。

针对这样的问题,React 16+中首次引入了 React Fiber,就是用于解决渲染复杂组件时产生的卡顿问题。

css 动画结束后触发一个事件怎么做

第一种方法:
用计时器,设定一个和动画时长一样的 time,过 time 事件去执行这个函数。

setTimeout(function () {}, time);

第二种方法:

当-webkit-animation 动画结束时有一个 webkitAnimationEnd 事件,只要监听这个事件就可以了。

不同浏览器的 AnimationEnd 写法

(webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend)

判断类型的方法

判断类型有四种方法(不全但是相对来说挺全了,而且也够用了)

1.typeof

2.instanceof

3.constructor

4.Object.prototypy.tostring.call()

ts 和 js 区别

TS 和 JS 是两种不同的编程语言。
JS(JavaScript)是一种流行的脚本语言,它通常用于在 Web 页面上实现交互和动态效果。它是一种动态类型语言,这意味着在编译时无需指定变量类型。

TS(TypeScript)是一种由 Microsoft 开发的开源编程语言,它是 JavaScript 的超集。TS 增加了类型注释、类、接口、枚举和其他面向对象编程的特性,这使得它更适合于大型项目。与 JS 不同的是,TS 编译器会在编译时检查类型错误,并提供更好的代码补全和文档。这些特性可以提高开发效率和代码质量。

总的来说,TS 在类型检查、面向对象编程和开发效率方面有明显的优势。但是,它也需要一些额外的学习和配置成本。对于小型项目或初学者来说,使用 JS 可能更为方便,而对于大型项目或需要更高质量保障的企业应用,则使用 TS 更为合适。

http2 特点

http2 采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,二进制协议解析起来更高效。 HTTP / 1 的请求和响应报文,都是由起始行,首部和实体正文(可选)组成,各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。HTTP/2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。

map 和 weakmap

在 ES6 中 新增了 Map 和 WeakMap 这俩个类

Map 用来存储映射关系键值对的,在普通对象在存储时,key 值只能为字符串或 Symbol 类型

Map 储存时就可以把对象或者其他类型作为一个对象的 key 值

Map 和 WeakMap 的区别:

Map 在储存时可以把任何类型作为对象的 key 值,并且它的内存引用为强引用

WeakMap 在储存时只能使用对象类型作为对象的 key 值,他的内存引用为弱引用,且不能被迭代

css 中 align-content 和 align-items 区别

1:共同点:它们对齐方向为交叉轴

2:不同点:align-content 应用于为 多行 而 align-items:应用于单行。

css 中 padding 和 margin 区别

margin 是用来隔开元素与元素的间距;
padding 是用来隔开元素与内容的间隔。
margin 用于布局分开元素使元素与元素互不相干;
padding 用于元素与内容之间的间隔,让内容(文字)与(包裹)元素之间有一段“呼吸距离”。

css 中图片旋转 90 度用哪个属性

让图片旋转 90 度的方法:利用 transform 属性来进行图片旋转,如transform:rotate(90deg)

transform属性用于元素的 2D 或 3D 转换,该属性允许我们将元素旋转、缩放、移动、倾斜。

避免重复渲染的方式 function 组件和 class 组件两种

避免重新渲染的思想其实也是对比 Props 的引用。决定是否渲染!

1.React.memo 的使用

2.使用 memo, useMemo, useCallback

webpack 的原理

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

Webpack 是一个现代化的 JavaScript 应用程序打包工具,它的主要功能是将多个 JavaScript 文件打包成 一个或多个文件,以便于在浏览器中加载和运行。Webpack 的原理是将所有的模块打包成个或多个 bundle 文件,这些文件可以是 JavaScript、CSS、图片等资源文件

Webpack 的工作原理可以分为四个步骤:入口、输出、加载器和插件。

Webpack 需要指定一个或多个入口文件,这些文件是 Webpack 打包的起点。Webpack 会从入口文件开始递归地查找所有的依赖模块,直到找到所有的模块为止。

Webpack 需要指定一个或多个输出文件,这些文件是 Webpack 包后生成的文件。Webpack 会将所有的模块打包成一个或多个 bundle 文件,并将这些文件输出到指定的日录中

第二,Webpack 需要使用加载器来处理不同类型的文件。加载器是个转换器,它可以将不同类型的文件转换成 JavaScript 模块 Webpack 支持多种类型的加载器,包括 CSS 加载器、图片加载器、字体加载器等。
Webpack 还支持插件,浙件可以扩展 Webpack 的功能。Webpack 的插件可以用来压缩代码、优化资源、生成 HTML 文件等

总的来说,Webpack 的原理是将所有的模块打包成一个或多个 bundle 文件,并将这些文件输出到指定的目录中。Webpack 通过入口、输出、加载器和插件四个步骤来实现这个过程。Webpack 的工作原理非常灵活,可以根据不同的需求进行配置,使得 Webpack 成为一个非常强大的 JavaScript 应用程序打包工具。

跨域怎么解决

跨域就是当在页面上发送 ajax 请求时,由于浏览器同源策略的限制,要求当前页面和服务端必须同源,也就是协议、域名和端口号必须一致。如果协议、域名和端口号中有其中一个不一致,则浏览器视为跨域,进行拦截。

常见解决跨域手段

JSONP

CORS

搭建 Node 代理服务器

Nginx 反向代理

postMessage

Websocket

git 如何做指定版本回退

1.回滚到指定版本

首先进入项目根目录下,使用 git log 命令,找到需要返回的 commit id 号,使用 git reset --hard 后跟你需要的 commit id 号,这样你就回到了指定的版本,注意 git reset --hardgit reset --soft 的区别:

git reset –-soft:回退到某个版本,只回退了 commit 的信息,不会恢复到 index file 一级。通常使用在当你 git commit -m “注释”提交了你修改的内容,但内容有点问题想撤销,又还要提交,就使用 soft,相当于软着路;

git reset -–hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容,撤销的 commit 中所包含的更改被冲掉,相当于硬着路,回滚最彻底。

2.返回到最新版本

当你发现需要回滚到最新版本时,可以采用以下指今步骤

git log:查看历史提交

git reflog:查看每一次命令记录

通过 git reflog 命令查看到之后,我们再利用 git reset 来返回到相应的版本即可,HEAD 前面的一串字符为我们简写的 ID,所以最后输入

git reset –hard ca936c3 即回滚到了最新的版本号了

怎么改 this 指向

主要有三种方法:callapplybind

父组件怎么调用子组件的方法

父组件调子组件函数有两种情况
子组件无 HOC 嵌套:推荐使用 ref 直接调用
有 HOC 嵌套:推荐使用自定义 props 的方式

flex 布局怎么改主轴方向 (flex-direction)

设置主轴方向
主轴默认是水平方向, 侧轴默认是垂直方向

修改主轴方向属性: flex-direction

promise .then 的参数

then 方法是 Promise 对象中最重要的一个方法。它接收两个参数,一个是成功时的回调函数,一个是失败时的回调函数。在调用了 then 方法后,它将返回一个新的 Promise 对象,这个新 Promise 对象的状态和值是由当前 Promise 对象所完成的操作结果决定的。

动画旋转

anmimation(动画离不开的)

ts 联合类型怎么写

联合类型(Union Types)表示取值可以为多种类型中的一种。联合类型使用 | 分隔每个类型。

ts type 与 interface 的区别

interfacetype 都可以用来定义对象类型,那么在开发中时到底选择哪一个呢?

如果是定义非对象类型,通常推荐使用 type。
如果是定义对象类型,那么他们有区别的:

interface 可以重复的对某个接口来定义属性和方法;

而 type 定义的是别名,不能重复。

usememo 和 usecallback 的区别

useMemouseCallback 都是 reactHook 提供的两个 API,用于缓存数据,优化性能;两者接收的参数都是一样的,第一个参数表示一个回调函数,第二个表示依赖的数据。

共同作用
在依赖数据发生变化的时候,才会调用传进去的回调函数去重新计算结果,起到一个缓存的作用

两者的区别
useMemo 缓存的结果是回调函数中 return 回来的值,主要用于缓存计算结果的值,应用场景如需要计算的状态
useCallback 缓存的结果是函数,主要用于缓存函数,应用场景如需要缓存的函数,因为函数式组件每次任何一个 state 发生变化,会触发整个组件更新,一些函数是没有必要更新的,此时就应该缓存起来,提高性能,减少对资源的浪费;另外还需要注意的是,useCallback 应该和 React.memo 配套使用,缺了一个都可能导致性能不升反而下降。

父组件获取子组件的方法

调用方法:
1、类组件中的调用可以利用 React.createRef()、ref 的函数式声明或 props 自定义 onRef 属性来实现;

2、函数组件、Hook 组件中的调用可以利用 useImperativeHandle 或 forwardRef 抛出子组件 ref 来实现。

安全问题

1.同源策略

2.xss 攻击

3.HTTPS

3.CSRF

高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

首屏优化

一般首屏加载速度慢得原因可能如下几点:

1.网络延时问题,

2.资源文件体积是否过大,

3.资源是否重复发送请求去加载了

4.加载脚本的时候,渲染内容堵塞了

常见的 SPA 优化方式解决方案

1.减小入口文件体积,

2.静态资源本地缓存,

3.UI 框架按需引入,

4.图片资源的压缩,

5.组件重复打包,

6.开启 GZip 打包,

7.开启 SSR

8.去除 loading

useeffect 跟 uselayouteffect 的区别

这两个函数的使用方式其实非常简单,他们都接受一个函数一个数组,只有在数组里面的值改变的情况下才会再次执行 effect。
差异:
useEffect 是异步执行的,而 useLayoutEffect 是同步执行的。
useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。

父组件怎么使用子组件的方法

1.子组件是类组件,使用 Refs

2.子组件是函数组件,使用 Refs 转发

2.1① 使用 forwardRef 转发 Refs 到 DOM 组件,就是将 父组件的 ref 向下传递给子组件。

2.2② forwardRef 结合 useImperativeHandle 使用,自定义暴露给父组件的实例值。

2.3③ 在高阶组件(HOC)中转发 Refs

首页渲染做过哪些优化

前端优化大概可以有以下几个方向:

网络优化

页面渲染优化

JS 优化

图片优化

webpack 打包优化

React 优化

Vue 优化

网络优化

setstate 怎么同步拿数据

setState({})去更新状态时,发现更新后拿到的状态不是最新的,还是更新前的,所以 setState 是异步更新,如果想在状态更新页面完成渲染后立即做一些事情,那么这时我们就要用到 setState 的第二个参数

css 页面顶部底部固定,中间自适应几种方法

1,使用相对定位和绝对定位

/* // 第一种方式 */
.container {
  /* // 使用子绝父相 */
  position: relative;
  height: 100vh;
  background: #f1f3f4;
}

header,
main,
footer {
  height: 80px;
  line-height: 80px;
  width: 100%;
}

/* // 设置绝对定位 */
footer {
  height: 60px;
  line-height: 60px;
  position: absolute;
  bottom: 0;
  left: 0;
}
<div class="container">
  <header style="background: #f9cc9d">Header</header>
  <main style="background: #c3d08b">Content</main>
  <footer style="background: #2b93f5">Footer</footer>
</div>

2,使用 flex 弹性布局,将 footer 的 margin-top 设置为 auto

注意:在浏览器中,100vh 的高度包括了底部的工具栏,而我们真实需要的高度其实是浏览器的可视高度也就是 100%;
在使用 100vh 的时候,手机浏览器底部的操作栏可能会遮挡底部按钮;
所以这时页面的高度应该使用 100%;

.container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: #f1f3f4;
}
header,
main,
footer {
  height: 80px;
  line-height: 80px;
  width: 100%;
}
footer {
  margin-top: auto;
  height: 60px;
  line-height: 60px;
}

        
Header
Content
Footer

3,通过 css 内置函数 calc()动态计算内容的高度

min-height: calc(100vh - 130px); 130 其实就是 header 和 footer 的高度;

.container {
  height: 100vh;
}
header,
main,
footer {
  text-align: center;
}
header {
  height: 80px;
  line-height: 80px;
  width: 100%;
}

footer {
  height: 50;
  line-height: 50px;
}

main {
  min-height: calc(100vh - 130px);
}
    
Header
Content
Footer

文字超出部分变成省略号的三种方式

1.单行文本溢出显示省略号

.box {
  /*强制文本在一行内显示*/
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

2.多行文本溢出显示省略号

.box {
  overflow: hidden;
  text-overflow: ellipsis;
  /* 将对象作为弹性伸缩盒子模型显示 */
  display: -webkit-box;
  /* 限制在一个块元素显示的文本的行数 */
  /* -webkit-line-clamp 其实是一个不规范属性,使用了WebKit的CSS扩展属性,该方法适用于WebKit浏览器及移动端;*/
  -webkit-line-clamp: 2;
  /* 设置或检索伸缩盒对象的子元素的排列方式 */
  -webkit-box-orient: vertical;
}

js 方法实现

第一种方法用到的是 substring(from, to )方法,括号内第一个参数是索引起始值,第二个参数是索引的结束值。substring()从字符串中抽取一个子串,然后赋值给 textCon,最后再用 concat()连接省略号。需要注意的是 substring()的返回值是它本身(抽取的值),字符串的第一个索引值是 0。

textCon = textCon.substring(0, 80);
textEnd = textCon.concat("……");

第二种方法的思路是:先用 split(“”)将字符串转化为数组,split(separator)将字符串分割成数组,默认用逗号分隔,对本身字符串没有产生影响;转成数组后我们再用 splice(开始的索引, 要删除的个数, 插入的元素)对后面多余的文本进行删除;最后用 join(“”)把数组连接成字符串,后面加上 concat(“……”)。这里需要注意的是 splice()的返回值是改变后的数组。

textCon = textCon.split(""); //将字符串转化为数组
textCon = textCon.splice(80, textCon.length);
textEnd = textCon.join("").concat("……");
text.innerText = textEnd;

第三种则是用到 replace()字符串替换方法,它的思路很简单,就是把后面多余的文本字符串替换为空。这里需要注意的地方是:用 for 语句进行循环,每将一个字符替换为空后,它后面的字符就会自动往前移,所以我们只要在某个固定的位置将字符替换为空,后面的字符就会顶上来,就像流水线一样。这是三种方法里面最简单的一种。

for (var i = textCon.length; i > 80; i--) {
  textCon = textCon.replace(textCon[80], "");
}
textEnd = textCon.concat("……");
text.innerText = textEnd;

实现字符串反转

使用数组的 reverse()方法

步骤如下:

将字符串转为数组
使用数组的 reverse()方法进行反转
使用数组的 join()方法将数组转化为字符串
示例代码如下:

const str = "Hello World!";
const reversedStr = str.split("").reverse().join("");
console.log(reversedStr); // 输出:'!dlroW olleH'

字符串替换

1.使用 replace()方法.

replace()方法用于在字符串中查找并替换指定的子串。它接受两个参数:第一个参数是需要替换的子串或用于匹配子串的正则表达式,第二个参数是需要替换成的字符串。

let str1 = "hello world";
let str2 = str1.replace("world", "javascript");
console.log(str2); // hello javascript

2.使用正则表达式替换

如果需要使用正则表达式进行替换,可以使用 replace()方法的第一个参数传入一个正则表达式。例如:

let str1 = "2022-02-22";
let str2 = str1.replace(/-/g, "/");
console.log(str2); // 2022/02/22

3.使用 replaceAll()方法

从 ECMAScript 2021 规范开始,JavaScript 原生支持了 replaceAll()方法。它也是用于在字符串中替换指定的子串,但是相比于 replace()方法,它可以直接替换所有匹配的子串,而不需要使用正则表达式。

例如:

let str1 = "hello world";
let str2 = str1.replaceAll("o", "O");
console.log(str2); // hellO wOrld

注意,由于 replaceAll()方法目前还不是所有浏览器都支持,因此在实际开发中需要进行兼容性处理。

async 里面多个 await,如何优化

 async initData() {
    const cache = await this.cachePromise;  // 出错了
    const cache2 = await this.cachePromise2;  //不执行
    const cache3 = await this.cachePromise3;  //不执行
    const cache4 = await this.cachePromise4;  //不执行
  }

// 修改后
 async initData() {
    try {
       const cache = await this.cachePromise;  // 出错了
       const cache2 = await this.cachePromise2;  //执行
       const cache3 = await this.cachePromise3;  //执行
       const cache4 = await this.cachePromise4;  //执行
     } catch(err) {
       console.log(err)
     }
  }

使用 promise 封装 Ajax 请求

function myaxios(url) {
    var p1 = new Promise(function (n1, n2) {
        var xhr = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP")
        xhr.open("GET", url, true)
        xhr.send()
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4 && xhr.status == 200) {
                n1(xhr.responseText)
            } else if (xhr.readyState == 4 && xhr.status == 404) {
                n2(xhr.responseText)
            }
        }
    })
    return p1
}

调用时的方法:



    

实现瀑布流 (瀑布流布局常见场景用于展示照片)

瀑布流布局是一种新型的布局方式,可以将大小不一的图片完整的显示在页面上,并且在杂乱的布局中保持着一定的美感,

<div class="container" id="con">
  <div class="item">
    <img src="../img/1.jpeg" alt="" />
  </div>
  <div class="item">
    <img src="../img/2.jpeg" alt="" />
  </div>
  <div class="item">
    <img src="../img/3.jpeg" alt="" />
  </div>
  <div class="item">
    <img src="../img/4.jpeg" alt="" />
  </div>
  <div class="item">
    <img src="../img/5.jpeg" alt="" />
  </div>
</div>

1.CSS 布局
使用 column-count 属性

.container {
  column-count: 4; /* 想要排成的列数 */
  column-gap: 0;
}
.item img {
  width: 100%;
}

2.使用 flex 布局

.container {
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  /* 需要指定高度 */
  height: 800px;
}
.item {
  width: 25%;
}

.item img {
  width: 100%;
}

小程序的登录流程

1.通过 wx.login() 获取到用户的 code 判断用户是否授权读取用户信息,调用 wx.getUserInfo 读取用户数据。

2.由于小程序后台授权域名无法授权微信的域名,所以需要自身后端调用微信服务器获取用户信息。

3.通过 wx.request() 方法请求业务方服务器,后端把 appid , appsecret 和 code 一起发送到微信服务器。
appid 和 appsecret 都是微信提供的,可以在管理员后台找到。

4.微信服务器返回了 openid 及本次登录的会话密钥 session_key。

5.后端从数据库中查找 openid ,如果没有查到记录,说明该用户没有注册,如果有记录,则继续往下走。

6.session_key 是对用户数据进行加密签名的密钥。为了自身应用安全,session_key 不应该在网络上传输。

7.然后生成 session 并返回给小程序。

8.小程序把 session 存到 storage 里面。

9.下次请求时,先从 storage 里面读取,然后带给服务端。

10.服务端对比 session 对应的记录,然后校验有效期。

登录逻辑: 1.调用 wx.login() 获取 临时登录凭证 code,有效期为 5 分钟;(临时登录凭证 code 只能使用一次)

2.将临时 code 传到我们的后端,后端调用 auth.code2Session 接口,换取用户唯一标识 OpenID 和 会话密钥 session_key;( openid 是用户唯一标识,session_key 能保证当前用户进行会话操作的有效性)

注意:获取 session_key 出于安全性的考虑,要在后端调用。如果我们在前端通过 request 调用此接口,就不可避免的需要将我们小程序的 appid 、secret 和服务端下发的 session_key 暴露在外部,会给我们的业务安全带来极大的风险。
session_key 拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效。具体时效逻辑由微信维护,对开发者透明。开发者需要调用 wx.checkSession 接口检测当前用户登录态是否有效。

3.后端自定义新的密钥并关联返回的 session_key 和 openid,将新的密钥返给前端,前端将其存储在 storage 中。(会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥,所以要定义新的密钥)。
之所以存在 storage 中,是因为小程序没有 cookie,相应的后端 set-cookie 在小程序中不起作用。

4.前端发送请求的时候,带着密钥,后端根据密钥识别用户身份,返回数据。

bff 项目来处理什么问题

BFF(Backend-for-Frontend)中间层是一种架构模式,主要用于解决前后端协作和微服务架构中的数据聚合问题。在这种架构下,前端应用程序不直接与后端服务通信,而是通过一个专门为前端定制的 BFF 中间层与后端服务交互。BFF 中间层负责与多个后端服务进行通信,聚合数据,并将结果返回给前端应用程序。

BFF 中间层的主要作用是:

为前端应用程序提供一个统一的 API 接口,简化前端应用程序的开发和维护。

负责与多个后端服务进行通信,聚合数据,降低前端应用程序的复杂度。
提供负载均衡、服务发现、缓存、错误处理和重试策略等功能,提高系统的性能和可用性。

空值合并运算符 ‘??’与’||’运算符区别

||运算符和??运算符在链式使用方法中有相同的地方,例如:

let first = null;
let secon = null;
let third = "Beautiful Girl";
let r1 = first ?? secon ?? third ?? "undefined"; //(1)
let r2 = first || secon || thrid || "undefined"; //(2)

上述代码中,(1)(2)两行的作用是完全相同的,但是相对于共同点,二者的区别更大。

从定义上:

||返回表达式中的第一个真值;
??返回表达式中的第一个已定义值;
二者的区别就在于真值和已定义值的区别上:

||无法区分 0、false、””和 null/undefined,对于||而言,它们都是一样的。

??可以区分 null/undefined 和其他值的区别。

所以,在处理代码时,分真假用||,分有无用??。

微前端的理解

概念:微前端是一种类似于微服务的架构。它使得各个前端应用可以独立运行、独立开发、独立部署,微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用(微前端的核心在于拆,拆完之后再合)

技术框架:目前常用的微前端技术框架有 single-spa 以及基于 single-spa 开发的微前端实现库 qiankun

使用场景:

1.我们需要兼容遗留的系统时

2.当项目需要聚合应用时

3.当不同的团队开发同一个应用,所选技术栈不同时

HTML <iframe>标签

iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。所有浏览器都支持 <iframe> 标签。您可以把需要的文本放置在 <iframe></iframe> 之间,这样就可以应对无法理解 iframe 的浏览器。使用 iframe,可以配置不同的 src 加载不同的子应用页面

js 中的 set 与 map

简述:Set 和 Map 主要的应用场景在于 数据重组 和 数据储存。Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。

Set:
成员唯一、无序且不重复。
[value, value],键值与键名是一致的(或者说只有键值,没有键名)。
可以遍历,方法有:add、delete、has。

Map:
本质上是键值对的集合,类似集合。
可以遍历,方法很多可以跟各种数据格式转换。

组件封装如何做的?封装时考虑的因素?

一般步骤如下: 1.创建一个新的 Vue 单文件组件(.vue 文件),并命名为你的组件名,例如 MyComponent.vue。

2 在组件文件中,使用 <template> 标签定义组件的模板结构,使用 <script> 标签定义组件的逻辑,使用 <style> 标签定义组件的样式。

3 在 <script> 标签中,使用 export default 导出一个 Vue 组件对象。

4 在组件对象中,定义组件的属性(props),数据(data),方法(methods),生命周期钩子(lifecycle hooks)等。

5 如果需要,可以在组件中引入其他的子组件或插件。

6 在需要使用该组件的地方,使用 import 语句导入该组件,然后在父组件的模板中使用该组件的标签。

封装时考虑的因素

1.我们需要考虑到未来的可维护性和复用性,这时就需要抽象出通用的逻辑或者功能,以便在不同的项目中使用

2.在开发组件时,需要将组件内部的数据和展示进行分离,以便组件的复用性和可维护性更高。通过 props 属性将外部的数据传递给组件,以便组件可以根据外部的数据进行展示。

3.抽象组件样式:在进行组件封装时,我们需要考虑到组件的样式问题

http 常见状态码有哪些?

一、1 开头的状态码(信息类)

100,接受的请求正在处理,信息类状态码

二、2 开头的状态码(成功类)

2xx(成功)表示成功处理了请求的状态码
200(成功)服务器已成功处理了请求。

三、3 开头的状态码(重定向)

3xx(重定向)表示要完成请求,需要进一步操作。通常这些状态代码用来重定向。
301,永久性重定向,表示资源已被分配了新的 URL
302,临时性重定向,表示资源临时被分配了新的 URL
303,表示资源存在另一个 URL,用 GET 方法获取资源
304,(未修改)自从上次请求后,请求网页未修改过。服务器返回此响应时,不会返回网页内容

四、4 开头的状态码(客户端错误)

4xx(请求错误)这些状态码表示请求可能出错,妨碍了服务器的处理
400(错误请求)服务器不理解请求的语法
401 表示发送的请求需要有通过 HTTP 认证的认证信息
403(禁止)服务器拒绝请求
404(未找到)服务器找不到请求网页

五、5 开头的状态码(服务器错误)

5xx(服务器错误)这些状态码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求的错误
500,(服务器内部错误)服务器遇到错误,无法完成请求
503,表示服务器处于停机维护或超负载,无法处理请求

flex:1 和 flex:auto 的区别,flex 是哪些属性的缩写?

flex:1 和 flex:auto 区别:规则基准使用值不同、子元素不同、包裹块不同。

flex: 1=== flex: 1 1 0;

在说 flex:1 和 flex:auto 区别之前先回顾 flex:0 1 atuo;

从默认值上可以看出它有三个属性,分别是 flex-grow、flex-shrink、flex-basis

flex-grow:项目的放大比例,默认为 0,如果存在剩余空间,不放大

flex-shrink:项目的缩小比例,默认为 1,如果空间不足,会适当缩小

flex-basis:在分配空间之前,项目的主轴空间,相当于我们设置的 width,

Vue 的生命周期有哪些?created 和 mounted 区别?

beforeCreate( 创建前 ):el 和 data 并未初始化,因此无法访问 methods, data, computed 等上的方法和数据,无法使用 this。

created ( 创建后 ):完成了 data 数据的初始化,el 没有。 主要用来初始化一些数据。

beforeMount:完成了 el 和 data 初始化,$el 属性已存在,是虚拟 dom,只是数据未挂载到模板中(没有挂在 html 到页面上)。

mounted:dom 操作在此时能正常进行。此时一般可以做一些 ajax 操作,mounted 只会执行一次。Dom 操作一般是在 mounted 钩子函数中进行的

beforeUpdate (更新前):在数据更新之前被调用,此时数据是新的,页面依旧还是旧的

updated (更新后):此时组件 DOM 已经更新,所以可以执行依赖于 DOM 的操作

beforeDestrioy (销毁前):在实例销毁之前调用,实例仍然完全可用,一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的 dom 事件
destroyed(销毁后):执行 destroy 方法后,对 data 的改变不会再触发周期函数,此时的 vue 实例已经解除了事件监听以及和 dom 的绑定,但是 dom 结构依然存在。

区别:

created:在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图。

mounted:在模板渲染成 html 后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作。

Vue3 的生命周期钩子函数的变化 (6 个)

在 Vue3 中,一些常见的钩子函数发生了变化。例如,vue2 中的 created 和 beforeCreate 钩子函数被替换为了 setup(),并且 setup()在二者之前执行。beforeMount 和 mounted 函数被替换成了 onBeforeMount 和 onMounted。beforeUpdate 和 update 被替换为 onBeforeUpdate 和 onupdate。beforeDestroy 和 destroyed 被替换为 beforeUnmount 和 unmounted。这些钩子函数的执行顺序与 Vue2 的版本相同,但是有所不同的是,在 Vue3 中,它们是使用 ES6 类定义的。

写一个对象,这个对象还得包含一个取消定时器的方法, 需要从 0-100 依次定时打印出来

let i=0
let timer=setInterval(() => {
    console.log('当前值为 :>> ', i++);
    if(i>100){
        clearInterval(i)
    }


}, 1000);

数组的去重,排序,倒叙

数组的去重 1.使用 set 结构去重

2.使用 for+indexOf 方法去重

3.使用 for+includes 方法去重

排序:sort

var arr2 = [20,13,11,8,0,11];

按升序排列,前减后

arr2.sort(function(a,b){
    //a,b表示相邻的两个元素
    //若返回值>0,数组元素将按升序排列
    //若返回值<0,数组元素将按降序排列
    return a-b;
});
console.log(arr2); //[0,8,11,11,13,20]; 新数组按升序排列

按降序排列,后减前

arr2.sort(function(a,b){
    //a,b表示相邻的两个元素
    //若返回值>0,数组元素将按升序排列
    //若返回值<0,数组元素将按降序排列
    return b-a;
});
console.log(arr2); //[20,13,11,11,8,0]; 新数组按降序排列

css 样式,同一行中三个元素平均分布

采用父盒子设置弹性盒布局,子盒子则 flex:1,等比均分

<div class="box">
  <div class="son">1</div>
  <div class="son">1</div>
  <div class="son">1</div>
</div>
.box {
  display: flex;
}

.son {
  flex: 1;
  background: pink;
  height: 100px;
  margin: 10px;
  text-align: center;
  line-height: 100px;
}

css 九宫格布局

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
公共样式:

flex 实现

ul {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
  height: 100%;
}

li {
  width: 30%;
  height: 30%;
  margin-right: 5%;
  margin-bottom: 5%;
}

li:nth-of-type(3n) {
  margin-right: 0;
}

li:nth-of-type(n + 7) {
  margin-bottom: 0;
}

(2)grid 实现
grid 布局相对于 flex 布局来说,实现九宫格就更加容易了,只需要设置几个属性即可:

ul {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: 30% 30% 30%;
  grid-template-rows: 30% 30% 30%;
  grid-gap: 5%;
}

两个页签中保存的 cookie 都是一样的,cookie 在一个浏览器下同一个域名下是全部共享一致的,和页签没任何关系的。

同一个浏览器的两个页面是一个 session 吗? 不一定

同一个浏览器的两个页面是不一定是一个 session 的。如果是在同一个浏览器内打开两个标签,那么这两个标签的页面是一个 session,但是用同一个浏览器打开两个不同的窗口页面的话,那么两个页面不是一个 session。

浏览器打开两个标签页,localstorage 是否一样

IE、Edge localstorage 两个页签的值不一致的问题

localstorage 本是浏览器的属性,chrome 下会多个页签共享该属性值,一个变更,其他页签下取值也会取得变更后的最新值

IE、Edge 下两个页签,一个发生了变化,另外一个没有同步,经查找,需要手动对 localstorage 对象触发 change 事件,随便给某个属性设置一个值,原来的属性值也会跟着更新
localStorage.setItem(**,);
随意设置变量,只要触发 localStorge 对象就可以

Object.prototype.toString.call()

1.用处:该方法是用来监测数据类型的一种好方法

console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(new Date())); // [object Date]

检测原理:

Object 对象本身就有一个 toString()方法,返回的是当前对象的字符串形式,原型上的 toString()返回的才是我们真正需要的包含对象数据类型的字符串。至于 call,就是改变对象的 this 指向,当一个对象想调用另一个对象的方法,可以通过 call 或者 apply 改变其 this 指向,将其 this 指向拥有此方法的对象,就可以调用该方法了。

vue 响应式原理

vue 响应式也叫作数据双向绑定,大致原理阐述:

​ 首先我们需要通过 Object.defineProperty()方法把数据(data)设置为 getter 和 setter 的访问形式,这样我们就可以在数据被修改时在 setter 方法设置监视修改页面信息,也就是说每当数据被修改,就会触发对应的 set 方法,然后我们可以在 set 方法中去调用操作 dom 的方法。

​ 此外,如果页面有 input 用 v-model 绑定数据,我们需要在这种绑定了 data 的 input 元素上添加监听,添加 input 事件监听,每当 input 事件被触发时,就修改对应的 data。

map 和 object 的区别

区别:
1、Map 的键可以是任意值,而 Object 的键必须是一个 String 或是 Symbol。

2、Map 中的 key 是有序的,而 Object 的键是无序的。

3、Map 的键值对个数可以轻易地通过 size 属性获取,而 Object 的键值对个数只能手动计算。

4、Map 可以直接被迭代,而 Object 不可以直接被迭代。

5、Map 在频繁增删键值对的场景下表现更好,而 Object 的效率比较差。

Promise 大致的工作流程是

创建 Promise 对象 => 进入等待处理阶段 Pending

处理完成后,切换到 Fulfilled 状态/ Rejected 状态

根据状态,执行 then 方法/执行 catch 方法 内的回调函数

pinia 和 vuex 区别

Vuex 和 Pinia 都是 Vue.js 中常用的状态管理库,用于在项目中实现集中式的状态管理。

虽然两者都可以实现对状态的管理,但是在使用上还是有一些区别:

代码规模:vuex 可以轻松地处理大型应用程序的状态管理,而是 pinia 则是更专注于小型中型的项目。

状态访问方式:在 vuex 中,我们可以通过 mapState、mapGetters、mapMutations 和 mapActions 方便地将 store 中的状态映射到组件的计算属性和方法中,而在 pinia 中,我们使用内部提供的 useStore hook 来获取 store 实例,并直接访问 store 中的状态和操作函数

API 设计:vuex 遵循了 Flux 模式中的单向数据流理念,其中包括 state(状态)、mutations(变更)和 actions(行为)等概念;pinia 则采用了更加简化 API 设计,主要包含 state 跟 getters、actions 和 mutations 这三部分内容,同时也大力推广 Typescript 使用,并且底层采用了 proxy 来优化性能

运行依赖:vuex 依赖于 vue 本身,需要引入 Vue.use(Vuex)才能在项目中使用它;而 pinia 是完全独立于 vue 的,不需要再进行任何额外操作就可以安装和使用它。

综上所述,Vuex 更适用于大型应用程序的状态管理,而 Pinia 更适用于小型中型项目,同时在 API 设计上也有着不少的差异。但两者都可以为 Vue.js 中的状态管理提供便利,并根据个人需求和喜好进行选择。

什么是事件循环?

事件循环是 JavaScript 中一种异步执行机制,它的作用是协调和管理各种异步任务的执行顺序,保证 JavaScript 代码的执行顺序和预期一致。

整体的 script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;

同步任务会直接进入主线程依次执行;

异步任务会再分为宏任务(进入宏任务队列) 和 微任务(进入微任务队列)。

当主线程内的任务执行完毕(主线程为空时),会检查微任务的任务队列,如果有任务,就进入主线程全部执行,如果没有就从宏任务队列读取下一个宏任务执行;

每执行完一个宏任务就清空一次微任务队列,此过程会不断重复,这就是 Event Loop;

宏任务

setInterval();
setTimeout();
setImmediate();
ajax;

事件绑定

微任务
new Promise()后的then与catch函数
new MutaionObserver()
process.nextTick(Nodejs)

微任务和宏任务的执行顺序是先执行同步任务,先执行同步后异步,异步分为宏任务和微任务两种,异步遇到微任务先执行微任务,执行完后如果没有微任务,就执行下一个宏任务。

ES6 的新特性

1.let 和 const 声明变量的方式,取代了 var 关键字。

2.箭头函数(Arrow Function),简化了函数的书写方式。

3.模板字符串(Template String),允许在字符串中使用变量和表达式,而不需要使用字符串连接符号。

4.解构赋值(Destructuring Assignment),允许从数组和对象中提取值并赋值给变量。

5.默认参数(Default Parameter),在定义函数时可以给参数设置默认值。

6.扩展操作符(Spread Operator),可以在函数调用时展开数组或对象。

7.类(Class),引入了面向对象编程中类的概念。

8.模块化(Module),提供了一种组织代码的方式,可以将代码分割成独立的模块,方便重用和维护。

9.Promise,用于处理异步操作,避免回调地狱的问题。

10.for…of 循环,用于遍历可迭代对象(如数组、Map 和 Set)中的元素。

11.Symbol,引入了一种新的数据类型,用于创建唯一的属性键。

12.Map 和 Set,引入了两种新的数据结构,分别用于存储键值对和唯一值。

13.Proxy,允许在对象和函数调用等操作前后添加自定义的行为。

14.Reflect,提供了一组可以操作对象的内置方法,可以替代一些对象方法(如 Object.defineProperty)的实现。

Promise.allSettled,用于处理多个 Promise 的状态并返回一个包含每个 Promise 状态的数组。

箭头函数与普通函数的区别

1、外形不同:箭头函数使用箭头定义,普通函数中没有。

2、 箭头函数全都是匿名函数:普通函数可以有匿名函数,也可以有具名函数

3、箭头函数不能用于构造函数:普通函数可以用于构造函数,以此创建对象实例。

4、箭头函数中 this 的指向不同:在普通函数中,this 总是指向调用它的对象,如果用作构造函数,它指向创建的对象实例。

5、箭头函数不具有 arguments 对象:每一个普通函数调用后都具有一个
arguments 对象,用来存储实际传递的参数。但是箭头函数并没有此对象。

6、其他区别:箭头函数不具有 prototype 原型对象。箭头函数不具有 super。

箭头函数不具有 new.target

vue 实现 tab 组件,使用双向数据绑定

父组件



子组件




写一个函数,用于比较两个对象是否相等

function isObjectEqual(obj1, obj2) {
  const obj1Keys = Object.keys(obj1); // [ 'a', 'b' ]
  const obj2Keys = Object.keys(obj2); // [ 'a', 'b' ]

  //判断对象key个数是否相等
  if (obj1Keys.length !== obj2Keys.length) {
    return false;
  }

  //遍历对象的value值
  for (let key of obj1Keys) {
    // key:遍历数组的value值
    if (obj1[key] !== obj2[key]) {
      return false;
    }
  }

  return true;
}

const obj1 = { a: 1, b: "hello" };
const obj2 = { a: 1, b: "world" };
const obj3 = { a: 1, b: "hello" };

console.log(isObjectEqual(obj1, obj2)); // false
//   console.log(isObjectEqual(obj1, obj3)); // true

es6 与 commonjs 的区别

1.区别:
1、CommonJS 输出的是一个值的拷贝,ES6 输出的是值的引用;

2、CommonJS 是运行时加载,ES6 是编译时输出接口;

3、CommonJS 的 require 是同步加载模块,ES6 的 import 是异步加载,有独立模块依赖的解析阶段。

JSON.parse(JSON.stringify(obj))深拷贝的问题

1、如果 obj 里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。

2、如果 obj 里有 RegExp、Error 对象,则序列化的结果将只得到空对象。

3、如果 obj 里有函数,undefined,则序列化的结果会把函数, undefined 丢失。

4、如果 obj 里有 NaN、Infinity 和-Infinity,则序列化的结果会变成 null。

5、JSON.stringify()只能序列化对象的可枚举的自有属性。如果 obj 中的对象是有构造函数生成的, 则使用 JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的 constructor。

6、如果对象中存在循环引用的情况也无法正确实现深拷贝。


const obj={
    age : NaN,
  name:"88",
  isbol:false,
  Reg: new RegExp('\\w+'),
  err:new Error('1'),
  ubd:undefined,
  isDate:new Date( ),
  fun :function (){
    console.log( 'object :>');

  },
  }
let newObj=JSON.parse(JSON.stringify(obj))
console.log('newObj :>> ', newObj);
newObj :>>  {
  age: null,  //有 NaN、Infinity 和-Infinity,则序列化的结果会变成 null。
  name: '88',
  isbol: false,
  Reg: {}, //有 RegExp、Error 对象,则序列化的结果将只得到空对象。
  err: {}, //有 RegExp、Error 对象,则序列化的结果将只得到空对象。
  isDate: '2023-07-25T23:46:27.476Z' //存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
}

为什么 Hooks 不能写在条件语句或循环语句中? (为了保证执行顺序一致性)

因为 React 需要保证在每次组件渲染时,Hooks 的执行顺序都是一致的。如果将 Hooks 写在循环或条件语句中,那么每次渲染时,Hooks 的执行顺序都可能会发生变化。例如,当循环重新运行时,React 将无法确定哪个 Hook 应该先运行,哪个应该后运行。这样会导致组件状态发生不可预测的变化,从而影响组件的正确性。为了解决这个问题,React 强制要求我们在函数的顶部使用 Hooks。这样可以确保每次渲染时 Hooks 的执行顺序是一致的,从而保证组件状态能够正确地更新。

React Hooks —— useMemo&useCallback 区别

相同点:useMemo 和 useCallback——用于优化性能的 Hook 函数

不同点:useMemo 缓存数据——类似 vue 的计算属性 ,useCallback 缓存函数引用

推荐 useMemo,useCallback 不要乱用
因为并不是所有组件内部的函数都拿起包起来处理回归,虽然减少了堆内存的开辟,但是 useCallback 本身也有自己的处理逻辑和缓存的机制,这个也消耗时间,而 useMemo 对性能的提升很明显
所以 useMemo 能用则用,useCallback 能不用则不用,就算用了也不一定有好的效果

跨域是存在于浏览器端还是服务器端?

跨域主要是浏览器行为,是客户端行为,浏览器根据策略,判断是否是跨域。服务器端,是没有跨域这种说法的,其次因为浏览器使用门槛非常低,为了防止别有用心的人攻击普通用户,所以引入跨域策略。


文章作者: BiLiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 BiLiang !
评论
  目录