公司动态
HTML5实战测评:跨浏览器兼容、Canvas性能与存储优化
其实做这个HTML5系列测验做到第七期我自己也没想到能坚持这么久。最初只是整理一份带新人用的内部题库后来越写越觉得HTML5这门“基础课”里藏着太多想当然的知识盲区。这一期的选题我不打算考你那些背一背就能答上来的标签属性而是把重心放在真实项目里最容易翻车的地方跨浏览器的视频播放兼容、Canvas粒子特效的性能瓶颈、语义化标签的实际使用边界、表单验证和本地存储的隐藏坑。这份测验适合正在系统补基础的前端新人也适合工作一两年突然想停下来查缺补漏的同学。做七期系列测验有一个好处每一期的题目会自然形成知识梯度。第一期到第三期考的是标签、语法这类死记硬背的东西第四期开始加入布局与响应式第五期和第六期偏向动画与交互。到了第七期我特意把重心转向“真实项目里的高频踩雷场景”。这么调整的原因其实很现实。我带项目的时候经常发现很多同学背知识点很熟练但一遇到媒体格式兼容、Canvas画布发糊、跨域图片导致Canvas被污染、localStorage写入导致页面卡顿这类“非常规场景”就开始抓瞎。这些问题是任何标签文档里都不会直接告诉你的只能在项目里踩过坑才会明白。所以第七期的出题思路和w3school那类知识点列表完全不一样我是从这些年攒下的项目问题池里抽题。考点覆盖四个方向媒体播放的跨浏览器差异、Canvas粒子特效与性能、语义化与表单、存储与多线程。每题后面是答案和解析解析不只是说“为什么对”还会说“错在哪”“真实项目里遇到这个问题要怎么排查”。建议你先自己过一遍题目再对照解析看效果会好很多。2. 媒体播放与跨浏览器兼容第一组考题2.1 题目一移动端自动播放策略这道题放到真实业务里太常见了。需求方一句“进页面视频自动播放”前端就得和浏览器厂商的自动播放策略斗智斗勇好一阵子。先看题目。在iOS Safari中以下哪种方式可以让video元素在页面加载后自动播放且带声音A. 设置video的autoplay属性B. 同时设置autoplay和mutedC. 在touchstart事件回调里调用video.play()D. 等待window.onload之后调用video.play()答案是C。解析一下。iOS Safari以及大多数现代移动端浏览器的自动播放策略是这样的带声音的视频不允许在无用户手势的情况下自动播放。A选项的autoplay属性在iOS Safari里基本是无效的写了也白写。B选项设置为muted后确实可以自动播放但那是静音状态不满足“带声音”这个前提。C选项在touchstart事件回调里调用play()浏览器会认为这次播放行为是由用户手势直接触发的因此允许带声音播放。D选项的window.onload不属于用户手势依然会被拦截播放的Promise会被reject掉。补充一个我在真实项目里的经验不要只监听touchstart建议同时注册click事件。原因很简单桌面端没有touchstart而在桌面浏览器上点击产生的click事件同样属于用户手势。只监听touchstart会导致桌面端点按钮没反应。另一个更稳的方案是做一个播放按钮引导用户点击既满足自动播放的诉求也兼顾了用户体验。实际测试下来这个方案的兼容性比任何取巧写法都可靠。2.2 题目二source多源回退的写法假设需要往网页里嵌入一段视频希望优先播放MP4格式如果浏览器不支持再回退到WebM最后再显示一段提示文字。请写出video标签的完整结构。这道题是HTML5媒体方案里最经典的渐进增强写法参考答案是这样的。video controls width640 height360 source srcdemo.mp4 typevideo/mp4 source srcdemo.webm typevideo/webm 你的浏览器不支持 HTML5 视频播放。 /video这里有一个很关键的细节浏览器在真正下载视频内容之前会先读取source标签的type属性做预判断。如果声明的MIME类型不被当前浏览器支持浏览器会直接跳过这个source不会发起网络请求。所以type属性一定要写准确否则浏览器会先下载一段视频发现播放不了才去尝试下一个source白白浪费用户流量和时间。很多人在写视频标签时的直觉是“兼容性最好的格式应该放最前面”这个思路在原理上是对的。但现实情况是只要MP4用的是H.264视频编码和AAC音频编码现在的主流浏览器基本都能播放所以把MP4放第一个没有问题。关键点在于编码格式必须选对——H.264加AAC是解码器支持范围最广的组合。选成MPEG-4编码的MP4就算后缀名一样照样有一堆浏览器播放不了。补充一个冷知识Chrome对MP4和H.264的支持也有过一段历史变化。早期Chromium因为H.264是专利编码默认不内置解码器用户需要手动安装后来主流版本才把H.264解码器直接内置进去。所以如果你需要兼容一些还在用旧内核的国产浏览器只给MP4可能不够WebM格式也值得一并提供。2.3 题目三浏览器对HTML5播放器的真实差异判断判断题主流浏览器对HTML5 video的支持是完全一致的只要写好一个video标签任何浏览器都能播放同样格式的视频。答案是错误。这个判断错得很典型。不同浏览器对视频格式的支持差异主要出在解码器层面。Safari对H.264和HEVC的支持很好但对WebM的支持一直很弱。Firefox历史上长期原生支持WebM的VP8/VP9编码和Vorbis/Opus音频H.264解码支持是后来一步步补上的。Edge和Chrome大部分场景下H.264和WebM都没有问题但个别旧版本、特殊内核的浏览器行为又不一样。“浏览器对HTML5播放器的支持”这句话听起来像一个整体概念但真要判断兼容性得拆成三层来看格式支持是一层API行为是一层移动端策略又是一层。格式支持我刚才说了API层面的差异也很常见比如进入全屏的requestFullscreen在某些旧浏览器里需要加浏览器前缀在iOS上video元素要加playsinline属性才能内联播放不加的话系统会自动进入全屏播放器。移动端策略就更典型了自动播放限制、音量限制、后台播放限制在不同系统上五花八门。所以别指望写一个video标签就天下太平。在没有真机测试条件的时候至少要保证项目目标用户占比最高的两三个浏览器里跑一遍重点看自动播放、全屏、倍速、画中画这几个交互是否正常。这几个功能都是播放器需求里的高频功能也是最容易出差异的地方。3. Canvas粒子特效与性能优化第二组考题3.1 题目四requestAnimationFrame 和 setInterval 之争做Canvas粒子爱心闪烁、烟花爆炸这类特效时用requestAnimationFrame代替setInterval进行逐帧绘制的主要原因有哪些A. requestAnimationFrame会匹配屏幕刷新率减少掉帧B. 页面切换后台时requestAnimationFrame会自动暂停节省CPUC. requestAnimationFrame有统一的时间参数做匀速动画更精确D. setInterval无法实现动画答案是A、B、C。A选项是最核心的原因。普通屏幕刷新率是60Hz也就是每16.7毫秒刷新一帧但现在的手机和电脑显示器越来越多是90Hz、120Hz了。setInterval固定每16.7毫秒执行一次回调如果屏幕是120Hz这个节奏就对不上可能出现画面撕裂或者多余帧被丢弃。requestAnimationFrame会跟屏幕的刷新率自动对齐屏幕60Hz就每秒执行60次屏幕120Hz就每秒执行120次每一帧都能被完整绘制出来。B选项是省资源的利器。页面切到后台时setInterval仍然在运行只是浏览器会节流到每秒一次如果动画里做了大量计算照样会耗费不少CPU。requestAnimationFrame则干脆得多页面一旦不可见就直接暂停执行回到前台再继续。这个行为对做视频会议、直播间的特效尤其重要用户切走标签页再回来动画不会堆积一大串积压帧然后把CPU拉满。C选项很多人不知道。requestAnimationFrame回调接收一个DOMHighResTimeStamp时间戳参数。用这个参数做匀速运动计算比用Date.now()自己掐表精确得多。即使某一帧因为慢设备延迟了计算出来的位置依然是正确的不会导致粒子速度忽快忽慢。D选项当然是错的。setInterval当然可以实现动画只是体验和资源消耗都不如requestAnimationFrame。网上有些旧教程还在用setInterval做动效那是当时的环境限制现在写新代码建议直接用requestAnimationFrame。这里放一个参考代码结构。let lastTime 0; function animate(time) { const delta time - lastTime; lastTime time; // 根据delta更新粒子位置 updateParticles(delta); drawParticles(); requestAnimationFrame(animate); } requestAnimationFrame(animate);有一个小坑需要注意第一次回调时lastTime是0time是一个很大的时间戳数值delta会非常大。所以初始化时最好把lastTime赋成当前time或者加一个判断跳过第一帧的计算否则第一帧粒子会以异常速度跳出去。3.2 题目五devicePixelRatio 导致的特效模糊问题一段同样的Canvas烟花特效代码在MacBook Pro Retina屏幕上做出来明显比普通屏幕糊线条发虚。最可能的原因是什么如何彻底解决原因是设备像素比英文简称DPRDevice Pixel Ratio。Retina屏的DPR大于1意味着一个CSS像素对应多个物理像素。而Canvas画布的默认尺寸是按照CSS像素设置的。举个例子设置canvas.width为800CSS样式里宽度也是800px在DPR等于2的屏幕上画布只有800个物理像素宽度却要拉伸铺满1600个物理像素的显示区域。浏览器就只能把每个像素插值放大画面自然就糊了。解决办法是让画布的实际像素数量等于物理像素数量再用scale方法把坐标系缩放回CSS像素尺度这样后续所有绘图代码都按CSS像素坐标写但每个点都稳稳落在真实物理像素上。const canvas document.getElementById(fireworks); const dpr window.devicePixelRatio || 1; const rect canvas.getBoundingClientRect(); canvas.width rect.width * dpr; canvas.height rect.height * dpr; const ctx canvas.getContext(2d); ctx.scale(dpr, dpr);这段代码有三个细节值得注意。第一getBoundingClientRect取的是元素在页面上的实际布局尺寸比直接用canvas.offsetWidth更可靠因为CSS可能通过transform等方式影响了元素的最终尺寸。第二DPR可能是小数比如某些浏览器的缩放比例是1.25或者1.5直接乘没问题Canvas会按像素处理。第三DPR适配是有性能成本的DPR为2时画布像素数变为原来的4倍绘制一万个粒子时计算量和填充量都明显增加。如果特效本身很简单或者不需要高清显示可以不做这个适配如果粒子数量很大需要在高质量和流畅度之间做取舍必要时把画布尺寸控制在较小范围。真实项目里建议把这段DPR适配封装成resizeCanvas工具函数并监听window的resize和orientationchange事件在屏幕旋转或窗口尺寸变化时重新调整画布大小否则横屏变竖屏时画布会被拉伸变形。3.3 题目六跨域图片污染画布的安全机制用Canvas做一张“爱心烟花加上人物照片”的海报图片先执行了下面的代码结果运行到toDataURL时浏览器报了SecurityError。请说明原因并给出解法。const img new Image(); img.src https://third-party.example.com/photo.jpg; img.onload () { ctx.drawImage(img, 0, 0); const url canvas.toDataURL(image/jpeg); };原因出在Canvas的“画布污染”安全机制。浏览器为了防止跨域信息窃取规定一旦Canvas里绘制了跨域来源的图片并且这张图片没有以CORS许可的方式加载这块画布就会被标记为“被污染”。被污染之后所有会暴露像素数据的API都会被禁止调用toDataURL、toBlob、getImageData都在这份黑名单里。浏览器这么做不是故意给开发者添堵而是防止恶意脚本把用户在其他域名下的图片内容读取出来再传走。解法是给Image对象加上crossOrigin属性。const img new Image(); img.crossOrigin anonymous; img.src https://third-party.example.com/photo.jpg;但这里有一个很容易被忽略的前提图片所在服务器必须返回Access-Control-Allow-Origin响应头明确允许你的站点读取这张图片。如果服务器不返回这个头前端即使设置了crossOrigin也白搭图片会直接加载失败onload事件根本不会触发。crossOrigin的取值也有讲究anonymous表示跨域请求不带凭证服务端返回的Access-Control-Allow-Origin不能是通配符需要明确指定你的源use-credentials表示请求携带Cookie等凭证此时服务端必须返回Access-Control-Allow-Credentials为true并且Access-Control-Allow-Origin同样不能是。真实项目里经常出现一种情况本地开发环境跑得好好的一部署到线上就报SecurityError。排查方向很明确先打开浏览器Network面板找到那张图片的请求看响应头里有没有Access-Control-Allow-Origin。如果缺失就去找后端或者运维在静态资源服务器上补一个CORS头配置。如果这张图片是来自第三方图床而第三方又不支持CORS那就只能改走自己的服务器代理转发图片或者放弃在Canvas里导出这个图片。4. 语义化、表单与网页设计第三组考题4.1 题目七section、article、div 的边界下列哪些情况适合使用section标签A. 一个商品详情页面按“商品参数”“用户评价”“售后说明”三个板块划分B. 一个网页顶部导航栏区域C. 一个只会出现一次的网站欢迎语D. 一组样式上自成一体、但没有语义含义的外包装容器答案是A。section标签的语义定义是“文档中的一个区域性主题”它要求内容在概念上是一个独立的主题区块并且官方建议每个section都带一个标题。A选项里的商品参数、用户评价、售后说明每个都是独立主题结构清晰适合用section包裹。B选项是nav的典型应用场景导航栏应该用nav标签不要再套一层section。C选项这种一次性欢迎语既不是文档的主体结构也没有独立的标题含义直接用div就够了。说实话C选项是很多人最容易过度使用section的地方一看到页面有个区块就顺手包一层section结果整个页面到处都是section反而让屏幕阅读器无法正确理解文档大纲。D选项是div的经典使用场景。div本身没有任何语义就是纯粹的容器用它来做样式分组没有任何问题。反而是给纯视觉容器套section会误导无障碍设备让读屏软件区分不出真正的结构层级。再说一下article和section的区别这在页面设计里经常让人拿捏不准。article表示一个完整独立的内容单元可以脱离页面单独分发比如一篇文章、一条用户评论、一个产品卡片——它的核心特征是“独立存在也有意义”。section更偏向页面内部的章节划分它强调的是一个页面结构上的组织关系。判断方法很简单如果这段内容既能在这个页面里看也可以单独推送到站外或者被RSS订阅那它更像article如果它只是把一个长页面拆分成几个阅读板块用section更合适。用一句话总结样式需要就用div结构需要就用section独立内容需要就用article。不要觉得用div布局太老土就把所有div都换成sectionW3C规范里明确写了section并不是div的替代品。4.2 题目八表单验证不能只依赖HTML5为一个输入框设置typeemail并加上required属性之后是否可以认为邮箱格式校验就完成了答案是不能。HTML5的表单验证API确实好用。它让浏览器在用户提交表单前先做一次本地校验输入不合法直接拦下来不让提交不需要等服务器回来报错体验非常好。但它的定位仅仅是一层前端提示不是安全边界。绕过这个校验的方式实在太多了。用户可以打开DevTools直接删掉输入框上的required属性可以通过JavaScript操作form元素的submit方法绕过原生校验逻辑直接提交FormData甚至可以完全绕过浏览器用curl或者Postman直接向服务器发请求。这些路径里HTML5表单验证根本不会被执行。只要请求能打到服务端服务端不校验非法数据就进去了。正确的做法是三层校验配合。第一层前端用HTML5表单验证提供即时反馈提升用户体验。第二层JavaScript里用自定义正则做更严格的补充校验尤其是国别手机号、身份证号、密码强度这类HTML5标准type覆盖不了的需求。第三层服务端必须做最终校验格式、长度、业务规则、权限边界都要在服务端再验一遍。这三层缺一不可。还有一个容易被忽略的点是HTML5表单验证的原生报错文案在不同浏览器里长得完全不一样而且很难通过CSS自定义。Chrome的提示是一条深色气泡Firefox的提示是一段文字气泡Safari的表现又不一样。如果产品对报错文案有统一要求建议在form上加上noValidate属性关闭原生校验然后自己通过checkValidity()检测状态再配合setCustomValidity()或者自定义错误提示DOM来统一展示。4.3 题目九data-* 属性与状态管理在一个HTML5圣诞贺卡页面里每张贺卡需要记录一个“样式编号”和一个“点击次数”。以下哪种做法更合理A. 每次点击按钮后用document.getElementById修改样式编号和点击次数B. 把数据放在data-*属性里JS从dataset中读取修改时同步更新属性C. 在全局定义一个变量存样式编号另一个变量存点击次数答案是B。>[data-stateactive] { border-color: #ffcc00; box-shadow: 0 0 12px rgba(255, 204, 0, 0.6); }用data-*属性标记UI状态状态和样式放在同一处业务代码里只负责改data-state的值样式自动切换可读性比用classList反复增删类名高不少。这个方式在做节日卡片、抽奖转盘、H5互动页这类场景里很实用。5. 存储、性能与进阶实战第四组考题5.1 题目十localStorage 的容量与隐私模式陷阱下面关于localStorage的说法正确的一项是A. localStorage的容量通常是5MB左右比cookie大很多所以可以放心存大文件B. localStorage在隐私无痕模式下一定会失效C. localStorage的写操作是同步的频繁的大数据写入可能阻塞主线程D. localStorage的键值对中可以存储任意JavaScript对象答案是C。A选项前半句是对的。主流浏览器单域下localStorage的容量确实在5MB左右不同浏览器有差异。但“比cookie大很多”不代表可以放心往里塞大文件。有些人喜欢把用户头像转成base64字符串直接塞进localStorage一张大图就可能占掉几百KB塞几十张就把存储空间用满了。真正的本地大文件存储应该用IndexedDB这个API支持存储Blob和File对象容量也大得多。B选项说得太绝对了。隐私无痕模式下localStorage通常是可用的但数据在会话结束之后会被清空。也就是说无痕模式下写入后立刻读取是没问题的刷新页面数据可能还在关闭浏览器窗口再重新打开数据就没了。不同浏览器的具体实现细节也有差异所以不能把localStorage当持久存储用在无痕模式场景里。C选项是正确的原因而且这个知识点在项目里很容易被忽略。localStorage的getItem和setItem都是同步操作如果频繁写入大量数据比如每秒把几MB的数据往localStorage里塞主线程会被阻塞页面就会卡顿甚至短暂无响应。在处理高频数据缓存时要控制写入频率和单次数据量必要的时候做一次节流或批量合并写入。D选项错得很典型。localStorage只能存字符串。存对象的时候JavaScript会隐式调用toString结果变成[object Object]取回来根本没法用。正确写法是手动序列化。const user { name: 张三, age: 25 }; localStorage.setItem(user, JSON.stringify(user)); const parsed JSON.parse(localStorage.getItem(user) || {});还有一个容易踩的坑是跨标签页同步。如果用了localStorage做多标签页之间的数据同步要记得监听storage事件。这个事件的特点是其他标签页修改localStorage数据时当前页面会触发该事件但当前页面自己修改数据时当前页面不会触发。也就是说不能靠storage事件来同步本页面发起的修改只能捕获其他页面带来的变化。5.2 题目十一Web Worker 解决长任务卡顿做HTML5网页设计时需要在页面里实时计算一万个粒子的下一次位置结果页面明显卡顿滚动都开始掉帧。这个问题如何解决答案是使用Web Worker把计算任务从主线程搬到后台线程。先理解一下为什么卡。JavaScript默认运行在浏览器主线程上而主线程同时负责DOM解析、样式计算、页面渲染和用户事件处理。如果主线程被一段耗时很长的计算任务占住渲染帧就没机会执行表现出来就是页面点不动、滚动不跟手、按钮点了没反应画面直接冻结。Web Worker解决的就是这个问题。它创建一个独立的后台线程执行脚本通过postMessage和主线程通信互不阻塞。改造步骤很简单。第一步新建一个worker.js文件把粒子位置计算逻辑放进去。第二步在主线程里创建Worker实例。第三步把粒子数据通过postMessage发给Worker。第四步Worker计算完把结果postMessage回来主线程拿到数据后直接绘制。// main.js const worker new Worker(worker.js); worker.postMessage(particles); worker.onmessage (e) { drawParticles(e.data); };// worker.js self.onmessage (e) { const particles e.data; // 执行粒子位置更新计算 const result updatePositions(particles); self.postMessage(result); };这里有几个注意点。Worker内不能直接访问DOM也不能操作Canvas的2D上下文所以纯计算交给Worker绘图还是留在主线程。Worker里创建的嵌套Worker在部分现代浏览器里支持但兼容性一般不建议依赖。另外Worker脚本的加载受同源策略限制如果用file://协议打开本地HTML文件通常无法加载Worker调试时要用本地服务器。如果还想继续优化有一个叫OffscreenCanvas的API值得关注。它允许把Canvas的控制权转移给Worker让Worker直接在后台线程里执行绘制命令主线程完全解放出来。不过这个API的浏览器兼容性还需要仔细评估在目标用户浏览器覆盖范围内测试通过才能上。5.3 题目十二圣诞贺卡综合题这是一道综合设计题。假设你接到一个HTML5圣诞贺卡需求要求是这样的打开页面自动播放背景音乐但初始状态是静音用户点击“点灯”按钮后音乐解除静音并播放雪花开始飘落贺卡顶部有一个固定的贺词标题下方是可以滚动的长内容区域展示祝福语和照片页面需要适配手机和桌面浏览器。请列出实现方案中必须考虑的技术点。我没有标准答案说说我的方案思路。背景音乐的自动播放要利用浏览器自动播放策略的规则。音乐元素加上muted属性后可以绕过“必须用户手势才能播放声音”的限制实现自动播放。用户点击按钮时再调用audio.play()并设置muted为false这个操作在用户手势回调里触发所有主流浏览器都会允许。这个方案是当前兼容性最好的移动端自动播放带声音做法。雪花飘落的实现方式取决于是不是需要交互。如果只是视觉装饰雪花数量20到50片直接用CSS动画用transform属性的translate和rotate做位移性能很轻。如果需要点击雪花就消失这种交互或者雪花数量很大用Canvas配合requestAnimationFrame更合适同时别忘了处理DPR不然雪花在Retina屏幕上就是糊的。顶部固定标题可以用position:sticky配合top:0也可以用position:fixed。两者区别在于sticky只在父容器滚动到一定位置后才固定fixed是始终固定在视口内。如果“固定”需求是从页面顶部开始就固定用fixed更省事。要注意sticky在某些情况下会受到父元素overflow属性的影响需要排查一下。照片和音频资源都要用source标签做多格式回退。移动端的video元素记得加playsinline属性否则iOS上会自动进入全屏播放器。贺卡结构的语义化也有讲究外层用header、main、section组织层级照片用figure加figcaption包裹祝福语列表用ul系列标签。响应式布局建议优先用clamp()函数控制标题字号和卡片宽度比如font-size: clamp(1.2rem, 3vw, 2rem)这样大部分场景不需要写媒体查询就能自适应。只有到了极端窄屏或极端宽屏才需要补充媒体查询。你看这道题它不考具体API的写法而是考察方案设计能力。能写出自动播放策略、Canvas性能、DPR适配、语义化、响应式这几个点基本就证明你对真实HTML5项目有了一个全局性的概念而不是只停留在零散标签的认知上。6. 测验之外的几点补强建议6.1 从错题反推知识体系缺口做完这份测验如果你错的题目集中在某一个方向比如媒体播放或者Canvas那说明你的知识体系里有对应的盲区。这时候不要只是对个答案、把正确答案记下来就算完那样下次换一个场景还是不会。建议找一个小项目把这块知识练熟。比如错了Canvas相关的题就真的去写一个粒子爱心动效从画布初始化、DPR适配到requestAnimationFrame循环完整跑一遍过程中踩过的坑会远比背答案记得牢。错了播放器相关的题就搭一个跨浏览器视频播放测试页把自己能接触到的浏览器都跑一遍记录每种浏览器对自动播放、全屏、倍速、画中画的支持情况。6.2 用“最小demo验证”代替“背面试题”HTML5这门学问和数学不太一样它不纯粹靠逻辑推演反而充满了“不同浏览器实现不一致”的经验知识。我个人的工作习惯是遇到不确定的兼容性问题先写一个最小demo验证可行性再往里面加业务逻辑。比如要判断某个移动端浏览器支不支持带声音的自动播放不需要翻半天文档直接写一个包含video标签和一段音频的空页面在真机上跑一下就知道了。文档只能告诉你规范是怎么规定的但真实用户手里的浏览器不一定完全遵循规范。尤其是国内安卓设备的浏览器环境相当碎片化WebView内核版本差异很大最小demo验证是成本最低、可信度最高的容错手段。6.3 我的HTML5学习路线参考如果你是从零开始系统地补HTML5我建议按这条路线走标签语义、表单与媒体、Canvas与SVG、存储与多线程、浏览器兼容与性能。标签语义是地基不然后面写出来的页面结构经不起推敲无障碍访问和SEO都会受影响。表单和媒体是实际项目里最高频的组件面试也爱考。Canvas和SVG是锦上添花也是做出互动性强、视觉效果好的活动页面的核心能力。存储与多线程解决的是“数据放哪、页面为什么卡”的底层问题。最后再把兼容性意识内化成工作习惯每写一个API先问一句这个API在项目目标浏览器里都支持吗不支持的话降级方案是什么这个习惯会让你在未来省大量排查Bug的时间。第七期这套题就是从真实项目的问题池里抽出来的。做完之后你会发现HTML5的难点从来不在标签写法本身而在各浏览器对标准的落地差异上。我带项目时经常跟组员讲一句话不怕你不会写标签就怕你不知道同一个标签在不同环境下会有什么不同表现。把这份测验里的题目吃透以后再遇到视频自动播放失效、Canvas特效发糊、localStorage写入卡顿、Canvas导出图片报安全错误这类问题你就不会一头雾水至少知道该往哪个方向排查了。希望这份解析能帮你补上几块知识盲区。我们下一期见。