【场景精选】前端面试题 - 大厂 javaScript 篇(2026精选附详细答案)

【场景精选】前端面试题 - 大厂 javaScript 篇(2026精选附详细答案)

【支持与反对请投票和评论区留言】书接上一回,这期我们来学习一下javaScript篇(大厂面试题),涵盖了从基础概念到高级用法的各个方面。每个问题都提供了代码示例和实际应用场景,帮助你深入理解和掌握这些知识点。还在等什么,赶紧卷起来

直接上干货!!

前端面试题详细解答(第二部分javaScript 篇)

1. Proxy能够监听到对象中的对象的引用吗?

可以监听,但需要递归代理

// 基础示例:Proxy只能监听第一层属性const obj ={a:1,b:{c:2}}const proxy =newProxy(obj,{get(target, key){ console.log(`get ${key}`)return target[key]},set(target, key, value){ console.log(`set ${key} = ${value}`) target[key]= value returntrue}}) proxy.a =3// 会触发 set a = 3 proxy.b.c =4// 不会触发任何监听,因为访问的是b.c,不是b的set

实现深度监听

// 方法一:递归代理functiondeepProxy(obj, handler){if(typeof obj !=='object'|| obj ===null)return obj for(let key in obj){if(typeof obj[key]==='object'&& obj[key]!==null){ obj[key]=deepProxy(obj[key], handler)}}returnnewProxy(obj, handler)}const obj ={a:1,b:{c:2,d:{e:3}}}const proxy =deepProxy(obj,{get(target, key){ console.log(`get ${key}`)return target[key]},set(target, key, value){ console.log(`set ${key} = ${JSON.stringify(value)}`) target[key]= value returntrue}}) proxy.b.c =4// 触发 set b = {"c":4,"d":{"e":3}} proxy.b.d.e =5// 触发 set b = {"c":4,"d":{"e":5}}

Vue 3的响应式实现原理

// Vue 3的reactive就是递归Proxy的实现import{ reactive }from'vue'const state =reactive({user:{name:'张三',address:{city:'北京',detail:'朝阳区'}}})// 修改嵌套属性也会触发响应式更新 state.user.address.city ='上海'// 会触发更新

注意事项

  1. 性能考虑:递归代理会带来性能开销
  2. 循环引用:需要处理循环引用的情况
  3. 避免重复代理:同一对象不应被代理多次

2. 如何让var[a,b]={a:1,b:2}解构赋值成功?

正确写法

// 对象解构赋值,变量名必须与属性名相同var{a, b}={a:1,b:2} console.log(a)// 1 console.log(b)// 2// 如果变量名不同,需要使用别名var{a: x,b: y}={a:1,b:2} console.log(x)// 1 console.log(y)// 2// 带默认值的解构var{a =0, b =0, c =0}={a:1,b:2} console.log(a, b, c)// 1 2 0// 嵌套解构var{a:{x, y}, b}={a:{x:1,y:2},b:3} console.log(x, y, b)// 1 2 3

题目中错误的原因

// 错误写法var[a,b]={a:1,b:2}// 错误原因:数组解构语法用于对象// 正确应该是var{a, b}={a:1,b:2}// 或者如果是数组解构(但对象不符合数组结构)var[a, b]=[1,2]

实际应用场景

// 函数参数解构functiongetUserInfo({name, age, city ='未知'}){ console.log(`${name}, ${age}岁, 来自${city}`)}getUserInfo({name:'张三',age:25})// 从函数返回多个值functiongetCoordinates(){return{x:100,y:200}}var{x, y}=getCoordinates()// 交换变量值var a =1, b =2;[a, b]=[b, a]// 数组解构才能这样交换 console.log(a, b)// 2 1

3. 什么是作用域链?

作用域链的概念

作用域链是JavaScript查找变量的一套规则,决定了代码区块中变量和其他资源的访问权限。

// 示例:作用域链查找var globalVar ='global'functionouter(){var outerVar ='outer'functioninner(){var innerVar ='inner' console.log(innerVar)// inner,当前作用域 console.log(outerVar)// outer,父级作用域 console.log(globalVar)// global,全局作用域 console.log(nonExist)// ReferenceError,无法找到}inner()}outer()

作用域链的创建过程

// 1. 全局作用域链// 全局上下文:VO(global) -> null// 2. 函数调用时的作用域链functionfoo(){var a =1functionbar(){var b =2 console.log(a + b)// 3}bar()}foo()

作用域链与闭包

// 闭包:函数记住并访问其词法作用域functioncreateCounter(){let count =0// 被内部函数引用,形成闭包return{increment:function(){ count++return count },decrement:function(){ count--return count },getCount:function(){return count }}}const counter =createCounter() console.log(counter.increment())// 1 console.log(counter.increment())// 2// count变量通过闭包保持存活

ES6的作用域变化

// let/const 的块级作用域{var a =1let b =2const c =3} console.log(a)// 1,var是函数作用域或全局作用域// console.log(b) // ReferenceError,let是块级作用域// console.log(c) // ReferenceError,const是块级作用域// 作用域链在循环中的表现for(var i =0; i <3; i++){setTimeout(()=>{ console.log(i)// 输出3次3},100)}for(let j =0; j <3; j++){setTimeout(()=>{ console.log(j)// 输出0,1,2},100)}

4. 不会冒泡的事件有哪些?

不会冒泡的事件列表

// 1. focus 和 blur(但focusin和focusout会冒泡) element.addEventListener('focus',(e)=>{ console.log('focus不会冒泡')},false)// false表示冒泡阶段,但focus仍然不会冒泡// 2. mouseenter 和 mouseleave(但mouseover和mouseout会冒泡) element.addEventListener('mouseenter',(e)=>{ console.log('mouseenter不会冒泡')})// 3. load、unload、abort、error img.addEventListener('load',(e)=>{ console.log('load不会冒泡')})// 4. resize window.addEventListener('resize',(e)=>{ console.log('resize不会冒泡')})// 5. scroll(在某些浏览器中可能不会冒泡) element.addEventListener('scroll',(e)=>{ console.log('scroll不会冒泡')})

事件冒泡机制对比

<divid="parent"><buttonid="child">点击我</button></div><script>const parent = document.getElementById('parent')const child = document.getElementById('child')// 会冒泡的事件示例(click) parent.addEventListener('click',()=>{ console.log('父元素点击事件 - 会冒泡')}) child.addEventListener('click',(e)=>{ console.log('子元素点击事件')// e.stopPropagation() // 可以阻止冒泡})// 不会冒泡的事件示例(focus) parent.addEventListener('focus',()=>{ console.log('父元素focus事件 - 不会触发,因为不会冒泡')},true)// 使用捕获阶段可以监听到 child.addEventListener('focus',()=>{ console.log('子元素获得焦点')})</script>

实际应用

// 监听不会冒泡的事件的技巧// 方法1:使用捕获阶段 document.addEventListener('focus',(e)=>{ console.log('捕获阶段监听到focus:', e.target)},true)// 方法2:直接在目标元素上监听const inputs = document.querySelectorAll('input') inputs.forEach(input=>{ input.addEventListener('focus', handleFocus)})// 方法3:使用会冒泡的替代事件// 用focusin代替focus(focusin会冒泡) element.addEventListener('focusin',(e)=>{ console.log('focusin会冒泡')})// 用mouseover代替mouseenter(mouseover会冒泡) element.addEventListener('mouseover',(e)=>{ console.log('mouseover会冒泡')})

5. async、await的实现原理

async/await的本质

async/await是Generator函数的语法糖,基于Promise实现。

// async函数示例asyncfunctionfoo(){const result =awaitsomeAsyncFunction()return result }// 等价于下面的Generator函数function*fooGenerator(){const result =yieldsomeAsyncFunction()return result }

实现原理:Generator + 自动执行器

// 1. Generator函数function*myGenerator(){const a =yield Promise.resolve(1)const b =yield Promise.resolve(2)return a + b }// 2. 手动执行Generatorconst gen =myGenerator() gen.next().value.then(val1=>{ gen.next(val1).value.then(val2=>{const result = gen.next(val2) console.log(result.value)// 3})})// 3. 自动执行器(简化版)functionasyncToGenerator(generatorFunc){returnfunction(...args){const gen =generatorFunc.apply(this, args)returnnewPromise((resolve, reject)=>{functionstep(key, arg){let result try{ result = gen[key](arg)}catch(error){returnreject(error)}const{ value, done }= result if(done){returnresolve(value)}else{// value可能不是Promise,用Promise.resolve包装return Promise.resolve(value).then(val=>step('next', val),err=>step('throw', err))}}step('next')})}}// 使用自动执行器const myAsyncFunc =asyncToGenerator(myGenerator)myAsyncFunc().then(result=> console.log(result))// 3

Babel编译后的async/await

// 编译前的async函数asyncfunctiontest(){const a =await1const b =await Promise.resolve(2)return a + b }// Babel编译后(简化版)function_asyncToGenerator(fn){returnfunction(){var self =this, args = arguments returnnewPromise(function(resolve, reject){var gen =fn.apply(self, args)function_next(value){asyncGeneratorStep(gen, resolve, reject, _next, _throw,"next", value)}function_throw(err){asyncGeneratorStep(gen, resolve, reject, _next, _throw,"throw", err)}_next(undefined)})}}functionasyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg){try{var info = gen[key](arg)var value = info.value }catch(error){reject(error)return}if(info.done){resolve(value)}else{ Promise.resolve(value).then(_next, _throw)}}var test =_asyncToGenerator(function*(){const a =yield1const b =yield Promise.resolve(2)return a + b })

错误处理机制

// async/await的错误处理asyncfunctionfetchData(){try{const response =awaitfetch('/api/data')const data =await response.json()return data }catch(error){ console.error('请求失败:', error)throw error // 重新抛出错误}}// 等价于Promise的catchfunctionfetchDataPromise(){returnfetch('/api/data').then(response=> response.json()).catch(error=>{ console.error('请求失败:', error)throw error })}// 多个await的错误处理asyncfunctionmultiFetch(){// 方式1:分别try-catchtry{const user =awaitfetchUser()}catch(error){ console.error('获取用户失败')}try{const posts =awaitfetchPosts()}catch(error){ console.error('获取帖子失败')}// 方式2:Promise.all + 统一处理try{const[user, posts]=await Promise.all([fetchUser(),fetchPosts()])}catch(error){ console.error('某个请求失败')}}

6. script标签放在header里和放在body底部里有什么区别?

位置对比分析

<!-- 放在header中 --><!DOCTYPEhtml><html><head><scriptsrc="header-script.js"></script></head><body><div>页面内容</div></body></html><!-- 放在body底部 --><!DOCTYPEhtml><html><head><!-- 没有阻塞的script --></head><body><div>页面内容</div><scriptsrc="body-script.js"></script></body></html>

性能影响对比

// 测试脚本 console.log('脚本开始执行')const start = performance.now()while(performance.now()- start <2000){// 模拟2秒的脚本执行时间} console.log('脚本执行完成')// 性能指标对比:/* 放在header中: 1. HTML解析被阻塞 2. 页面白屏时间长 3. DOMContentLoaded事件延迟 4. 用户看到内容慢 放在body底部: 1. HTML解析不被阻塞 2. 页面快速呈现 3. 用户尽早看到内容 4. 脚本最后执行 */

