乐闻世界logo
搜索文章和话题

面试题手册

如何优化 Zustand 的性能,减少不必要的重渲染?

Zustand 性能优化策略:选择订阅(Selective Subscription)只订阅组件真正需要的状态部分使用回调函数选择状态,而不是订阅整个状态示例: // 好的做法:只订阅需要的状态 const count = useStore((state) => state.count); // 避免:订阅整个状态 const store = useStore(); const count = store.count;使用稳定的选择器避免在组件渲染过程中创建新的选择器函数使用 useCallback 或 useMemo 缓存选择器示例: import { useCallback } from 'react'; function Counter() { const increment = useCallback( () => useStore.getState().increment(), [] ); const count = useStore((state) => state.count); return ( <div> <h1>{count}</h1> <button onClick={increment}>Increment</button> </div> ); }批量更新使用 set 函数的函数式更新,可以批量处理多个状态变更示例: javascript const useStore = create((set) => ({ count: 0, user: null, updateData: (newCount, newUser) => set((state) => ({ count: state.count + newCount, user: newUser, })), }));使用中间件persist 中间件的合理配置,避免过度持久化自定义中间件进行性能监控状态结构设计合理组织状态结构,避免深层嵌套将相关状态放在一起,便于选择订阅避免在选择器中执行复杂计算对于复杂计算,考虑使用 useMemo 或在 store 中预计算使用 getState() 和 setState()对于非 React 环境或不需要触发重渲染的场景示例: javascript // 在非 React 代码中使用 const store = useStore.getState(); store.increment();性能优化检查清单:[ ] 使用选择订阅而非全量订阅[ ] 缓存选择器函数[ ] 批量处理状态更新[ ] 合理设计状态结构[ ] 避免在选择器中进行复杂计算[ ] 按需使用 getState() 和 setState()
阅读 0·3月6日 21:41

Cookie 和 LocalStorage 有什么区别?在什么场景下使用哪种存储方式?

Cookie 和 LocalStorage 都是浏览器提供的客户端存储机制,但它们在容量、作用域、API 和使用场景上有重要区别。容量限制Cookie:每个 Cookie 约 4KB,每个域名约 50-100 个LocalStorage:每个域名约 5-10MB数据传输Cookie:每次 HTTP 请求都会自动发送到服务器LocalStorage:不会自动发送,需要手动通过 JavaScript 读取API 操作Cookie:通过 document.cookie 字符串操作,需要手动解析LocalStorage:提供简洁的键值对 API(setItem、getItem、removeItem)过期时间Cookie:可设置过期时间或会话级别LocalStorage:永久存储,除非手动删除或清除浏览器数据作用域Cookie:可通过 Domain 和 Path 属性精确控制作用域LocalStorage:同源策略,仅在同域名、同协议、同端口下共享安全性Cookie:可设置 HttpOnly 防止 JavaScript 访问LocalStorage:完全可被 JavaScript 访问,不适合存储敏感信息使用场景对比Cookie:会话管理、CSRF 防护、服务器需要的数据LocalStorage:用户偏好设置、缓存数据、离线应用数据代码示例// Cookie 操作document.cookie = "name=value; expires=...; path=/";// LocalStorage 操作localStorage.setItem('key', 'value');const value = localStorage.getItem('key');选择建议需要发送到服务器的数据用 Cookie纯客户端数据用 LocalStorage敏感信息用 Cookie + HttpOnly大量数据用 LocalStorage
阅读 0·3月6日 21:41

Cookie 在不同浏览器中的表现有什么差异?如何处理浏览器兼容性问题?

