面试大厂,这些 DOM 相关操作要掌握
在前端中,我将与浏览器环境以及操作相关的统称为 DOM 相关,大致可分为各种 DOM 操作以及 Web API。
DOM 操作如 DOM 的增删改查操作以及事件监听等,见 DOM。
Web API 包括 Fetch API、Canvas API、Web Worker、WebRTC、WebGL 等,见 MDN。
统计当前页面出现次数最多的标签
这是一道前端基础与编程功底具备的面试题:
- 如果你前端基础强会了解
document.querySelectorAll(*)能够列出页面内所有标签 - 如果你编程能力强能够用递归/正则快速实现同等的效果
有三种 API 可以列出页面所有标签:
document.querySelectorAll('*'),标准规范实现document.getElementsByTagName('*')$$('*'),devtools 实现document.all,非标准规范实现
如果你已经快速答了上来,那么还有两道拓展的面试题在等着你
- 如何找到当前页面出现次数前三多的 HTML 标签
- 如过多个标签出现次数同样多,则取多个标签
跨域
- 题目:什么是跨域,如何解决
协议,域名,端口,三者有一不一样,就是跨域
案例一:www.baidu.com 与 zhidao.baidu.com 是跨域
目前有两种最常见的解决方案:
- CORS,在服务器端设置几个响应头,如
Access-Control-Allow-Origin: * - Reverse Proxy,在 nginx/traefik/haproxy 等反向代理服务器中设置为同一域名
- JSONP,详解见 JSONP 的原理是什么,如何实现
图片懒加载
最新的实现方案是使用 IntersectionObserver API。
const observer = new IntersectionObserver((changes) => {
changes.forEach((change) => {
// intersectionRatio
if (change.isIntersecting) {
const img = change.target
img.src = img.dataset.src
observer.unobserve(img)
}
})
})
observer.observe(img)sessionStorage 与 localStorage 有何区别
略
如何设置一个支持过期时间的 localStorage
设置如下数据结构,当用户存储数据时,存储至 __value 字段。并将过期时间存储至 __expires 字段。
{ __value, __expires }而当每次获取数据时,判断当前时间是否已超过 __expires 过期时间,如果超过,则返回 undefined,并删除该数据。
Cookie 属性
Cookie 有以下属性
- Domain
- Path
- Expire/MaxAge
- HttpOnly: 是否允许被 JavaScript 操作
- Secure: 只能在 HTTPS 连接中配置
- SameSite
Cookie maxAge
如果没有 maxAge,则 cookie 的有效时间为会话时间。
Cookie SameSite
- None: 任何情况下都会向第三方网站请求发送 Cookie
- Lax: 只有导航到第三方网站的 Get 链接会发送 Cookie,跨域的图片、iframe、form表单都不会发送 Cookie
- Strict: 任何情况下都不会向第三方网站请求发送Cookie
目前,主流浏览器 Same-Site 的默认值为 Lax,而在以前是 None,将会预防大部分 CSRF 攻击,如果需要手动指定 Same-Site 为 None,需要指定 Cookie 属性 Secure,即在 https 下发送
Cookie 增删改查
- 题目:如何设置一个 Cookie
- 题目:如何删除一个 Cookie
通过把该 cookie 的过期时间改为过去时即可删除成功,具体操作的话可以通过操作两个字段来完成
max-age: 将要过期的最大秒数,设置为-1即可删除expires: 将要过期的绝对时间,存储到cookies中需要通过date.toUTCString()处理,设置为过期时间即可删除
很明显,max-age 更为简单,以下代码可在命令行控制台中进行测试
// max-age 设置为 -1 即可成功
document.cookie = 'a=3; max-age=-1'> document.cookie
< ""
> document.cookie = 'a=3'
< "a=3"
> document.cookie
< "a=3"
// 把该字段的 max-age 设置为 -1
> document.cookie = 'a=3; max-age=-1'
< "a=3; max-age=-1"
// 删除成功
> document.cookie
< ""同时,也可以使用最新关于 cookie 操作的 API: CookieStore API 其中的 cookieStore.delete(name) 删除某个 cookie
addEventListener()
详见 MDN https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
什么是事件冒泡和事件捕获
可以使用一道代码题,完全理解事件冒泡和事件捕获。
代码见: 事件捕获和冒泡 - Codepen
以下代码输出多少:
<div class="container" id="container">
<div class="item" id="item">
<div class="btn" id="btn">
Click me
</div>
</div>
</div>document.addEventListener('click', (e) => {
console.log('Document click')
}, {
capture: true
})
container.addEventListener('click', (e) => {
console.log('Container click')
// e.stopPropagation()
}, {
capture: true
})
item.addEventListener('click', () => {
console.log('Item click')
})
btn.addEventListener('click', () => {
console.log('Btn click')
})
btn.addEventListener('click', () => {
console.log('Btn click When Capture')
}, {
capture: true
})
什么是事件委托,e.currentTarget 与 e.target 有何区别

