面试大厂,这些 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