Cookie 在不同浏览器中的实现和行为存在差异,了解这些差异对于开发兼容性良好的 Web 应用非常重要。主要浏览器 Cookie 差异容量限制Chrome:每个 Cookie 4KB,每个域名约 180 个Firefox:每个 Cookie 4KB,每个域名约 150 个Safari:每个 Cookie 4KB,每个域名约 600 个Edge:与 Chrome 相同第三方 Cookie 限制Chrome:逐步限制,计划 2024 年开始默认阻止Firefox:默认阻止第三方追踪 CookieSafari:默认阻止所有第三方 Cookie(ITP 策略)Edge:与 Chrome 相同SameSite 默认行为Chrome 80+:默认 SameSite=LaxFirefox 79+:默认 SameSite=LaxSafari 12+:默认 SameSite=LaxEdge 80+:默认 SameSite=LaxSecure 标志要求Chrome:SameSite=None 必须配合 SecureFirefox:SameSite=None 必须配合 SecureSafari:SameSite=None 必须配合 SecureEdge:SameSite=None 必须配合 SecureSafari ITP(智能追踪预防)自动删除 7 天后未访问的第三方 Cookie限制 Cookie 的有效期最长 7 天影响跨域登录和追踪功能Firefox ETP(增强追踪保护)严格模式阻止所有第三方追踪 Cookie标准模式阻止已知的追踪器 Cookie可通过 about:preferences#privacy 配置Chrome 隐私沙盒计划逐步淘汰第三方 Cookie引入 FLoC、Topics API 等替代方案开发者需要提前准备迁移方案兼容性处理建议检测浏览器支持function supportsSameSiteNone() { try { document.cookie = 'test=1; SameSite=None; Secure'; return document.cookie.includes('test=1'); } catch (e) { return false; }}渐进增强策略const cookieOptions = { httpOnly: true, secure: true, sameSite: 'Lax' // 默认使用 Lax};// 如果需要跨域,检测支持后再使用 Noneif (supportsSameSiteNone()) { cookieOptions.sameSite = 'None';}提供降级方案第三方 Cookie 不可用时使用 LocalStorage实现服务器端会话管理作为备选使用 PostMessage 进行跨域通信测试建议在多个浏览器中测试 Cookie 功能使用浏览器开发者工具检查 Cookie 设置测试隐私模式下的 Cookie 行为验证跨域 Cookie 的兼容性
阅读 0·3月6日 21:40

Shell 脚本中常用的文本处理工具有哪些?如何使用 grep、sed、awk 和 cut?