事件委托指当有大量子元素触发事件时,将事件监听器绑定在父元素进行监听,此时数百个事件监听器变为了一个监听器,提升了网页性能。
另外,React 把所有事件委托在 Root Element,用以提升性能。
e.preventDefault
如下:
e.preventDefault(): 取消事件e.cancelable: 事件是否可取消
如果 addEventListener 第三个参数 { passive: true},preventDefault 将会会无效
input 事件
重点要了解下 input 事件,比如 React 的 onChange 在底层实现时,就是用了原生的 input 事件,可观察以下代码输出。
import "./styles.css";
export default function App() {
return (
<div className="App">
<input
onChange={(e) => {
console.log("Event: ", e);
console.log("NativeEvent: ", e.nativeEvent);
console.log("CurrentTarget: ", e.nativeEvent.currentTarget);
console.log("NativeEvent Type: ", e.nativeEvent.type);
}}
/>
</div>
);
}ClipBoard API
- 题目:在浏览器中如何获取剪切板中内容
- 题目:浏览器的剪切板中如何监听复制事件
- 题目:如何实现页面文本不可复制
通过 Clipboard API 可以获取剪切板中内容,但需要获取到 clipboard-read 的权限,以下是关于读取剪贴板内容的代码:
// 是否能够有读取剪贴板的权限
// result.state == "granted" || result.state == "prompt"
const result = await navigator.permissions.query({ name: "clipboard-read" })
// 获取剪贴板内容
const text = await navigator.clipboard.readText()注: 该方法在
devtools中不生效
有 CSS 和 JS 两种方法禁止复制,以下任选其一或结合使用
使用 CSS 如下:
user-select: none;或使用 JS 如下,监听 selectstart 事件,禁止选中。
当用户选中一片区域时,将触发 selectstart 事件,Selection API 将会选中一片区域。禁止选中区域即可实现页面文本不可复制。
document.body.onselectstart = e => {
e.preventDefault();
}
document.body.oncopy = e => {
e.preventDefault();
}fetch 中 credentials 指什么意思
credentials 指在使用 fetch 发送请求时是否应当发送 cookie
omit: 从不发送cookie.same-origin: 同源时发送cookie(浏览器默认值)include: 同源与跨域时都发送cookie
如何取消请求的发送
- 题目:如何取消请求的发送
以下两种 API 的方式如下
- XHR 使用
xhr.abort() - fetch 使用
AbortController
如何判断在移动端
判断 navigator.userAgent,对于 Android/iPhone 可以匹配以下正则
const appleIphone = /iPhone/i;
const appleIpod = /iPod/i;
const appleTablet = /iPad/i;
const androidPhone = /\bAndroid(?:.+)Mobile\b/i; // Match 'Android' AND 'Mobile'
const androidTablet = /Android/i;当然,不要重复造轮子,推荐一个库: https://github.com/kaimallea/isMobile
import isMobile from 'ismobilejs'
const mobile = isMobile()requestIdleCallback
requestIdleCallback 维护一个队列,将在浏览器空闲时间内执行。它属于 Background Tasks API,你可以使用 setTimeout 来模拟实现
window.requestIdleCallback = window.requestIdleCallback || function(handler) {
let startTime = Date.now();
return setTimeout(function() {
handler({
didTimeout: false,
timeRemaining: function() {
return Math.max(0, 50.0 - (Date.now() - startTime));
}
});
}, 1);
}以上实现过于复杂以及细节化,也可以像 swr 一样做一个简单的模拟实现,以下代码见 https://github.com/vercel/swr/blob/8670be8072b0c223bc1c040deccd2e69e8978aad/src/use-swr.ts#L33
const rIC = window['requestIdleCallback'] || (f => setTimeout(f, 1))在 rIC 中执行任务时需要注意以下几点:
- 执行重计算而非紧急任务
- 空闲回调执行时间应该小于 50ms,最好更少
- 空闲回调中不要操作 DOM,因为它本来就是利用的重排重绘后的间隙空闲时间,重新操作 DOM 又会造成重排重绘
如何把 DOM 转化为图片
简单总结:DOM -> SVG -> Canvas -> JPEG/PNG
JSONP 的原理是什么,如何实现
JSONP,全称 JSON with Padding,为了解决跨域的问题而出现。虽然它只能处理 GET 跨域,虽然现在基本上都使用 CORS 跨域,但仍然要知道它,毕竟面试会问。
JSONP 基于两个原理:
- 动态创建
script,使用script.src加载请求跨过跨域 script.src加载的脚本内容为 JSONP: 即PADDING(JSON)格式
异步加载 JS 脚本时,async 与 defer 有何区别
以下图片取自 whatwg 的规范,可以说是最权威的图文解释了,详细参考原文
在正常情况下,即 <script> 没有任何额外属性标记的情况下,有几点共识
- JS 的脚本分为加载、解析、执行几个步骤,简单对应到图中就是
fetch(加载) 和execution(解析并执行) - JS 的脚本加载(fetch)且执行(execution)会阻塞 DOM 的渲染,因此 JS 一般放到最后头
而 defer 与 async 的区别如下:
- 相同点: 异步加载 (fetch)
- 不同点:
- async 加载(fetch)完成后立即执行 (execution),因此可能会阻塞 DOM 解析;
- defer 加载(fetch)完成后延迟到 DOM 解析完成后才会执行(execution)**,但会在事件
DomContentLoaded之前
React/Vue 中的 router 实现原理如何
前端路由有两种实现方式:
history API
- 通过
history.pushState()跳转路由 - 通过
popstate event监听路由变化,但无法监听到history.pushState()时的路由变化
hash
- 通过
location.hash跳转路由 - 通过
hashchange event监听路由变化
浏览器中如何读取二进制信息

可在 MDN 中熟读以下 API
- File/Blob API
- TypedArray/ArrayBuffer API
- FileReader API