前端知识
接下来的内容会是我 面试 / 遇到 不会的点,或者是掌握得不清楚的点,我会尽可能将整个流程写明白,让我自己能跑通整个流程。
浏览器的同源策略? What
当我们写好一个后端程序和前端程序的时候,前端向后端发送请求,提示CORS未通过,这通常是浏览器的同源策略导致的。
同源策略是浏览器对跨源读写 的限制,例子:js不能读另一个源的响应内容。
链路:浏览器先执行同源策略 – 如果跨源,则需要带上CORS响应头显式访问 – 否则浏览器拦截
同源策略是拦截策略,而CORS是服务器给浏览器的许可证明
Why
这是为了防止Web里的跨源攻击,运行在某个 origin 下的脚本,不能读取其他 origin 的存储(localStorage/cookie/DOM)和响应内容;哪怕这些请求是用户本人在同一个浏览器里触发的。
How
服务端通过 CORS 配置决定向哪些 Origin 返回允许头;若未允许,浏览器会拦截前端 JS 读取响应(即使请求到达并拿到 200)。
校验三大部分:协议、主机名、端口
1 2 3 4 5 6 7 8 9 10 11 12 public CorsFilter corsFilter () { CorsConfiguration config = new CorsConfiguration (); config.setAllowedOrigins(List.of("http://localhost:5173" )); config.setAllowedMethods(List.of("GET" , "POST" , "PUT" , "DELETE" , "OPTIONS" )); config.setAllowedHeaders(List.of("*" )); config.setAllowCredentials(true ); config.setMaxAge(3600L ); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); source.registerCorsConfiguration("/**" , config); return new CorsFilter (source); }
XSS攻击? What
XSS攻击是用户将一段脚本(js)代码伪装成评论插入到系统里,当别人展开评论的时候,这段脚本会在网站里执行(因为当你展开评论的时候,浏览器把用户输入当 HTML 解析,前端可能会将评论使用innerHTML插入进到页面里,脚本被执行)
用户可能会看到奇奇怪怪的弹窗或者跳转到别的页面
账号被莫名改资料
如果你是权限比较高的用户,执行了恶意脚本,很可能就会让系统崩溃。
How to prevent
核心原则 :永远不要把用户的输入当成HTML渲染
React默认渲染字符串是转义的,{content}
坑:如果为了支持富文本(图片,代码,换行)使用了dangerouslySetInnerHTML,这样就会插入脚本。
解决方法:
输出编码要默认转义,用纯文本来渲染,不允许用html
白名单清洗:如果必须要富文本,过滤掉事件脚本,事件属性,危险的url结构
token尽量不要放在localstorage里,可以放在HttpOnly cookie,这样js读不到cookie
设置SameSite,有三种模式,strict,lax以及none,分别控制跨站是否自动带上cookie
解决markdown富文本的XSS攻击策略
设置白名单,只允许我们可以的标签,比如p,a,strong,code等,对属性也要白名单,禁止所有的事件属性,比如on*
禁止inline script
代码块永远escape(转义)后放进code和pre里展示
JWT更安全的登录?双Token What
我发现了原来的逻辑是改Localstorage就可以让前端渲染出管理员的界面,这样不行
于是引入了JWT的accessToken以及refreshToken
accessToken一般时间短,15-30分钟,存放在React的内存里,一刷新就没有
refreshToken时间在7-30天,存放在httponly的cookie里
此外这个refreshToken还存放在了redis里,使用版本号控制,让非法用户强制下线
How 登录功能
用户首次登陆的时候后端校验账户和密码是否合法,如果合法就签发两个token
access token 过期 → 前端请求业务接口返回 401
前端调用 /api/v1/auth/refresh(不带 refresh token,浏览器会自动附带 HttpOnly Cookie)
后端从 Cookie 里拿 refresh token,去 Redis 校验是否存在/是否有效
校验通过 → 生成新 access token(和新的 refresh token),并在 Redis 里更新
返回新的 access token 给前端,继续请求
来回时间就是一个rtt
踢人功能
在redis里添加tokenVersion,因为我们登陆的时候用户还是有当前的accessToken,所以他还能存活这么久
加一个user:tokenVersion:<uid>在redis里,签发的时候把ver写进JWT,
踢人就增加版本号,这样让他的所有旧accessToken失效。原因是我们的在redis里的逻辑是 user:tokenVersion:<uid> = N,拦截器校验时比较Redis ver,如果不一致就401
既然有了踢人功能,那能不能把accessToken设置为24h?
不建议
jwt最大的优点就是无状态,后端只需要使用cpu进行简单的数学运算就可以得到结果
如果是24h,那用户每一次调用接口的时候都会查询redis,那本质上和session没有区别,失去了 JWT 无状态校验的性能优势。
缩短风险窗口: 短时效(15-30min)的 AT 配合长时效的 RT,可以在‘不需要频繁查库’和‘用户被盗号后的损失’之间取得平衡。即便不实时查 Redis,黑客的攻击窗口也仅限于这几分钟。
项目登录流转 How
用户点击页面触发接口请求前端,拦截器从localstorage取 hnu_token,放到请求头 Authorization: Bearer xxx
后端从 Authorization 里取Bearer token解析JWT,再校验token是否一致
token过期/无效时,接口会返回401,前端响应拦截器会调用refresh的api,这个请求会自动带上httpOnly的 hnu_refresh_token的cookie
refresh成功后,后端返回新的accessToken,前端setToken()写回localStorage,然后重放刚刚失效的请求。
为什么刷新页面登录态不掉,因为不是只放内存:accessToken 持久化在 localStorage。页面刷新后,AuthProvider 初始化会重新读 localStorage,并调用 /api/v1/users/me 做一次有效性校验。
refresh token 为什么放 Cookie,不放前端 localStorage? 答:refresh token 通过 HttpOnly Cookie 下发,前端 JS 拿不到,减少 XSS 窃取风险;access token 才放 localStorage 走请求头。 “每次请求都带 Bearer accessToken,后端拦截器验 JWT + Redis 版本;401 时用 cookie 里的 refreshToken 换新 token;刷新页面靠 localStorage 恢复,所以不会直接掉线。”
项目路由管理
公开路由
/login、/register、/、/posts/:id 直接可访问。
登录保护路由
我封装了 RequireAuth,基于 useAuth() 的 token + loading 判断。
没登录就 Navigate 到 /login。
管理员路由
我再封装 RequireAdmin,在登录基础上再判断 user.role === ‘ADMIN’,否则跳回首页。
浏览器的事件循环机制是什么? What
事件循环是浏览器在单线程上运行 JS 的调度机制:把“同步 JS 执行”与“外部事件/异步结果(定时器、网络、交互等)”通过任务队列串起来,循环取任务执行。
由三部分组成:调用栈、Web APIS、任务队列
1 2 3 4 5 6 7 setTimeout (()=> { console .log (1 ); },0 ) Promise .resolve ().then (()=> { console .log (2 ); })
会打印2,1。是因为这一段script是一个宏任务,然后setTimeout先是注册了一个定时器任务,把这个回调函数log1放到了宏任务队列里,就到微任务,微任务将这一段script的宏任务收尾,所以先log,然后清空了微任务之后。script 这个宏任务已经执行完了;之后从宏任务队列里取的是 定时器回调这个新的宏任务。
宏任务:整段Script,setTimeout\setInterval回调,DOM事件回调(Click,Input)
微任务:Promise
Why
js是单线程的,同一时间只能做一件事,避免多线程共享DOM带来的竞态
让页面响应可控,长任务不会永远占住
How
“执行一个宏任务 → 然后清空微任务队列 →(必要时渲染)→ 下一轮宏任务”。
执行的顺序
宏任务:定时器(setTimeout,setInterval)回调、点击回调、整段Script
微任务:Promise.then、catch/finally用于“本轮宏任务的收尾/延续”
如果浏览器一直执行宏任务?
问题:理论上来说因为是单线程,宏任务如果一直不返回(同步死循环/超长计算),浏览器确实会一直卡在这个宏任务里。
解决方法
在浏览器的事件循环标准中,UI 渲染通常发生在所有微任务(Microtasks)执行完毕之后,以及下一个宏任务(Macrotask)开始之前。
节流和防抖有什么区别? What
防抖(debounce):在连续触发中不断重置计时器,停止触发一段时间后才执行一次(“取最后一次”)。
节流(throttle):在连续触发中按固定间隔执行,单位时间最多执行一次(“限速”)。
How 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function debounce (fn,wait = 300 ) { let timer = null return function (...args ) { if (timer) { clearTimeout (timer)} timer = setTimeout (()=> { fn.apply (this ,args) },wait) } } function throttle (fn,wait = 200 ) { let last = 0 return function (...args ) { const now = Date .now () if (now - last >= wait) { last = now fn.apply (this ,args) } } }
Why
防抖:解决“抖动触发导致的重复计算/重复请求”,减少无效开销(如输入联想、频繁点击)。
节流:解决“持续高频触发导致的性能压力/掉帧”,让回调执行频率可控(如滚动、鼠标移动、拖拽)。
为什么要return function,因为function是一个函数,需要返回值,这里的返回值就是一个函数
遍历找到D盘的两个重复文件? How
文件夹有递归嵌套的结构,每一个文件夹就像是树一样,遍历树就使用DFS/BFS/递归
构建 Map<size, filePaths[]>,只保留 filePaths.length >= 2 的桶作为候选(不同 size 必不相同)。
对候选文件用流式读取计算内容 hash(如 SHA-256/BLAKE3),构建 Map<hash, filePaths[]>;同一 hash 下的文件认为“高度可能相同”。
深拷贝和浅拷贝 What
浅拷贝:只复制一层属性;若属性值是对象/数组/函数等引用类型,复制的是引用,因此会共享同一份深层数据。
深拷贝:递归复制各层级,让嵌套对象/数组变成新的引用,通常不共享深层引用。
Object.create 不是拷贝,是“以某对象作为原型”创建新对象。
Why
React/Redux 等依赖不可变更新 + 浅比较(===)做渲染与 memo 判断:你只需要“沿修改路径拷贝”(结构共享),而不是全量深拷贝。
全量深拷贝会带来:CPU/内存开销、GC 压力、缓存失效(引用每次都变,memo 失效)。
How 深拷贝 1 2 3 const p = { name : 'lala' , age : 18 , arr : [1 ,2 ,3 ] }let s = structuredClone (p)
浅拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const p = { name : 'lala' , age : 18 , arr : [1 ,2 ,3 ] }let s = {...p}Demo const target = { a : 1 , b : 2 };const source = { b : 4 , c : 5 };const returnedTarget = Object .assign (target, source);console .log (target);console .log (returnedTarget === target);s = Object .assign ({},p)
对React的理解 What
JSX 不是 HTML,它会被编译成 React.createElement(...) 的调用,产出的是一棵React Element 树(普通 JS 对象描述 UI)。它是“UI 的描述”,不是浏览器 DOM。
React 以组件为单位,由 state/props 驱动渲染:每次 render 本质是“根据当前 state/props 计算下一棵 element 树”。
How
当 state/props 变化时,React 会重新执行组件函数得到 next element tree,再和 prev element tree 做 reconciliation(协调/对比),决定哪些地方需要更新。
所以React是一个声明式的UI框架,依托于Props/State,JSX会被编译为createElement,得到一个Element树,当状态变化 时就会render一颗新树,然后通过diff算法来比较计算最小开销,最后把差异提交到真实的DOM
Bad 虚拟DOM也有一些不好的地方,它不是免费的抽象,在一些场景里会引入额外成本/复杂度。
额外的计算与内存开销:每次更新 React 都要重新执行组件、生成 element/fiber、做 reconciliation,再 commit。对超高频更新(动画、拖拽、实时渲染)可能比“直接命令式改 DOM/Canvas/WebGL”更重。
启发式 diff 不是全局最优:React 的对比依赖规则(同层、同类型、key),不是最小编辑距离;key 写错/用 index 会导致错误复用、状态串位或不必要重建,性能和正确性都受影响。
误用会导致性能问题:不稳定引用、无意义 re-render、巨型列表不做虚拟滚动、在 render 里做重计算——这些都会放大虚拟 DOM 的成本,所以工程上常配 memo/useMemo/useCallback、列表虚拟化、拆分组件。
useMemo和useCallback是什么?(react优化相关) What
useMemo:记住“计算出来的值”(数组/对象/数字都行),依赖不变就复用上次结果。
useCallback:记住“函数本身”(保持函数引用稳定)。它等价于:
useCallback(fn, deps) ≈ useMemo(() => fn, deps) 它们都不会阻止组件 re-render;组件仍会 render,只是你拿到的值/函数不重新创建。就类似于,你唯一的输入一定会得到唯一的输出
How 1 2 3 4 5 const list = useMemo (() => expensiveFilter (data, keyword), [data, keyword])const onSend = useCallback (() => { sendMessage (text) }, [text])
Why
省计算 :过滤/排序/聚合很贵时,用 useMemo 避免每次 render 重算。
稳定引用,配合浅比较优化 :用 useMemo/useCallback 让引用稳定,避免“不必要的触发”。
“数组/对象/函数每次 render 都会创建新引用;如果它们作为 props/依赖,会让浅比较认为变了,从而触发不必要更新。useMemo/useCallback 用来稳定这些引用。”
浅比较
对基本类型(Number,Boolean,String):比 值(===) 。
对引用类型(arr[],obj{},function()):比 引用地址(===) ,不看里面内容。
1 2 3 4 5 6 7 8 9 10 const a = { x : 1 }const b = { x : 1 }a === b const c = aa === c const arr1 = [1 ,2 ]const arr2 = [1 ,2 ]arr1 === arr2
在 React 里常见浅比较场景:
React.memo 默认用浅比较 props:只要某个 prop 引用变了,就认为“变了”。
useEffect/useMemo/useCallback 的依赖数组比较:每一项用 Object.is(和 === 很像)做浅比较。
数组怎么合并? How 1 2 3 4 5 6 let a = [1 ,2 ,3 ]let b = [4 ,5 ,6 ]let c = [].concat (a,b)let c = [...a,...b]
如何写一个间隔一秒自动切换颜色的正方形 How
间隔:setInterval
自动切换 <==> 状态监听 <==> 使用hook(useState)
清除定时器 <==> useEffect,需要返回清除函数
react里传样式传的是一个JSON
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { useEffect, useState } from "react" function App ( ) { const color = ['red' ,'green' ,'pink' ] const [col,setCol] = useState (0 ) useEffect (()=> { const timer = setInterval (()=> { setCol ((prev ) => (prev + 1 ) % color.length ) },1000 ) return ()=> clearInterval (timer) },[]) return ( <div > <div style ={{ width: 100 , height: 100 , backgroundColor: color [col ] }} /> </div > ); } export default App
useEffect是什么? What
useEffect 是 React 的“副作用hook”:让你在组件渲染到页面之后去做一些事,并且能在需要时清理这些事。
副作用
定时器 setInterval/setTimeout
订阅/事件监听 addEventListener
发请求 fetch/axios
操作 DOM、设置标题 document.title = …
这些都不应该放在组件函数体里反复执行。
1 2 3 4 5 6 useEffect (() => { return () => { }; }, [deps]);
How 依赖数组 [deps] 决定“执行频率”
不写第二个参数:每次渲染后都执行(很少用,容易反复绑定/请求)
写 []:只在组件首次挂载后执行一次(适合建定时器/订阅一次)
写 [a, b]:a 或 b 变化时执行(常用于“根据某个 state/prop 变化去请求/同步”)
return 的清理函数什么时候执行
组件卸载时执行一次
或者依赖变化导致 effect 重新执行时:先清理上一次,再执行新的
内存泄漏有哪些场景? Where
循环依赖,A依赖B, B依赖A
定时器/轮询没有清理 (useEffect return 一个clearInterval)
闭包长期持有对象
数组有哪些遍历方法?
正常的for循环
forEach:用于执行回调函数,但是一般不修改原数组内容,用于打印值
1 2 3 4 5 6 7 let arr = [1 ,2 ,3 ]arr.forEach ((x )=> console .log (x)) arr.forEach ((x )=> console .log (x*x)) arr.forEach ((x,i )=> arr[i] = x*x)
map: 用于创建一个新的数组,本质也是遍历
1 2 3 let arr = [1 ,4 ,9 ]let b = arr.map ((x )=> x*x*x)console .log (b);
for … of / for…in 用于遍历值
对Promise的理解 Why
避免回调地狱的出现,在没有promise之前,需要很多个if else来列举出全部的情况,现在只需要成功或失败
Promise 把异步结果抽象成一个对象,提供统一的组合方式(then 链、catch、all 等),让控制流更清晰。
What
Promise是未来承诺会有返回值,然后去执行对应的逻辑
三个状态:pending、fulfilled、rejected。状态不可逆
then去执行fulfilled的回调,catch去执行rejected的回调
How 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const p = new Promise ((resolve,reject )=> { if (true ) { resolve ('ok' ) } else { reject (new Error ('error' )) } }) p.then ((data )=> { console .log (data) }) .catch ((err )=> { console .log (err) })
两个API
Promise.all: 所有成功才会走then,如果有一个失败了,就会报错;
1 2 3 4 5 6 7 8 9 const p1 = Promise .resolve ('data1' )const p2 = Promise .resolve ('data2' )const p3 = Promise .reject ('data3' )Promise .all ([p1,p2]).then ((res )=> console .log (res)) .catch ((err )=> console .log (err)) [ 'data1' , 'data2' ]
Promise.allSettled: 为什么会出现他,如果我们主页有四个图表,肯定不想因为某一个挂了就不渲染其他正常的地方,allSettled只会返回一个then,附上它的status
1 2 3 4 5 6 7 8 9 10 11 const p1 = Promise .resolve ('成功' )const p3 = Promise .reject ('网络延迟' )Promise .allSettled ([p1,p3]).then ((res )=> console .log (res)) .catch ((err )=> console .log (err)) [ { status : 'fulfilled' , value : '成功' }, { status : 'rejected' , reason : '网络延迟' } ]
对async和await的理解 What
async 保证函数返回 Promise;return x 等价 Promise.resolve(x),throw err 等价 Promise.reject(err);await 会等待一个 Promise,成功返回值,失败抛异常。
Why
async和await是Promise的语法糖,将.then拍平,用try/catch包裹promise的执行结果
async function f(){}:保证这个函数返回 Promise
在 async 里 return x,等价于 return Promise.resolve(x)
在 async 里 throw err,等价于 return Promise.reject(err)
await 后面接受任何值,但如果是 Promise 才会“等待” ;不是 Promise 就立刻得到该值(等价于 await Promise.resolve(x))。这个async函数里就需要await后面的Promise对象的状态,如果是fulfilled,走try里面的逻辑,如果是rejected,走catch里的逻辑
How 1 2 3 4 5 6 7 8 9 async function requestJSON (url ) { const res = await fetch (url); if (!res.ok ) throw new Error (`HTTP ${res.status} ` ); return await res.json (); } requestJSON ("https://api.github.com/users/octocat" ) .then (console .log ) .catch (e => console .log ("err:" , e.message ));
受控组件和非受控组件 What
受控组件(Controlled):表单值 由 React state 控制。value/checked 绑定 state,onChange 更新 state。单一数据源:state。
非受控组件(Uncontrolled):表单值 由 DOM 自己维护。React 不用 state 绑住值 ,需要时用 ref 读取(或表单提交时读)。数据源:DOM。
浏览器存数据有哪些方式? localStorage和sessionStorage有什么区别 What
Cookie:最早的存储方式,容量小(约 4KB),每次请求都会带在 HTTP 头部,主要用于身份验证(Session ID)。
Web Storage:包含 localStorage 和 sessionStorage。
IndexedDB:浏览器内置的“非关系型数据库”,可以存储大量结构化数据(甚至是文件和二进制数据),且支持索引和事务。
Cache API:主要用于 Service Worker,用来缓存网络资源(如 HTML、JS、图片),实现离线访问。
Why
localStorage
永久有效。除非你手动删除或用户清理浏览器缓存,否则关闭浏览器后数据依然存在。
同源共享。同一域名下的所有页面、标签页都可以读写同一个 localStorage。
sessionStorage
会话级有效。一旦标签页(Tab)或窗口被关闭,数据就会被清空。
标签页独立。即使是同一个网站,在不同标签页打开,它们的 sessionStorage 也是互不干扰的。
状态码问题 如果前端报404该如何排查?
确认请求地址:看看url是不是拼错了
查看方法是不是请求错了:如果接口是post,但是使用了get
确认前端代理:nginx或者next.config是不是正确代理到了api
确认后端接口是否存在
路径部署可能错误
全栈开发遇到500如何排查?
500几乎一定是服务端问题,先看日志或者控制台
用postman/curl去直接调接口
检查请求参数列表:JSON格式,字段名,类型
代码逻辑错误
中间件炸了或者没开:SQL、Redis、第三方api
评论如果有很多的话,如何处理?
作为后端
作为前端
虚拟列表What
虚拟列表的核心思想是只渲染可视区域的DOM
传统方法,假设10000条评论,那就会渲染出10000个DOM,会很卡,浏览器真正慢的是 DOM,不是 JS。
1 comments.map (c => <Comment /> )
当DOM很多的时候,Layout计算慢,滚动也需要重排
How
如果用户屏幕大小就这么多,为什么要去渲染全部的评论,只需要用评论区容器的px / 每条评论容器的px 就是渲染出来的评论数。
滚动时候用scrollTop,它表示滚动了多少px,1600px / 80px = 20条
1 startIndex = Math .floor (scrollTop / itemHeight)
所以只需要渲染 startIndex + 评论数
核心只有 4 步:
获取滚动距离 scrollTop
计算起始 index start = scrollTop / itemHeight
计算需要多少条 visibleCount = containerHeight / itemHeight
渲染 data.slice(start, start + visibleCount)
手写一个倒计时的拓展 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { useEffect, useState } from 'react' import './App.css' function App ( ) { const [count, setCount] = useState (100 ) console .log (count) useEffect (()=> { const timer = setInterval (()=> { setCount (count - 1 ) },1000 ) return ()=> clearInterval (timer) },[]) return ( <div > {count}</div > ) } export default App
What
useEffect(fn,[]),[]表示依赖列表为空,意思是:
effect 不依赖任何 state / props,因此只在组件首次挂载时执行一次。
所以第一次,如果使用函数式的更新,即使依赖项为[],也会拿到最新的prev
第二次由于没填写依赖项,永远拿的是第一个值,因为没有依赖,所以不会重新执行
console.log两次是因为开发时有了strict模式,默认走两次,会让副作用出现。
React 的通信方式有哪些?
Props
Callback:父组件传函数,子组件调用
兄弟组件通信:把状态提升到共同父组件
全局状态管理
能把hook写在条件判断里吗?
不能,从React的渲染机制来看,Props/State改变了就会重新渲染,如果我们写在了某一个循环判断里,然后成功渲染了,但是由于数据改变了,又会调用App()去渲染,这样就会多次渲染,会报错。
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
图片的懒加载是怎么实现的?
原生:img标签里的loading属性设置为lazy
Intersection Observer API
1 2 3 4 5 6 7 8 9 10 11 12 13 const images = document .querySelectorAll ('img[data-src]' );const observer = new IntersectionObserver ((entries, observer ) => { entries.forEach (entry => { if (entry.isIntersecting ) { const img = entry.target ; img.src = img.dataset .src ; observer.unobserve (img); } }); }); images.forEach (img => observer.observe (img));
预热加载: 在 Intersection Observer 中设置 rootMargin: “200px”,让图片在进入视口前 200 像素就开始加载,用户几乎感知不到加载过程。
大模型这种“蹦字”的效果,前端是怎么拿到的?底层协议是什么?
通过fetch 配合 ReadableStream,fetch配合ReadableStream
使用 fetch 请求大模型接口时,返回的 response.body 是一个流
1 2 3 4 5 6 7 8 9 10 11 12 const response = await fetch ('/api/chat' , { method : 'POST' , body : JSON .stringify ({ prompt }) });const reader = response.body .getReader (); const decoder = new TextDecoder (); while (true ) { const { done, value } = await reader.read (); if (done) break ; const chunk = decoder.decode (value); processText (chunk); }
响应式布局? What
页面根据设备尺寸自动调整布局(媒体查询、flex、grid)
Why
How
使用 @media、flex/grid(使用弹性布局替代固定像素。)、百分比布局、rem/vw(相对单位)、移动端优先设计。
State和Props的区别 What
都是js对象,用于保存信息并触发渲染,Props是组件的配置参数(外部传入) ,State是组件的私有状态(内部管理)
Props只读,State可变
如何区分一个变量是数组还是对象? How
typeof是没用的,只能认出基本类型,typeof {}和typeof []的结果都是object
用instanceof在原型链上找
1 2 3 4 const a = {}const b = []console .log (a instanceof Object ) console .log (b instanceof Object )
API,isArray()
Object.prototype.toString.call()