Shell 脚本中常用的文本处理工具包括 grep、sed、awk 和 cut 等。grep - 文本搜索工具基本用法# 在文件中搜索文本grep "pattern" file.txt# 搜索多个文件grep "pattern" file1.txt file2.txt# 递归搜索目录grep -r "pattern" /path/to/directory# 忽略大小写grep -i "pattern" file.txt# 显示行号grep -n "pattern" file.txt# 反向匹配(不包含)grep -v "pattern" file.txt# 只显示匹配的文件名grep -l "pattern" *.txt# 统计匹配行数grep -c "pattern" file.txt正则表达式# 匹配行首grep "^start" file.txt# 匹配行尾grep "end$" file.txt# 匹配数字grep "[0-9]" file.txt# 匹配特定次数grep "a\{3\}" file.txt # 匹配 3 个 a# 使用扩展正则表达式grep -E "pattern1|pattern2" file.txt实际应用# 查找进程ps aux | grep "nginx"# 查找日志中的错误grep "ERROR" /var/log/syslog# 查找包含特定内容的文件grep -r "TODO" ./src# 统计代码行数grep -c "^" *.pysed - 流编辑器基本用法# 替换文本sed 's/old/new/' file.txt# 全局替换sed 's/old/new/g' file.txt# 删除行sed '3d' file.txt # 删除第 3 行sed '/pattern/d' file.txt # 删除匹配的行# 打印特定行sed -n '5p' file.txt # 打印第 5 行sed -n '1,5p' file.txt # 打印 1-5 行# 插入和追加sed '2i\new line' file.txt # 在第 2 行前插入sed '2a\new line' file.txt # 在第 2 行后追加高级用法# 使用正则表达式sed 's/[0-9]\+//g' file.txt# 多个替换sed -e 's/old1/new1/g' -e 's/old2/new2/g' file.txt# 就地编辑(修改原文件)sed -i 's/old/new/g' file.txt# 备份后编辑sed -i.bak 's/old/new/g' file.txt# 使用变量var="pattern"sed "s/$var/replacement/g" file.txt实际应用# 替换配置文件中的值sed -i 's/port=8080/port=9090/' config.ini# 删除注释行sed '/^#/d' file.txt# 删除空行sed '/^$/d' file.txt# 格式化输出sed 's/\s\+/ /g' file.txtawk - 文本处理工具基本用法# 打印特定列awk '{print $1}' file.txt# 打印多列awk '{print $1, $3}' file.txt# 指定分隔符awk -F: '{print $1}' /etc/passwd# 打印行号awk '{print NR, $0}' file.txt# 条件打印awk '$3 > 100 {print $0}' file.txt内置变量NR # 当前记录号(行号)NF # 当前记录的字段数$0 # 完整的记录$1, $2 # 第 1、2 个字段FS # 字段分隔符(默认空格)OFS # 输出字段分隔符RS # 记录分隔符(默认换行)ORS # 输出记录分隔符模式和动作# 模式匹配awk '/pattern/ {print $0}' file.txt# BEGIN 和 END 块awk 'BEGIN {print "Start"} {print $0} END {print "End"}' file.txt# 计算总和awk '{sum += $1} END {print sum}' file.txt# 计算平均值awk '{sum += $1; count++} END {print sum/count}' file.txt实际应用# 统计文件大小ls -l | awk '{sum += $5} END {print sum}'# 查找最大值awk '{if ($1 > max) max = $1} END {print max}' file.txt# 格式化输出awk '{printf "%-10s %10s\n", $1, $2}' file.txt# 处理 CSV 文件awk -F, '{print $1, $3}' data.csvcut - 文本切割工具基本用法# 按字符切割cut -c 1-5 file.txt # 提取 1-5 字符cut -c 1,5,10 file.txt # 提取第 1、5、10 字符# 按字节切割cut -b 1-10 file.txt# 按字段切割cut -d: -f1 /etc/passwd # 提取第 1 个字段cut -d: -f1,3 /etc/passwd # 提取第 1、3 个字段实际应用# 提取用户名cut -d: -f1 /etc/passwd# 提取 IP 地址ifconfig | grep "inet " | cut -d: -f2 | cut -d' ' -f1# 提取文件扩展名echo "file.txt" | cut -d. -f2组合使用示例日志分析# 统计错误数量grep "ERROR" /var/log/app.log | wc -l# 查找特定时间段的日志sed -n '/2024-01-01 10:00/,/2024-01-01 11:00/p' /var/log/app.log# 提取 IP 地址grep "ERROR" /var/log/app.log | awk '{print $5}' | cut -d: -f2# 统计各错误类型的数量grep "ERROR" /var/log/app.log | awk '{print $6}' | sort | uniq -c文本处理# 删除空行和注释sed '/^$/d; /^#/d' file.txt# 替换多个空格为单个空格sed 's/\s\+/ /g' file.txt# 提取特定列并去重awk '{print $1}' file.txt | sort -u# 计算平均值awk '{sum += $1} END {print sum/NR}' file.txt系统管理# 查找占用 CPU 最高的进程ps aux | sort -rk 3 | head -n 5# 查找占用内存最多的进程ps aux | sort -rk 4 | head -n 5# 统计各用户进程数ps aux | awk '{print $1}' | sort | uniq -c# 查找特定端口的进程lsof -i :8080 | awk '{print $2}' | tail -n +2最佳实践使用管道组合工具: grep | awk | sort | uniq优先使用 grep 进行搜索: 简单搜索时最快使用 sed 进行替换: 文本替换的首选工具使用 awk 处理列数据: 处理结构化文本的最佳选择使用 cut 提取固定位置: 简单的文本切割任务注意正则表达式语法: grep 和 sed 的正则表达式略有不同测试命令: 在处理重要文件前先测试命令
阅读 0·3月6日 21:40

Shell 脚本中如何使用 if 语句和 case 语句进行条件判断?