现代最佳实践

<!DOCTYPEhtml><html><head><!-- 1. 必要的、不阻塞的脚本使用async/defer --><scriptsrc="analytics.js"async></script><!-- 2. 关键CSS内联,非关键CSS异步加载 --><style>/* 关键CSS */.above-the-fold{ styles }</style><linkrel="stylesheet"href="non-critical.css"media="print"onload="this.media='all'"><!-- 3. 预加载重要资源 --><linkrel="preload"href="critical-script.js"as="script"></head><body><!-- 页面内容 --><!-- 4. 非关键脚本放在底部 --><scriptsrc="non-critical-script.js"></script><!-- 5. 关键脚本优先执行 --><scriptsrc="critical-script.js"></script></body></html>

script属性的使用

<!-- 1. async:异步加载,不阻塞HTML解析,加载完后立即执行 --><scriptsrc="async-script.js"async></script><!-- 执行顺序不确定,先加载完的先执行 --><!-- 2. defer:异步加载,不阻塞HTML解析,在DOMContentLoaded前按顺序执行 --><scriptsrc="defer-script1.js"defer></script><scriptsrc="defer-script2.js"defer></script><!-- 执行顺序确定:1 -> 2 --><!-- 3. module:ES6模块,默认defer行为 --><scripttype="module"src="module.js"></script><!-- 4. 动态加载 --><script>// 动态创建script标签const script = document.createElement('script') script.src ='dynamic.js' script.async =true document.body.appendChild(script)</script>

7. 子组件是一个Portal,发生点击事件能冒泡到父组件吗?

Portal事件冒泡机制

// React Portal示例import React from'react'import ReactDOM from'react-dom'// Portal组件functionPortalComponent({ children }){const portalRoot = document.getElementById('portal-root')return ReactDOM.createPortal(children, portalRoot)}// 父组件functionParentComponent(){consthandleClick=(e)=>{ console.log('父组件收到点击事件') console.log('事件目标:', e.target) console.log('当前目标:', e.currentTarget)}return(<div onClick={handleClick}><h3>父组件区域</h3>{/* 普通子组件 */}<div className="normal-child"> 普通子组件(点击会冒泡) </div>{/* Portal子组件 */}<PortalComponent><div className="portal-child"> Portal子组件(点击也会冒泡到React树中的父组件) </div></PortalComponent></div>)}// 渲染 ReactDOM.render(<ParentComponent />, document.getElementById('root'))

事件冒泡的两种情况

// 1. React合成事件的冒泡(沿着React组件树)// Portal中的事件会冒泡到React父组件// 2. 原生DOM事件的冒泡(沿着DOM树)// Portal中的事件不会冒泡到DOM父节点,因为不在同一DOM层级// 示例验证classEventTestextendsReact.Component{componentDidMount(){// 监听原生DOM事件 document.getElementById('portal-root').addEventListener('click',(e)=>{ console.log('portal-root原生事件')}) document.querySelector('.normal-child').addEventListener('click',(e)=>{ console.log('普通子组件原生事件')})}handleReactClick=(e)=>{ console.log('React合成事件触发') console.log('e.nativeEvent:', e.nativeEvent)// 原生事件}render(){return(<div onClick={this.handleReactClick}><div className="normal-child">普通子组件</div>{ReactDOM.createPortal(<div className="portal-child">Portal子组件</div>, document.getElementById('portal-root'))}</div>)}}

实际应用场景

// 场景1:Modal对话框functionModal({ isOpen, onClose, children }){if(!isOpen)returnnullreturn ReactDOM.createPortal(<div className="modal-overlay" onClick={onClose}><div className="modal-content" onClick={e=> e.stopPropagation()}>{children}<button onClick={onClose}>关闭</button></div></div>, document.body )}// 使用ModalfunctionApp(){const[isModalOpen, setModalOpen]=useState(false)consthandleButtonClick=()=>{setModalOpen(true)}consthandleModalClose=()=>{setModalOpen(false)}return(<div><button onClick={handleButtonClick}>打开Modal</button><Modal isOpen={isModalOpen} onClose={handleModalClose}><h2>Modal标题</h2><p>Modal内容...</p></Modal></div>)}// 场景2:Tooltip提示框functionTooltip({ children, content }){const[isVisible, setIsVisible]=useState(false)const tooltipRef =useRef()useEffect(()=>{if(isVisible && tooltipRef.current){// 计算位置等逻辑}},[isVisible])return(<><span onMouseEnter={()=>setIsVisible(true)} onMouseLeave={()=>setIsVisible(false)}>{children}</span>{isVisible && ReactDOM.createPortal(<div ref={tooltipRef} className="tooltip">{content}</div>, document.body )}</>)}

8. 使用Promise实现红绿灯交替重复亮

// 方案1:使用Promise链functiontrafficLight(){functionlight(color, duration){returnnewPromise(resolve=>{ console.log(`${color}灯亮`)setTimeout(()=>{ console.log(`${color}灯灭`)resolve()}, duration *1000)})}// 执行一次循环functionrunCycle(){returnlight('红',3).then(()=>light('黄',1)).then(()=>light('绿',2))}// 无限循环functioninfiniteLoop(){returnrunCycle().then(infiniteLoop)}// 开始infiniteLoop().catch(err=>{ console.error('红绿灯出错:', err)})}// 方案2:使用async/awaitasyncfunctiontrafficLightAsync(){asyncfunctionlight(color, duration){ console.log(`${color}灯亮`)awaitnewPromise(resolve=>setTimeout(resolve, duration *1000)) console.log(`${color}灯灭`)}try{while(true){awaitlight('红',3)awaitlight('黄',1)awaitlight('绿',2)}}catch(error){ console.error('红绿灯出错:', error)}}// 方案3:带控制功能的红绿灯classTrafficLight{constructor(){this.isRunning =falsethis.cycleCount =0this.lights =[{color:'红',duration:3},{color:'黄',duration:1},{color:'绿',duration:2}]}asynclightOn(color, duration){ console.log(`🚦 ${color}灯亮 - 持续${duration}秒`)awaitnewPromise(resolve=>setTimeout(resolve, duration *1000)) console.log(`🚦 ${color}灯灭`)}asyncstart(){if(this.isRunning)returnthis.isRunning =true console.log('红绿灯开始运行')try{while(this.isRunning){for(const light ofthis.lights){if(!this.isRunning)breakawaitthis.lightOn(light.color, light.duration)}this.cycleCount++ console.log(`已完成 ${this.cycleCount} 个循环`)}}catch(error){ console.error('红绿灯异常:', error)this.stop()}}stop(){this.isRunning =false console.log('红绿灯停止')}// 控制特定灯asyncmanualControl(color, duration){this.stop()awaitthis.lightOn(color, duration)this.start()}}// 使用const trafficLight =newTrafficLight() trafficLight.start()// 10秒后停止setTimeout(()=>{ trafficLight.stop()},10000)// 方案4:可视化红绿灯functionvisualTrafficLight(){const colors ={red:'#ff0000',yellow:'#ffff00',green:'#00ff00',off:'#333333'}const lightElement = document.getElementById('traffic-light')const redLight = lightElement.querySelector('.red')const yellowLight = lightElement.querySelector('.yellow')const greenLight = lightElement.querySelector('.green')functionsetLight(element, color){ element.style.backgroundColor = color }asyncfunctionchangeLight(color, duration){// 先关掉所有灯setLight(redLight, colors.off)setLight(yellowLight, colors.off)setLight(greenLight, colors.off)// 点亮指定颜色的灯switch(color){case'红':setLight(redLight, colors.red)breakcase'黄':setLight(yellowLight, colors.yellow)breakcase'绿':setLight(greenLight, colors.green)break} console.log(`${color}灯亮`)awaitnewPromise(resolve=>setTimeout(resolve, duration *1000)) console.log(`${color}灯灭`)}// 运行asyncfunctionrun(){while(true){awaitchangeLight('红',3)awaitchangeLight('绿',2)awaitchangeLight('黄',1)}}return{ run }}

9. 什么是DOM和BOM?

DOM(文档对象模型)

// DOM示例:操作HTML文档const element = document.getElementById('myId') element.innerHTML ='新内容' element.style.color ='red'// DOM树结构/* document ├── html │ ├── head │ │ ├── title │ │ └── meta │ └── body │ ├── div │ │ ├── h1 │ │ └── p │ └── script */

BOM(浏览器对象模型)

// BOM主要对象// 1. window对象(全局对象) console.log(window.innerWidth)// 视口宽度 console.log(window.location)// 地址信息 console.log(window.navigator)// 浏览器信息 console.log(window.history)// 历史记录 console.log(window.screen)// 屏幕信息 console.log(window.localStorage)// 本地存储// 2. location对象 console.log(location.href)// 完整URL console.log(location.protocol)// 协议 console.log(location.host)// 主机 console.log(location.pathname)// 路径 console.log(location.search)// 查询参数// 3. history对象 history.back()// 后退 history.forward()// 前进 history.go(-2)// 后退2页 history.pushState({},'','/new-url')// 添加历史记录// 4. navigator对象 console.log(navigator.userAgent)// 用户代理 console.log(navigator.platform)// 平台 console.log(navigator.language)// 语言 console.log(navigator.onLine)// 是否在线// 5. screen对象 console.log(screen.width)// 屏幕宽度 console.log(screen.height)// 屏幕高度 console.log(screen.availWidth)// 可用宽度// 6. 弹窗相关const result =confirm('确定要删除吗?')const name =prompt('请输入姓名','张三')alert('操作成功!')

DOM和BOM的区别

/* DOM vs BOM 对比表 ┌─────────────┬─────────────────────────┬─────────────────────────┐ │ 特性 │ DOM │ BOM │ ├─────────────┼─────────────────────────┼─────────────────────────┤ │ 标准 │ W3C标准 │ 没有统一标准 │ │ 用途 │ 操作文档内容 │ 操作浏览器窗口 │ │ 核心对象 │ document │ window │ │ 包含内容 │ 元素、属性、文本节点 │ 窗口、历史、地址栏等 │ │ 操作 │ 增删改查HTML元素 │ 控制浏览器行为 │ │ 事件 │ click、change等 │ load、resize等 │ └─────────────┴─────────────────────────┴─────────────────────────┘ */// 实际应用中的结合使用functioninitPage(){// BOM:获取浏览器信息const isMobile =/Mobi|Android/i.test(navigator.userAgent)// DOM:根据设备类型设置样式if(isMobile){ document.body.classList.add('mobile')}// BOM:监听窗口变化 window.addEventListener('resize',()=>{// DOM:调整布局const container = document.getElementById('container')if(window.innerWidth <768){ container.classList.add('collapsed')}else{ container.classList.remove('collapsed')}})// BOM:使用localStorageconst theme = localStorage.getItem('theme')||'light'// DOM:应用主题 document.body.setAttribute('data-theme', theme)}

10. 简单描述从输入网址到页面显示的过程

