其他✅
ResizeObserver 作用是什么
答案
核心概念
ResizeObserver
是一种原生 Web API,用于高效、精准地监听 DOM 元素尺寸的变化(宽高等),无论变化来源于内容、样式还是窗口调整。它能在元素尺寸发生变化时自动回调,适合响应式布局、动态组件等场景。
详细解释
- 传统的
window.resize
事件只能监听窗口变化,无法直接感知单个元素的尺寸变化,且需轮询或定时器,效率低。 ResizeObserver
通过回调机制,能实时捕获元素尺寸变化,避免性能浪费和延迟。- 支持同时监听多个元素,回调参数为变化元素的列表,可批量处理。
代码示例
const target = document.querySelector('.resizable')
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
// entry.contentRect 包含新尺寸
console.log('size:', entry.contentRect.width, entry.contentRect.height)
}
})
observer.observe(target)
常见误区
- 只监听元素本身的尺寸变化,不会响应子元素内容变化,除非影响父元素尺寸。
- 回调是异步批量触发,避免在回调中频繁操作 DOM。
- 兼容性较好,但 IE 不支持,需注意降级处理。
推荐在响应式组件、图表自适应、弹窗等场景优先使用 ResizeObserver
,替代定时器和 resize
事件监听。
延伸阅读
IntersectionObserver api?
答案
核心概念:
IntersectionObserver
是一个高性能的 Web API,用于异步监听元素与视口或指定根元素的交叉状态变化。它避免了频繁的滚动事件监听和DOM查询,适用于懒加载、无限滚动、广告曝光统计等场景。当目标元素的可见性发生变化时,会触发回调函数,提供详细的交叉信息。
实际示例:
// 1. 基本用法:懒加载图片
function setupLazyLoading () {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src // 加载真实图片
img.classList.remove('lazy')
observer.unobserve(img) // 停止观察已加载的图片
}
})
})
// 观察所有懒加载图片
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img)
})
}
// 2. 无限滚动实现
class InfiniteScroll {
constructor (container, loadMore) {
this.container = container
this.loadMore = loadMore
this.isLoading = false
this.setupObserver()
}
setupObserver () {
const options = {
root: null,
rootMargin: '100px', // 提前100px触发
threshold: 0
}
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !this.isLoading) {
this.handleLoadMore()
}
})
}, options)
// 创建观察目标(底部加载指示器)
this.sentinel = document.createElement('div')
this.container.appendChild(this.sentinel)
this.observer.observe(this.sentinel)
}
async handleLoadMore () {
this.isLoading = true
try {
await this.loadMore()
} finally {
this.isLoading = false
}
}
}
// 3. 元素动画触发
function setupScrollAnimations () {
const animationObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in')
} else {
entry.target.classList.remove('animate-in')
}
})
}, {
threshold: 0.3, // 30%可见时触发
rootMargin: '-50px' // 视口缩小50px
})
document.querySelectorAll('.animate-on-scroll').forEach(el => {
animationObserver.observe(el)
})
}
// 4. 广告曝光统计
function trackAdViews () {
const adObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const adId = entry.target.dataset.adId
const visibleRatio = entry.intersectionRatio
// 50%以上可见才算有效曝光
if (visibleRatio > 0.5) {
console.log(`广告 ${adId} 曝光,可见度: ${(visibleRatio * 100).toFixed(1)}%`)
// 发送曝光统计
fetch('/api/ad-view', {
method: 'POST',
body: JSON.stringify({ adId, visibleRatio })
})
// 停止观察已统计的广告
adObserver.unobserve(entry.target)
}
}
})
}, {
threshold: [0.5, 1.0] // 50%和100%可见时都触发
})
document.querySelectorAll('.ad-container').forEach(ad => {
adObserver.observe(ad)
})
}
// 使用示例
setupLazyLoading()
setupScrollAnimations()
trackAdViews()
// 无限滚动示例
const infiniteScroll = new InfiniteScroll(
document.getElementById('content'),
async () => {
const response = await fetch('/api/more-content')
const html = await response.text()
document.getElementById('content').insertAdjacentHTML('beforeend', html)
}
)
面试官视角:
要点清单:
- 理解IntersectionObserver的性能优势
- 掌握基本配置选项(root、rootMargin、threshold)
- 了解典型应用场景的实现方式
加分项:
- 提及与传统scroll事件监听的性能对比
- 了解多个threshold值的使用技巧
- 知道如何处理浏览器兼容性问题
常见失误:
- 不理解异步回调的特性
- 忽略unobserve的重要性导致内存泄漏
- threshold配置不当影响触发时机
延伸阅读:
- Intersection Observer API - MDN — 完整API文档
- Intersection Observer polyfill — 兼容性解决方案
- Lazy loading performance guide — 懒加载最佳实践
MutationObserver
答案
核心概念:
MutationObserver
是一个强大的 Web API,用于监听 DOM 树的变化并异步响应这些变化。它可以观察元素的添加、删除、属性变化、文本内容修改等,是现代前端开发中处理动态DOM变化的核心工具。相比传统的DOM事件,它提供了更全面和高效的DOM变化监听机制。
实际示例:
// 1. 基本用法:监听DOM变化
function setupDOMWatcher () {
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
switch (mutation.type) {
case 'childList':
console.log('子节点发生变化:', mutation)
if (mutation.addedNodes.length > 0) {
console.log('新增节点:', mutation.addedNodes)
}
if (mutation.removedNodes.length > 0) {
console.log('删除节点:', mutation.removedNodes)
}
break
case 'attributes':
console.log('属性变化:', {
target: mutation.target,
attributeName: mutation.attributeName,
oldValue: mutation.oldValue,
newValue: mutation.target.getAttribute(mutation.attributeName)
})
break
case 'characterData':
console.log('文本内容变化:', mutation)
break
}
})
})
// 配置观察选项
const config = {
childList: true, // 观察子节点变化
attributes: true, // 观察属性变化
attributeOldValue: true, // 记录属性旧值
characterData: true, // 观察文本内容变化
subtree: true // 观察所有后代节点
}
// 开始观察
observer.observe(document.body, config)
return observer
}
// 2. 实际应用:单页应用路由监听
class SPARouteWatcher {
constructor () {
this.setupObserver()
}
setupObserver () {
this.observer = new MutationObserver((mutations) => {
let titleChanged = false
let urlChanged = false
mutations.forEach(mutation => {
if (mutation.type === 'childList' &&
mutation.target === document.head) {
// 检查title变化
const titleEl = document.querySelector('title')
if (titleEl && titleEl !== this.lastTitle) {
titleChanged = true
this.lastTitle = titleEl
}
}
})
// 检查URL变化
if (this.lastUrl !== window.location.href) {
urlChanged = true
this.lastUrl = window.location.href
}
if (titleChanged || urlChanged) {
this.handleRouteChange()
}
})
this.observer.observe(document.head, {
childList: true,
subtree: true
})
this.lastUrl = window.location.href
}
handleRouteChange () {
// 发送页面浏览统计
console.log('路由变化:', {
url: window.location.href,
title: document.title,
timestamp: Date.now()
})
// 执行页面级的初始化逻辑
this.initPageFeatures()
}
initPageFeatures () {
// 重新初始化页面功能
// 如:埋点、懒加载、动画等
}
}
// 3. 性能监控:监听DOM性能指标
class DOMPerformanceMonitor {
constructor () {
this.mutationCount = 0
this.largeChangeThreshold = 50
this.setupObserver()
}
setupObserver () {
this.observer = new MutationObserver((mutations) => {
this.mutationCount += mutations.length
// 检查是否有大量DOM变化
const largeMutations = mutations.filter(m =>
m.addedNodes.length > 10 || m.removedNodes.length > 10
)
if (largeMutations.length > 0) {
console.warn('检测到大量DOM变化,可能影响性能:', largeMutations)
this.reportPerformanceIssue(largeMutations)
}
// 定期重置计数器
this.throttledReset()
})
this.observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false // 不监听属性变化,减少噪音
})
}
throttledReset = (() => {
let timeout
return () => {
clearTimeout(timeout)
timeout = setTimeout(() => {
if (this.mutationCount > this.largeChangeThreshold) {
console.log(`在过去的时间段内发生了 ${this.mutationCount} 次DOM变化`)
}
this.mutationCount = 0
}, 5000)
}
})()
reportPerformanceIssue (mutations) {
// 发送性能警告到监控系统
fetch('/api/performance-warning', {
method: 'POST',
body: JSON.stringify({
type: 'excessive-dom-mutations',
count: mutations.length,
timestamp: Date.now(),
url: window.location.href
})
})
}
disconnect () {
this.observer.disconnect()
}
}
// 4. 第三方脚本监控
function monitorThirdPartyScripts () {
const scriptObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT') {
const src = node.src || 'inline'
console.log('检测到新的脚本:', src)
// 检查是否为未授权的第三方脚本
if (src && !isAuthorizedScript(src)) {
console.warn('未授权的第三方脚本:', src)
// 可以选择移除或报告
reportUnauthorizedScript(src)
}
}
})
})
})
scriptObserver.observe(document.documentElement, {
childList: true,
subtree: true
})
}
function isAuthorizedScript (src) {
const authorizedDomains = [
'cdn.example.com',
'analytics.google.com',
'static.example.com'
]
return authorizedDomains.some(domain => src.includes(domain))
}
// 使用示例
const domWatcher = setupDOMWatcher()
const routeWatcher = new SPARouteWatcher()
const performanceMonitor = new DOMPerformanceMonitor()
monitorThirdPartyScripts()
面试官视角:
要点清单:
- 理解MutationObserver的异步特性和性能优势
- 掌握不同观察类型的配置和使用场景
- 了解如何合理控制观察范围避免性能问题
加分项:
- 提及在单页应用中的路由监听应用
- 了解性能监控和安全监控的实际用途
- 知道何时应该断开观察以避免内存泄漏
常见失误:
- 过度观察导致性能问题
- 忘记调用disconnect()造成内存泄漏
- 不理解异步回调的批量处理特性
延伸阅读:
- MutationObserver - MDN — 完整API文档
- DOM performance monitoring — DOM性能监控指南
- Security monitoring with MutationObserver — 安全监控应用
PerformanceObserver 如何测量页面性能
答案
核心概念:
PerformanceObserver
是 Web Performance API 的核心组件,提供异步、高效的性能数据收集机制。它可以监听各种性能条目类型(如导航、资源、绘制、交互等),实时收集关键性能指标。相比轮询 performance.getEntries()
,它提供事件驱动的数据收集方式,减少性能开销并确保数据的及时性。
实际示例:
// 1. 核心Web性能指标监控
class WebVitalsMonitor {
constructor () {
this.metrics = {}
this.initObservers()
}
initObservers () {
// 监听FCP和LCP (绘制性能)
this.observePaint()
// 监听FID和其他交互性能
this.observeInteraction()
// 监听CLS (布局稳定性)
this.observeLayoutShift()
// 监听资源加载性能
this.observeResources()
// 监听导航性能
this.observeNavigation()
}
observePaint () {
const paintObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
this.metrics.fcp = entry.startTime
console.log('FCP:', entry.startTime.toFixed(2), 'ms')
}
if (entry.name === 'largest-contentful-paint') {
this.metrics.lcp = entry.startTime
console.log('LCP:', entry.startTime.toFixed(2), 'ms')
// LCP超过2.5秒发出警告
if (entry.startTime > 2500) {
this.reportPerformanceIssue('lcp', entry.startTime)
}
}
}
})
paintObserver.observe({
type: 'paint',
buffered: true // 获取观察器创建前的条目
})
// LCP监听
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries()
const lastEntry = entries[entries.length - 1]
this.metrics.lcp = lastEntry.startTime
console.log('LCP (Latest):', lastEntry.startTime.toFixed(2), 'ms')
})
lcpObserver.observe({
type: 'largest-contentful-paint',
buffered: true
})
}
observeInteraction () {
// First Input Delay (FID)
const fidObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.metrics.fid = entry.processingStart - entry.startTime
console.log('FID:', this.metrics.fid.toFixed(2), 'ms')
// FID超过100ms发出警告
if (this.metrics.fid > 100) {
this.reportPerformanceIssue('fid', this.metrics.fid)
}
}
})
fidObserver.observe({
type: 'first-input',
buffered: true
})
}
observeLayoutShift () {
let clsValue = 0
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 只计算意外的布局偏移
if (!entry.hadRecentInput) {
clsValue += entry.value
}
}
this.metrics.cls = clsValue
console.log('CLS:', clsValue.toFixed(4))
// CLS超过0.1发出警告
if (clsValue > 0.1) {
this.reportPerformanceIssue('cls', clsValue)
}
})
clsObserver.observe({
type: 'layout-shift',
buffered: true
})
}
observeResources () {
const resourceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 分析慢资源
const loadTime = entry.responseEnd - entry.startTime
if (loadTime > 3000) { // 超过3秒的资源
console.warn('慢资源警告:', {
name: entry.name,
loadTime: loadTime.toFixed(2),
size: entry.transferSize,
type: entry.initiatorType
})
}
// 统计不同类型资源的性能
this.analyzeResourceType(entry)
}
})
resourceObserver.observe({
type: 'resource',
buffered: true
})
}
observeNavigation () {
const navObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 计算关键导航时间
const timings = {
dns: entry.domainLookupEnd - entry.domainLookupStart,
tcp: entry.connectEnd - entry.connectStart,
ssl: entry.connectEnd - entry.secureConnectionStart,
ttfb: entry.responseStart - entry.requestStart,
download: entry.responseEnd - entry.responseStart,
domParse: entry.domContentLoadedEventEnd - entry.responseEnd,
total: entry.loadEventEnd - entry.startTime
}
this.metrics.navigation = timings
console.log('导航性能:', timings)
// TTFB超过800ms发出警告
if (timings.ttfb > 800) {
this.reportPerformanceIssue('ttfb', timings.ttfb)
}
}
})
navObserver.observe({
type: 'navigation',
buffered: true
})
}
analyzeResourceType (entry) {
const resourceTypes = ['script', 'link', 'img', 'fetch', 'xmlhttprequest']
const type = entry.initiatorType
if (resourceTypes.includes(type)) {
if (!this.metrics.resources) {
this.metrics.resources = {}
}
if (!this.metrics.resources[type]) {
this.metrics.resources[type] = { count: 0, totalTime: 0, totalSize: 0 }
}
this.metrics.resources[type].count++
this.metrics.resources[type].totalTime += entry.duration
this.metrics.resources[type].totalSize += entry.transferSize || 0
}
}
reportPerformanceIssue (metric, value) {
// 发送性能问题报告
fetch('/api/performance-issues', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
metric,
value,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
})
})
}
getMetricsSummary () {
return {
webVitals: {
fcp: this.metrics.fcp,
lcp: this.metrics.lcp,
fid: this.metrics.fid,
cls: this.metrics.cls
},
navigation: this.metrics.navigation,
resources: this.metrics.resources
}
}
}
// 2. 用户自定义性能标记
class CustomPerformanceTracker {
constructor () {
this.startTimes = new Map()
this.setupObserver()
}
setupObserver () {
const measureObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('自定义测量:', {
name: entry.name,
duration: entry.duration.toFixed(2),
start: entry.startTime.toFixed(2)
})
// 发送自定义性能数据
this.sendCustomMetric(entry)
}
})
measureObserver.observe({
type: 'measure',
buffered: true
})
}
startTimer (name) {
performance.mark(`${name}-start`)
this.startTimes.set(name, performance.now())
}
endTimer (name) {
if (this.startTimes.has(name)) {
performance.mark(`${name}-end`)
performance.measure(name, `${name}-start`, `${name}-end`)
this.startTimes.delete(name)
}
}
sendCustomMetric (entry) {
// 发送到分析系统
if (typeof gtag !== 'undefined') {
gtag('event', 'custom_performance', {
event_category: 'Performance',
event_label: entry.name,
value: Math.round(entry.duration)
})
}
}
}
// 使用示例
const webVitalsMonitor = new WebVitalsMonitor()
const customTracker = new CustomPerformanceTracker()
// 测量自定义操作
customTracker.startTimer('api-call')
fetch('/api/data')
.then(response => response.json())
.then(data => {
customTracker.endTimer('api-call')
})
// 页面卸载时发送性能报告
window.addEventListener('beforeunload', () => {
const metrics = webVitalsMonitor.getMetricsSummary()
navigator.sendBeacon('/api/performance-report', JSON.stringify(metrics))
})
面试官视角:
要点清单:
- 理解PerformanceObserver的异步性能数据收集机制
- 掌握Core Web Vitals的监控和分析方法
- 了解不同性能条目类型的应用场景
加分项:
- 提及Web性能指标的优化目标和行业标准
- 了解如何结合RUM(真实用户监控)进行性能分析
- 知道如何处理性能数据的上报和分析
常见失误:
- 不理解buffered选项的作用和重要性
- 忽略observer的disconnect导致内存泄漏
- 过度收集性能数据影响页面性能
延伸阅读:
- PerformanceObserver - MDN — 完整API文档
- Web Vitals — 核心Web性能指标指南
- Performance API — Web性能API概览
requestAnimationFrame 是什么,有什么作用?
答案
方法 | 主要用途 | 执行时机 | 优势 | 典型场景 |
---|---|---|---|---|
requestAnimationFrame | 浏览器动画帧调度 | 重绘前 | 与刷新率同步,节能流畅 | JS动画、进度条等 |
补充说明
- requestAnimationFrame 会在浏览器每次重绘前自动调用回调,通常约每秒60次,能保证动画与屏幕刷新同步,减少卡顿和掉帧。
- 返回的ID可用于 cancelAnimationFrame 取消动画,适合实现高性能动画循环。
- 与 setTimeout/setInterval 相比,requestAnimationFrame 更节能且动画更平滑,页面切换到后台时会自动暂停,节省资源。
let id
function loop () {
// 动画逻辑
id = requestAnimationFrame(loop)
}
loop()
// 取消动画
cancelAnimationFrame(id)
推荐所有涉及 DOM 动画的场景优先使用 requestAnimationFrame,提升性能和流畅度。
延伸阅读
canvas 是如何处理复杂事件交互的?
答案
核心概念
Canvas 本身不具备 DOM 事件绑定能力,复杂事件交互需通过监听容器事件并结合图形坐标判定实现。常见做法是监听鼠标或触摸事件,计算事件点在 canvas 内的坐标,再判断是否命中特定图形,实现点击、悬停、拖拽等交互。
详细实现步骤
- 监听事件:在 canvas 或其父容器上监听
mousemove
、mousedown
、mouseup
、touchstart
等事件。 - 坐标转换:通过
getBoundingClientRect
获取 canvas 的位置,将事件的clientX/Y
转换为 canvas 内部坐标。 - 命中检测:遍历所有图形对象,判断事件点是否落在某个图形区域(如圆形、矩形等)。
- 状态管理:可维护图形对象数组,记录每个图形的状态(如选中、悬停),实现动态交互效果。
代码示例
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
const shapes = [
{ type: 'circle', x: 100, y: 100, radius: 50 },
{ type: 'rectangle', x: 200, y: 200, width: 100, height: 50 }
]
canvas.addEventListener('mousemove', function (event) {
const rect = canvas.getBoundingClientRect()
const mouseX = event.clientX - rect.left
const mouseY = event.clientY - rect.top
shapes.forEach(shape => {
if (shape.type === 'circle' && isPointInCircle(mouseX, mouseY, shape)) {
ctx.fillStyle = 'red'
} else {
ctx.fillStyle = 'blue'
}
drawShape(shape)
})
})
function isPointInCircle (x, y, { x: cx, y: cy, radius }) {
const dx = x - cx
const dy = y - cy
return dx * dx + dy * dy <= radius * radius
}
function drawShape (shape) {
if (shape.type === 'circle') {
ctx.beginPath()
ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2)
ctx.fill()
} else if (shape.type === 'rectangle') {
ctx.fillRect(shape.x, shape.y, shape.width, shape.height)
}
}
开发建议
- 复杂交互建议封装图形对象和事件处理逻辑,或使用如 Fabric.js、Konva.js 等第三方库简化开发。
- 需注意每次交互后需清空并重绘 canvas,避免残影。
延伸阅读
HTML5 <video>
和 <audio>
元素如何使用?
答案
核心概念:
HTML5 媒体元素提供原生的音视频播放功能:
<video>
- 视频播放元素,支持多种格式和控制<audio>
- 音频播放元素,轻量级音频解决方案controls
属性 - 显示播放控制界面autoplay
属性 - 自动播放(需考虑用户体验)loop
属性 - 循环播放muted
属性 - 静音播放(绕过autoplay限制)poster
属性 - 视频封面图片- 支持多种格式兼容和响应式适配
示例说明:
面试官视角:
要点清单:
- 了解video和audio元素的基本属性
- 知道如何处理多格式兼容性
- 理解autoplay策略和用户体验影响
加分项:
- 提及现代浏览器的autoplay限制
- 了解preload属性的性能优化作用
- 知道如何创建自定义媒体控制器
常见失误:
- 忽略浏览器兼容性问题
- 不考虑移动端的数据流量
- 滥用autoplay影响用户体验
延伸阅读:
- Video Element - MDN — video元素完整参考
- Audio Element - MDN — audio元素说明
- Media formats for HTML audio and video — 媒体格式支持
媒体 API 事件和控制方法有哪些?
答案
核心概念:
HTML5 媒体API提供丰富的事件和控制方法:
- 控制方法:
play()
,pause()
,load()
,canPlayType()
- 属性控制:
currentTime
,volume
,playbackRate
,duration
- 状态属性:
paused
,ended
,readyState
,networkState
- 关键事件:
loadstart
,canplay
,play
,pause
,ended
,error
- 进度事件:
loadeddata
,progress
,timeupdate
- 错误处理:
error
事件和error
属性 - 支持programmatic控制和用户交互响应
示例说明:
面试官视角:
要点清单:
- 掌握核心的播放控制方法
- 了解重要的媒体事件生命周期
- 知道如何检测播放状态和错误
加分项:
- 理解readyState不同状态的含义
- 了解如何实现播放进度条
- 知道如何处理网络错误和格式错误
常见失误:
- 不等待canplay事件就调用play()
- 忽略Promise返回值的错误处理
- 没有适当的错误处理机制
延伸阅读:
- HTMLMediaElement - MDN — 媒体元素API参考
- Media events - MDN — 媒体事件完整列表
- Autoplay policy — 自动播放策略变化
现代Web媒体技术有哪些?
答案
核心概念:
现代Web平台提供多种先进的媒体技术:
- Media Source Extensions (MSE) - 动态构建媒体流
- WebRTC - 实时音视频通信
- Web Audio API - 高级音频处理
- Canvas + Video - 视频帧操作和特效
- Picture-in-Picture API - 画中画模式
- Media Session API - 媒体会话控制
- Screen Capture API - 屏幕录制和共享
- Media Recorder API - 录制音视频流
示例说明:
面试官视角:
要点清单:
- 了解MSE在流媒体中的作用
- 知道WebRTC的基本应用场景
- 理解Web Audio API的音频处理能力
加分项:
- 提及HLS和DASH等流媒体协议
- 了解WebCodecs等新兴标准
- 知道如何优化媒体性能和体验
常见失误:
- 混淆不同API的适用场景
- 不了解浏览器兼容性限制
- 忽略媒体相关的安全和隐私问题
延伸阅读:
- Media Source Extensions API — MSE API文档
- WebRTC API — WebRTC完整指南
- Web Audio API — Web Audio API参考
如何判断用户设备
答案
方法 | 主要原理 | 优点 | 局限 | 典型代码 |
---|---|---|---|---|
User-Agent分析 | 解析navigator.userAgent 字符串 | 实现简单,兼容性好 | 易被伪造,部分设备难区分 | /mobile/i.test(ua) |
视口尺寸判断 | 检测window.innerWidth | 无需依赖UA,适合响应式 | 横屏/分屏等场景不准 | width<768 |
媒体查询 | window.matchMedia 结合CSS断点 | 与响应式设计一致 | 仅能判断尺寸,非设备类型 | matchMedia('(max-width:767px)') |
核心概念与原理
- User-Agent(UA)字符串包含浏览器、系统、设备等信息,常用于初步判断设备类型,但易被修改或伪造。
- 视口尺寸法通过检测窗口宽度推断设备类别,适合响应式布局,但对特殊场景(如横屏、缩放)不够精确。
- 媒体查询法利用 CSS 断点,结合 JS 的
window.matchMedia
,可动态判断当前视口是否符合某类设备标准。
代码示例
// 1. User-Agent 判断
function detectDevice () {
const ua = navigator.userAgent
if (/mobile/i.test(ua)) return 'Mobile'
if (/tablet/i.test(ua)) return 'Tablet'
if (/iPad|iPhone|iPod/.test(ua)) return 'iOS Device'
return 'Desktop'
}
// 2. 视口尺寸判断
function detectDeviceByViewport () {
const w = window.innerWidth
if (w < 768) return 'Mobile'
if (w < 992) return 'Tablet'
return 'Desktop'
}
// 3. 媒体查询判断
function detectDeviceByMediaQuery () {
if (window.matchMedia('(max-width:767px)').matches) return 'Mobile'
if (window.matchMedia('(min-width:768px) and (max-width:991px)').matches) return 'Tablet'
return 'Desktop'
}
常见误区与开发建议
- UA 检测不可靠,仅适合作为辅助手段,不能作为安全或唯一依据。
- 视口尺寸和媒体查询更适合响应式布局,建议优先采用“自适应内容”而非“设备定制”。
- 设备检测应结合多种手段,避免因单一方法导致误判。
延伸阅读
- MDN: navigator.userAgent - UA 字符串说明
- MDN: window.innerWidth - 视口宽度
- MDN: window.matchMedia - 媒体查询 API
前端如何实现截图?
答案
核心概念:
前端截图主要通过以下技术实现:html2canvas库(DOM转Canvas)、getDisplayMedia API(屏幕截图)、Canvas API(特定元素截图)。核心原理是将DOM元素转换为Canvas或直接获取屏幕内容,然后通过toDataURL()等方法输出图片数据。需要考虑跨域限制、性能优化、兼容性等因素。
实际示例:
面试官视角:
要点清单:
- 理解不同截图方式的原理和适用场景
- 掌握html2canvas等主流截图库的使用
- 了解Canvas API的基本操作和限制
加分项:
- 提及getDisplayMedia API的屏幕截图能力
- 了解跨域图片的处理方法(CORS)
- 知道如何优化大图截图的性能
常见失误:
- 忽略跨域限制导致的安全错误
- 不了解不同截图方式的浏览器兼容性
- 截图质量和性能平衡处理不当
延伸阅读:
- html2canvas — 主流DOM截图库
- Screen Capture API - MDN — 屏幕截图API
- Canvas API - MDN — Canvas操作指南
web 网页如何禁止别人移除水印
答案
核心概念:
Web水印防护是通过技术手段增加水印移除难度的防护策略。主要包括:明水印(可见水印)和暗水印(隐形水印)两种类型。防护手段涉及MutationObserver监听DOM变化、CSS保护措施、JavaScript动态重建、样式混淆等技术。完全阻止移除是不可能的,但可以显著提高移除成本和技术门槛。
实际示例:
面试官视角:
要点清单:
- 理解明水印和暗水印的区别和实现原理
- 掌握MutationObserver API的监听和防护机制
- 了解多种水印生成方式(DOM、Canvas、SVG)
加分项:
- 提及CSS样式保护和混淆技术
- 了解服务端验证和数字指纹技术
- 知道水印防护的局限性和攻防平衡
常见失误:
- 认为前端技术可以完全防止水印移除
- 忽略性能影响和用户体验
- 不了解暗水印的技术原理
延伸阅读:
- MutationObserver - MDN — DOM变化监听API
- Canvas API - MDN — Canvas水印实现
- Digital Watermarking — 数字水印技术原理
富文本里面, 是如何做到划词的(鼠标滑动选择一组字符, 对组字符进行操作)
答案
核心概念:
富文本划词功能基于浏览器的Selection API和Range API实现。核心流程包括:监听鼠标事件、获取文本选择范围、计算选择位置、处理用户操作。主要涉及window.getSelection()
、Range
对象、contextmenu
事件等API。广泛应用于富文本编辑器、文档阅读器、在线协作工具等场景,需要考虑跨浏览器兼容性、性能优化和用户体验。
实际示例:
富文本划词功能的实现包含以下关键技术点:
- Selection API: 使用
window.getSelection()
获取用户选择的文本范围 - Range API: 通过
Range
对象精确控制文本选择的起始和结束位置 - 事件监听: 监听
mouseup
、selectionchange
、contextmenu
等事件 - 位置计算: 使用
getBoundingClientRect()
计算选择区域的屏幕位置 - 动态菜单: 根据选择状态动态显示工具栏或右键菜单
面试官视角:
主要考察 DOM 操作方法,特别是
getSelection
API
属于较为冷门的知识点,通常在富文本编辑器开发经验的候选人面试中会涉及
这个问题考查候选人对浏览器原生文本选择API的理解程度。优秀的回答应该包含:Selection和Range API的使用、事件处理机制、位置计算方法、性能优化策略。进阶讨论可能涉及:跨浏览器兼容性处理、移动端适配、与现代框架的集成方案。
延伸阅读:
如何在划词选择的文本上添加右键菜单(划词:鼠标滑动选择一组字符, 对组字符进行操作)
答案
核心概念:
划词右键菜单功能通过监听contextmenu事件和getSelection API实现。核心流程包括:监听文本选择、捕获右键事件、获取选中文本范围、创建自定义菜单、处理菜单操作。常用于富文本编辑器、文档阅读器等场景,需要考虑位置计算、事件冒泡、菜单隐藏等细节。
实际示例:
主要考察 dom 方法,
getSelection
属于很冷门知识, 只会在做过富文本的同学面试过程中可能会问得到。
要在划词选择的文本上添加右键菜单,可以按照以下步骤进行操作:
- 监听鼠标右键事件
在文档或富文本区域上添加
contextmenu
事件的监听。
document.addEventListener('contextmenu', function (event) {
// 阻止默认的浏览器右键菜单
event.preventDefault()
// 在此处显示自定义右键菜单
showCustomMenu(event)
})
- 显示自定义右键菜单 创建一个自定义的菜单元素,并根据选择的文本设置菜单选项。
function showCustomMenu (event) {
const customMenu = document.createElement('div')
customMenu.style.position = 'absolute'
customMenu.style.left = event.clientX + 'px'
customMenu.style.top = event.clientY + 'px'
// 添加菜单选项
const menuItem1 = document.createElement('div')
menuItem1.textContent = '复制'
menuItem1.addEventListener('click', function () {
// 处理复制操作
copySelectedText()
})
customMenu.appendChild(menuItem1)
// 可以添加更多的菜单选项
document.body.appendChild(customMenu)
}
- 处理菜单选项的操作 例如,实现复制选中文本的功能。
function copySelectedText () {
const selection = window.getSelection()
if (selection) {
const range = selection.getRangeAt(0)
const clipboardData = new ClipboardEvent('copy', {
clipboardData: { text: range.toString() },
bubbles: true
}).clipboardData
document.execCommand('copy', false, clipboardData)
}
}
- 隐藏右键菜单 当用户点击菜单之外的区域时,隐藏自定义右键菜单。
document.addEventListener('click', function (event) {
const customMenu = document.querySelector('.custom-menu')
if (customMenu && !customMenu.contains(event.target)) {
customMenu.remove()
}
})
面试官视角:
要点清单:
- 理解getSelection API和文本选择原理
- 掌握contextmenu事件处理和默认行为阻止
- 了解自定义菜单的创建、定位和管理
加分项:
- 实现复杂的菜单功能(复制、搜索、翻译等)
- 处理跨元素选择和Rich Text情况
- 提供良好的用户体验和错误处理
常见失误:
- 忘记阻止默认右键菜单
- 菜单定位计算不准确
- 没有处理菜单的隐藏和清理
延伸阅读:
- Selection API - MDN — 文本选择API
- Range API - MDN — 文本范围操作
- contextmenu event - MDN — 右键菜单事件