1. <img>标签的alt属性用来做什么的?
如果图片显示不出来用于进行文字提示;帮助seo索引。
2. 简述HTML新特性,请至少列举5个。
- 语义化标签,方便阅读,代码清晰,以及SEO索引
- 新增
<audio>和<video>标签,原生支持音视频播放 - 新增
<canvas>标签,支持画布 - 新增localStorage和sessionStorage两种存储方式
- 新增更多的Form格式,比如
<input>标签新增type字段,像是password/date等等 - 提供了web workers的语法,支持浏览器多线程执行代码。
3. <meta>标签的作用是什么
提供相关浏览器编码的元数据,必填的像是charset和viewport。其他还有向页面提供SEO索引的name="description",name="keywords";控制浏览器加载的http-equiv="Cache-Control",http-equiv="Refresh"等等。
4. <a>标签的target有哪些取值?
target决定超链接跳转页面在哪个窗口、标签页打开。
_self:打开的页面会替换当前页面,不会新开页面_blank: 会新建空白标签页打开链接。同时需要添加rel="noopener noreferer",防止新打开的页面通过window.opener篡改原页面,提升安全性。
因为当设置
target="_blank"打开标签页的时候,新页面可以通过window.opener这个属性拿到原页面的window对象,所以设置noopener禁止拿到原网页的window对象,直接将原页面的属性变成null;同时不加的时候跳转到新页面的HTTP请求头会包含refer字段,包含前网页的url字段,url上有时候query会包含部分隐私信息,noreferer直接禁止新网页包含这个字段。
_parent: 仅在iframe框架中使用,在当前父框架里面替换掉iframe里面的页面内容_top: 在浏览器的顶级窗口替换对应的链接内容。
5. 请求头常见的Header有哪些?
- 请求地址Request URL: https: // baidu.com /rest/api
- 请求方式Request Method:POST/GET/DELETE/PUT
- 状态码Status code:2XX正常接收/3XX重定向/4XX客户端请求不存在或者出问题/5XX服务器没响应或者响应错误
- 200 正常返回数据;
- 201 请求成功并创建了新资源,比如创建了新用户表单,上传文件成功
- 204 请求成功但是无内容返回(比如删除成功,无需返回数据)
- 301 永久重定向,比如域名更换
- 302 临时重定向,未登录的用户临时重定向到登录页
- 304 协商缓存发现用户的资源没有变化,不需要重新拉取数据
- 400 请求参数的字段有问题,比如请求参数漏传错传
- 401 未授权,需要登录
- 403 当前用户无权限访问该内容
- 404 当前请求的资源不存在
- 500 服务器内部有问题
- 502 网关错误
- 503 服务器正在更新、维护中
- 连接方式Connection:KEEP-ALIVE长连接
- 响应体的编码类型Content-type: application/json; application/javascript; image/png; text/html;
- 客户端支持的请求体的类型Accept:和Content-type基本一致
- 客户端支持的请求体的编码格式Accept-Encoding:gzip,deflate,br
- 客户端支持的语言格式Accept-Language:zh-CN;zh
- 客户端支持的缓存形式Cache-control:no-cache;no-store;max-age。如果是强缓存,会有expires字段。如果是协商缓存,则可能会有Etag,LastModified等字段。
- Cookie
- 前置页面的链接Referer: https: //baidu.com /a11111
- 用户使用的设备User-Agent
- 客户端认证信息Authorization:存放Token
6. 请简述CSS定位position的几种取值及其含义
- static 默认值
- relative 相对于自身的位置偏移,不脱离文档流
- absolute 相对于父组件的位置偏移,脱离文档流,经常和relative一起使用,父组件使用relative固定位置,子组件使用absolute相对变化
- fixed 固定在屏幕某个位置不发生变化,比如面包屑、顶部滚动提示条等等
- sticky 未触发滚动时遵循relative定位,触发后会fixed
7. display存在哪些常见取值?
- block 块级显示,元素单独占一行,可自由设置width/height,margin/padding
- inline 行内显示,无法设置宽高
- inline-block 行内显示,但是可以设置宽高
- none 隐藏该空间,没有该div在dom树上
- flex 弹性盒子布局
- grid 网格布局
8. CSS3中的新属性,至少列举5个。
- border-radius 圆角
- box-shadow 盒阴影
- linear-gradient 线渐变
background: linear-gradient(to right,red,blue) - transition 过渡,为css属性变化提供过渡动画
transition: all 0.3s ease - transform 变换 里面包含平移translate,
transform:translate(-50%,-50%); 缩放scale,transform:scale(1.2); 旋转rotate等等 - flexbox 弹性盒子布局
- gridbox 网格布局
- media 媒体查询
- CSS变量
9. 如何实现一个三角形
.div{
width: 0px;
height:0px;
border-top:50px solid red;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: solid transparent;
}
9. 设置元素可见的方法,至少列举三种
- display:none 元素完全不见,占位空间也消失
- visibility:hidden 元素不可见,但占位空间存在
- opacity:0 元素不可见,但占位空间存在
10. 如何判断一个变量的数据类型
- typeof 判断是否是基础的数据类型,缺点是没办法区分object的细节。
typeof ipt === 'number' - instanceof 从原型链上判断是否继承这个构造函数的实例,缺点是只能判断出来引用类型
ipt instanceof Array - Object.prototype.toString.call() 精准判断
Object.prototype.toString.call(ipt) === '[object Array]' - constructor 判断其构造函数,但是null和undefined没有constructor属性,同时这个属性可能被篡改
ipt.constructor === Array - Array.isArray 判断是否是数组(最精准的判断是否是数组的方法)
11. 什么是闭包?闭包有什么作用?请举例说明。
一个函数内部封装另外的函数和变量,并且内部函数可以访问外部函数的变量,由此外部函数内部形成封闭的私有作用域,变量会被缓存和私有化,在外部函数被垃圾回收前,都不会被垃圾回收掉
function counter() {
let count = 0;
return function () {
return ++count;
};
}
let myCounter = counter();
console.log(myCounter()); // 输出 1
console.log(myCounter()); // 输出 2
12. 前端安全相关的问题,XSS和CSRF攻击,如何防范
- 跨站脚本攻击XSS:
攻击者将危险的代码注入到网页中,当其他用户访问该网页时,恶意脚本会被执行,致使用户Cookie等信息被窃取。
常见攻击场景:用户输入、富文本编辑器等
常见防范手段:对于用户输入内容进行HTML转义;避免直接操作innerHTML等操作,改用innerText等;将部分数据,比如Cookie在服务器NGINX设置为HttpOnly
- 跨站请求伪造csrf:
攻击者冒充用户发起请求,窃取信息、资料以及转账等等。因为csrf涉及到token等知识,所以后续会单独整理一期。
13. localStorage,sessionStorage和cookie的区别以及应用场景
-
cookie 内存很小,有效期可以自己设定,不设定有效期的话会话关闭会自动删除,会随着请求一起发给后端,同源共享,可以通过document.cookie访问,也可以设置HTTPOnly禁止js访问
-
localstorage 内存5MB,相对偏大,有效期永久,不会随请求发给后端,同源共享,打开两个相同页面,修改其中一个页面localstorage增加一个key-value,另一个页面会自动在localstorage下面增加该信息数据,通过localstorage.getItem等api获取数据,没办法设置httpOnly
-
sessionStorage 内存5MB, 标签页关闭就失效了,并且只在自己的标签下生效,打开两个相同页面,修改其中一个页面localstorage增加一个key-value,另一个页面的sessionstorage下面不会有该数据,通过sessionstorage.getItem等api获取数据,没办法设置httpOnly
14. react响应式原理
react的响应式原理主要依托于虚拟DOM和diff算法。
- 虚拟DOM: 本质上是一串json字符串,用来替换真实DOM,setState变化变量后,会批量更新变量到虚拟DOM上,最后再一次性更新反馈在真实DOM上,减少浏览器的渲染次数,优化渲染性能。虚拟DOM包含的attrs有:tagName标识dom节点的标签;props标识属性,里面有className,id,src等等属性;有子节点为children
{
// 标签名
"tagName": "div",
// 标签属性
"props": {
"className": "container"
},
// 子节点,这里包含两个子节点
"children": [
{
"tagName": "h1",
"props": {},
"children": ["Hello, Virtual DOM"]
},
{
"tagName": "p",
"props": {},
"children": ["This is an example of virtual DOM structure."]
}
]
}
- DIFF算法:react的diff算法是分层识别,同层先比较tag类别,如果不一致则整个更新;如果一致则比较其props,更新对应的属性;最后比较其children。
这也是为什么key对于react的性能优化十分重要,因为如果是同层数组div比较,react是从左到右比较,如果没有key,就会直接按照顺序比较,即使原div没有发生变化,只是数组内顺序发生变化,也会导致div被删除更新;而如果被标记了key,就会在diff算法中定位到对应的dom节点位置,对比更新前后dom是否变化
15. flex弹性布局
首先设置display:flex开启弹性布局
- flex-direction 设置弹性布局的方向,比如column和row
- justify-content 设置水平方向的布局,比如center,flex-end,space-between等等
- align-items 设置垂直方向的布局,参数和justify-content是一致的
- flex-wrap 是否开启换行,比如wrap和no-wrap
- flex-shrink 收缩倍率,取值0-1
- flex-grow 拉伸倍率,取值0-1
- flex-basis 拉伸伸缩规则,取值有0 和 auto,0是可以拉伸内部的宽高比例适应外部空间;auto是按照宽高倍率等比拉伸空间;所以flex:1本质上是 flex-shrink:1 flex-grow:1 flex-basis:0
16. JS事件循环机制
JS事件循环主要是解决JS作为单线程语言,处理密集浏览器任务而提出来的机制;防止耗时的异步任务阻塞导致浏览器后续同步任务都被卡住。
任务队列包含同步任务队列,微任务队列以及宏任务队列。
浏览器会先执行同步任务,中途遇到宏任务和微任务,都会先放入对应的任务队列中,等到同步任务执行完,再去微任务队列中将微任务清空,最后清空宏任务,如果执行宏任务的中途插入微任务,会优先执行插入的微任务,完成后再继续执行接下来的宏任务。
常见的宏任务有:setTimeout/setInterval,I/O操作,script标签等等
常见的微任务有:promise.then / MutationObserver等等
17. async和await是宏任务还是微任务?
都不是,async和await是语法糖,宏任务还是微任务,取决于他“拦截”的是宏任务还是微任务,await会拦截之后的代码,直到await执行完,才会继续执行后续的结果
const func = async()=>{
const promise = Promise.resolved("resolved")
const res = await promise
console.log(0, res)
}
func()
console.log(1)
//输出顺序:1->0,res
18. 谈谈对于前端工程化的理解
工程化本质的目的是实现更高效,更好管理,更加风险可控,团队风格一致的代码方案。
- 框架技术选型:react/vue,优先采用团队成员更熟悉的现代响应式框架
- 前端编码语言:ts,作为类型限制语言更加安全规范
- 组件化和模块化的代码构建
- 代码自动检测库,与语法格式化库:eslint/prettier
- 打包构建工具:webpack/vite
- 远程版本代码管理仓:github
- CICD流水线打包管理:jenkins
19. 在React Hook中useReducer和useState有什么区别
- useState适用于简单的状态管理
import React, { useState } from'react';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
- useReducer适用于复杂的状态更新逻辑
import React, { useReducer } from'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
};
20. vue中的keep-alive组件的作用,以及原理和使用场景
keep-alive是组件缓存,被缓存的组件不会被销毁,而是会处于deactivited的失活状态,再次进入组件会activited激活组件状态,而不是重新创建组件挂载。一般会设置includes和excludes判断包裹的组件哪些需要被缓存,哪些不需要缓存,同时会设置max参数,避免缓存的组件数量太多,影响浏览器性能,一般缓存的组件都会控制在最多3个左右。
本质上是keep-alive会内部使用闭包缓存组件实例。
常见场景,比如小说浏览器,希望切到外面再切进来的时候还是定位在之前的段落,而不是回到顶层。
21. 在react中,什么是高阶组件?请举例说明其使用场景和实现方式
高阶组件本质上是一个函数,它接收一个组件作为参数,并返回一个新的组件。在hooks提出前使用偏多,但现在主流使用还是hooks,因为高阶组件是组件嵌套组件,在功能复用以及代码迭代上都很困难。
import React from'react';
const withLogging = (WrappedComponent) => {
return (props) => {
console.log('Component will render');
return <WrappedComponent {...props} />;
};
};
const MyComponent = () => {
return <div>Hello, World!</div>;
};
const LoggedComponent = withLogging(MyComponent);
export default LoggedComponent;
22. 说说react的fiber机制
fiber主要解决的问题是:在提出fiber机制之前,整个组件去执行浏览器的diff,更新,渲染过程,不会中间中断,这就导致如果这个过程耗时很长,浏览器就会被卡死在这个流程。
而fiber机制,支持整个组件更新过程以更小的链表单位标记effectTag,中途可以中断停止,插入执行更高优先级的任务。
过程使用scheduler调度器,会分析当前任务的优先级顺序以及判断现在浏览器是否有空闲,比如如果更新中途出现用户的交互任务,会判断当前任务的优先级较高,先暂停缓存当前diff标记进度,优先执行高优先级的任务,等到执行完,浏览器空闲下来,再从缓存中继续执行更新流程。
等到currentTree虚拟DOM变更节点全部标记完再整个更新workProgress工作树,最后依据工作树更新到真实DOM上,最后工作树更新到虚拟DOM这个过程则是不可中断的。这是Fiber的双缓存树。
为什么说react即使有了fiber机制还是比vue的更新效率更差,因为vue是精准定位到响应式变量,react的最小更新单元只能定位到DOM节点上,比如一个div节点,而不是div节点内部的某个变量参数。
最后:fiber使用的是深度优先的先序遍历。先找到最左子树,再根节点再右子树,遍历完一颗子树后,再溯源到其他分支。
23. 说说Javascript的原型链
每个Javascript的对象都有自己的proto属性,只想其构造函数的prototype,然后其构造函数也有自己的proto属性,向上指向对应的prototype,最后层层向上,指向null,这样的链式结构就叫做原型链。
原型链的作用是什么? 实现继承作用,让子类实例能够共享父类原型上的方法和属性。比如如果一个对象的属性或方法,如果自身没有,就会沿着原型链向上查找。
24. ES6的class继承和原型链的继承有什么区别?
25. 说说浏览器的渲染过程
- 首先是DNS解析阶段会根据DNS解析到对应的域名地址,然后这个DNS的域名地址会缓存下来,方便下一次请求的时候直接走缓存,跳过搜索解析阶段
- 接着进入到下一步的TCP阶段开启三次握手,这个过程,请求客户端会向服务端发送一段报文和一个标记码,服务器返回对应的报文和标记码表示自己能接收到信息,客户端再发送对应的通信开启的标识信息开始通信正式建立。
至于为什么是三次握手而不是两次握手,是因为需要确认双方都能接收到消息。
- 接着进入到HTTP通信阶段,获取对应的HTML,CSS,JS资源,然后构建DOMTREE和CSSTREE,两者结合生成渲染树。同时JS代码更新渲染树,进行重绘和重排完成最终的布局。
27. watch和created谁先执行
看immediate是否设置为true,如果是则是watch先执行,然后再执行created,如果不是则是先created再执行watch
因为watch注册监听器的时间节点是create时间,这个时候是初始化参数变量,然后再created,然后数据变化后,再执行watch的回调参数。
28. 在 Vue 里面挂载全局方法?
- vue2选项式API的方法优先是Vue.prototype上挂载全局方法,组内直接用this.FUNCNAME访问该方法
// main.js(Vue2)
// 1. 定义并挂载全局方法(前缀加$,符合Vue内置API规范)
Vue.prototype.$myToast = function (msg, type = 'info') {
alert(`[${type}] ${msg}`);
};
// 2. 挂载全局工具函数(比如格式化时间)
Vue.prototype.$formatTime = function (timestamp) {
return new Date(timestamp).toLocaleString();
};
// 任意组件中使用
<template>
<button @click="callGlobalMethod">调用全局方法</button>
</template>
<script>
export default {
methods: {
callGlobalMethod() {
// 直接通过this调用全局方法
this.$myToast('操作成功', 'success');
console.log(this.$formatTime(Date.now()));
}
}
}
</script>
- vue3实现全局方法的主流是在main.ts或者APP.vue上使用provide/inject依赖注入
- vue3兼容的选项式方法是app.config.globalProperties,然后通过getCurrentInstance拿到vue3的实例,调用instance.appContext.config.globalProperties使用
// main.js(Vue3)
const app = createApp(App);
// 挂载全局方法
app.config.globalProperties.$myToast = (msg) => {
alert(`全局提示:${msg}`);
};
app.mount('#app');
// 场景2:setup语法中使用
<script setup>
import { getCurrentInstance } from 'vue'
// 获取组件实例,拿到全局方法
const instance = getCurrentInstance();
const $myToast = instance.appContext.config.globalProperties.$myToast;
const showToast = () => {
$myToast('Setup中调用全局方法');
};
</script>
29. Vue怎么强制刷新组件?
- 通过组件外部绑定修改Key
<template>
<!-- 给需要刷新的组件绑定动态key -->
<MyTargetComponent :key="refreshKey" />
<button @click="refreshComponent">强制刷新组件</button>
</template>
<script setup>
import { ref } from 'vue'
import MyTargetComponent from './MyTargetComponent.vue'
// 定义刷新标识,初始值为0
const refreshKey = ref(0);
// 刷新方法:仅需修改key值
const refreshComponent = () => {
refreshKey.value++; // 每次+1,触发组件重建
};
</script>
- 绑定v-if=mainShow,配合nextTick改变mainShow的值两次(我们组的项目使用的方法)
<template>
<MyTargetComponent v-if="isComponentShow" />
<button @click="refreshComponent">强制刷新</button>
</template>
<script setup>
import { ref, nextTick } from 'vue'
import MyTargetComponent from './MyTargetComponent.vue'
const isComponentShow = ref(true);
const refreshComponent = async () => {
// 先销毁组件
isComponentShow.value = false;
// 等待DOM更新后重建
await nextTick()
isComponentShow.value = true;
};
</script>
30. React Hooks的核心原理
函数组件最开始的痛点是没办法持久化响应式变量的状态,Hooks的作用是提供这个能力。
- 闭包变量缓存;
- 提供fiber节点setState和useEffect等
- 闭包里面维护对应fiber节点的一个链表,这个链表因为闭包被状态持久化保存在函数组件内部,由此实现状态持久化管理能力。
这也是为什么fiber节点必须按照顺序写在最上面,因为对于函数组件的内部管理的是fiber节点构成的链表。
Hook 触发更新后,React 会标记对应的 Fiber 节点为「待更新」,通过调度器根据当前事件的优先级,安排组件重渲染,重渲染时按顺序遍历 整个Hook 链表,同步最新状态并执行副作用。这也是为什么react需要使用缓存等方式优化渲染性能,因为react是以组件为单位进行重渲染的。
31. hooks的闭包,为什么拿不到最新的值
因为react hooks实际上拿到的是数据当时生命周期时间节点的数据快照,如果需要摆脱快照状态,需要增加数据依赖更新
比如以下这个问题:
import { useState, useEffect, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// 场景1:useEffect 里的定时器(最常见)
useEffect(() => {
const timer = setInterval(() => {
// 期望:每秒打印最新的 count,但实际一直打印 0
console.log('定时器中的count:', count);
}, 1000);
return () => clearInterval(timer);
}, []); // 依赖为空,只在首次挂载执行
// 场景2:useCallback 包裹的回调(点击时拿不到最新值)
const handleClick = useCallback(() => {
// 点击按钮时,期望打印最新 count,但实际打印的是绑定回调时的 count
console.log('点击时的count:', count);
}, []); // 依赖为空,回调只创建一次
return (
<div>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>加1</button>
<button onClick={handleClick}>打印count</button>
</div>
);
}
处理办法是添加详细的依赖
// 修复 useEffect 定时器
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器中的count:', count); // 能拿到最新值
}, 1000);
return () => clearInterval(timer);
}, [count]); // 依赖 count,count更新时重新创建定时器
// 修复 useCallback 回调
const handleClick = useCallback(() => {
console.log('点击时的count:', count); // 能拿到最新值
}, [count]); // 依赖 count,count更新时重新创建回调
或者拿useRef保存最新值(不常用),useRef的作用是创建一个可变的引用容器,容器具备全局上下文信息,一般useRed的使用场景和vue的dom节点的this.$ref一样,获取组件实体。
function Counter() {
const [count, setCount] = useState(0);
// 用 ref 保存最新的 count
const countRef = useRef(count);
// 每次 count 更新时,同步更新 ref
useEffect(() => {
countRef.current = count;
}, [count]);
// 定时器闭包访问 ref.current(最新值)
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器中的count:', countRef.current); // 始终拿到最新值
}, 1000);
return () => clearInterval(timer);
}, []); // 依赖为空,只创建一次定时器
// ... 其他代码
}
32. 路由懒加载的原理
利用闭包的原理,ES模块的()=>import('./Home')是异步加载模块,会让组件在打包的时候单独打包成chunk文件,这样子在只有真的执行到这行代码的时候,才会从服务器获取这个chunk文件,由此实现的路由懒加载。
33. 常见react hooks的常用场景
-
useRed的使用场景和vue的dom节点的this.$ref一样,获取组件实体
-
useCallback和useMemo的常用场景:
(1)因为引用变量地址的原因,使用数组作为useEffect的依赖时,使用useMemo缓存对应的数组;同样如果是函数作为useEffect的依赖时,使用useCallback作为依赖。
function DataFetch() {
const [id, setId] = useState(1);
// 缓存请求函数,只有 id 变化时才重新创建
const fetchData = useCallback(async () => {
const res = await fetch(`/api/data?id=${id}`);
const data = await res.json();
console.log('请求结果:', data);
}, [id]); // 依赖 id,id 变才重新创建函数
// 依赖 fetchData,只有 fetchData 引用变(即 id 变)时,才重新请求
useEffect(() => {
fetchData();
}, [fetchData]); // 若不用 useCallback,每次渲染 fetchData 引用变,会重复请求
return (
<div>
<button onClick={() => setId(id + 1)}>修改id,重新请求</button>
</div>
);
}
(2)传递给子组件的回调函数,用 useCallback 缓存函数引用,只有依赖变化时才更新引用,子组件才会真正重渲染。
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('张三');
// 优化写法:用 useCallback 缓存函数引用,只有 name 变化时,函数引用才变
const handleClick = useCallback(() => {
console.log('点击了', name);
}, [name]); // 依赖数组:只有 name 变,函数才重新创建
return (
<div>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>修改count</button>
<button onClick={() => setName('李四')}>修改name</button>
{/* 点击「修改count」时,handleClick 引用不变,ChildButton 不重渲染;
点击「修改name」时,handleClick 引用变,ChildButton 重渲染 */}
<ChildButton onClick={handleClick} text="子组件按钮" />
</div>
);
}
(3)防抖节流函数需要使用useCallback;防抖 / 节流函数需要同一个函数引用才能生效(比如防抖函数要记录上次触发时间),若每次渲染都创建新的防抖函数,会导致防抖失效(每次都是新函数,无法记录时间)
function SearchInput() {
const [keyword, setKeyword] = useState('');
// 缓存防抖函数,只有组件卸载才会重新创建
const handleSearch = useCallback(
debounce((val) => {
console.log('搜索:', val); // 输入停止 500ms 后执行
}, 500),
[] // 依赖为空,函数引用永久不变
);
const onChange = (e) => {
setKeyword(e.target.value);
handleSearch(e.target.value); // 调用缓存的防抖函数
};
return <input value={keyword} onChange={onChange} placeholder="搜索..." />;
}
(4)缓存昂贵的计算结果常使用用useMemo,比如大数据处理、复杂逻辑运算(如循环遍历上千条数据)、数学公式计算等,这些操作若每次渲染都执行,会严重消耗性能。
(5)缓存复杂静态的JSX 片段可以使用useMemo缓存
function ComplexJSX() {
const [show, setShow] = useState(true);
// 缓存复杂 JSX 片段,只有 show 变化时才重新创建
const content = useMemo(() => {
if (!show) return null;
// 模拟复杂 JSX 结构
return (
<div className="complex-content">
<h1>标题</h1>
<p>内容1</p>
<p>内容2</p>
{/* ... 更多内容 */}
</div>
);
}, [show]);
return (
<div>
<button onClick={() => setShow(!show)}>切换显示</button>
{content}
</div>
);
}
34. SameSite:Strict 和 None 有什么区别?
SameSite 是Cookie 的一个属性,用来控制跨站时是否携带 Cookie,防 CSRF。
-
SameSite=Strict(最严格),跨站请求完全不携带 Cookie,即使你从别的网站点链接进来,也不会带 Cookie,只允许同站使用,完全禁止跨站
-
SameSite=None,任何情况都可以携带 Cookie,必须配合
Secure(HTTPS),允许跨站携带 -
SameSite=Lax(现代浏览器默认),普通跨站请求不发,链接跳转、GET 表单会发
35. http1 http2 有什么区别?
- http1.1: 1. 长连接connection:keep-alive请求响应头;2.只能支持最多6-8个TCP请求同时发送;3.纯文本协议,以文件为单位进行信息传输,一个TCP请求只能同时处理一个文件传输;4. 每次都会发送完整的Header信息,导致信息冗余
- http2.0: 核心是二进制01字段帧的信息传送。1. 只开一个TCP请求,但是因为不再是文本传输协议,而是二进制传输,所以可以并发很多请求,不受到TCP数量的限制;实现多路复用,解决HTTP层的队首阻塞 2. 使用HPACK压缩Header字段,HPACK压缩本质上是前后端信息传输会维护一个缩写和全程的map,压缩使用缩写,再通过map转换得到最终的Header
36. 如何在原生事件中实现setState的批量更新,而不是立刻更新?
使用react提供的原生API ReactDOM.unstable_batchedUpdates 包裹原生事件的回调逻辑,强制开启批量更新模式。
handleNativeClick = () => {
// 核心:用 unstable_batchedUpdates 包裹多次 setState
ReactDOM.unstable_batchedUpdates(() => {
// 多次 setState 会被批量合并,只触发一次重渲染
this.setState({ count: this.state.count + 1 });
console.log('第一次 setState 后:', this.state.count); // 输出 0(异步批量,未更新)
this.setState({ count: this.state.count + 1 });
console.log('第二次 setState 后:', this.state.count); // 输出 0(异步批量,未更新)
});
// 若想拿到更新后的状态,用 setState 回调
this.setState(
(prev) => ({ count: prev.count + 1 }),
() => console.log('最终状态:', this.state.count) // 输出 2(合并两次更新)
);
};
37. JS的继承方式有哪些
- 原型链继承
Child函数的prototype指向父函数的实例,但是Child构造函数指向自己
缺点是一旦父函数修改部分属性,所有子属性都会被修改;并且创建初始化的时候没办法立刻改变继承的数据,必须后续再次赋值
function Parent(){
this.name = "父类"
this.hobbies = ["打球","看书"]
}
Parent.prototype.sayHi = function(){
console.log(this.name)
}
function Child(){}
Child.prototype = new Parent()
Child.prototype.constructor = Child
- 构造函数继承
通过call改变this指向,能在初始化的时候进行赋值
缺点是无法继承父函数后续通过prototype添加的方法
function Parent(){
this.name = "父类"
this.hobbies = ["打球","看书"]
}
Parent.prototype.sayHi = function(){
console.log(this.name)
}
function Child(name,age){
//转移父函数的this指向,并且传值name
Parent.call(this,name)
this.age = age
}
- 组合继承(原型链+构造函数继承)
所有功能都能实现,只是继承这个动作会实现两次,但实际其实无感知
function Parent(name,age){
this.name = name
this.age = age
}
Parent.prototype.sayHi = function(){
console.log(this.age)
}
function Child(name,hobbies){
Parent.call(this,name)
this.hobbies = ["学习"]
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
- 寄生继承
不用构造函数就可以实现继承,但是缺点还是子对象没办法在初始化的时候更改继承属性,父对象一旦修改属性,所有的子对象都会发生属性变更
function createChild(parent){
const child = Object.create(parent)
child.age = "18"
child.sayAge = function(){
console.log(this.age)
}
return child
}
const parent1 = {
name:'父对象',
sayHi:function(){
console.log("hi",this.name)
}
}
const child = createChild(parent1)
child.sayAge()//18
child.sayHi()//hi 父对象
说到这个题目,可以看看下面的题:
//题1
function createChild(parent){
const child = Object.create(parent)
child.age = "18"
child.sayAge = function(){
console.log(this.age)
}
return child
}
const parent1 = {
name:'父对象',
sayHi:()=>{
console.log("hi",this.name)
}
}
const child = createChild(parent1)
child.sayAge()//18
child.sayHi()//报错
//题2
function createChild(parent) {
const child = Object.create(parent);
child.age = "18";
child.sayAge = function () {
console.log(this.age);
};
return child;
}
const parent1 = {
name: "父对象",
sayHi: () => {
let name = "中间值"
return function () {
console.log("hi", this.name);
};
},
};
const child = createChild(parent1);
child.sayAge();//18
child.sayHi()();//报错,没有找到name
//题3
function createChild(parent) {
const child = Object.create(parent);
child.age = "18";
child.sayAge = function () {
console.log(this.age);
};
return child;
}
const parent1 = {
name: "父对象",
sayHi: function(){
let name = "中间值"
return function () {
console.log("hi", this.name);
};
},
};
const child = createChild(parent1);
child.sayAge();//18
child.sayHi()();//报错,没有找到name
const child2 = createChild(parent1);
child2.sayAge();//18
const res = child2.sayHi();
res()//报错,没有找到name
至于为什么改成非箭头函数而是普通函数还是显示没有找到,因为如果是单层普通函数,实际上是child.sayHi(),是child在调用,双层普通函数解构出来,是window.res()或者外部函数.res(),而外部函数没有name属性,所以才会报错。
- 组合寄生继承
最理想的继承方式,也是ES6 CLASS语法糖的底层实现。
function createChild(child,parent){
const prototype = Object.create(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
function Parent(name,age){
this.name = "父类"
this.age = 18
}
Parent.prototype.sayHi = function(){
console.log(this.age)
}
function Child(name,hobbies){
Parent.call(this,name)
this.hobbies = []
}
createChild(Child,Parent)
- ES6 class继承
super关键字实际上封装的就是call的this指向,extends实际上是封装prototype的原型链指向
class Parent {
constructor(name) {
this.name = name;
this.hobbies = ["打球", "看书"];
}
sayHi() {
console.log(`Hi,我是${this.name}`);
}
}
// 子类
class Child extends Parent {
constructor(name, age) {
super(name); // 必须先调用super,才能使用this
this.age = age;
}
sayAge() {
console.log(`我${this.age}岁`);
}
}
38. useState 直接 setState 和 setState 传入一个函数做处理,这个两者的区别
直接传值的场景,一般是新值不依赖旧值,因为react的useState的机制是异步的,所以如果直接useStATE里面传值,会导致拿到的很有可能是旧值:
function Demo() {
const [name, setName] = useState('张三');
// 直接传值:新值是固定的,不依赖旧name
const changeName = () => {
setName('李四');
};
return (
<div>
<p>{name}</p>
<button onClick={changeName}>改名字</button>
</div>
);
}
传入函数的场景:
function Counter() {
const [count, setCount] = useState(0);
// 点击后期望count从0→2,但实际只到1!
const handleClickWrong = () => {
// 两次setCount都捕获了闭包中旧的count=0,最终都是0+1=1
setCount(count + 1); //1
setCount(count + 1); //1
//修正的方法
//setCount(prev=>prev+1) //1
//setCount(prev=>prev+1) //2
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClickWrong}>错误累加</button>
</div>
);
}
39. useEffect和useLayoutEffect的区别有哪些,应用场景有哪些
首先需要清楚react的组件更新过程:effect触发组件更新 => 计算新UI => DOM计算变更 => useLayoutEffect执行同步 => 浏览器绘制页面 => useEffect执行异步
所以对于耗时的DOM更新操作以及对于更新感受不明显,最好使用useEffect,避免长时间白屏没有反馈;对于闪烁高敏感的场景,最好是使用useLayoutEffect,比如某些追踪鼠标的提示tooltip。
40. react里面的useTranslation是做什么用的
用于国际化的一个函数。
根目录配置i18n.js文件
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend'; // 加载远程/本地翻译文件
import LanguageDetector from 'i18next-browser-languagedetector'; // 自动检测浏览器语言
i18n
.use(Backend) // 加载翻译文件
.use(LanguageDetector) // 自动检测语言(如浏览器默认语言)
.use(initReactI18next) // 绑定到 react-i18next
.init({
fallbackLng: 'zh', // 兜底语言(检测不到时用)
debug: false, // 开发时可设为true,控制台打印调试信息
interpolation: {
escapeValue: false, // React 已自带XSS防护,无需重复处理
},
react: {
useSuspense: true, // 适配 React 18 的 Suspense(可选)
},
});
export default i18n;
创建翻译的json文件
//zh.json文件
{
"common": {
"submit": "提交",
"welcome": "欢迎{{name}}来到我的网站"
},
"button": {
"cancel": "取消"
}
}
//en.json
{
"common": {
"submit": "Submit",
"welcome": "Welcome {{name}} to my website"
},
"button": {
"cancel": "Cancel"
}
}
最终的组件使用文件
import React from 'react';
import { useTranslation } from 'react-i18next';
function MyComponent() {
// 解构核心API:t(翻译函数)、i18n(语言管理实例)
const { t, i18n } = useTranslation();
// 切换语言的方法
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);
};
return (
<div>
{/* 基础翻译:根据当前语言渲染对应文本 */}
<button>{t('common.submit')}</button>
<button>{t('button.cancel')}</button>
{/* 带插值的翻译:动态替换文本中的变量 */}
<p>{t('common.welcome', { name: '张三' })}</p>
{/* 切换语言按钮 */}
<button onClick={() => changeLanguage('zh')}>中文</button>
<button onClick={() => changeLanguage('en')}>English</button>
</div>
);
}
export default MyComponent;
41. useTransition和useDeferredValue的区别是什么
都是用于标记任务优先级,只是useTransition标记的是需要延迟的函数;useDeferredValue标记的是延迟的依赖传入,因为依赖变更感知延迟,所以最终得到的结果也会自然延迟。
useTransition标记更新、
import { useState, useTransition } from 'react';
function Search() {
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition({ timeoutMs: 300 });
// 输入框更新(高优先级,立即执行)
const handleChange = (e) => {
const value = e.target.value;
setInput(value); // 高优先级:输入框立即响应
// 主动标记列表更新为低优先级
startTransition(() => {
// 耗时操作:生成10万条数据(模拟接口返回大量数据)
const newList = Array(100000).fill(value);
setList(newList); // 低优先级:等输入完成后再渲染
});
};
return (
<div>
<input value={input} onChange={handleChange} placeholder="输入搜索" />
{isPending && <div>加载中...</div>}
<ul>
{list.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
);
}
useDeferredValue 实现(被动派生延迟值)
import { useState, useDeferredValue } from 'react';
function Search() {
const [input, setInput] = useState('');
// 基于原input创建延迟值,最大延迟300ms
const deferredInput = useDeferredValue(input, { timeoutMs: 300 });
// 基于延迟值渲染耗时列表(低优先级)
const list = Array(100000).fill(deferredInput);
// 输入框更新(高优先级,立即执行)
const handleChange = (e) => {
setInput(e.target.value); // 原状态立即更新,输入框不卡顿
};
return (
<div>
<input value={input} onChange={handleChange} placeholder="输入搜索" />
{/* 列表基于延迟值渲染,不会阻塞输入 */}
<ul>
{list.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
);
}
42. ts中的satisfies有什么用
声明一个变量满足一种类型的类型约束,同时保留这个变量推断出来的自身的特性
// 定义一个类型:颜色可以是字符串,或 RGB 数字数组
type Color = string | [number, number, number];
// 问题1:用 : 注解类型 → 约束了类型,但丢失具体字面量类型
const red: Color = '#ff0000';
// ❌ TS 无法推断 red 是具体的 '#ff0000',只能推断为 Color(string | [number,number,number])
// 比如想访问 red.length 会报错:因为 Color 可能是数组,TS 不确定
// red.length; // 报错:Property 'length' does not exist on type 'Color'
// 问题2:不用 : 注解 → 保留具体类型,但无法约束结构
const green = [0, 255, 0]; // TS 推断为 number[],而非 [number, number, number]
// ❌ 可以随意修改数组长度,破坏约束
green.push(100); // 数组变成 [0,255,0,100],不符合 Color 类型,但 TS 不报错
type Color2 = string | [number, number, number];
// ✅ satisfies 做两件事:
// 1. 校验 '#ff0000' 符合 Color2 类型(约束);
// 2. 保留 red2 的具体类型为 '#ff0000'(而非 Color2)。
const red2 = '#ff0000' satisfies Color2;
// ✅ TS 知道 red2 是具体的字符串 '#ff0000',可以安全访问 length
console.log(red2.length); // 输出 7
// ✅ 校验 [0,255,0] 符合 Color2 类型,且保留具体类型为 [0,255,0](元组)
const green2 = [0, 255, 0] satisfies Color2;
// ❌ 尝试修改数组长度会报错:因为 green2 是具体的元组 [0,255,0],长度固定
// green2.push(100); // 报错:Argument of type '100' is not assignable to parameter of type 'never'
// ❌ 如果值不符合 Color 类型,satisfies 会直接报错
// const blue2 = [0, 0] satisfies Color2; // 报错:长度为2的数组不符合 Color2 约束
// 定义配置类型:theme 只能是 light/dark,size 只能是 sm/md/lg
type ComponentConfig = {
theme: 'light' | 'dark';
size: 'sm' | 'md' | 'lg';
disabled?: boolean;
};
// ✅ satisfies 约束对象符合 ComponentConfig,同时保留每个值的具体类型
const buttonConfig = {
theme: 'dark',
size: 'md',
disabled: false
} satisfies ComponentConfig;
// ✅ TS 知道 buttonConfig.theme 是具体的 'dark',而非 'light'|'dark'
if (buttonConfig.theme === 'dark') {
console.log('暗黑模式'); // 类型安全,无报错
}
// ❌ 如果对象结构不符合,会报错
// const inputConfig = {
// theme: 'red', // 报错:'red' 不符合 'light'|'dark'
// size: 'md'
// } satisfies ComponentConfig;
43. ts 的 enum、const enum 的区别
- enum 编译后会生成一个索引对象,所以可以支持反向取值
// 编译前
enum Status {
Success = 200,
Error = 500,
NotFound = 404
}
const code = Status.Success;
console.log(Status.Error);
// 编译后
var Status;
(function (Status) {
Status[Status["Success"] = 200] = "Success";
Status[Status["Error"] = 500] = "Error";
Status[Status["NotFound"] = 404] = "NotFound";
})(Status || (Status = {}));
var code = Status.Success;
console.log(Status.Error);
//因此可以反向取值,当做对象索引使用
function getStatusText(code: number): string {
return StatusCode[code] || 'Unknown';
}
console.log(getStatusText(200));
- const enum编译后会在使用的地方自动替换成枚举之后的数据
//编译前
const enum Status {
Success = 200,
Error = 500,
NotFound = 404
}
const code = Status.Success;
console.log(Status.Error);
//编译后,不会生成枚举对象,无法反向索引,但是编译出来的结果更加轻量
var code = 200 /* Success */;
console.log(500 /* Error */);
44.如何实现动态组件
component 和 :is相结合,动态输入需要绑定展示的组件