完整流程概述

1. 输入URL并回车 2. 浏览器解析URL 3. DNS解析(域名 -> IP地址) 4. 建立TCP连接(三次握手) 5. 发送HTTP请求 6. 服务器处理请求 7. 服务器返回HTTP响应 8. 浏览器接收响应 9. 浏览器解析HTML,构建DOM树 10. 解析CSS,构建CSSOM树 11. 合并DOM和CSSOM,构建渲染树 12. 布局(计算元素位置和大小) 13. 绘制(将像素绘制到屏幕) 14. 合成(层合并,GPU加速) 

详细步骤分析

// 步骤1:URL解析const url =newURL('https://www.example.com:443/path?query=1#section') console.log(url.protocol)// https: console.log(url.hostname)// www.example.com console.log(url.port)// 443 console.log(url.pathname)// /path console.log(url.search)// ?query=1 console.log(url.hash)// #section// 步骤2:DNS解析过程/* 1. 浏览器缓存 2. 操作系统缓存(hosts文件) 3. 路由器缓存 4. ISP DNS缓存 5. 递归查询(根域名服务器 -> 顶级域 -> 权威域名服务器) */// 步骤3:TCP连接(三次握手)/* 客户端 -> SYN -> 服务器 客户端 <- SYN+ACK <- 服务器 客户端 -> ACK -> 服务器 连接建立! */// 步骤4:HTTPS的TLS握手(如果需要)/* 1. ClientHello 2. ServerHello + Certificate 3. Client Key Exchange 4. Change Cipher Spec 5. Finished */// 步骤5:HTTP请求const request =`GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 Accept: text/html Connection: keep-alive`// 步骤6-7:服务器处理并响应const response =`HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 1234 Cache-Control: max-age=3600 <!DOCTYPE html> <html>...</html>`// 步骤8-14:浏览器渲染过程asyncfunctionbrowserRender(html, css, js){// 解析HTML,构建DOM树const parser =newDOMParser()const dom = parser.parseFromString(html,'text/html')// 解析CSS,构建CSSOM树const stylesheet =newCSSStyleSheet() stylesheet.replaceSync(css)// 构建渲染树(排除display:none的元素)const renderTree =buildRenderTree(dom, stylesheet)// 布局(重排)const layout =calculateLayout(renderTree)// 绘制const paintLayers =paint(layout)// 合成compositeLayers(paintLayers)// 执行JavaScript(可能引起重排重绘)eval(js)// DOMContentLoaded事件(DOM解析完成) document.addEventListener('DOMContentLoaded',()=>{ console.log('DOM准备就绪')})// load事件(所有资源加载完成) window.addEventListener('load',()=>{ console.log('页面完全加载')})}

性能优化相关

// 关键性能指标const performanceMetrics ={// DNS查询时间dnsLookup: performance.timing.domainLookupEnd - performance.timing.domainLookupStart,// TCP连接时间tcpConnect: performance.timing.connectEnd - performance.timing.connectStart,// SSL握手时间(HTTPS)sslConnect: performance.timing.connectEnd - performance.timing.secureConnectionStart,// 请求响应时间requestResponse: performance.timing.responseEnd - performance.timing.requestStart,// DOM解析时间domParse: performance.timing.domInteractive - performance.timing.responseEnd,// DOMContentLoaded时间domReady: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,// 页面完全加载时间pageLoad: performance.timing.loadEventEnd - performance.timing.navigationStart }// 优化建议const optimizations ={dns:'使用DNS预连接:<link rel="dns-prefetch" href="//cdn.example.com">',tcp:'使用HTTP/2,支持多路复用',ssl:'使用TLS 1.3,会话恢复',request:'减少HTTP请求,使用资源合并',render:{css:'将CSS放在头部,避免阻塞渲染',js:'将非关键JS放在底部或使用defer/async',image:'使用懒加载和响应式图片'},cache:'合理使用缓存策略',cdn:'使用CDN加速静态资源'}

11. 说说new操作符具体干了什么?

new操作符的执行过程

// 模拟new操作符的实现functionmyNew(constructor,...args){// 1. 创建一个空对象,将其原型指向构造函数的prototypeconst obj = Object.create(constructor.prototype)// 2. 执行构造函数,将this绑定到新创建的对象const result =constructor.apply(obj, args)// 3. 判断构造函数返回的是否是对象// 如果是对象,则返回该对象;否则返回新创建的对象return result instanceofObject? result : obj }// 使用示例functionPerson(name, age){this.name = name this.age = age // 如果构造函数返回一个对象,new表达式会返回该对象// return { custom: 'object' } // 如果取消注释,new Person()会返回这个对象}const person1 =newPerson('张三',25)const person2 =myNew(Person,'李四',30) console.log(person1)// Person { name: '张三', age: 25 } console.log(person2)// Person { name: '李四', age: 30 }

详细步骤分析

// 步骤1:创建新对象并设置原型functionstep1(constructor){// 创建一个新对象const obj ={}// 将新对象的__proto__指向构造函数的prototype// 这样新对象就可以访问构造函数原型上的属性和方法 Object.setPrototypeOf(obj, constructor.prototype)// 或者 obj.__proto__ = constructor.prototype(不推荐直接使用__proto__)return obj }// 步骤2:执行构造函数functionstep2(constructor, obj, args){// 调用构造函数,将this绑定到新对象const result =constructor.apply(obj, args)return result }// 步骤3:处理返回值functionstep3(result, obj){// 如果构造函数返回了一个对象,则返回该对象if(result &&(typeof result ==='object'||typeof result ==='function')){return result }// 否则返回新创建的对象return obj }// 完整实现functioncreateInstance(constructor,...args){const obj = Object.create(constructor.prototype)const result =constructor.apply(obj, args)return result instanceofObject? result : obj }

特殊情况处理

// 情况1:构造函数返回基本类型functionFoo(){this.name ='Foo'return123// 返回基本类型,会被忽略}const foo =newFoo() console.log(foo)// Foo { name: 'Foo' }// 情况2:构造函数返回对象functionBar(){this.name ='Bar'return{custom:'object'}// 返回对象,取代新创建的对象}const bar =newBar() console.log(bar)// { custom: 'object' }// 情况3:箭头函数不能作为构造函数constArrowFunc=()=>{this.value =1}try{const instance =newArrowFunc()// TypeError: ArrowFunc is not a constructor}catch(error){ console.error(error.message)}// 情况4:class中的newclassAnimal{constructor(name){this.name = name }speak(){ console.log(`${this.name} makes a sound`)}}const dog =newAnimal('Dog') dog.speak()// Dog makes a sound

实际应用

// 应用1:实现继承functionParent(name){this.name = name this.colors =['red','blue']}Parent.prototype.sayName=function(){ console.log(this.name)}functionChild(name, age){// 调用父类构造函数Parent.call(this, name)// 相当于super(name)this.age = age }// 继承父类原型Child.prototype = Object.create(Parent.prototype)Child.prototype.constructor = Child Child.prototype.sayAge=function(){ console.log(this.age)}const child =newChild('小明',10) child.sayName()// 小明 child.sayAge()// 10// 应用2:单例模式functionSingleton(){if(Singleton.instance){return Singleton.instance }this.createdAt =newDate() Singleton.instance =this}const s1 =newSingleton()const s2 =newSingleton() console.log(s1 === s2)// true// 应用3:工厂函数替代newfunctioncreateUser(role){const user = Object.create(userMethods)if(role ==='admin'){ user.permissions =['read','write','delete']}else{ user.permissions =['read']}return user }const userMethods ={hasPermission(perm){returnthis.permissions.includes(perm)}}const admin =createUser('admin') console.log(admin.hasPermission('delete'))// true

12. 说说你对低代码的了解

低代码的核心概念

// 低代码平台的典型架构const lowCodePlatform ={// 1. 可视化设计器designer:{dragAndDrop:true,// 拖拽式界面构建wysiwyg:true,// 所见即所得componentPalette:[// 组件库'Button','Form','Table','Chart']},// 2. 模型驱动开发modelDriven:{dataModel:'可视化定义数据结构',businessLogic:'配置式业务规则',workflow:'可视化流程设计'},// 3. 代码生成codeGeneration:{frontend:'Vue/React代码',backend:'Java/Node.js代码',database:'SQL脚本'},// 4. 集成能力integration:{apiConnector:'API连接器',databaseConnector:'数据库连接',thirdParty:'第三方服务集成'}}

低代码的优势和劣势

const lowCodeAnalysis ={advantages:[{name:'开发效率',description:'减少70%-80%的编码工作量',example:'一个CRUD页面传统开发需要1-2天,低代码可能只需要30分钟'},{name:'降低门槛',description:'业务人员也能参与应用开发',example:'通过拖拽和配置即可完成简单应用'},{name:'标准化',description:'统一的技术栈和开发规范',example:'生成的代码遵循最佳实践'},{name:'维护成本',description:'可视化修改,无需深入代码',example:'业务规则变更可直接在界面调整'}],disadvantages:[{name:'灵活性限制',description:'复杂定制化需求难以实现',example:'特殊动画效果或复杂业务逻辑'},{name:'性能问题',description:'生成的代码可能不够优化',example:'大型数据表性能可能不如手写代码'},{name:'厂商锁定',description:'迁移到其他平台成本高',example:'特定平台的配置无法直接迁移'},{name:'学习曲线',description:'需要学习平台特定的概念和操作',example:'平台的工作流引擎、数据模型等'}]}

低代码的实际应用

// 应用场景1:企业内部管理系统const internalSystems =['OA办公自动化','CRM客户关系管理','ERP企业资源计划','HRM人力资源系统','项目管理工具']// 应用场景2:数据可视化报表const dataVisualization ={components:[{type:'Chart',config:{chartType:'line|bar|pie',dataSource:'API或数据库',filters:['时间范围','部门筛选']}},{type:'Dashboard',config:{layout:'可拖拽栅格',widgets:['指标卡','趋势图','排行榜']}}]}// 应用场景3:移动端应用const mobileAppBuilder ={features:['表单采集','信息展示','流程审批','数据同步','消息推送'],deployment:['生成iOS/Android原生应用','生成微信小程序','生成H5页面']}

主流低代码平台对比

const platforms ={outSystems:{type:'专业级低代码',target:'企业级复杂应用',features:['全栈开发','高性能','AI辅助'],pricing:'较高'},mendix:{type:'模型驱动低代码',target:'企业数字化',features:['微流','领域模型','协作开发'],pricing:'企业级'},powerApps:{type:'微软生态低代码',target:'Office 365用户',features:['Excel集成','Power Automate','Teams集成'],pricing:'按用户订阅'},amis:{type:'前端低代码',target:'前端开发者',features:['JSON配置','开源','Vue/React支持'],pricing:'免费开源'},h5Dooring:{type:'H5页面搭建',target:'营销页面',features:['拖拽生成','多模板','数据统计'],pricing:'开源版本免费'}}

13. Map和Set的用法以及区别

Map(键值对集合)

