1.写一个转换数据格式方法,将对象转换为数组?(2次)
可以使用 JavaScript 的 Object.entries()
方法和数组的 map()
方法来将对象转换为数组。以下是一个示例函数实现:
javascriptCopy Codefunction objectToArray(obj) { return Object.entries(obj).map(([key, value]) => ({ key, value })); } const obj = { name: 'Alice', age: 25, city: 'New York' }; const arr = objectToArray(obj); console.log(arr);
在上述代码中,我们定义了一个 objectToArray
函数,它接收一个对象作为参数,并返回一个由对象的键值对组成的数组。
在函数内部,我们使用 Object.entries()
将对象转换为键值对的数组,然后使用 map()
方法对每个键值对进行处理,生成新的对象格式 { key, value }
。
2. useEffect 的第二个参数, 传空数组和传依赖数组有什么区别?
在 React 的 useEffect
钩子函数中,第二个参数用于指定依赖项数组。这个参数可以影响 useEffect
的触发时机和频率。
-
当传递空数组
[]
作为第二个参数时:-
useEffect
只会在组件初始化渲染完成后执行一次,相当于componentDidMount
。 -
它不会再次触发,即使组件的 props 或 state 发生变化。
-
这表明
useEffect
不依赖于任何值,仅在初始化时运行一次,不会进行后续的更新。
-
-
当传递依赖项数组
[dependency1, dependency2, ...]
作为第二个参数时:-
useEffect
在组件初始化渲染完成后执行一次,并在每次指定的依赖项发生变化时再次执行。 -
这意味着只有当依赖项发生变化时,
useEffect
才会被调用。 -
如果依赖项数组为空,
useEffect
将不会被调用。
-
使用空数组 []
作为依赖项,主要适用于希望在组件渲染之后执行一些只需执行一次的副作用操作,例如发送网络请求、订阅事件等。而使用具体的依赖项数组可以控制 useEffect
在特定的依赖项发生变化时进行调用,常用于处理需要根据依赖项更新的副作用操作。
需要注意的是,在使用具体的依赖项数组时,应确保其中包含所有在副作用函数中所使用的依赖项。如果某个依赖项被遗漏,可能会导致副作用函数中使用的数据不是最新的。
3. TypeScript 的内置数据类型有哪些?
TypeScript 的内置数据类型包括以下几种:
-
原始类型(Primitive Types):
-
number
:表示数字类型,包括整数和浮点数。 -
string
:表示字符串类型。 -
boolean
:表示布尔类型,只有true
和false
两个取值。 -
null
:表示空值或空对象引用。 -
undefined
:表示未定义的值。 -
symbol
:表示唯一的、不可修改的值,通常用于创建对象的唯一属性名。
-
-
数组类型:
-
Array
或T[]
:表示一个元素类型为T
的数组。
-
-
元组类型(Tuple Types):
-
[T1, T2, ...]
:表示固定长度的数组,每个位置上的元素类型可以不同。
-
-
对象类型(Object Types):
-
object
:表示非原始类型的对象(排除原始类型)。 -
{}
或Record
:表示任意类型的对象。 -
自定义接口(Interface)或类(Class)。
-
-
函数类型:
-
(arg1: T1, arg2: T2, ...) => ReturnType
:表示函数类型,包括参数类型和返回值类型。
-
-
枚举类型(Enum Types):
-
enum
:表示一组具有命名值的常量。
-
-
其他类型:
-
any
:表示任意类型,允许在编程过程中逐渐切换到更严格的类型检查。 -
void
:表示没有返回值的函数。 -
never
:表示永远不会有返回值的函数或抛出异常的函数。
-
这些是 TypeScript 的内置数据类型,可以根据具体需求选择合适的类型来声明变量、参数、函数等。同时,TypeScript 还支持通过泛型(Generic Types)来定义更灵活的数据类型。
4. rgba()和opacity的透明效果有什么不同
rgba()
和 opacity
都可以用于实现元素的透明效果,但它们的实现方式和应用场景有所不同。
-
rgba()
是一种 CSS 函数,用于设置元素的颜色和透明度。它接受四个参数:红、绿、蓝和透明度(alpha 值),取值范围都在 0 到 1 之间。通过调整透明度(alpha 值),可以使元素变得半透明或完全透明。示例:rgba(255, 0, 0, 0.5)
表示红色半透明。 -
opacity
是 CSS 属性,用于设置元素的整体透明度。它接受一个 0 到 1 的值,其中 0 表示完全透明,1 表示完全不透明。通过设置元素的opacity
属性,可以使元素以整体的方式改变透明度。示例:opacity: 0.5
表示元素半透明。
区别:
-
rgba()
可以针对元素的颜色和透明度进行单独设置,可以实现更具体的透明效果,同时保持元素内部内容的不透明性。 -
opacity
则是对整个元素及其内容进行透明度设置,会影响元素内部所有内容的透明度。
在使用时,应根据具体需求选择合适的方式。如果只需要调整元素本身的透明度而不影响内部内容,可以使用 rgba()
来精确控制透明效果。如果希望整个元素及其内容都具有相同的透明度,可以使用 opacity
属性更加简洁方便。
5. TypeScript支持的访问修饰符有哪些?(2次)
TypeScript 支持以下几种访问修饰符:
-
public
(默认):公共访问修饰符,表示成员可以在任何地方被访问。 -
private
:私有访问修饰符,表示成员只能在类内部被访问。派生类也无法访问。 -
protected
:受保护的访问修饰符,表示成员可以在类内部以及派生类中被访问,但不能在类外部被访问。 -
readonly
:只读访问修饰符,表示成员只能在声明时或构造函数内赋值,赋值后不可修改。
这些访问修饰符可以应用于类的属性、方法和构造函数参数上。默认情况下,类的属性和方法是公共的(public
),但如果显式指定了其他访问修饰符,属性或方法将具有相应的访问权限。
6. 哪些操作会造成内存泄漏?(2)
内存泄漏指的是程序中分配的内存空间无法被释放,导致内存占用不断增加。以下是常见导致内存泄漏的操作:
-
意外的全局变量:如果在函数内部或代码块中声明变量时没有使用关键字(如
let
、const
或var
),则该变量会成为全局变量,而且不会被垃圾回收器回收。因此,在编写JavaScript时,一定要注意使用关键字声明变量。 -
定时器和回调函数未清理:如果使用了
setTimeout
、setInterval
或事件监听器等等,但是未及时清理它们,这些计时器和回调函数将会长时间占用内存。 -
DOM元素未释放:在页面中创建了大量的DOM元素,但是没有正确地删除或释放它们。如果DOM元素被移除或替换了,却仍然存在对它的引用,那么它将无法被垃圾回收器清理。
-
闭包:在JavaScript中,闭包可以让变量在函数执行完毕后继续存在于内存中,进而导致内存泄漏。如果闭包内部包含着外部引用的变量,则这个变量就不会被回收。
-
内存中无法释放的大对象:如果在JavaScript中创建了大量的对象或大数组,它们将会占用大量内存,并且可能导致内存泄漏。在JavaScript中,最好使用对象池等方法来尽量减少内存占用。
为了避免内存泄漏,应该注意及时释放不再使用的资源或引用,正确管理事件监听器、定时器和对象的生命周期,并避免循环引用等造成内存泄漏的操作。使用工具进行内存泄漏检测和分析也是一种有效的方式。
7. 如何将 unknown 类型指定为一个更具体的类型?(2次)
在 TypeScript 中,可以使用类型断言(Type Assertion)将 unknown
类型指定为一个更具体的类型。类型断言的语法是使用尖括号或者 as
关键字来表示
我们将 unknown
类型的变量 val1
和 val2
分别指定为具体的字符串和布尔类型。其中,尖括号语法使用 ` 将
val1断言为
string类型,而
as语法使用
as boolean将
val2断言为
boolean` 类型。
需要注意的是,使用类型断言进行类型转换时,需要确保目标类型确实可以兼容原始类型,否则可能会出现运行时错误。此外,在将 unknown
类型断言为其他类型时,建议使用非空断言运算符(!
)或类型守卫进行进一步的类型检查,避免潜在的类型安全问题。
8.offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别(2)
offsetWidth和offsetHeight是一个元素在页面上的宽度和高度,包括元素的可见内容区域、内边距和边框。
clientWidth和clientHeight是指一个元素的可见内容区域的宽度和高度,不包括内边距、边框和滚动条
scrollWidth和scrollHeight是指一个元素的内容区域的实际大小,包括被隐藏的内容。
总结一下它们之间的区别:
-
offsetWidth/offsetHeight:元素在页面上的完整宽度和高度,包括内容区域、内边距和边框。
-
clientWidth/clientHeight:元素可见内容区域的宽度和高度,不包括内边距、边框和滚动条。
-
scrollWidth/scrollHeight:元素内容区域的实际大小,包括被隐藏的内容,可以通过滚动来查看整个内容。
9. 解释一下TypeScript中的枚举?(2次)
在 TypeScript 中,枚举(Enum)是一种特殊的数据类型,可以用于描述一组相关的常量或命名的值。枚举中的成员一般被称为枚举成员,它们都有一个名称和一个关联的值。使用枚举可以使代码更加直观并且易于维护。
我们定义了一个名为 Direction
的枚举类型,其中包含四个枚举成员 Up
、Down
、Left
和 Right
。由于没有为这些枚举成员指定具体的值,因此它们分别默认从 0 开始自增。
枚举类型的变量 dir
被声明为 Direction
类型,并初始化为 Direction.Up
。此后,我们使用枚举成员名称 Direction.Right
来更新变量 dir
的值。最终输出结果为 3,即 Right
对应的默认值。
需要注意的是,每个枚举成员都可以通过指定关联的数值来进行隐式或显式赋值,
10. 谈谈变量提升,变量的?函数的?(2次)
变量提升是指在 JavaScript 代码执行前,JavaScript 引擎会将函数声明和变量声明的语句提升到当前作用域的顶部。这就意味着,在声明之前,这些变量和函数就已经可以被访问并使用了。
在 JavaScript 中,变量提升的规则和作用域有关。JavaScript 共有两种作用域:全局作用域和函数作用域。
变量的提升:
在全局作用域中声明的变量会被全部提升,但是不会被赋值
在函数作用域中声明的变量也会被提升,但是不会被赋值
函数的提升:
在全局作用域中声明的函数会被整个提升。
在函数作用域中声明的函数也会被整个提升。
需要注意的是,只有函数声明才能被整个提升,函数表达式不会被提升。
11. TypeScript中的方法重写是什么?(2次)
在 TypeScript 中,方法重写(Method Overriding)是面向对象编程中的一个概念。它允许子类重新定义继承自父类的方法,以适应子类的特定需求。通过方法重写,子类可以改变继承的方法的实现细节,而不必修改父类的源代码。
方法重写的基本原则是子类中定义的方法与父类中同名的方法具有相同的签名(包括参数列表和返回类型)但提供了不同的实现。
12. Typescript中 interface 和 type 的区别是什么?(2次)
在 TypeScript 中,interface
和 type
都用于定义类型,但它们有一些区别。
1. 语法差异:
-
interface
关键字用于声明接口,使用interface
可以定义对象的形状、函数的签名等。 -
type
关键字用于声明类型别名,可以给一个类型起一个新的名字。
2. 合并能力:
-
interface
具有合并能力,即同名的接口会自动合并为一个接口,合并后的接口会继承所有同名接口的成员。 -
type
不具有合并能力,同名的类型别名会报错。
3. 实现能力:
-
interface
可以被类实现(使用implements
),用于类与接口的约束关系。 -
type
不能被类实现,它只是给类型起别名,无法用于类与类型的约束关系。
4. 扩展能力:
-
interface
可以通过extends
关键字扩展其他接口或类,实现接口的继承。 -
type
可以使用交叉类型(&
)或联合类型(|
)组合多个类型来创建新的类型。
总结来说,interface
用于定义对象的形状 [Something went wrong, please try again later.]
13. Umi路由跳转传参方式都有哪些?(2次)
1.query 参数传递:使用 history.push
或 Link
组件进行路由跳转时,可以通过 query
属性传递参数,路由对应的组件可以通过 location.query
获取参数。
2.params 参数传递:使用 history.push
或 Link
组件进行路由跳转时,也可以通过 params
属性传递参数。需要在路由配置文件中定义 param,路由对应的组件可以通过 match.params
获取参数。
3.state 参数传递:使用 history.push
或 Link
组件进行路由跳转时,还可以通过 state
属性传递参数,location.state
14. Js中浅拷贝和深拷贝有什么区别,如何实现?(3次)
在 JavaScript 中,浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是两种常见的数据拷贝方式,它们的区别在于拷贝的程度。
-
浅拷贝:浅拷贝仅拷贝对象或数组的引用,而不是拷贝其内部的值。这意味着原始对象和拷贝后的对象会共享同一个内存地址,当其中一个对象修改了属性或元素时,另一个对象也会受到影响。
-
深拷贝:深拷贝会创建一个完全独立的对象或数组,并递归地拷贝其所有的属性或元素。这意味着原始对象和拷贝后的对象不共享内存地址,修改其中一个对象不会对另一个对象产生影响。
下面是一些常见的实现方法:
浅拷贝的实现方法:
-
使用
Object.assign()
方法进行浅拷贝: -
使用展开运算符进行浅拷贝:
深拷贝的实现方法:
-
使用
JSON.parse(JSON.stringify())
进行深拷贝,但注意该方法有一些限制和注意事项(比如无法拷贝函数、循环引用会导致错误等): -
使用递归实现自定义的深拷贝函数:
15. 为什么不能在循环、条件或嵌套函数中调用 Hooks?(2次)
在 React 中,Hooks 是一种用于在函数组件中添加状态和其他 React 特性的方式。Hooks 通过使用特定的钩子函数(如 useState
、useEffect
等)来管理组件的状态和副作用。
根据 React 的规范,Hooks 只能在函数的顶层调用,也就是说不能在循环、条件或嵌套函数中调用 Hooks。这是因为 React 依赖于 Hooks 的调用顺序来正确地追踪组件的状态。如果在循环、条件或嵌套函数中调用 Hooks,会导致 React 无法正常追踪和管理组件的状态,可能引发一些错误或不一致的行为。
16. React有哪些性能优化的方法?(3次)
1.使用虚拟列表和虚拟表格:对于大量数据的情况下,使用虚拟化技术来优化列表和表格的性能。
2.批量更新状态:使用setState的函数式形式(setState(updater)),将多个状态更新合并为一个,从而减少React重复渲染的次数。
3.懒加载组件:使用React.lazy和Suspense进行组件懒加载,在需要时再动态加载组件,提高初始加载性能。
4.使用css过渡和动画:使用CSS过渡和动画来优化组件的过渡效果和用户交互体验,从而提高用户满意度。
5.避免在render方法中调用函数和组件方法:例如在render方法中调用this.props、this.state、this.forceUpdate、this.refs等方法, 会引起组件的重渲染。
17. Webpack有哪些核心概念?
Webpack 是一个现代的 JavaScript 应用程序静态模块打包工具,它的核心概念有以下几个:
-
入口(Entry):指示 Webpack 从哪个文件开始构建依赖关系图。入口可以是一个或多个文件,形成一个依赖图的根节点。
-
输出(Output):指示 Webpack 将打包后的文件输出到哪里,以及如何命名这些文件。输出通常是一个或多个打包后的文件,可以是 JavaScript、CSS 等类型。
-
加载器(Loaders):Webpack 可以通过加载器处理非 JavaScript 文件。加载器允许你在导入文件时进行预处理转换,例如将 Sass 编译为 CSS、将 ES6/TypeScript 转译为 ES5 等。
-
插件(Plugins):插件用于扩展 Webpack 的功能。它们可以执行范围更广的任务,从而实现优化、资源管理、注入环境变量等功能。例如,添加 HtmlWebpackPlugin 插件可以为生成的 HTML 文件添加自动引用打包后的脚本。
-
模式(Mode):模式指定了 Webpack 的构建环境,可以是开发模式(development)、生产模式(production)或其他自定义环境。不同的模式会触发不同的内置优化策略,例如压缩代码、启用浏览器调试工具等。
-
代码分割(Code Splitting):Webpack 支持将代码分割成多个块,以实现按需加载和优化加载性能。通过代码分割,可以将常用的第三方库或业务代码拆分成不同的文件,按需加载,减少初始加载时间。
-
缓存(Caching):Webpack 通过使用哈希值作为文件名的一部分,可以根据文件内容的更改来更新缓存。这样,在文件内容不变的情况下,可以利用浏览器缓存,减少重新下载的开销。
-
DevServer:Webpack DevServer 是一个轻量级的开发服务器,提供了热更新、代理转发、自动刷新等开发时常用的功能。
这些都是 Webpack 的核心概念,了解并熟练使用它们可以帮助你更好地使用 Webpack 进行模块打包和构建前端应用程序。
18. 常用的hooks都有哪些,说出他们的作用,最少列出来5个?(2次)
以下是常用的 React Hooks,并列出了它们的作用:
-
useState:用于在函数组件中添加状态管理。它返回一个包含当前状态和更新状态的数组,可以通过解构赋值来使用。
-
useEffect:用于在函数组件中执行副作用操作,比如订阅外部数据、操作 DOM 等。它接收一个回调函数和依赖数组,并在每次渲染后执行回调函数。
-
useContext:用于从 React 的 Context 中获取当前的上下文值。它接收一个 Context 对象,并返回该 Context 的当前值。
-
useReducer:类似于 Redux 中的 reducer,用于管理复杂的状态逻辑。它返回当前状态和一个 dispatch 函数,用于派发状态更新的动作。
-
useCallback:用于性能优化,避免不必要的函数重新创建。它接收一个回调函数和依赖数组,并返回一个记忆化的版本,只在依赖数组变化时才会重新创建。
-
useMemo:用于性能优化,缓存计算结果。它接收一个回调函数和依赖数组,并返回计算结果。只有当依赖数组发生变化时,才会重新计算结果。
-
useRef:用于在函数组件中创建可变的 ref 对象。它返回一个包含
current
属性的对象,该属性可以保存任意可变值,并在组件重新渲染时保持不变。 -
useLayoutEffect:类似于
useEffect
,但它在 DOM 更新之后同步执行。在大多数情况下,可以使用useEffect
替代,但在需要获取真实 DOM 属性时,可能需要使用useLayoutEffect
。 -
useImperativeHandle:用于自定义暴露给父组件的实例值。它接收一个 ref 对象和一个工厂函数,在父组件中可以通过 ref 访问工厂函数返回的值。
-
useDebugValue:用于在 React 开发者工具中显示自定义的钩子值,方便调试和跟踪。它接收一个值和一个格式化函数,用于显示在工具中的自定义标签。
这些是常用的 React Hooks,每个 Hook 都有特定的作用,能够方便地处理组件的状态管理、副作用操作、上下文等功能。使用 Hooks 可以使函数组件更易于编写、理解和维护。
19。Typescript中泛型是什么?(2次)
泛型是 TypeScript 中一个重要的特性,它允许在函数、类、接口等类型声明中使用类型参数,从而实现更加抽象和灵活的类型定义。
通过使用泛型,可以编写具有通用性的代码,不受特定类型的限制。例如,一个数组排序函数不需要关心数组元素的具体类型,只需要确保数组元素是可比较的,就可以实现一个通用的排序方法。
在 TypeScript 中泛型可以针对以下几个部分进行声明:
-
函数泛型:通过在函数参数列表前声明类型参数,可以使函数参数和返回值的类型更加灵活。
-
类泛型:在声明类时添加泛型类型参数,可以使类的属性和方法可以使用泛型类型参数。
-
接口泛型:可以使用泛型定义接口的属性类型和方法参数类型。
20umijs.lugin-access插件如果使用?
umijs/plugin-access 是 Umi.js 的一个插件,它提供了一种方便的方式来管理和控制路由权限。通过配置路由权限规则,可以根据用户的身份、权限等信息来限制用户对某些路由的访问。
要使用 umijs/plugin-access 插件,可以按照以下步骤进行操作:
\1. 安装插件:
shellCopy Code npm install @umijs/plugin-access --save-dev
\2. 在 .umirc.ts 或者 config/config.ts 文件中配置插件:
javascriptCopy Code export default { plugins: ['@umijs/plugin-access'], access: { **// 配置权限规则** **// 可以是一个函数,也可以是一个包含多个函数的数组** **// 函数的返回值为 boolean 表示是否有权限访问该路由** canAccessRoute: (route) => { **// 在这里根据用户的身份、权限等信息判断是否有权限访问该路由** **// 返回 true 表示有权限,返回 false 表示没有权限** }, }, };
\3. 在路由配置文件 config/routes.ts 中,可以使用 access 属性来指定每个路由的权限要求:
javascriptCopy Code const routes = [ { path: '/public', component: 'PublicPage', }, { path: '/private', component: 'PrivatePage', access: 'user', **// 这个路由需要用户登录才能访问** }, **// ...** ];
\4. 在页面组件中,可以通过 access 对象访问当前路由的权限信息:
javascriptCopy Code import { useAccess } from 'umi'; export default function PrivatePage() { const access = useAccess(); return ( <div> {access.canAccessRoute('/private') ? ( • <h1>Welcome to private page!</h1> ) : ( • <h1>Sorry, you don't have access to this page.</h1> )} </div> ); }
21.数组【1,2,3,4,5,3,2,2,4,2,2,3,1,3,5】中出现次数最多的数,并统计出现多 少次,编写一个函数?
function findMostFrequentNumber(arr) { const counter = arr.reduce((acc, num) => { if (num in acc) { acc[num]++; } else { acc[num] = 1; } return acc; }, {}); let maxNum = null; let maxCount = 0; for (const num in counter) { if (counter[num] > maxCount) { maxNum = num; maxCount = counter[num]; } } return { number: maxNum, count: maxCount }; } **// 调用示例** const arr = [1,2,3,4,5,3,2,2,4,2,2,3,1,3,5]; const result = findMostFrequentNumber(arr); console.log(`出现次数最多的数字是 ${result.number},出现了 ${result.count} 次`);
22大文件如何做断点续传?(2)
\1. 将大文件分割为小块:将大文件分割成多个固定大小的小块,例如每个小块的大小可以设置为 1MB。这样可以方便进行分片上传和管理。
\2. 记录上传进度:在客户端和服务端都需要记录上传进度。客户端可以通过记录已成功上传的分片信息来跟踪上传进度,服务端可以通过记录已接收的分片信息来跟踪接收进度。
\3. 分片上传:将分割后的小块逐个上传到服务器,并记录已成功上传的分片信息。可以使用 HTTP 或其他协议来进行分片上传。
\4. 上传失败处理:如果某个分片上传失败,需要记录下该分片的状态,以便后续重新上传。可以保存上传失败的分片信息,并将其标记为未完成。
\5. 断点续传:在下次上传时,检查之前的上传进度和已完成的分片信息。根据记录的信息,跳过已成功上传的分片,只上传未完成的分片。确保已上传的分片不会重复上传。
\6. 合并分片:当所有分片都上传完成后,在服务端将这些分片按顺序合并为完整的文件。
23.元素js如何实现上拉加载下拉刷新?(2)
<script> var content = document.getElementById('content'); var items = document.getElementsByClassName('item');
var maxItems = 10; // 总共可加载的项 var visibleItems = 5; // 每次加载可见的项 var loadedItems = visibleItems; // 已加载的项 // 监听滚动事件,判断是否需要上拉加载 content.addEventListener('scroll', function() { if (content.scrollTop + content.clientHeight >= content.scrollHeight) { loadMoreItems(); } }); // 下拉刷新 content.addEventListener('touchstart', handleTouchStart, false); content.addEventListener('touchmove', handleTouchMove, false); var startY; function handleTouchStart(event) { startY = event.touches[0].clientY; } function handleTouchMove(event) { var touch = event.touches[0].clientY; var diff = touch - startY; if (diff > 0 && content.scrollTop === 0) { event.preventDefault(); refreshItems(); } } // 上拉加载更多项 function loadMoreItems() { if (loadedItems >= maxItems) { return; // 如果已加载的项达到了总共可加载的项,就不再加载 } for (var i = loadedItems; i < loadedItems + visibleItems; i++) { var newItem = document.createElement('div'); newItem.className = 'item'; newItem.textContent = 'Item ' + (i + 1); content.appendChild(newItem); } loadedItems += visibleItems; } // 下拉刷新项 function refreshItems() { // 清空已加载的项 while (content.firstChild) { content.firstChild.remove(); } loadedItems = visibleItems; // 重置已加载的项计数 for (var i = 0; i < loadedItems; i++) { var newItem = document.createElement('div'); newItem.className = 'item'; newItem.textContent = 'Item ' + (i + 1); content.appendChild(newItem); } }
</script>
24说说设备像素、css像素、设备独立像素、dpr、ppi之间的区别?(2)
\1. 设备像素 设备像素指物理屏幕上的像素点数量,通常用“px”表示。屏幕上的每个px将有一个相对应的硬件像素来对应显示。设备像素决定了屏幕的绝对分辨率。
\2. CSS 像素 CSS 像素在设计中用来表示页面中图形和文本等元素尺寸的虚拟单位,通常用“px”表示。CSS 像素并不是“真实”的像素,它是相对于屏幕分辨率独立的。
\3. 设备独立像素 设备独立像素(DP,Density-Independent Pixel),也称为逻辑像素,是一种基于屏幕密度的抽象概念,通常用“dp”或“dip”表示,Android系统中也将其称为“sp”。设备独立像素是与屏幕像素密度有关的像素单位,屏幕像素密度越高,同样大小的物体所占设备独立像素就越高。
\4. DPR(设备像素比) DPR指的是设备像素和CSS像素的比例关系,即“设备像素 / CSS像素” 。它用来描述物理像素与逻辑像素之间的关系。常见的 DPR 值包括 1、2、3 等。
\5. PPI(每英寸像素数) PPI指的是屏幕上每英寸显示的像素点数量,通常用来表示屏幕密度以及屏幕显示清晰程度。PPI 值可以通过计算设备像素和屏幕尺寸之间的比例得到。
25说说TCP为什么需要三次握手和四次握手?(2)
\1. 三次握手 初始化双方 seq。 确认双方信道可以实现最低限度的全双工三次握手是指在建立 TCP 连接时,客户端与服务器之间需要进行三次通信以确认连接的建立。其流程如下:
· 第一次握手:客户端向服务器发送 SYN 报文,并随机生成一个初始序号(ISN)。
· 第二次握手:服务器收到SYN报文后,回复一个SYN+ACK报文作为确认,其也会随机生成一个ISN值。
· 第三次握手:客户端再次向服务器回复一个ACK报文,确认连接已建立。
\2. 四次握手 TCP 要支持半关闭连接四次握手是指在释放 TCP 连接时,客户端与服务器之间需要通过四次通信来确认连接已经关闭。其流程如下:
· 第一次握手:客户端向服务器发送FIN报文,请求关闭连接。
· 第二次握手:服务器收到FIN报文后,回复一个ACK报文作为确认,但此时仍可向客户端发送数据。
· 第三次握手:服务器完成数据的发送后,则向客户端发送一个FIN报文,表示服务器的数据发送完毕,请求关闭连接。
第四次握手:客户端收到服务器的FIN报文后,向服务器回复一个ACK报文作为确认,进入 TIME_WAIT 状态,等待 2MSL 后断开连接。
26.react之间如何通信(3)
1.props:通过向子组件传递 props,可以实现父子组件之间的通信。这是 React 中最基本的通信方式。父组件可以将需要传递给子组 件的数据作为 props 传递给子组件,子组件可以通过 this.props
访问这些数据。
2.Context:Context 是 React 中的一种全局数据管理方式。它可以让子组件在不通过 props 传递的情况下,直接访问父组件或者祖先 组件中的数据。使用 Context 需要先创建一个 Context 对象,然后在祖先组件中通过 Provider 提供数据,在子孙组件中通 过 Consumer 访问数据。
3.Refs:Refs 允许我们访问在组件中创建的 DOM 或者其他组件实例。通过 Refs,组件可以在不通过 props 或者 context 的情况下, 直接修改子组件或者 DOM 元素的属性。
4.Event Bus:Event Bus 是一种跨组件通信方式,它可以让任何两个组件之间都可以进行通信。
27.什么是闭包,应用场景是什么(2)
闭包是指在函数内部创建的函数,并且该函数能够访问到其外部函数的作用域。
闭包有以下特点:
-
内部函数可以访问外部函数中的变量和参数。
-
外部函数的执行上下文被保留在内存中,即使外部函数执行完成后,内部函数仍然可以访问外部函数的作用域。
-
多个内部函数可以共享同一个父级作用域,形成一个闭包函数族。
闭包的应用场景包括但不限于:
-
保护变量:通过使用闭包,可以隐藏变量,只提供对外部函数公开的接口。这样,可以确保变量不会被外部直接修改,增加了数据的安全性。
-
计数器和累加器:通过闭包,可以在函数外部保存一个内部状态,并在每次调用内部函数时修改该状态。这一特性可用于实现计数器、累加器等功能。
-
延迟执行和回调函数:将函数作为返回值,可以实现延迟执行或者在特定条件满足时回调执行。
28.JS数组之间进行合并,写出三种合并方式?
concat() 方法:
concat()
方法可以合并两个或多个数组,并返回一个新数组。它会将所有输入数组中的元素添加到一个新的数组中。
spread 运算符(扩展运算符):
ES6 中引入的 spread 运算符可以很方便地合并数组。它可以将一个数组转换为独立的参数列表,从而可以很容易地合并多个数组。
push() 方法:
push()
方法可以将一个或多个元素添加到数组的末尾,并返回修改后的数组长度。因此,可以通过循环或迭代来将一个数组中的元素添加到另一个数组中。
29.为什么 useState 返回的是数组而不是对象?
在 React 中,useState
是一个用于在函数组件中声明状态的钩子函数。它的返回值是一个长度固定为 2 的数组,而不是一个对象,这是由设计选择所决定的。
使用数组来表示状态,是因为它具有以下优势:
-
简单直观:将状态以数组的形式进行管理,可以更容易地理解和使用。
-
顺序保持一致:使用
useState
声明多个状态时,它们的顺序和声明的顺序是完全一致的。你可以根据索引访问和更新每个状态,而不需要命名或记住状态的特定名称。 -
无需每次指定键:当更新状态时,不需要像使用对象那样指定键。只要调用
useState
返回的第二个元素(通常命名为setXXX
)即可。
30.React 的生命周期?每个生命周期都做了什么?
在React中,组件的生命周期可以分为三个主要的阶段:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。以下是每个阶段对应的方法:
-
挂载阶段(Mounting Phase):
-
constructor:组件实例被创建时调用,用于初始化状态和绑定事件处理函数。
-
static getDerivedStateFromProps:在组件实例化和更新时都会调用,它接收新的属性和当前状态,并返回一个更新后的状态。
-
render:根据组件的状态和属性,返回JSX元素进行渲染,负责生成组件的UI结构。
-
componentDidMount:组件已经被渲染到DOM中后调用,此时可以进行网络请求、订阅事件等副作用操作。
-
更新阶段(Updating Phase):
-
static getDerivedStateFromProps:在组件更新时调用,接收新的属性和当前状态,并返回一个更新后的状态。
-
shouldComponentUpdate:在组件即将更新之前调用,返回一个布尔值,用于判断是否需要进行组件的重新渲染。
-
render:重新渲染组件的UI结构。
-
componentDidUpdate:组件已经重新渲染并且更新到DOM上后调用,此时可以进行状态更新、网络请求等副作用操作。
-
卸载阶段(Unmounting Phase):
-
componentWillUnmount:组件将要从DOM中移除时调用,可以进行清理工作,如取消订阅、清除定时器等。
32.Js中深拷贝和浅拷贝的定义以及具体实现都有哪些方法?
浅拷贝是指创建一个新对象或数组,该对象或数组的值是原始对象或数组中的引用。换句话说,浅拷贝只复制对象或数组的第一层,而不会递归复制嵌套的对象或数组。
常见的实现浅拷贝的方法有:
-
扩展运算符(Spread Operator)
javascriptCopy Codeconst originalArray = [1, 2, 3]; const newArray = [...originalArray];
javascriptCopy Codeconst originalObject = { name: "John", age: 25 }; const newObject = { ...originalObject };
-
Object.assign()
javascriptCopy Codeconst originalObject = { name: "John", age: 25 }; const newObject = Object.assign({}, originalObject);
注意:
Object.assign()
在浅拷贝对象时是深拷贝属性值。 -
Array.slice()
javascriptCopy Codeconst originalArray = [1, 2, 3]; const newArray = originalArray.slice();
深拷贝是指创建一个全新的对象或数组,并递归地复制原始对象或数组的所有嵌套对象或数组。
常见的实现深拷贝的方法有:
-
使用 JSON 序列化和反序列化
javascriptCopy Codeconst originalObject = { name: "John", age: 25 }; const newObject = JSON.parse(JSON.stringify(originalObject));
这种方法可以处理大多数情况,但是对于包含函数、正则表达式等特殊对象的深拷贝可能会有问题。
-
递归实现深拷贝
javascriptCopy Codefunction deepCopy(obj) { if (typeof obj === "object" && obj !== null) { const copy = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = deepCopy(obj[key]); } } return copy; } return obj; } const originalObject = { name: "John", age: 25 }; const newObject = deepCopy(originalObject);
33.Umi中如何实现路由权限,实现按钮权限?
在 Umi 中实现路由权限和按钮权限可以通过以下步骤:
\1. 路由权限:
· 在路由配置文件中,为每个需要进行权限控制的路由添加 authority 属性,值为权限标识,例如角色名或权限码。
在全局的路由守卫(比如 layouts 中的页面组件)中,判断当前用户是否有权限访问该路由。可以使用 Access 组件配合 authority 属性来进行权限控制。
\2. 按钮权限:
· 在页面组件里,在需要进行权限控制的按钮上添加 authority 属性,值为权限标识,例如角色名或权限码。
自定义一个 Button 组件,根据当前权限判断是否显示按钮。
然后,在页面组件中使用 AccessibleButton 来替代 antd 的 Button 组件,传递相应的 authority 属性。
32.props和state相同点和不同点?render方法在哪些情况下会执行?
Props(属性)和 State(状态)是 React 组件中用于管理数据的两个概念。
相同点:
-
它们都是用于存储和管理组件数据的。
-
当它们的值发生变化时,都可以触发组件重新渲染。
不同点:
-
Source(数据来源):Props 是从父组件传递给子组件的,而 State 则是在组件内部定义和管理的。
-
Mutability(可变性):Props 是只读的,无法在子组件内部直接修改,只能通过父组件重新传递新的 Props。而 State 可以在组件内部进行修改和更新。
-
更新方式:当 Props 发生变化时,React 会自动进行组件的重新渲染。而 State 的更新需要调用组件的
setState
方法来触发重新渲染。
关于 render
方法的执行情况:
-
初始渲染:组件首次被渲染时,
render
方法会被调用,并生成对应的虚拟 DOM 树。 -
数据变化:当组件的 Props 或 State 发生变化时,React 会重新调用
render
方法来更新虚拟 DOM,并与之前的虚拟 DOM 进行对比。 -
强制更新:通过调用组件的
forceUpdate
方法可以强制触发render
方法的执行,即使 Props 和 State 没有发生变化。 -
父组件更新:如果父组件进行重新渲染,子组件的
render
方法也会被调用。
需要注意的是,只有在 render
方法中返回的虚拟 DOM 与之前的虚拟 DOM 不同,React 才会重新渲染真实 DOM,并更新到页面上。
33.说说你对Object.defineProperty()的理解?
Object.defineProperty() 是 JavaScript 中用于定义或修改对象属性的方法。通过这个方法,可以精确地控制属性的特性,例如可枚举性、可写性、可配置性以及属性的值。
这个方法接受三个参数:要定义属性的对象、属性名和属性描述符对象。属性描述符对象可以包含以下属性:
-
value:属性的值。
-
writable:属性是否可写。
-
enumerable:属性是否可枚举。
-
configurable:属性是否可配置。
使用 Object.defineProperty() 方法可以实现对属性的定制化控制,例如可以定义一个不可枚举、不可写、但可配置的属性,从而实现对属性的严格保护或隐藏。
34.说说React中的虚拟dom?在虚拟dom计算的时候diff和key之间有什么关系?
虚拟 DOM(Virtual DOM)是 React 中的一种机制,通过在内存中构建一棵轻量级的虚拟 DOM 树来代替操作浏览器 DOM,从而提高组件的渲染性能和用户体验。
在 React 中,当组件的 Props 或 State 发生变化时,React 会根据最新的数据重新生成一棵虚拟 DOM 树,并与之前的虚拟 DOM 树进行对比。在对比的过程中,React 会找到两棵树中不同的节点,并将它们对应的真实 DOM 节点进行修改、删除或添加,最终将更新后的 DOM 渲染到页面上。
虚拟 DOM 的 diff 算法是 React 优化渲染性能的核心。在 diff 算法中,每个节点都有一个唯一的标识符,称为 key。当新旧虚拟 DOM 树进行对比时,React 会通过 key 来判断两个节点是否表示相同的内容。在判断过程中,React 会先以新节点为基准,在旧节点中查找对应的节点。如果找到了相同的节点,则进行更新;否则,将新节点插入到旧节点中或从旧节点中删除。
在使用 React 进行开发时,我们应该尽量避免使用索引作为 key,因为索引本身并没有表示唯一性,容易造成错误的判断结果和性能问题。相反,我们应该在数据中为每个元素提供一个唯一的标识符,例如数据库中的 ID 或者全局唯一的 UUID。
需要注意的是,虽然虚拟 DOM 可以有效地降低浏览器对真实 DOM 的操作次数,但也会带来一些额外的开销。例如,在生成和比较虚拟 DOM 树时,需要进行大量的计算和判断,可能会影响应用的整体性能。因此,在实际开发中,我们需要根据具体情况,权衡使用虚拟 DOM 的益处和代价,选择最适合自己应用的方案。
35.react新出来两个钩子函数是什么?和删掉的will系列有什么区别?(2)
react新出的哪两个钩子函数? 新出的getDerivedStateFromProps 与 getSnapshotBeforeUpdate 两个钩子。 少了componentWillMount,componentWillReceiveProps与componentWillUpdate三个都带有will的钩子。 1、getDerivedStateFromProps 这个钩子的作用其实就是从props获取衍生的state。getDerivedStateFromProps中返回一个对象用于更新当前组件的state,而不是直接取代。 2、getSnapshotBeforeUpdate 这个钩子的意思其实就是再组件更新前获取快照,此方法一般是结合componentDidUpdate使用,getSnapshotBeforeUpdate中返回的值将作为第三参数传递给componentDidUpdate。 为什么删掉will? 三个钩子函数是被废弃,但是不是直接不能用了,而是官方会给出警告并推荐我们在这三个钩子前添加UNSAFE_前缀。 为什么被废弃呢,因为使用率并不太高,这三个钩子很容易被误解和滥用,这几个钩子不稳定。
36.React的props.children使用map函数来遍历会收到异常显示,为什么?应该 如何遍历?
使用 map
函数遍历 React 组件的 props.children
时,可能会遇到异常显示的问题。这是因为 props.children
在不同情况下的类 型不同,导致 map
函数操作的方式也不同。
解决方法:
1.首先检查 props.children
的类型。
2.如果它是一个数组,那么可以直接使用 map
函数进行遍历。
3.如果它只有一个子元素,需要先将它转换为数组,然后再进行遍历
37.ts中抽象类的理解?
在 TypeScript 中,抽象类是一种特殊的类,用于作为其他类的基类或父类,并且不能直接实例化。抽象类主要用于定义一组通用的属性和方法,然后让其他类继承并实现这些抽象类中定义的方法。
抽象类通过使用 abstract
关键字进行声明。抽象类可以包含抽象方法、普通方法和属性。
抽象类的主要特点包括:
-
不能直接实例化,只能被继承。
-
可以包含抽象方法、普通方法和属性。
-
抽象方法必须在派生类中实现。
-
子类可以覆盖或扩展抽象类中的方法和属性。
38.redux本来是同步的,为什么它能执行异步代码?实现原理是什么?中间件的实现原理是什么?
Redux 是一个使用单一数据源和不可变数据的状态管理库,它本身是同步的,但通过中间件可以实现异步操作。Redux 中最常用的异步中间件是 Redux Thunk、Redux Saga 和 Redux Observable。
具体来说,当我们需要在 Redux 中执行异步代码时,通常会使用如下流程:
-
在 View 中触发一个异步 Action。
-
Action 发送到 Reducer,Reducer 更新 Store 中的状态。
-
如果需要执行异步操作,Middleware 捕获到这个 Action,并执行异步代码。
-
异步代码完成后, Middleware 发送新的 Action 到 Reducer,Reducer 再次更新 Store 中的状态。
实现原理是中间件利用了 Redux 提供的 action 和 reducer 的单向数据流机制,使得 action 可以被拦截并且异步处理后再次派发出去。中间件本质上是对 dispatch 函数的重写,并且它可以执行某些操作,例如异步处理、日志记录、错误报告等。
中间件的原理是 Redux 通过在 dispatch 中间注入中间件的执行代码,在 action 到达 reducer 之前对 action 进行了修改或者是对应的 side effect 操作。具体来说,每个中间件都是一个函数,它接收 store 的 dispatch 和 getState 函数作为参数,返回一个新的函数,这个函数被用来包装 action creator,在 dispatch 前后进行操作。这种方式支持链式调用多个中间件,以便进行不同操作,例如数据处理、异步调用、日志记录、错误报告等。
39.redux中同步action与异步action最大的区别是什么?
1.同步 Action: 同步 action 是指在触发后立即执行并完成的 action。具体表现为,在 Redux 中通过 dispatch 触发同步 action 后,它 会立即被发送到 reducer 进行状态更新
2.异步 Action: 异步 action 是指在触发后需要一定时间进行处理的 action,通常用于处理异步操作,比如网络请求、定时器等。异步 action 通常会触发多个相关的同步 action,以表示异步操作的不同阶段
40.redux-saga和redux-thunk的区别与使用场景?
Redux Saga 和 Redux Thunk 都是用于处理异步操作的 Redux 中间件,它们在实现和使用上有一些区别,适用于不同的场景。
-
区别:
-
实现方式:Redux Thunk 是一个函数,允许我们在 Action Creator 中返回一个函数,这个函数可以进行异步操作并手动调用 dispatch。而 Redux Saga 则基于 ES6 的 Generator 函数,通过使用特定的语法来处理异步操作。
-
控制流程:Redux Thunk 使用简单的回调函数来处理异步操作,通常是通过链式调用多个 Action,从而实现异步流程控制。而 Redux Saga 则使用生成器来定义和控制异步操作的流程,通过监听和响应不同的 Action 来执行相应的操作。
-
异常处理:Redux Thunk 需要手动处理错误,通过 try-catch 捕获异常,并在回调函数中 dispatch 错误信息。而 Redux Saga 具备异常捕获和处理的能力,在 Generator 函数内部可以使用 try-catch 捕获异常,并且可以派发不同的 Action 处理异常情况。
-
使用场景:
-
Redux Thunk 适用于简单的异步场景,例如发起一个 AJAX 请求并在请求成功后更新状态。它的学习曲线比较低,容易上手,适合于对异步处理需求不复杂的项目。
-
Redux Saga 适用于复杂的异步场景,例如需要处理多个连续的异步操作、任务取消、并发请求等。它提供了更强大和灵活的异步处理能力,并且通过 Generator 函数的形式使得异步流程易于阅读和维护。但是相对复杂性也较高,需要掌握 Generator 函数和 Saga 相关的代码结构。
总而言之,Redux Thunk 适用于简单的异步操作,学习曲线较低;而 Redux Saga 适用于复杂的异步场景,提供了更强大和灵活的异步处理能力。选择哪个中间件取决于项目的具体需求和开发团队的技术背景。
41.在使用redux过程中,如何防止定义的action-type的常量重复?(2)
在 Redux 中,为了避免定义的 action type 常量重复,可以采用以下几种方式:
-
使用字符串常量:定义 action type 时使用字符串常量,在不同的模块或文件中使用不同的命名空间来确保唯一性。
-
使用枚举类型:使用 TypeScript 的枚举类型来定义 action type,枚举成员的名称是唯一的
-
使用工具库:可以使用一些辅助工具库来帮助管理和生成唯一的 action type,例如
redux-act
、redux-actions
等。
42.CDN的特点及意义?
CDN(Content Delivery Network,内容分发网络)是一种分布式的网络架构,其目标是通过将内容缓存在离用户较近的边缘节点上,提供更快速、稳定的内容分发服务。CDN的特点和意义如下:
-
高效快速:CDN利用分布在全球各地的边缘节点,将内容缓存到离用户最近的节点上,当用户请求内容时,可以从最近的节点获取,减少了传输距离和网络拥堵,提供了更快的响应速度和较低的延迟。
-
负载均衡:CDN通过智能路由和负载均衡技术,将用户请求分配到最优的节点,避免了单一服务器的过载,提高了整体的系统吞吐量和可用性。
-
提升用户体验:由于CDN提供了更快的响应速度和较低的延迟,用户可以更快速地访问和加载网页、图片、视频等内容,提升了用户的体验感受,降低了用户的等待时间。
-
减轻源站压力:通过将静态内容缓存到CDN节点上,可以减轻源站的负载压力,提高源站的可扩展性和稳定性,同时减少了源站与用户之间的直接流量,降低了网络成本。
-
抵御网络攻击:CDN可以通过分布式的架构和安全防护机制,提供一定程度的网络攻击防护,如分布式拒绝服务攻击(DDoS)的缓解。
-
全球覆盖:CDN网络分布在全球各地的节点,可以实现全球范围内的内容分发,无论用户身处何地,都可以享受到较快速的网络访问。
综上所述,CDN具有高效快速、负载均衡、提升用户体验、减轻源站压力、抵御网络攻击和全球覆盖等特点。在大规模的网络应用中,使用CDN能够显著提升网站性能、可靠性和安全性,为用户提供更好的体验,并降低运营成本。
43.为什么for循环比forEach性能高
在 JavaScript 中,常用的循环有 for 循环和 forEach 循环。虽然两者都可以遍历数组,但它们的实现方式不同,因此性能也有所不同。
for 循环是一种基于索引值(或下标)的循环方式,通过数组的下标索引来访问数组元素。而 forEach 循环则是一种迭代器,对数组中的每个元素都执行一次回调函数。
因此,for 循环相对于 forEach 循环具有以下优势:
-
for 循环不需要编写额外的函数,可以直接对数组进行操作,因此其执行过程相对更加高效。
-
在 for 循环中,可以通过定义一个本地变量(如
var len = arr.length
)将数组长度缓存起来,避免多次访问arr.length
属性导致的性能损失。 -
在需要对数组进行修改时,for 循环比 forEach 循环更为方便且高效。因为在 for 循环中,可以通过获取数组的下标索引来修改数组元素,而在 forEach 循环中无法直接修改数组元素。
总体而言,在大多数情况下,for 循环比 forEach 循环更具有优势。但是,对于需要进行异步操作的情况,forEach 循环可能更为适用,因为它可以支持 async/await
操作,而 for 循环不支持。
需要注意的是,虽然 for 循环比 forEach 循环更快,但在实际应用中,在性能方面的差别通常不会太大。因此,选择使用哪种循环方式应该根据实际情况而定,以符合代码的可读性、可维护性和执行效率等方面的要求。
44.redux实现原理?
Redux 是一个 JavaScript 状态管理库,它可以用于管理应用程序中的状态。Redux 的实现原理可以简单概括为以下几个步骤:
创建 store:创建一个全局的存储对象作为状态管理的中心。Store 由 Redux 提供,它包含应用程序的当前状态,还包括实现更新状态的 reducer。
创建 reducer:reducer 是纯函数,它接收旧的 state 和 action,返回新的 state。Reducer 的主要作用是根据 action 的类型来更新 state,同时保证 state 的不可变性。
创建 action:action 是一个普通的 JavaScript 对象,它描述了要发生的操作。Action 包含一个 type 属性,表示要执行的行动类型,还可以包含其他数据。
分发 action:通过调用 store.dispatch() 方法将 action 分发到 reducer,这个过程会触发 state 的更新。
更新 state:reducer 根据接收的 action 类型更新 store 中的 state,同时返回一个新的 state。
订阅 state 的变化:可以通过 store.subscribe() 方法监听 state 的变化,在 state 更新时执行相应的操作。
45.React render方法的原理,在什么时候会触发?(2)
render()
方法的原理:
根据组件的状态和属性(props)来生成对应的虚拟 DOM 树。React 使用虚拟 DOM 来表示组件的界面结构,并通过比较更新前后 的虚拟 DOM 树找出差异,然后将差异部分进行高效地更新到真实的 DOM 上,从而实现页面的局部更新
render()
方法会在以下情况下触发:
1.组件首次挂载:当组件第一次被渲染到真实的 DOM 上时,render()
方法会被调用
2.组件的状态或属性发生变化:当组件的状态(通过 setState()
方法更新)或属性发生变化时,React 会自动重新调用 render()
方法,生成新的虚拟 DOM,并进行比较和更新
3.父组件重新渲染:如果父组件的 render()
方法被调用,那么其中包含的子组件的 render()
方法也会被触发
46.谈谈你是如何做移动端适配的?
对于移动端适配,一种常用的方法是响应式布局(Responsive Layout)和媒体查询(Media Queries)。以下是一些常见的移动端适配策略:
-
使用Viewport meta标签:在HTML文档的头部添加Viewport meta标签,通过设置
width=device-width
,可以告诉浏览器使用设备的宽度作为页面的宽度。另外,还可以设置initial-scale
、maximum-scale
和minimum-scale
等属性来控制缩放行为。 -
使用相对单位:在样式表中使用相对单位(如百分比、em、rem)来定义元素的尺寸和布局,相对单位可以根据屏幕尺寸进行适配。
-
弹性布局:使用弹性盒模型(Flexbox)或网格布局(Grid)等弹性布局方式,使得元素能够根据可用空间自动调整大小和位置。
-
媒体查询:利用CSS3中的媒体查询功能,根据不同的屏幕尺寸和设备特性,为不同的屏幕宽度范围应用不同的样式规则。可以根据需要调整字体大小、布局结构、隐藏或显示某些元素等。
-
图片适配:通过设置图片的最大宽度为100%来保证图片在不同尺寸的屏幕上自适应。同时,使用高分辨率的图片(如Retina屏幕),以提供更清晰的图像。
-
测试和调试:在开发过程中,使用模拟器或真实设备进行测试,并通过调试工具(如Chrome开发者工具)来检查和优化页面在不同屏幕大小下的样式和布局。
综合使用以上方法,可以使网页在不同尺寸的移动设备上呈现出良好的用户体验,适应不同的屏幕大小和设备特性。
当然,对于较为复杂的移动端适配需求,可以考虑使用CSS预处理器(如Sass、Less)或CSS框架(如Bootstrap、Foundation)等工具来简化和优化适配过程。
47.移动端1像素的解决方案?
在移动端开发中,由于不同设备的像素密度差异,1像素问题成为了一个常见的难题。如果我们不对这个问题进行针对性的解决,那么会导致页面显示效果不美观,甚至影响用户体验。
以下是一些解决方案:
-
使用css3的scale属性:将要渲染的元素放大一倍,然后通过scale缩小回去。例如,将一个1像素的边框放大到2像素,再通过scale(0.5)恢复原来大小。这种方法可以使边框看起来更加清晰,但是可能会影响元素的布局和性能。
-
通过伪元素实现:使用伪元素before或after,并设置其content属性为空,然后通过border设置为1像素粗细的边框。例如:
cssCopy Code.box::before { content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; border: 1px solid #ddd; -webkit-box-sizing: border-box; box-sizing: border-box; z-index: -1; }
这种方法可以避免影响元素布局,但是可能会增加HTML代码量和CSS复杂度。
-
通过JavaScript动态设置viewport缩放比例: 使用JavaScript获取设备物理像素和设备独立像素的比例,然后动态设置viewport的缩放比例,从而实现1像素问题的解决。例如:
javascriptCopy Codevar scale = 1 / window.devicePixelRatio; document.querySelector('meta[name="viewport"]').setAttribute('content', 'width=device-width,initial-scale=' + scale + ',maximum-scale=' + scale + ',minimum-scale=' + scale + ',user-scalable=no');
这种方法可以根据设备分辨率进行动态适配,但是可能会对页面布局和性能产生影响。
-
使用第三方库:有一些开源的第三方库可以帮助我们解决1像素问题,例如border.css、postcss-1px等。这些库可以通过CSS预处理器或者PostCSS等工具使用。
48.弹性盒中的缩放机制是怎样的?
在弹性盒(Flexbox)布局中,存在一种缩放机制,可以通过调整弹性盒子的基准尺寸和剩余空间的分配方式来实现元素的缩放。这个机制称为「Flex的缩放」。
Flex缩放是通过flex-grow
、flex-shrink
和flex-basis
这三个属性来控制的:
-
flex-grow
属性:确定弹性盒子在剩余空间中分配的比例,它决定了元素在主轴方向上的扩展能力。默认值为0,即不进行扩展。如果多个弹性盒子都设置了flex-grow
,它们将按照数值比例分配剩余空间。 -
flex-shrink
属性:定义了元素在空间不足时的收缩能力,即在元素溢出容器时的缩放比例。默认值为1,表示会收缩。如果多个弹性盒子都设置了flex-shrink
,它们将按照数值比例进行收缩。 -
flex-basis
属性:指定了弹性盒子在分配多余空间之前的初始尺寸。可以用像素(px)、百分比(%)或者auto
(根据内容大小自动计算)来设置。默认值为auto
。
下面是一个示例代码,演示了Flex缩放的效果:
htmlCopy Code<div class="container"> <div class="box"></div> <div class="box"></div> <div class="box"></div> </div> cssCopy Code.container { display: flex; } .box { flex-grow: 1; /* 元素扩展比例为1 */ flex-shrink: 1; /* 元素收缩比例为1 */ flex-basis: 100px; /* 初始尺寸为100px */ background-color: gray; margin: 10px; }
在上述示例中,.box
元素设置了flex-grow: 1;
,表示在剩余空间中按比例进行扩展。当容器宽度增加时,.box
元素会根据比例分配到的空间进行水平扩展。当容器宽度减小时,.box
元素会根据flex-shrink
属性进行水平收缩。
需要注意的是,Flex缩放只会发生在弹性盒子的主轴方向上。在交叉轴方向上,元素的尺寸由align-self
属性来控制,默认情况下不进行缩放。
49。shouldComponentUpdate有什么作用?
shouldComponentUpdate 是 React 组件的生命周期函数之一,用于控制组件是否需要重新渲染。
当组件的 Props 或 State 发生变化时,React 会自动触发组件的重新渲染,但是在渲染之前,React 会调用 shouldComponentUpdate 方法来判断是否需要重新渲染。如果 shouldComponentUpdate 返回 true,则组件将继续进行重新渲染;如果返回 false,则组件将停止重新渲染,直接使用之前的结果。
shouldComponentUpdate 的作用主要有以下两个方面:
-
性能优化:在某些情况下,组件可能会因为不必要的重新渲染而浪费大量性能。比如,在组件的某个 Prop 值没有发生变化时,我们可以通过重写 shouldComponentUpdate 方法来告诉 React 直接复用上次的渲染结果,从而避免不必要的计算和渲染,提升应用性能。
-
控制组件渲染的粒度:有些组件可能包含多个子组件,当 Props 或 State 发生变化时,所有子组件都会被重新渲染,即使对于某些子组件来说,数据并没有发生变化。此时,我们可以通过在父组件中重写 shouldComponentUpdate 来手动控制子组件的重新渲染,从而优化渲染性能和用户体验。
需要注意的是,shouldComponentUpdate 每次在重新渲染之前都会被调用,因此返回 false 可以有效地防止组件重复渲染。
50.Vue中自定义指令的理解,应用场景有哪些?
在Vue中,自定义指令是一种扩展Vue功能的方式。它允许你在DOM元素上添加自定义行为。自定义指令可以用于处理特定的DOM操作、事件处理、动画效果等。
下面是一些自定义指令的常见应用场景:
1)操作DOM元素:自定义指令可以用于直接操作DOM元素,例如改变元素的样式、添加/移除类名、设置属性等。
2)表单验证:自定义指令可以用于表单的验证,例如验证输入的内容是否符合规则、验证手机号码、密码强度等。
3)事件处理:自定义指令可以用于处理特定的事件,例如点击元素外部区域关闭弹窗、长按元素触发某个操作等。
4)动画效果:自定义指令可以用于实现动画效果,例如淡入淡出动画、滑动特效等。
5)第三方库的集成:自定义指令可以用于集成第三方库,例如集成日期选择器、拖拽库等。
总的来说,自定义指令可以在需要对DOM元素添加特定行为或功能的场景下发挥作用。通过自定义指令,你可以更加灵活地操作和控制DOM,使你的应用功能更加丰富和可扩展。
51.说说Connect组件的原理是什么?
Connect
组件是react-redux
库中的一个高阶组件,它用于连接React组件与Redux的状态管理库之间的桥梁。Connect
组件的原理如下:
1)Connect
组件通过一个函数来生成一个新的代理组件。这个函数接收两个参数:mapStateToProps
和mapDispatchToProps
。
2)mapStateToProps
函数定义了如何从Redux的状态树中取出组件所需的状态数据,并将其以props
的形式传递给组件。
3)mapDispatchToProps
函数定义了如何将Redux的指令(actions)以props
的形式传递给组件,并提供了一种将指令与Redux的dispatch
方法绑定的方式。
4)当Redux的状态发生改变时,Connect
组件会重新计算和比较mapStateToProps
返回的结果,如果有变化,就会重新渲染组件。
5)在代理组件中,Connect
组件会订阅Redux的状态变化,使得在状态更新时能够触发重新渲染,并将更新后的状态数据通过props
传递给包装的React组件。
通过以上的流程,Connect
组件实现了将Redux的状态和指令与React组件进行绑定,使得React组件能够获取并响应Redux的状态变化,同时也能够触发Redux的指令来改变状态。
需要注意的是,Connect
组件还有一些优化机制,比如对状态的浅比较以减少不必要的重新渲染、对指令和状态的批量处理等,这些机制都可以提升应用的性能和效率。
52.谈谈你对BFC的理解?
理解:
BFC(Block Formatting Context)是指块级格式化上下文,在 CSS 布局中起着重要的作用。BFC 是一个独立的渲染区域,其中的元素 按照一定的规则进行布局,它可以影响到元素的大小、位置等属性。
下面是 BFC 的一些特性:
-
BFC 内部的元素会在垂直方向上一个接一个地排列,元素之间的间隔由 margin 属性决定,不会发生重叠现象。
-
在同一个 BFC 中的两个相邻元素的 margin 会发生折叠(margin collapsing)现象,即两个相邻元素的 margin 只会取其中较大的一个值。
-
BFC 区域不会与浮动元素重叠,浮动元素只会影响到 BFC 环境中没有成为 BFC 的元素。
-
BFC 可以防止元素被父级或者兄弟元素的 float 影响而产生错位现象。
-
BFC 中的元素可以包含浮动元素,并且不会发生高度塌陷。
53.最少说出三种前端清除浮动的方法?
1.使用空元素清除浮动:
优点:
简单易懂,容易实现
缺点:
无法添加额外得HTML元素
无法清除浮动元素之外的其他内容,可能会影响布局
2.使用 overflow 属性清除浮动:
优点:
不需要添加额外的 HTML 元素
缺点:
当浮动元素超出父元素范围时,可能会被裁剪
只适用于包含块具有确定高度的情况
3.使用 clearfix 类清除浮动(使用伪类)
优点:
不需要添加额外的 HTML 元素
兼容性较好
缺点:
有时需要设置 display: table,可能会对部分布局产生影响
54.什么是强缓存和协商缓存?
强缓存(Strong Caching):
强缓存是指在缓存过期前,浏览器直接从本地缓存中加载资源,而不需要向服务器发送请求。这是因为服务器在响应资源请求时,在响 应头中设置了合适的缓存策略(例如 Cache-Control 和 Expires),告诉浏览器可以在一段时间内直接使用缓存的资源。
协商缓存(Conditional Caching):
协商缓存是指在缓存过期后,浏览器发送一个请求到服务器,检查本地缓存的资源是否仍然有效。这是通过在请求头中包含一个条件标 签来实现的。如果服务器判断资源未发生变化,它会返回一个 304 Not Modified 响应,告诉浏览器可以继续使用本地缓存
55.说说React jsx转换成真实DOM的过程?
\1. 解析JSX:React会使用解析器(如Babel)将JSX代码解析成JavaScript对象。这个对象被称为虚拟DOM(Virtual DOM)或元素(Element)。
\2. 创建虚拟DOM树:React会根据解析得到的虚拟DOM对象,递归地创建一个虚拟DOM树。该树结构与真实DOM树相对应,但它是JavaScript对象的表示形式。
\3. Diff算法比较:当组件状态发生变化时,React会将新的虚拟DOM树与之前保存的旧的虚拟DOM树进行比较,使用Diff算法(如Real Diff算法)找出差异。
\4. 更新差异节点:React会根据找到的差异,进行相应的DOM操作来更新真实的DOM。这些DOM操作可以包括插入、移动或删除DOM节点。
\5. 触发生命周期方法:如果组件需要进行更新,React会触发相应的生命周期方法,如shouldComponentUpdate
、componentWillUpdate
和componentDidUpdate
等,以便组件可以根据需要执行自定义的逻辑。
\6. 应用更新:经过组件更新后,React会将变化应用到页面上,通过DOM操作将修改后的虚拟DOM转化为真实的DOM。React会尽可能高效地计算出需要进行的最小化DOM操作,以提高性能。
56.说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?
@reduxjs/toolkit
是 Redux 官方团队提供的一个工具包,旨在简化和加速 Redux 应用程序的开发。它提供了一组实用工具和约定,使得编写 Redux 代码更加简洁、可读性更高,并且减少了样板代码的量。
区别:
1.相比传统的 Redux 开发方式,使用 @reduxjs/toolkit
可以大大简化 Redux 相关的代码,提升开发效率,减少出错机会
2.@reduxjs/toolkit
是一个针对 Redux 的开发工具包,提供了一系列简化 Redux 开发的工具和约定;而 react-redux
则是一个用 于在 React 应用中使用 Redux 的库,提供了与 React 结合的相关 API。
3.@reduxjs/toolkit
可以简化 Redux 的使用,而 react-redux
则让我们更方便地在 React 组件中使用 Redux
57.说说你对git rebase 和git merge的理解?区别?
当在Git中合并分支时,可以使用git rebase
和git merge
这两种不同的方法。
\1. git merge
:git merge
用于将一个分支的更改合并到另一个分支上。它会创建一个新的合并提交,将两个分支的历史记录合并到一起。这意味着合并后的历史记录中会存在合并提交以及原始分支的提交。
\2. git rebase
:git rebase
用于将一个分支的更改应用到另一个分支上。它会将当前分支的提交按照顺序依次应用到目标分支上,并且在目标分支上生成一个新的提交记录。这样,目标分支的历史记录将变得更加线性,看起来像是连续开发,而不是合并过来的。
区别:
-
git merge
会保留合并提交,而
git rebase会将每个提交都重新应用到目标分支上。
-
git merge
会保留原始分支的历史记录,而git rebase
会将提交“移动”到目标分支的末尾。 -
git merge
相对较安全,因为它会在合并时保留原始分支的历史记录。但是,合并提交的数量可能会增加,导致历史记录变得杂乱。 -
git rebase
可以使目标分支的历史记录更加线性且清晰,因为提交是按顺序应用的。然而,它会改写提交的SHA,可能会导致其他开发人员在分支上有冲突。
选择使用git merge
还是git rebase
取决于具体的工作流和需求。通常情况下,如果你想要保留分支的历史记录并且合并的内容是相对独立的,使用git merge
较为合适。如果你想要保持干净的线性历史记录,并且合并的内容是在开发过程中产生的,使用git rebase
可能更合适。
需要注意的是,在合作开发时,使用git rebase
需要谨慎,因为它会改写提交的SHA,可能会引起其他开发人员的冲突。
58.调和阶段setState干了什么?
1.合并新状态:setState
将新的状态合并到组件当前的状态中。
2.标记组件为"脏"状态:React会将组件标记为"脏"(dirty)状态,表示该组件需要进行更新。
3.异步更新:默认情况下,React会将setState
操作放入一个队列中,并在合适的时机批量处理这些更新。
4.准备重新渲染:在异步更新被触发后,React会准备进行重新渲染。
5.组件更新:React会基于新的状态和组件的生命周期,在适当的时候调用render
方法生成新的虚拟DOM树。
6.比较差异:React会比较新旧虚拟DOM树之间的差异
7.差异更新:React会使用DOM操作将上一步中找到的差异应用于实际DOM上
8.触发生命周期方法:在组件更新完成后,React会触发相应的生命周期方法
60.React闭包陷阱产生的原因是什么,如何解决
react闭包陷阱产生的原因是由于在React组件中使用了异步操作(如定时器、事件监听等)时,闭包会保留对旧状态的引用,导致更新后的状态无法正确地被获取或使用。
这个问题的核心在于JavaScript的闭包特性。当在组件内部定义一个函数,并在该函数中引用了组件作用域中的变量时,闭包会创建一个对该变量的引用,而不是复制变量的值。当变量发生改变时,闭包中存储的引用依然指向旧值,从而产生问题。
为了解决React闭包陷阱,你可以采取以下方法:
-
使用函数式更新:React提供了函数式更新的方式,使用这种方式可以避免闭包陷阱。例如,使用setState((prevState) => ...)而不是setState({...})来更新状态,确保获取到的是最新的状态值。
-
使用useRef钩子:通过使用useRef钩子创建一个可变的引用对象,可以绕过闭包陷阱。将需要访问的变量保存在ref对象中,而不是直接引用组件作用域中的变量。
-
清除副作用:在组件卸载时,确保清除所有可能引起闭包陷阱的副作用。比如清除定时器、取消事件监听等。可以使用React的useEffect钩子来处理副作用,返回一个清理函数。
通过以上方法,可以避免React闭包陷阱产生的问题,确保正确地获取和使用最新的状态值。