Shell 脚本中常用的条件判断语句包括 if 语句、case 语句和测试命令。if 语句基本语法if [ condition ]; then commandselif [ condition ]; then commandselse commandsfi文件测试操作符[ -f file ] # 文件存在且为普通文件[ -d dir ] # 目录存在[ -e file ] # 文件或目录存在[ -r file ] # 文件可读[ -w file ] # 文件可写[ -x file ] # 文件可执行[ -s file ] # 文件大小大于零[ -L file ] # 文件是符号链接字符串测试操作符[ "$str1" = "$str2" ] # 字符串相等[ "$str1" != "$str2" ] # 字符串不等[ -z "$str" ] # 字符串为空[ -n "$str" ] # 字符串非空数值测试操作符[ $num1 -eq $num2 ] # 等于[ $num1 -ne $num2 ] # 不等于[ $num1 -gt $num2 ] # 大于[ $num1 -lt $num2 ] # 小于[ $num1 -ge $num2 ] # 大于等于[ $num1 -le $num2 ] # 小于等于逻辑操作符[ condition1 ] && [ condition2 ] # 逻辑与[ condition1 ] || [ condition2 ] # 逻辑或! [ condition ] # 逻辑非[ condition1 -a condition2 ] # 逻辑与(在 [] 内)[ condition1 -o condition2 ] # 逻辑或(在 [] 内)if 语句示例#!/bin/bash# 检查文件是否存在if [ -f "/etc/passwd" ]; then echo "File exists"else echo "File does not exist"fi# 检查数值age=25if [ $age -ge 18 ]; then echo "Adult"elif [ $age -ge 13 ]; then echo "Teenager"else echo "Child"fi# 复合条件if [ -f "$1" ] && [ -r "$1" ]; then echo "File exists and is readable"fi# 使用 [[ ]] 进行更复杂的测试if [[ $name == "John" || $name == "Jane" ]]; then echo "Welcome $name"ficase 语句基本语法case $variable in pattern1) commands ;; pattern2) commands ;; *) commands ;;esaccase 语句示例#!/bin/bash# 简单的菜单选择echo "Select an option:"echo "1. Start"echo "2. Stop"echo "3. Restart"read -p "Enter choice: " choicecase $choice in 1) echo "Starting service..." ;; 2) echo "Stopping service..." ;; 3) echo "Restarting service..." ;; *) echo "Invalid choice" ;;esac# 使用模式匹配file="document.txt"case $file in *.txt) echo "Text file" ;; *.jpg|*.png) echo "Image file" ;; *) echo "Unknown file type" ;;esac测试命令test 命令test condition# 等同于[ condition ]双括号 [[ ]]# [[ ]] 是 Bash 的扩展,功能更强大[[ $name == "John" && $age -gt 18 ]]双括号 (( ))# (( )) 用于算术运算(( age++ ))if (( age >= 18 )); then echo "Adult"fi实际应用示例#!/bin/bash# 检查命令行参数if [ $# -eq 0 ]; then echo "Usage: $0 <filename>" exit 1fifile=$1# 检查文件类型if [ -f "$file" ]; then echo "File exists" # 检查文件权限 if [ -r "$file" ]; then echo "File is readable" else echo "File is not readable" fi if [ -w "$file" ]; then echo "File is writable" else echo "File is not writable" fielif [ -d "$file" ]; then echo "Directory exists" # 检查目录内容 file_count=$(ls -1 "$file" | wc -l) if [ $file_count -eq 0 ]; then echo "Directory is empty" else echo "Directory contains $file_count items" fielse echo "File or directory does not exist" exit 1fi# 使用 case 处理文件扩展名extension="${file##*.}"case $extension in txt|md) echo "Text document" ;; sh) echo "Shell script" ;; py) echo "Python script" ;; *) echo "Unknown file type" ;;esac最佳实践始终使用引号包裹变量: "$variable" 防止空格和特殊字符问题优先使用 [[ ]] 而不是 []: 功能更强大,更安全使用 case 处理多条件匹配: 代码更清晰检查命令执行状态: 使用 $? 或 if command; then避免使用 -a 和 -o: 使用 && 和 || 更清晰
阅读 0·3月6日 21:40

Shell 脚本中如何定义和使用数组?常用的数组操作有哪些?