// 创建Mapconst map =newMap()// 基本操作 map.set('name','张三')// 添加键值对 map.set('age',25) map.set({id:1},'对象作为键')// 对象也可以作为键 console.log(map.get('name'))// 张三 console.log(map.has('age'))// true console.log(map.size)// 3// 遍历Map map.forEach((value, key)=>{ console.log(`${key}: ${value}`)})for(let[key, value]of map){ console.log(key, value)}// 获取键、值、条目 console.log([...map.keys()])// ['name', 'age', {id: 1}] console.log([...map.values()])// ['张三', 25, '对象作为键'] console.log([...map.entries()])// 键值对数组// 删除操作 map.delete('age')// 删除指定键 map.clear()// 清空Map

Set(值集合)

// 创建Setconst set =newSet()// 基本操作 set.add(1)// 添加值 set.add(2) set.add(2)// 重复值会被忽略 set.add('hello') set.add({name:'obj'})// 对象可以添加,但每个对象都是唯一的 console.log(set.has(1))// true console.log(set.size)// 4// 遍历Set set.forEach(value=>{ console.log(value)})for(let value of set){ console.log(value)}// 集合操作const set1 =newSet([1,2,3])const set2 =newSet([2,3,4])// 并集const union =newSet([...set1,...set2]) console.log([...union])// [1, 2, 3, 4]// 交集const intersection =newSet([...set1].filter(x=> set2.has(x))) console.log([...intersection])// [2, 3]// 差集const difference =newSet([...set1].filter(x=>!set2.has(x))) console.log([...difference])// [1]// 删除操作 set.delete(1)// 删除指定值 set.clear()// 清空Set

Map和Set的区别

const comparison ={// 数据结构对比dataStructure:{Map:'键值对集合,键可以是任意类型',Set:'值集合,值唯一'},// 初始化方式对比initialization:{Map:'new Map([["key1", "value1"], ["key2", "value2"]])',Set:'new Set([1, 2, 3, 3]) // 重复值会被去重'},// 常用方法对比methods:{Map:['set(key, value)','get(key)','has(key)','delete(key)','clear()'],Set:['add(value)','has(value)','delete(value)','clear()']},// 遍历方式对比iteration:{Map:'map.forEach((value, key) => {})',Set:'set.forEach(value => {})'},// 性能特点对比performance:{Map:'键的查找是O(1),适合做字典、缓存',Set:'值的查找是O(1),适合去重、集合运算'},// 应用场景对比useCases:{Map:['对象映射','数据缓存','需要键不是字符串的场景','需要保持插入顺序的场景'],Set:['数组去重','集合运算','存储唯一值','标签系统']}}

实际应用示例

// 应用1:使用Map实现LRU缓存classLRUCache{constructor(capacity){this.capacity = capacity this.cache =newMap()// 使用Map保持插入顺序}get(key){if(!this.cache.has(key))return-1const value =this.cache.get(key)// 更新访问顺序:先删除,再重新插入this.cache.delete(key)this.cache.set(key, value)return value }put(key, value){if(this.cache.has(key)){this.cache.delete(key)}elseif(this.cache.size >=this.capacity){// 删除最久未使用的(Map的第一个键)const firstKey =this.cache.keys().next().value this.cache.delete(firstKey)}this.cache.set(key, value)}}// 应用2:使用Set进行数组去重和集合运算functionarrayOperations(){// 数组去重const arr =[1,2,2,3,4,4,5]const uniqueArr =[...newSet(arr)] console.log(uniqueArr)// [1, 2, 3, 4, 5]// 求多个数组的交集functionintersection(...arrays){if(arrays.length ===0)return[]let result =newSet(arrays[0])for(let i =1; i < arrays.length; i++){ result =newSet([...result].filter(x=> arrays[i].includes(x)))}return[...result]} console.log(intersection([1,2,3],[2,3,4],[3,4,5]))// [3]// 检查数组是否有重复值functionhasDuplicates(array){returnnewSet(array).size !== array.length } console.log(hasDuplicates([1,2,3,2]))// true}// 应用3:使用Map存储对象元数据functionobjectMetadata(){const users =[{id:1,name:'Alice'},{id:2,name:'Bob'},{id:3,name:'Charlie'}]// 创建用户ID到用户对象的映射const userMap =newMap(users.map(user=>[user.id, user]))// 快速查找 console.log(userMap.get(2))// { id: 2, name: 'Bob' }// 添加额外元数据 userMap.forEach((user, id)=>{ userMap.set(id,{...user,lastLogin:newDate(),loginCount:0})})return userMap }

14. 最大子序和

问题描述

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

// 示例 输入:[-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6

解法1:动态规划(Kadane算法)

functionmaxSubArray(nums){if(!nums || nums.length ===0)return0let maxSum = nums[0]// 全局最大和let currentSum = nums[0]// 当前子数组的和for(let i =1; i < nums.length; i++){// 如果当前元素加上之前的和比当前元素还小,就从当前元素重新开始 currentSum = Math.max(nums[i], currentSum + nums[i])// 更新全局最大和 maxSum = Math.max(maxSum, currentSum)}return maxSum }// 测试 console.log(maxSubArray([-2,1,-3,4,-1,2,1,-5,4]))// 6 console.log(maxSubArray([1]))// 1 console.log(maxSubArray([5,4,-1,7,8]))// 23

解法2:分治法

functionmaxSubArrayDivide(nums){returndivide(nums,0, nums.length -1)functiondivide(nums, left, right){if(left === right)return nums[left]const mid = Math.floor((left + right)/2)// 递归计算左右两边的最大子序和const leftMax =divide(nums, left, mid)const rightMax =divide(nums, mid +1, right)// 计算跨越中间的最大子序和const crossMax =maxCrossing(nums, left, mid, right)// 返回三者中的最大值return Math.max(leftMax, rightMax, crossMax)}functionmaxCrossing(nums, left, mid, right){// 计算左半部分的最大后缀和let leftSum =-Infinitylet sum =0for(let i = mid; i >= left; i--){ sum += nums[i] leftSum = Math.max(leftSum, sum)}// 计算右半部分的最大前缀和let rightSum =-Infinity sum =0for(let i = mid +1; i <= right; i++){ sum += nums[i] rightSum = Math.max(rightSum, sum)}return leftSum + rightSum }}// 测试 console.log(maxSubArrayDivide([-2,1,-3,4,-1,2,1,-5,4]))// 6

解法3:返回最大子数组本身

functionmaxSubArrayWithIndices(nums){if(!nums || nums.length ===0)return{sum:0,subarray:[]}let maxSum = nums[0]let currentSum = nums[0]let maxStart =0, maxEnd =0let currentStart =0for(let i =1; i < nums.length; i++){if(nums[i]> currentSum + nums[i]){// 从当前元素重新开始 currentSum = nums[i] currentStart = i }else{// 继续累加 currentSum += nums[i]}// 更新最大和及其位置if(currentSum > maxSum){ maxSum = currentSum maxStart = currentStart maxEnd = i }}return{sum: maxSum,subarray: nums.slice(maxStart, maxEnd +1),start: maxStart,end: maxEnd }}// 测试const result =maxSubArrayWithIndices([-2,1,-3,4,-1,2,1,-5,4]) console.log(result.sum)// 6 console.log(result.subarray)// [4, -1, 2, 1] console.log(result.start)// 3 console.log(result.end)// 6

解法4:处理特殊情况

functionmaxSubArrayEnhanced(nums){if(!nums || nums.length ===0)return0// 处理全负数的情况let allNegative =truelet maxElement =-Infinityfor(let num of nums){if(num >=0) allNegative =falseif(num > maxElement) maxElement = num }if(allNegative)return maxElement // 正常情况使用Kadane算法let maxSum = nums[0]let currentSum = nums[0]for(let i =1; i < nums.length; i++){ currentSum = Math.max(nums[i], currentSum + nums[i]) maxSum = Math.max(maxSum, currentSum)}return maxSum }// 测试 console.log(maxSubArrayEnhanced([-1,-2,-3,-4]))// -1 console.log(maxSubArrayEnhanced([-2,1,-3,4,-1,2,1,-5,4]))// 6

性能对比

const testCases =[{name:'小数组',arr:[1,2,3,4,5]},{name:'混合数组',arr:[-2,1,-3,4,-1,2,1,-5,4]},{name:'全负数',arr:[-5,-4,-3,-2,-1]},{name:'大数组',arr: Array.from({length:10000},(_, i)=> Math.floor(Math.random()*2000)-1000)}]functionbenchmark(){ testCases.forEach(({name, arr})=>{ console.time(`动态规划-${name}`)maxSubArray(arr) console.timeEnd(`动态规划-${name}`) console.time(`分治法-${name}`)maxSubArrayDivide(arr) console.timeEnd(`分治法-${name}`) console.log('---')})}// 动态规划:时间复杂度O(n),空间复杂度O(1)// 分治法:时间复杂度O(n log n),空间复杂度O(log n)(递归栈)

15. 说说https的握手过程

HTTPS握手完整流程

// HTTPS = HTTP + TLS/SSLconst httpsHandshake ={// 第1步:Client HelloclientHello:{protocol:'TLS 1.2 或 TLS 1.3',random:'客户端随机数',cipherSuites:'支持的加密套件列表',compressionMethods:'压缩方法',extensions:['服务器名称指示(SNI)','支持的椭圆曲线','签名算法']},// 第2步:Server HelloserverHello:{protocol:'选择的TLS版本',random:'服务器随机数',cipherSuite:'选择的加密套件',compressionMethod:'选择的压缩方法',extensions:'服务器扩展'},// 第3步:服务器证书serverCertificate:{certificate:'服务器证书链',certificateRequest:'可选,要求客户端提供证书'},// 第4步:Server Key Exchange(如果需要)serverKeyExchange:{// 用于密钥交换的参数// 在RSA密钥交换中不需要此步骤},// 第5步:Server Hello DoneserverHelloDone:'表示服务器部分完成',// 第6步:Client Key ExchangeclientKeyExchange:{// 客户端生成预主密钥,用服务器公钥加密后发送encryptedPreMasterSecret:'加密的预主密钥'},// 第7步:Change Cipher SpecchangeCipherSpec:'通知对方开始使用加密通信',// 第8步:Finishedfinished:{message:'加密的Finished消息,包含所有握手消息的校验和'}}

TLS 1.2握手详细过程

// TLS 1.2完整握手(RSA密钥交换)functiontls12HandshakeRSA(){ console.log('=== TLS 1.2握手过程(RSA)===')// 1. TCP三次握手建立连接// 2. TLS握手开始// 客户端 -> 服务器: Client Helloconst clientHello ={version:'TLS 1.2',random:'client_random',cipherSuites:['TLS_RSA_WITH_AES_128_GCM_SHA256','TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256'],extensions:{server_name:'example.com'}}// 服务器 -> 客户端: Server Helloconst serverHello ={version:'TLS 1.2',random:'server_random',cipherSuite:'TLS_RSA_WITH_AES_128_GCM_SHA256'}// 服务器 -> 客户端: Certificate(服务器证书)const serverCertificate ='certificate_chain'// 服务器 -> 客户端: Server Hello Done console.log('Server Hello Done')// 客户端验证证书,生成预主密钥const preMasterSecret ='pre_master_secret'// 用服务器公钥加密const encryptedPreMasterSecret =encrypt(preMasterSecret, serverPublicKey)// 客户端 -> 服务器: Client Key Exchangeconst clientKeyExchange ={encryptedPreMasterSecret: encryptedPreMasterSecret }// 双方计算主密钥// master_secret = PRF(pre_master_secret, "master secret", // ClientHello.random + ServerHello.random)const masterSecret ='master_secret'// 客户端 -> 服务器: Change Cipher Spec console.log('Change Cipher Spec')// 客户端 -> 服务器: Finishedconst clientFinished ='encrypted_finished_message'// 服务器 -> 客户端: Change Cipher Spec console.log('Server Change Cipher Spec')// 服务器 -> 客户端: Finishedconst serverFinished ='encrypted_finished_message'// 3. 握手完成,开始加密通信 console.log('握手完成,开始加密通信')return{ masterSecret,clientRandom: clientHello.random,serverRandom: serverHello.random }}

TLS 1.3握手过程(更安全高效)

// TLS 1.3握手(1-RTT模式)functiontls13Handshake(){ console.log('=== TLS 1.3握手过程 ===')// TLS 1.3删除了不安全的加密套件和特性// 只支持前向安全的密钥交换算法(如ECDHE)// 客户端 -> 服务器: Client Helloconst clientHello ={version:'TLS 1.3',random:'client_random',cipherSuites:['TLS_AES_128_GCM_SHA256','TLS_CHACHA20_POLY1305_SHA256'],extensions:{supported_versions:'TLS 1.3',key_share:'客户端的密钥交换参数',signature_algorithms:'支持的签名算法'}}// 服务器 -> 客户端: Server Helloconst serverHello ={version:'TLS 1.3',random:'server_random',cipherSuite:'TLS_AES_128_GCM_SHA256',extensions:{key_share:'服务器的密钥交换参数'}}// 服务器 -> 客户端: Encrypted Extensions// 服务器 -> 客户端: Certificate(可选)// 服务器 -> 客户端: Certificate Verify(可选)// 服务器 -> 客户端: Finished// 客户端 -> 服务器: Finished console.log('TLS 1.3握手完成(1个RTT)')return{version:'TLS 1.3',cipher:'AES-128-GCM',forwardSecrecy:true}}

HTTPS握手中的关键概念

const httpsConcepts ={// 1. 证书验证certificateVerification:{purpose:'验证服务器身份',process:['验证证书链完整性','检查证书是否在有效期内','检查域名是否匹配','检查是否被吊销(CRL/OCSP)']},// 2. 密钥交换算法keyExchange:{RSA:{description:'服务器证书的公钥加密预主密钥',drawback:'不支持前向保密'},DiffieHellman:{description:'双方交换参数生成共享密钥',advantage:'支持前向保密'},ECDHE:{description:'椭圆曲线版本的DH',advantage:'更安全,性能更好'}},// 3. 对称加密算法symmetricCiphers:{AES:{modes:['GCM','CBC','CTR'],keyLength:[128,256]},ChaCha20:'Google开发的流密码,移动设备性能好'},// 4. 哈希算法hashAlgorithms:{SHA256:'目前最常用',SHA384:'更高安全性',SHA512:'最高安全性'},// 5. 前向保密forwardSecrecy:{definition:'即使服务器私钥泄露,过去的通信也无法被解密',implementation:'使用DHE或ECDHE密钥交换'}}

实际抓包分析

// 使用Node.js模拟HTTPS连接const https =require('https')const tls =require('tls')// 创建HTTPS服务器const options ={key:'服务器私钥',cert:'服务器证书',ciphers:['ECDHE-RSA-AES128-GCM-SHA256','ECDHE-ECDSA-AES128-GCM-SHA256'].join(':'),honorCipherOrder:true}// 客户端连接functionconnectToHttpsServer(){const req = https.request({hostname:'example.com',port:443,path:'/',method:'GET',// TLS选项secureProtocol:'TLSv1_2_method',ciphers:'ECDHE-RSA-AES128-GCM-SHA256',rejectUnauthorized:true// 验证服务器证书},(res)=>{ console.log('状态码:', res.statusCode) console.log('TLS版本:', res.socket.getProtocol()) console.log('加密套件:', res.socket.getCipher())}) req.on('error',(err)=>{ console.error('HTTPS请求失败:', err.message)}) req.end()}// 获取证书信息functiongetCertificateInfo(hostname){const socket = tls.connect(443, hostname,{servername: hostname },()=>{const cert = socket.getPeerCertificate() console.log('颁发者:', cert.issuer) console.log('有效期:', cert.valid_from,'至', cert.valid_to) console.log('签名算法:', cert.signatureAlgorithm) socket.destroy()})}

16. 用js实现二叉树的定义和基本操作

二叉树节点定义

classTreeNode{constructor(value){this.value = value this.left =null// 左子树this.right =null// 右子树this.parent =null// 父节点(可选)}}

二叉树类实现

classBinaryTree{constructor(){this.root =null// 根节点this.size =0// 节点数量}// 插入节点insert(value){const newNode =newTreeNode(value)if(this.root ===null){this.root = newNode this.size =1return newNode }// 使用队列进行层级插入(创建完全二叉树)const queue =[this.root]while(queue.length >0){const currentNode = queue.shift()if(currentNode.left ===null){ currentNode.left = newNode newNode.parent = currentNode break}elseif(currentNode.right ===null){ currentNode.right = newNode newNode.parent = currentNode break}else{ queue.push(currentNode.left, currentNode.right)}}this.size++return newNode }// 二叉搜索树插入insertBST(value){const newNode =newTreeNode(value)if(this.root ===null){this.root = newNode this.size =1return newNode }let currentNode =this.root let parent =nullwhile(currentNode !==null){ parent = currentNode if(value < currentNode.value){ currentNode = currentNode.left }elseif(value > currentNode.value){ currentNode = currentNode.right }else{// 值已存在,根据需求处理 console.log('值已存在:', value)return currentNode }} newNode.parent = parent if(value < parent.value){ parent.left = newNode }else{ parent.right = newNode }this.size++return newNode }// 查找节点find(value){returnthis._findNode(this.root, value)}_findNode(node, value){if(node ===null)returnnullif(node.value === value)return node const leftResult =this._findNode(node.left, value)if(leftResult)return leftResult returnthis._findNode(node.right, value)}// 删除节点remove(value){const node =this.find(value)if(!node)returnfalse// 情况1:叶子节点if(node.left ===null&& node.right ===null){this._replaceNode(node,null)}// 情况2:只有一个子节点elseif(node.left ===null){this._replaceNode(node, node.right)}elseif(node.right ===null){this._replaceNode(node, node.left)}// 情况3:有两个子节点else{// 找到右子树的最小节点const successor =this._findMin(node.right) node.value = successor.value this._replaceNode(successor, successor.right)}this.size--returntrue}_replaceNode(oldNode, newNode){if(oldNode.parent ===null){this.root = newNode }elseif(oldNode.parent.left === oldNode){ oldNode.parent.left = newNode }else{ oldNode.parent.right = newNode }if(newNode !==null){ newNode.parent = oldNode.parent }}_findMin(node){while(node.left !==null){ node = node.left }return node }}

遍历算法实现

classBinaryTreeTraversalextendsBinaryTree{// 前序遍历:根 -> 左 -> 右preorder(){const result =[]this._preorderRecursive(this.root, result)return result }_preorderRecursive(node, result){if(node ===null)return result.push(node.value)// 访问根节点this._preorderRecursive(node.left, result)// 遍历左子树this._preorderRecursive(node.right, result)// 遍历右子树}// 前序遍历(迭代)preorderIterative(){if(this.root ===null)return[]const result =[]const stack =[this.root]while(stack.length >0){const node = stack.pop() result.push(node.value)// 先右后左,保证左子树先被访问if(node.right !==null) stack.push(node.right)if(node.left !==null) stack.push(node.left)}return result }// 中序遍历:左 -> 根 -> 右inorder(){const result =[]this._inorderRecursive(this.root, result)return result }_inorderRecursive(node, result){if(node ===null)returnthis._inorderRecursive(node.left, result)// 遍历左子树 result.push(node.value)// 访问根节点this._inorderRecursive(node.right, result)// 遍历右子树}// 中序遍历(迭代)inorderIterative(){const result =[]const stack =[]let currentNode =this.root while(currentNode !==null|| stack.length >0){// 遍历到最左边的节点while(currentNode !==null){ stack.push(currentNode) currentNode = currentNode.left } currentNode = stack.pop() result.push(currentNode.value) currentNode = currentNode.right }return result }// 后序遍历:左 -> 右 -> 根postorder(){const result =[]this._postorderRecursive(this.root, result)return result }_postorderRecursive(node, result){if(node ===null)returnthis._postorderRecursive(node.left, result)// 遍历左子树this._postorderRecursive(node.right, result)// 遍历右子树 result.push(node.value)// 访问根节点}// 后序遍历(迭代)postorderIterative(){if(this.root ===null)return[]const result =[]const stack1 =[this.root]const stack2 =[]while(stack1.length >0){const node = stack1.pop() stack2.push(node)if(node.left !==null) stack1.push(node.left)if(node.right !==null) stack1.push(node.right)}while(stack2.length >0){ result.push(stack2.pop().value)}return result }// 层序遍历(广度优先)levelOrder(){if(this.root ===null)return[]const result =[]const queue =[this.root]while(queue.length >0){const levelSize = queue.length const currentLevel =[]for(let i =0; i < levelSize; i++){const node = queue.shift() currentLevel.push(node.value)if(node.left !==null) queue.push(node.left)if(node.right !==null) queue.push(node.right)} result.push(currentLevel)}return result }}

二叉树工具方法

classBinaryTreeUtilsextendsBinaryTreeTraversal{// 获取树的高度getHeight(){returnthis._calculateHeight(this.root)}_calculateHeight(node){if(node ===null)return0const leftHeight =this._calculateHeight(node.left)const rightHeight =this._calculateHeight(node.right)return Math.max(leftHeight, rightHeight)+1}// 判断是否是完全二叉树isComplete(){if(this.root ===null)returntrueconst queue =[this.root]let hasNullChild =falsewhile(queue.length >0){const node = queue.shift()if(node.left !==null){if(hasNullChild)returnfalse queue.push(node.left)}else{ hasNullChild =true}if(node.right !==null){if(hasNullChild)returnfalse queue.push(node.right)}else{ hasNullChild =true}}returntrue}// 判断是否是二叉搜索树isBST(){returnthis._isBSTRecursive(this.root,-Infinity,Infinity)}_isBSTRecursive(node, min, max){if(node ===null)returntrueif(node.value <= min || node.value >= max){returnfalse}returnthis._isBSTRecursive(node.left, min, node.value)&&this._isBSTRecursive(node.right, node.value, max)}// 查找最低公共祖先findLCA(value1, value2){const node1 =this.find(value1)const node2 =this.find(value2)if(!node1 ||!node2)returnnullreturnthis._findLCARecursive(this.root, node1, node2)}_findLCARecursive(root, p, q){if(root ===null|| root === p || root === q){return root }const left =this._findLCARecursive(root.left, p, q)const right =this._findLCARecursive(root.right, p, q)if(left !==null&& right !==null){return root }return left !==null? left : right }// 序列化和反序列化serialize(){returnthis._serializeRecursive(this.root)}_serializeRecursive(node){if(node ===null)return'null'const left =this._serializeRecursive(node.left)const right =this._serializeRecursive(node.right)return`${node.value},${left},${right}`}deserialize(data){const values = data.split(',')let index =0this.root =this._deserializeRecursive(values)returnthisfunction_deserializeRecursive(values){if(index >= values.length || values[index]==='null'){ index++returnnull}const node =newTreeNode(parseInt(values[index++])) node.left =_deserializeRecursive(values) node.right =_deserializeRecursive(values)return node }}}

使用示例

// 创建二叉树const tree =newBinaryTreeUtils()// 插入节点 tree.insertBST(5) tree.insertBST(3) tree.insertBST(7) tree.insertBST(2) tree.insertBST(4) tree.insertBST(6) tree.insertBST(8) console.log('前序遍历:', tree.preorder())// [5, 3, 2, 4, 7, 6, 8] console.log('中序遍历:', tree.inorder())// [2, 3, 4, 5, 6, 7, 8] console.log('后序遍历:', tree.postorder())// [2, 4, 3, 6, 8, 7, 5] console.log('层序遍历:', tree.levelOrder())// [[5], [3, 7], [2, 4, 6, 8]] console.log('树的高度:', tree.getHeight())// 3 console.log('是否是BST:', tree.isBST())// true console.log('是否完全二叉树:', tree.isComplete())// true// 序列化const serialized = tree.serialize() console.log('序列化:', serialized)// 反序列化const newTree =newBinaryTreeUtils() newTree.deserialize(serialized) console.log('反序列化后的中序遍历:', newTree.inorder())

17. 怎么预防用户快速连续点击,造成数据多次提交?

防抖(Debounce)

// 防抖函数:在事件被触发n秒后再执行,如果在这n秒内又被触发,则重新计时functiondebounce(func, wait, immediate =false){let timeout =nulllet result returnfunction(...args){const context =this// 如果已经设置了定时器,清除它if(timeout)clearTimeout(timeout)if(immediate){// 立即执行版本const callNow =!timeout timeout =setTimeout(()=>{ timeout =null}, wait)if(callNow){ result =func.apply(context, args)}}else{// 延迟执行版本 timeout =setTimeout(()=>{func.apply(context, args)}, wait)}return result }}// 使用示例const handleSubmit =debounce(function(formData){ console.log('提交数据:', formData)// 实际提交逻辑returnfetch('/api/submit',{method:'POST',body:JSON.stringify(formData)})},1000)// 1秒内多次点击只执行一次// 按钮点击事件 document.getElementById('submitBtn').addEventListener('click',()=>{const formData =collectFormData()handleSubmit(formData)})

节流(Throttle)

// 节流函数:在一定时间内只执行一次functionthrottle(func, wait, options ={}){let timeout =nulllet previous =0const{ leading =true, trailing =true}= options returnfunction(...args){const context =thisconst now = Date.now()if(!previous &&!leading){ previous = now }const remaining = wait -(now - previous)if(remaining <=0|| remaining > wait){if(timeout){clearTimeout(timeout) timeout =null} previous = now func.apply(context, args)}elseif(!timeout && trailing){ timeout =setTimeout(()=>{ previous = leading ? Date.now():0 timeout =nullfunc.apply(context, args)}, remaining)}}}// 使用示例:滚动加载更多const handleScroll =throttle(function(){if(isNearBottom()){loadMoreData()}},200)// 每200毫秒最多执行一次 window.addEventListener('scroll', handleScroll)

按钮禁用方案

// 方案1:简单禁用functionsimpleDisableButton(button, duration =2000){ button.disabled =true button.textContent ='提交中...'setTimeout(()=>{ button.disabled =false button.textContent ='提交'}, duration)}// 方案2:Promise感知的禁用asyncfunctionsubmitWithDisable(button, submitFunction){const originalText = button.textContent try{ button.disabled =true button.textContent ='提交中...'awaitsubmitFunction() button.textContent ='提交成功'setTimeout(()=>{ button.textContent = originalText button.disabled =false},1500)}catch(error){ button.textContent ='提交失败,重试' button.disabled =falsethrow error }}// 使用 document.getElementById('submitBtn').addEventListener('click',async()=>{awaitsubmitWithDisable(this,async()=>{awaitfetch('/api/submit',{method:'POST',body:JSON.stringify(formData)})})})

请求标识方案

// 方案1:请求ID标识classRequestManager{constructor(){this.pendingRequests =newMap()}asyncexecute(key, requestFunction){// 如果已经有相同的请求在处理中,直接返回它的Promiseif(this.pendingRequests.has(key)){ console.log('检测到重复请求,使用缓存结果')returnthis.pendingRequests.get(key)}try{const promise =requestFunction()this.pendingRequests.set(key, promise)const result =await promise return result }finally{this.pendingRequests.delete(key)}}cancel(key){if(this.pendingRequests.has(key)){// 这里可以实际取消请求(如使用AbortController)this.pendingRequests.delete(key)}}}// 使用const requestManager =newRequestManager()asyncfunctionsubmitForm(formData){const requestKey =`submit_${JSON.stringify(formData)}`return requestManager.execute(requestKey,async()=>{const response =awaitfetch('/api/submit',{method:'POST',body:JSON.stringify(formData)})return response.json()})}// 方案2:AbortController取消重复请求classRequestCanceler{constructor(){this.controllers =newMap()}asyncfetchWithCancel(key, url, options ={}){// 取消之前的相同请求if(this.controllers.has(key)){this.controllers.get(key).abort()}const controller =newAbortController()this.controllers.set(key, controller)try{const response =awaitfetch(url,{...options,signal: controller.signal })return response }finally{this.controllers.delete(key)}}}

综合解决方案

// 完整的防重复提交方案classAntiResubmit{constructor(options ={}){this.options ={debounceTime:1000,// 防抖时间disableTime:2000,// 禁用时间storageKey:'submit_lock',// 本地存储key...options }this.submitLock =false}// 方法1:防抖 + 按钮禁用debounceSubmit(button, submitFunc){let timer =nullreturnasync(...args)=>{if(this.submitLock){ console.log('提交锁定中,请稍候')return}// 清除之前的定时器if(timer)clearTimeout(timer)// 设置防抖 timer =setTimeout(async()=>{try{this.submitLock =truethis.disableButton(button,true)awaitsubmitFunc(...args)// 成功后的禁用时间setTimeout(()=>{this.submitLock =falsethis.disableButton(button,false)},this.options.disableTime)}catch(error){this.submitLock =falsethis.disableButton(button,false)throw error }},this.options.debounceTime)}}disableButton(button, disabled){ button.disabled = disabled button.style.opacity = disabled ?0.6:1if(disabled){const originalText = button.textContent button.textContent ='提交中...' button.setAttribute('data-original-text', originalText)}else{const originalText = button.getAttribute('data-original-text')if(originalText){ button.textContent = originalText }}}// 方法2:本地存储锁(防止刷新后重复提交)withStorageLock(key, func){returnasync(...args)=>{const lockKey =`${this.options.storageKey}_${key}`const lockTime = localStorage.getItem(lockKey)if(lockTime && Date.now()-parseInt(lockTime)<this.options.disableTime){thrownewError('操作太频繁,请稍后再试')}try{ localStorage.setItem(lockKey, Date.now().toString())const result =awaitfunc(...args)return result }finally{setTimeout(()=>{ localStorage.removeItem(lockKey)},this.options.disableTime)}}}// 方法3:请求令牌(token)asyncwithRequestToken(submitFunc){const token =this.generateToken()this.currentToken = token try{const result =awaitsubmitFunc(token)// 验证token是否仍然有效if(this.currentToken === token){return result }else{thrownewError('请求已过期')}}finally{this.currentToken =null}}generateToken(){return Date.now().toString(36)+ Math.random().toString(36).substr(2)}}// 使用示例const antiResubmit =newAntiResubmit({debounceTime:1000,disableTime:3000})const submitButton = document.getElementById('submitBtn')consthandleRealSubmit=async(formData)=>{const response =awaitfetch('/api/submit',{method:'POST',body:JSON.stringify(formData)})return response.json()}// 绑定防重复提交处理 submitButton.addEventListener('click', antiResubmit.debounceSubmit( submitButton, handleRealSubmit ))

18. 请简述==的机制

== 的类型转换规则

// == 的转换规则优先级/* 1. 如果类型相同,直接比较值 2. null == undefined 返回 true 3. 字符串和数字比较:将字符串转为数字 4. 布尔值和其他类型比较:将布尔值转为数字(true->1, false->0) 5. 对象和原始值比较:调用对象的valueOf()或toString()方法,将对象转为原始值 6. 如果转换后类型仍然不同,按上述规则继续转换 */

具体转换示例

// 示例1:类型相同,直接比较 console.log(1==1)// true console.log('a'=='a')// true console.log(true==true)// true console.log(null==null)// true console.log(undefined==undefined)// true// 示例2:null和undefined的特殊情况 console.log(null==undefined)// true console.log(null==null)// true console.log(undefined==undefined)// true console.log(null==0)// false console.log(undefined==0)// false// 示例3:字符串和数字比较 console.log('123'==123)// true,字符串转数字 console.log(''==0)// true,空字符串转数字0 console.log(' '==0)// true,空白字符串转数字0 console.log('abc'==0)// false,'abc'转数字是NaN// 示例4:布尔值和其他类型比较 console.log(true==1)// true,true转数字1 console.log(false==0)// true,false转数字0 console.log(true=='1')// true,布尔转数字,字符串转数字 console.log(false=='')// true,false转0,空字符串转0 console.log(true=='true')// false,true转1,'true'转数字是NaN// 示例5:对象和原始值比较 console.log([]==0)// true,[]转字符串'',''转数字0 console.log([1]==1)// true,[1]转字符串'1','1'转数字1 console.log([1,2]=='1,2')// true,[1,2]转字符串'1,2' console.log({}=='[object Object]')// true,{}转字符串'[object Object]'const obj ={valueOf(){return42},toString(){return'99'}} console.log(obj ==42)// true,优先调用valueOf()

== 的完整转换流程

// 模拟 == 的实现逻辑functionabstractEqualityComparison(x, y){// 1. 如果类型相同if(typeof x ===typeof y){// 特殊情况:NaNif(Number.isNaN(x)&& Number.isNaN(y))returnfalse// 正常比较return x === y }// 2. null和undefined比较if(x ==null&& y ==null)returntrueif((x ===null&& y ===undefined)||(x ===undefined&& y ===null)){returntrue}// 3. 字符串和数字比较if(typeof x ==='string'&&typeof y ==='number'){returnstringToNumber(x)=== y }if(typeof x ==='number'&&typeof y ==='string'){return x ===stringToNumber(y)}// 4. 布尔值比较if(typeof x ==='boolean'){returnabstractEqualityComparison(booleanToNumber(x), y)}if(typeof y ==='boolean'){returnabstractEqualityComparison(x,booleanToNumber(y))}// 5. 对象和原始值比较if((typeof x ==='object'||typeof x ==='function')&&(typeof y ==='string'||typeof y ==='number'||typeof y ==='symbol')){const primitive =toPrimitive(x)returnabstractEqualityComparison(primitive, y)}if((typeof y ==='object'||typeof y ==='function')&&(typeof x ==='string'||typeof x ==='number'||typeof x ==='symbol')){const primitive =toPrimitive(y)returnabstractEqualityComparison(x, primitive)}returnfalse}// 辅助函数functionstringToNumber(str){const num =Number(str)return Number.isNaN(num)?NaN: num }functionbooleanToNumber(bool){return bool ?1:0}functiontoPrimitive(obj){// 先尝试valueOf,再尝试toStringif(typeof obj.valueOf ==='function'){const value = obj.valueOf()if(value !== obj)return value }if(typeof obj.toString ==='function'){const value = obj.toString()if(value !== obj)return value }thrownewTypeError('Cannot convert object to primitive value')}

特殊情况和陷阱

// 陷阱1:数组的奇怪比较 console.log([]==![])// true// 解析:![] 先转布尔值,[]是truthy,![]是false// false转数字0,[]转字符串'',''转数字0// 所以 0 == 0 为 true console.log([]==[])// false// 对象比较的是引用,不是值// 陷阱2:对象的valueOf和toStringconst trickyObj ={valueOf:()=>1,toString:()=>'2'} console.log(trickyObj ==1)// true,优先使用valueOf console.log(trickyObj =='2')// false,不是直接调用toString// 陷阱3:Symbol的特殊性const sym =Symbol('foo') console.log(sym == sym)// true,相同Symbol console.log(sym =='foo')// false,Symbol不能隐式转换 console.log(Symbol('foo')==Symbol('foo'))// false,每个Symbol都唯一// 陷阱4:大数字的精度问题 console.log(0.1+0.2==0.3)// false,浮点数精度问题 console.log(0.1+0.2)// 0.30000000000000004

最佳实践建议

// 建议1:大多数情况下使用 ===// === 严格相等,不进行类型转换const bestPractices ={alwaysUseTripleEquals:['比较值和类型是否完全相同','避免隐式类型转换的意外','代码意图更清晰'],exceptions:[{case:'判断变量是否为null或undefined',example:'if (value == null) { /* value是null或undefined */ }'},{case:'与0比较,但允许空字符串和false',example:'if (count == 0) { /* count可能是0、false或空字符串 */ }'}],safeComparisons:` // 安全的方式 if (value === null || value === undefined) { ... } if (count === 0 || count === false || count === "") { ... } // 类型明确的比较 const num = Number(input) if (num === 42) { ... } // 布尔值明确转换 const bool = Boolean(value) if (bool === true) { ... } `}

19. 怎么做移动端的样式适配?

1. 视口配置(基础)

<!DOCTYPEhtml><html><head><!-- 关键:视口设置 --><metaname="viewport"content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"><!-- 防止电话号码自动识别为链接 --><metaname="format-detection"content="telephone=no"><!-- 防止邮箱自动识别 --><metaname="format-detection"content="email=no"><!-- iOS Safari全屏模式 --><metaname="apple-mobile-web-app-capable"content="yes"><!-- iOS状态栏样式 --><metaname="apple-mobile-web-app-status-bar-style"content="black-translucent"></head><body><!-- 页面内容 --></body></html>

2. 响应式布局方案

/* 方案1:媒体查询(Media Queries) *//* 移动端优先的媒体查询 *//* 超小屏幕(手机,小于576px) */.container{width: 100%;padding: 0 15px;margin: 0 auto;}/* 小屏幕(平板,576px以上) */@media(min-width: 576px){.container{max-width: 540px;}}/* 中等屏幕(平板横屏,768px以上) */@media(min-width: 768px){.container{max-width: 720px;}}/* 大屏幕(桌面,992px以上) */@media(min-width: 992px){.container{max-width: 960px;}}/* 超大屏幕(大桌面,1200px以上) */@media(min-width: 1200px){.container{max-width: 1140px;}}/* 方案2:Flexbox弹性布局 */.flex-container{display: flex;flex-wrap: wrap;justify-content: space-between;}.flex-item{/* 移动端:一行一个 */flex: 0 0 100%;margin-bottom: 20px;}/* 平板:一行两个 */@media(min-width: 768px){.flex-item{flex: 0 0 calc(50% - 10px);}}/* 桌面:一行四个 */@media(min-width: 992px){.flex-item{flex: 0 0 calc(25% - 15px);}}/* 方案3:Grid网格布局 */.grid-container{display: grid;grid-template-columns: 1fr;gap: 20px;}@media(min-width: 768px){.grid-container{grid-template-columns:repeat(2, 1fr);}}@media(min-width: 992px){.grid-container{grid-template-columns:repeat(4, 1fr);}}

3. 相对单位的使用

/* 相对单位适配方案 *//* 1. rem适配(推荐) */html{/* 设置根元素字体大小 */font-size: 16px;/* 默认 */}@media(max-width: 375px){html{font-size: 14px;/* iPhone 6/7/8 */}}@media(max-width: 320px){html{font-size: 12px;/* iPhone 5 */}}/* 使用rem */.container{width: 20rem;/* 20 * 根字体大小 */padding: 1rem;margin: 0 auto;}/* 2. vw/vh适配(视口单位) *//* 基于视口宽度 */.box{width: 50vw;/* 视口宽度的50% */height: 50vh;/* 视口高度的50% */font-size: 4vw;/* 字体大小随视口变化 */}/* 3. 百分比布局 */.parent{width: 100%;}.child{width: 50%;/* 父元素宽度的50% */padding: 5%;/* 父元素宽度的5% */}/* 4. 组合使用 */.responsive-box{/* 最小200px,最大父元素的80%,随视口变化 */width:max(200px,min(80%, 400px));/* 使用clamp函数 */font-size:clamp(14px, 2vw, 20px);/* 最小14px,最大20px,随视口变化 */}

4. 移动端特殊处理

/* 1. 1像素边框问题 */.border-1px{position: relative;}.border-1px::after{content:'';position: absolute;bottom: 0;left: 0;right: 0;height: 1px;background: #e0e0e0;transform:scaleY(0.5);transform-origin: 0 0;}/* 2. 防止点击高亮 */.no-tap-highlight{-webkit-tap-highlight-color: transparent;}/* 3. 优化滚动体验 */.scroll-container{-webkit-overflow-scrolling: touch;/* iOS惯性滚动 */overflow-scrolling: touch;}/* 4. 禁止文本选择 */.no-select{-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;}/* 5. 点击区域扩大 */.touch-area{padding: 12px;/* 扩大点击区域 */min-height: 44px;/* iOS推荐的最小点击高度 */}/* 6. 防止图片被拖动 */.no-drag{-webkit-user-drag: none;user-drag: none;}

5. 动态REM方案(JS辅助)

// 动态设置rem基准值(function(){const baseSize =16// 基准字体大小const designWidth =375// 设计稿宽度(iPhone 6/7/8)functionsetRem(){const scale = document.documentElement.clientWidth / designWidth document.documentElement.style.fontSize = baseSize * Math.min(scale,2)+'px'}setRem()// 窗口大小变化时重新设置 window.addEventListener('resize', setRem) window.addEventListener('pageshow',function(e){if(e.persisted){setRem()}})})()// 或者在CSS中使用vw html { font-size:calc(100vw /375*16);/* 基于375px设计稿 */}

6. 工具类和混合宏

// 使用Sass的混合宏 @mixin respond-to($breakpoint) { @if $breakpoint == 'phone' { @media (max-width: 599px) { @content; } } @else if $breakpoint == 'tablet' { @media (min-width: 600px) and (max-width: 899px) { @content; } } @else if $breakpoint == 'desktop' { @media (min-width: 900px) { @content; } } } // 使用 .box { width: 100%; @include respond-to('tablet') { width: 50%; } @include respond-to('desktop') { width: 33.33%; } } // 工具类:显示/隐藏 .hide-on-mobile { display: none; @media (min-width: 768px) { display: block; } } .show-on-mobile { display: block; @media (min-width: 768px) { display: none; } } 

7. 移动端适配最佳实践

/* 综合最佳实践 *//* 1. 使用CSS变量定义设计系统 */:root{/* 间距系统 */--space-xs: 4px;--space-sm: 8px;--space-md: 16px;--space-lg: 24px;--space-xl: 32px;/* 字体大小 */--text-xs: 12px;--text-sm: 14px;--text-md: 16px;--text-lg: 18px;--text-xl: 20px;/* 断点 */--breakpoint-sm: 576px;--breakpoint-md: 768px;--breakpoint-lg: 992px;--breakpoint-xl: 1200px;}/* 2. 容器组件 */.container{width: 100%;padding-left:var(--space-md);padding-right:var(--space-md);margin-left: auto;margin-right: auto;@media(min-width: 768px){max-width: 720px;padding-left: 0;padding-right: 0;}@media(min-width: 992px){max-width: 960px;}@media(min-width: 1200px){max-width: 1140px;}}/* 3. 响应式图片 */.responsive-image{max-width: 100%;height: auto;display: block;}/* 4. 触摸友好的交互 */.touch-button{padding:var(--space-md)var(--space-lg);min-height: 44px;/* iOS推荐的最小触摸高度 */font-size:var(--text-md);border-radius: 8px;background: #007aff;color: white;border: none;cursor: pointer;/* 禁用状态 */&:disabled{opacity: 0.5;cursor: not-allowed;}/* 激活状态 */&:active{transform:scale(0.98);}}

20. JavaScript中如何取消请求

1. XMLHttpRequest的取消

// 传统XMLHttpRequest取消functionmakeXHRRequest(){const xhr =newXMLHttpRequest() xhr.open('GET','/api/data',true) xhr.onreadystatechange=function(){if(xhr.readyState ===4){if(xhr.status ===200){ console.log('请求成功:', xhr.responseText)}else{ console.log('请求失败:', xhr.status)}}} xhr.send()// 返回取消函数returnfunctioncancel(){if(xhr.readyState !==4){ xhr.abort() console.log('请求已取消')}}}// 使用const cancelXHR =makeXHRRequest()// 5秒后取消请求setTimeout(()=>{cancelXHR()},5000)

2. Fetch API + AbortController

// 使用AbortController取消fetch请求asyncfunctionmakeFetchRequest(){// 创建AbortController实例const controller =newAbortController()const signal = controller.signal try{// 将signal传递给fetchconst response =awaitfetch('/api/data',{method:'GET',signal: signal // 关键:传入signal})if(!response.ok){thrownewError(`HTTP error! status: ${response.status}`)}const data =await response.json() console.log('请求成功:', data)return data }catch(error){// 检查是否是取消导致的错误if(error.name ==='AbortError'){ console.log('请求被取消')}else{ console.error('请求失败:', error)}throw error }}// 创建请求并获取取消函数functioncreateCancellableFetch(){const controller =newAbortController()const request =fetch('/api/data',{signal: controller.signal }).then(response=> response.json()).catch(error=>{if(error.name ==='AbortError'){ console.log('请求已取消')}throw error })return{promise: request,cancel:()=> controller.abort()}}// 使用const{ promise, cancel }=createCancellableFetch() promise.then(data=>{ console.log('数据:', data)}).catch(error=>{if(error.name !=='AbortError'){ console.error('错误:', error)}})// 3秒后取消setTimeout(()=>{cancel()},3000)

3. Axios的取消机制

// Axios取消请求的两种方式// 方式1:使用CancelToken(旧版,但仍然可用)functionmakeAxiosRequestWithCancelToken(){// 创建取消令牌源const source = axios.CancelToken.source() axios.get('/api/data',{cancelToken: source.token }).then(response=>{ console.log('请求成功:', response.data)}).catch(error=>{if(axios.isCancel(error)){ console.log('请求取消:', error.message)}else{ console.error('请求失败:', error)}})// 返回取消函数returnfunctioncancel(message ='请求取消'){ source.cancel(message)}}// 方式2:使用AbortController(新版推荐)functionmakeAxiosRequestWithAbortController(){const controller =newAbortController() axios.get('/api/data',{signal: controller.signal }).then(response=>{ console.log('请求成功:', response.data)}).catch(error=>{if(axios.isCancel(error)){ console.log('请求取消')}else{ console.error('请求失败:', error)}})returnfunctioncancel(){ controller.abort()}}// 使用示例const cancelAxios =makeAxiosRequestWithCancelToken()// 用户点击取消按钮 document.getElementById('cancelBtn').addEventListener('click',()=>{cancelAxios('用户手动取消')})

4. 请求管理器(统一管理)

classRequestManager{constructor(){this.pendingRequests =newMap()}// 添加请求addRequest(key, requestFunction, options ={}){// 如果已存在相同key的请求,先取消if(this.pendingRequests.has(key)){this.cancelRequest(key,'取消重复请求')}const controller =newAbortController()// 包装请求const request =requestFunction(controller.signal).then(response=>{this.pendingRequests.delete(key)return response }).catch(error=>{this.pendingRequests.delete(key)throw error })// 存储控制器和请求this.pendingRequests.set(key,{ controller, request,timestamp: Date.now()})// 设置超时自动取消if(options.timeout){setTimeout(()=>{if(this.pendingRequests.has(key)){this.cancelRequest(key,'请求超时')}}, options.timeout)}return request }// 取消单个请求cancelRequest(key, reason ='请求取消'){const item =this.pendingRequests.get(key)if(item){ item.controller.abort()this.pendingRequests.delete(key) console.log(`${key}: ${reason}`)}}// 取消所有请求cancelAll(reason ='取消所有请求'){for(const[key, item]ofthis.pendingRequests){ item.controller.abort() console.log(`${key}: ${reason}`)}this.pendingRequests.clear()}// 获取正在进行的请求数量getPendingCount(){returnthis.pendingRequests.size }// 清理过期的请求(可选)cleanup(expireTime =30000){const now = Date.now()for(const[key, item]ofthis.pendingRequests){if(now - item.timestamp > expireTime){this.cancelRequest(key,'请求过期')}}}}// 使用示例const requestManager =newRequestManager()// 创建可取消的fetch函数functioncreateFetch(signal){returnfetch('/api/data',{ signal }).then(response=> response.json())}// 发起请求const request1 = requestManager.addRequest('user_data',(signal)=>createFetch(signal),{timeout:10000}) request1.then(data=>{ console.log('获取到用户数据:', data)}).catch(error=>{if(error.name !=='AbortError'){ console.error('获取失败:', error)}})// 取消请求// requestManager.cancelRequest('user_data')// requestManager.cancelAll()

5. React/Vue中的请求取消

// React示例:在组件卸载时取消请求import React,{ useEffect, useState }from'react'import axios from'axios'functionDataComponent(){const[data, setData]=useState(null)const[loading, setLoading]=useState(true)useEffect(()=>{const source = axios.CancelToken.source()constfetchData=async()=>{try{setLoading(true)const response =await axios.get('/api/data',{cancelToken: source.token })setData(response.data)}catch(error){if(!axios.isCancel(error)){ console.error('获取数据失败:', error)}}finally{setLoading(false)}}fetchData()// 清理函数:组件卸载时取消请求return()=>{ source.cancel('组件卸载,取消请求')}},[])// 空依赖数组,只运行一次if(loading)return<div>加载中...</div>if(!data)return<div>暂无数据</div>return(<div>{/* 渲染数据 */}</div>)}// Vue 3示例import{ ref, onUnmounted }from'vue'import axios from'axios'exportfunctionuseFetchData(){const data =ref(null)const loading =ref(true)const error =ref(null)const controller =newAbortController()constfetchData=async()=>{try{ loading.value =trueconst response =await axios.get('/api/data',{signal: controller.signal }) data.value = response.data }catch(err){if(err.name !=='CanceledError'){ error.value = err }}finally{ loading.value =false}}fetchData()// 组件卸载时取消onUnmounted(()=>{ controller.abort()})return{ data, loading, error }}

6. 高级技巧:竞态处理

// 处理竞态条件(Race Condition)asyncfunctionfetchWithRaceProtection(url, signal){// 创建一个唯一的请求IDconst requestId =Symbol('request')// 存储当前活跃的请求let currentRequest =nullreturnasyncfunctionmakeRequest(){// 如果已经有请求在进行,取消它if(currentRequest){ currentRequest.controller.abort()}// 创建新的控制器const controller =newAbortController()const combinedSignal =(()=>{// 合并多个AbortSignalconst signals =[controller.signal]if(signal) signals.push(signal)if(signals.length ===1)return signals[0]const abortController =newAbortController()for(const s of signals){ s.addEventListener('abort',()=>{ abortController.abort()})}return abortController.signal })() currentRequest ={ controller, requestId }try{const response =awaitfetch(url,{signal: combinedSignal })// 检查是否是当前最新的请求if(currentRequest.requestId === requestId){return response.json()}// 如果不是最新请求,抛出取消错误thrownewDOMException('请求被新的请求取代','AbortError')}catch(error){// 清理当前请求if(currentRequest.requestId === requestId){ currentRequest =null}if(error.name ==='AbortError'){ console.log('请求被取消(可能是新的请求启动了)')}throw error }}}// 使用const protectedFetch =fetchWithRaceProtection('/api/data')const makeRequest =protectedFetch()// 快速连续调用多次makeRequest()// 第一次请求setTimeout(makeRequest,100)// 第二次请求会取消第一次setTimeout(makeRequest,200)// 第三次请求会取消第二次

最后,欢迎对前端岗位是否会被AI取代投票。如觉得面试题有用,评论区留言,会继续更新小中大厂常见精选面试题

Read more

C++——deque的了解和使用

C++——deque的了解和使用

目录 引言 标准库中的deque 一、deque的基本概念 二、deque的常用接口 1.deque的迭代器 2.deque的初始化 3.deque的容量操作 3.1 有效长度和容量大小 3.2 有效长度和容量操作 4.deque的访问操作 5.deque的修改操作 三、deque的应用场景 结束语 引言 在C++中,deque是STL(标准模板库)提供的一种容器类,专门用于存储各种类型的元素,并支持在两端进行快速的插入和删除操作。今天我们就试着来学习一下这一数据结构。   标准库中的deque 一、deque的基本概念 Deque是一种线性数据结构,它允许在两端进行插入和删除操作。这两端通常被称为前端(front)和后端(rear),或者端点1和端点2。Deque的灵活性在于,它既可以用作队列(FIFO,先进先出),也可以用作栈(

By Ne0inhk
C++的IO流和C++的类型转换----《Hello C++ Wrold!》(29)--(C/C++)

C++的IO流和C++的类型转换----《Hello C++ Wrold!》(29)--(C/C++)

文章目录 * 前言 * C++的类型转换 * 四种命名的强制类型转换操作符 * static_cast * reinterpret_cast * const_cast * dynamic_cast * RTTI(这个了解一下就行了) * C++的IO流 * C++文件的IO流 * stringstream 前言 在 C++ 编程体系中,类型转换与 IO 流是支撑程序数据处理与交互的两大核心环节。类型转换关乎数据在不同类型间的安全传递与运算适配,而 IO 流则负责程序与外部设备(如键盘、屏幕、文件)之间的数据输入与输出,二者共同构成了 C++ 程序实现功能、交互信息的基础框架。 C 语言中的类型转换方式虽简洁,却存在可视性差、难以追踪的问题,容易在复杂程序中引发潜在的逻辑错误。为解决这一痛点,C++ 引入了四种命名明确的强制类型转换操作符 ——static_cast、reinterpret_

By Ne0inhk
海康工业相机SDK二次开发(VS+QT+海康SDK+C++)

海康工业相机SDK二次开发(VS+QT+海康SDK+C++)

前言 工业相机在现代制造和工业自动化中扮演了至关重要的角色,尤其是在高精度、高速度检测中。海康威视工业相机以其性能稳定、图像质量高、兼容性强而受到广泛青睐。特别是搞机器视觉的小伙伴们跟海康打交道肯定不在少数,笔者在平常项目中跟海康相关人员对接也是比较多。 那么,本文将全面介绍如何基于海康工业相机的 SDK,使用 Visual Studio 和 Qt 构建上位机程序,逐步实现工业相机的图像采集、显示以及参数配置。 以下是巴斯勒相机开发 巴斯勒工业相机SDK二次开发(VS+QT+巴斯勒SDK+C++)-ZEEKLOG博客 一、海康工业相机简介 1. 工业相机的主要功能 * 图像采集:捕获高速、高清的静态或动态图像。 * 高速传输:通过 GigE 或 USB 接口将图像传输到上位机。 * 稳定运行:设计用于工业环境,具有高可靠性。 2. 海康工业相机优势 * 高分辨率:支持从 0.3MP 到

By Ne0inhk
C++ string 全面指南

C++ string 全面指南

一、模板 1. 函数模板 什么是模板呢?模板就是一个模具,只需要往这个模具里倒入不同的材料,就可以获得不同材料的铸件。 如果我们要实现一个交换函数呢?这是很容易的事情。 但是这种交换函数只能实现整型之间的交换,如果我想进行浮点数交换呢,字符型交换呢?是不是就不可以了。 虽然我们可以通过函数重载实现不同的交换函数,但是这样做太浪费时间了,没有意义。毕竟只是改变了交换函数参数的类型,代码不需要变化。所以,这种方法是有缺陷的。 1.代码复用率低。 2.可维护性差。 所以,有了函数模板,这是实现泛型编程的基础。 所谓泛型编程就是编写与类型无关的通用代码,是代码复用的一种手段。 template<typename T>就是定义了一个模板,通过一份代码就可以实现多个要求。 这里的typename也可以换成class,这两个的区别会在后面讲解。 这个就叫做函数模板,函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。 函数模板的格式:template<typename T1, typename

By Ne0inhk