Shell 脚本中的数组操作包括数组的定义、访问、遍历和常用操作。数组定义普通数组# 定义空数组arr=()# 定义数组(空格分隔)arr=(apple banana cherry)# 逐个定义arr[0]="apple"arr[1]="banana"arr[2]="cherry"# 使用命令输出定义数组arr=($(ls *.txt))关联数组(Bash 4.0+)# 声明关联数组declare -A arr# 定义关联数组arr[name]="John"arr[age]=25arr[city]="Beijing"# 一次性定义declare -A arr=([name]="John" [age]=25 [city]="Beijing")数组访问访问单个元素arr=(apple banana cherry)# 访问特定索引的元素echo ${arr[0]} # 输出: appleecho ${arr[1]} # 输出: bananaecho ${arr[2]} # 输出: cherry# 访问最后一个元素echo ${arr[-1]} # 输出: cherry# 访问不存在的索引echo ${arr[10]} # 输出: (空)访问所有元素# 访问所有元素echo ${arr[@]} # 输出: apple banana cherryecho ${arr[*]} # 输出: apple banana cherry# 访问所有元素的索引echo ${!arr[@]} # 输出: 0 1 2# 访问数组长度echo ${#arr[@]} # 输出: 3echo ${#arr[*]} # 输出: 3访问部分元素# 访问从索引 1 开始的所有元素echo ${arr[@]:1} # 输出: banana cherry# 访问从索引 1 开始的 2 个元素echo ${arr[@]:1:2} # 输出: banana cherry# 访问从倒数第 2 个元素开始echo ${arr[@]: -2} # 输出: banana cherry数组遍历遍历所有元素arr=(apple banana cherry)# 方法 1: 使用 for 循环for item in "${arr[@]}"; do echo "Item: $item"done# 方法 2: 使用索引遍历for i in "${!arr[@]}"; do echo "Index $i: ${arr[$i]}"done# 方法 3: 使用 C 风格循环for ((i=0; i<${#arr[@]}; i++)); do echo "Index $i: ${arr[$i]}"done遍历关联数组declare -A arr=([name]="John" [age]=25 [city]="Beijing")# 遍历键for key in "${!arr[@]}"; do echo "Key: $key"done# 遍历键值对for key in "${!arr[@]}"; do echo "$key: ${arr[$key]}"done数组操作添加元素arr=(apple banana)# 添加到末尾arr+=(cherry)arr+=("date" "fig")# 添加到特定位置arr[2]="cherry" # 替换或添加# 使用索引添加arr[${#arr[@]}]="grape" # 添加到末尾删除元素arr=(apple banana cherry date fig)# 删除特定索引的元素unset arr[2] # 删除索引 2 的元素# 删除整个数组unset arr# 删除关联数组的元素declare -A arr=([name]="John" [age]=25)unset arr[name]修改元素arr=(apple banana cherry)# 修改特定索引的元素arr[0]="orange"arr[1]="pear"# 批量修改for i in "${!arr[@]}"; do arr[$i]="${arr[$i]}_modified"done数组切片arr=(one two three four five)# 切片操作echo ${arr[@]:1:3} # 输出: two three four# 切片并赋值new_arr=(${arr[@]:1:3})数组排序简单排序arr=(banana apple cherry date)# 字典序排序sorted=($(echo "${arr[@]}" | tr ' ' '\n' | sort))echo "${sorted[@]}"# 数字排序nums=(5 2 8 1 9)sorted_nums=($(echo "${nums[@]}" | tr ' ' '\n' | sort -n))echo "${sorted_nums[@]}"# 逆序排序sorted_rev=($(echo "${arr[@]}" | tr ' ' '\n' | sort -r))echo "${sorted_rev[@]}"去重arr=(apple banana apple cherry banana)# 去重unique=($(echo "${arr[@]}" | tr ' ' '\n' | sort -u))echo "${unique[@]}"数组查找查找元素arr=(apple banana cherry)# 检查元素是否存在if [[ " ${arr[@]} " =~ " banana " ]]; then echo "Found banana"fi# 使用函数查找contains_element() { local e match="$1" shift for e; do [[ "$e" == "$match" ]] && return 0; done return 1}if contains_element "banana" "${arr[@]}"; then echo "Found banana"fi查找索引arr=(apple banana cherry)# 查找元素索引get_index() { local element=$1 shift local arr=("$@") for i in "${!arr[@]}"; do if [[ "${arr[$i]}" == "$element" ]]; then echo $i return 0 fi done return 1}index=$(get_index "banana" "${arr[@]}")echo "Index of banana: $index"实际应用示例文件处理# 获取所有 .txt 文件files=($(ls *.txt))# 遍历并处理for file in "${files[@]}"; do echo "Processing: $file" # 处理文件done# 检查文件是否存在if [ ${#files[@]} -eq 0 ]; then echo "No .txt files found"fi参数处理# 将参数存储到数组args=("$@")# 遍历参数for arg in "${args[@]}"; do echo "Argument: $arg"done# 检查特定参数if [[ " ${args[@]} " =~ " --verbose " ]]; then verbose=truefi数据统计# 读取数据到数组readarray -t lines < data.txt# 统计行数echo "Total lines: ${#lines[@]}"# 统计包含特定内容的行count=0for line in "${lines[@]}"; do if [[ "$line" =~ "pattern" ]]; then ((count++)) fidoneecho "Matching lines: $count"配置管理# 解析配置文件到关联数组declare -A configwhile IFS='=' read -r key value; do config[$key]="$value"done < config.txt# 访问配置echo "Database: ${config[db_host]}"echo "Port: ${config[db_port]}"数组最佳实践始终使用引号: "${arr[@]}" 防止空格和特殊字符问题使用 ${#arr[@]} 获取长度: 而不是 ${#arr}使用 ${!arr[@]} 获取索引: 遍历时更安全关联数组需要声明: declare -A 声明关联数组避免使用未初始化的索引: 检查索引是否存在使用函数封装复杂操作: 提高代码可读性注意数组索引从 0 开始: 与其他编程语言一致
阅读 0·3月6日 21:40