前言

网站开发中,如何实现图片的懒加载

懒加载,顾名思义,在当前网页,滑动页面到能看到图片的时候再加载图片

故问题拆分成两个:

  1. 如何判断图片出现在了当前视口 (即如何判断我们能够看到图片)
  2. 如何控制图片的加载

方案一: 位置计算 + 滚动事件 (Scroll) + DataSet API

如何判断图片出现在了当前视口
clientTop,offsetTop,clientHeight 以及 scrollTop 各种关于图片的高度作比对

这些高度都代表了什么意思?

这我以前有可能是知道的,那时候我比较单纯,喜欢死磕。我现在想通了,背不过的东西就不要背了

所以它有一个问题:复杂琐碎不好理解!

所以只知道静态的还不够,还要知道动态的。

如何动态?监听 window.scroll 事件

如何控制图片的加载?

1
<img data-src="jojo.jpg" />

设置个临时的 data 属性 data-src,控制加载的时候使用 src 替代。利用 DataSet API 实现

1
img.src = img.datset.src;

方案二: getBoundingClientRect API + Scroll with Throttle + DataSet API

改进一下

如何判断图片出现在了当前视口
引入一个新的 API, Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。

判断出现在视口的代码如下:

1
2
// clientHeight 代表当前视口的高度
img.getBoundingClientRect().top < document.documentElement.clientHeight;

监听 window.scroll 事件也优化一下

1
_.throttle(func, [(wait = 0)], [(options = {})]);

方案三: IntersectionObserver API + DataSet API

再改进一下

如何判断图片出现在了当前视口
方案二使用的方法是: window.scroll 监听 Element.getBoundingClientRect() 并使用 _.throttle 节流

一系列组合动作太复杂了,于是浏览器出了一个三合一事件: IntersectionObserver API,一个能够监听元素是否到了当前视口的事件,一步到位!

事件回调的参数是 IntersectionObserverEntry (opens new window)的集合,代表关于是否在可见视口的一系列值

其中,entry.isIntersecting 代表目标元素可见

1
2
3
4
5
6
7
8
9
10
11
12
13
const observer = new IntersectionObserver((changes) => {
// changes: 目标元素集合
changes.forEach((change) => {
// intersectionRatio
if (change.isIntersecting) {
const img = change.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});

observer.observe(img);

方案四: LazyLoading 属性

方案二简单 demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>图片懒加载</title>
<style>
img {
width: 100%;
height: 600px;
}
</style>
</head>
<body>
<img
src="https://cdn.pixabay.com/photo/2021/08/24/15/38/sand-6570980_960_720.jpg"
alt="1"
/>
<img
src="https://cdn.pixabay.com/photo/2013/02/21/19/06/drink-84533_960_720.jpg"
alt="2"
/>
<img
data-src="https://cdn.pixabay.com/photo/2014/12/15/17/16/boardwalk-569314_960_720.jpg"
alt="3"
/>
<img
data-src="https://cdn.pixabay.com/photo/2013/07/18/20/26/sea-164989_960_720.jpg"
alt="4"
/>
<img
data-src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg"
alt="5"
/>
<img
data-src="https://cdn.pixabay.com/photo/2017/03/26/21/54/yoga-2176668_960_720.jpg"
alt="6"
/>
<img
data-src="https://cdn.pixabay.com/photo/2015/03/17/14/05/sparkler-677774_960_720.jpg"
alt="7"
/>
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js"></script>
<script>
const images = document.querySelectorAll("img");
const lazyLoad = () => {
images.forEach((item) => {
// 触发条件为img元素的CSSOM对象到视口顶部的距离 < 100px + 视口高度,+100px为了提前触发图片加载
if (
item.getBoundingClientRect().top <
document.documentElement.clientHeight + 100
) {
if ("src" in item.dataset) {
item.src = item.dataset.src;
}
}
});
};
document.addEventListener("scroll", _.throttle(lazyLoad, 200));
</script>
</body>
</html>

总结:图片懒加载有 4 种方式,首先最常用就是插件的懒加载,其次是位置计算加监视 scroll 事件结合 dataset api 替换图片路径。然后是 getBoundingClientRect 方法,监听滚动事件,对应 img 元素到视口顶部的距离小于视口高度的时候触发,并用上节流,接着是 IntersectionObserver 方法,他是上面操作的合体,他第一个参数接收元素的集合,然后遍历元素集合,如果元素.isIntersecting 目标元素可见的话,就用 dataset api 替换。

浏览器剪切板

  1. 第三方库
  2. Clipboard API
1
2
把内容输入到剪切板;
navigator.clipboard.writeText(text);
  1. Selection API/Range API
1
2
3
4
5
6
7
8
9
10
const selection = window.getSelection();
const range = document.createRange();

// RangeAPI: 制造区域
range.selectNodeContents(element);

// Selection: 选中区域
selection.addRange(range);

selectedText = selection.toString();

取消选择的代码如下

1
window.getSelection().removeAllRanges();

localhost:3000 与 localhost:5000 的 cookie 信息是否共享

根据同源策略,cookie 是区分端口的,但是浏览器实现来说,“cookie 区分域,而不区分端口,也就是说,同一个 ip 下的多个端口下的 cookie 是共享的!

CSRF

CSRF (Cross-site request forgery),跨站请求伪造,又称为 one-click attack,顾名思义,通过恶意引导用户一次点击劫持 cookie 进行攻击。

  1. 使用 JSON API。当进行 CSRF 攻击时,请求体通过
    构建,请求头为 application/www-form-urlencoded。它难以发送 JSON 数据被服务器所理解。
  2. CSRF Token。生成一个随机的 token,切勿放在 cookie 中,每次请求手动携带该 token 进行校验。
  3. SameSite Cookie。设置为 Lax 或者 Strict,禁止发送第三方 Cookie。

读取剪切板内容

1
2
3
4
5
6
// 是否能够有读取剪贴板的权限
// result.state == "granted" || result.state == "prompt"
const result = await navigator.permissions.query({ name: "clipboard-read" });

// 获取剪贴板内容
const text = await navigator.clipboard.readText();

json 转 demo.json 并下载

和 img 点击下载差不多

不过 json 有两种方法转,第一种是 DataURL,第二种是 Blob=>ObjectURL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function download(url, name) {
const a = document.createElement("a");
a.download = name;
a.href = url;
a.dispatchEvent(new MouseEvent("click"));
// a.click();
}
// let url = 'https://cdn.jsdelivr.net/gh/Zlinni/Pic/img/20220621084732.png';
// let name = 'test'
// download(url,name);
const json = {
a: 3,
b: 4,
c: 5,
};
// JSON.stringify(value[, replacer[, space]])
// 首行缩进
const str = JSON.stringify(json, null, 2);
const dataUrl = `data:,${str}`;
// download(dataUrl,"demo.json");
// new Blob(array,options);
const url = URL.createObjectURL(new Blob(str.split("")));
download(url, "demo.json");

总结:下载是利用 a 标签加模拟点击,json 使用是转 dataurl 或者 objecturl,注意 dataurl 的时候需要序列化,objecturl 的时候需要传入数组(简单 split 即可)

简单介绍 requestIdleCallback 及使用场景

requestIdleCallback 维护一个队列,在浏览器空闲的时间去执行。

白屏时间首屏时间的计算

白屏时间:window.performance.timing.domLoading - window.performance.timing.navigationStart
首屏时间:window.performance.timing.domInteractive - window.performance.timing.navigationStart

重排重绘

重排和重绘是关键渲染路径中的两步

重排 重新排列(元素位置发生变动) 代价极高

重绘 样式发生变动,但位置没有变化。此时在关键渲染路径中的 Paint 阶段,将渲染树中的每个节点转换成屏幕上的实际像素,这一步通常称为绘制或栅格化

重排必定造成重绘

  1. 使用 DocumentFragment 进行 DOM 操作,不过现在原生操作很少也基本上用不到
  2. CSS 样式尽量批量修改
  3. 避免使用 table 布局
  4. 为元素提前设置好高宽,不因多次渲染改变位置

DataURL 是什么

DataURL 是前缀为 data:协议的 URL,允许创建者向文档插入小文件,比如图片等。DataURL 由四个部分组成,

  1. 前缀 data:
  2. 指示数据类型的 MIME 类型。例如:image/jpeg 表示 jpeg 文件,如果省略,默认值为 text/plain;charset=US-SACII
  3. 非文本,可选 base64
  4. 数据

如何取消请求的发送

根据发送网络请求的 API 不同,取消方法不同

  • xhr
  • fetch
  • axios
    如果使用 XMLHttpRequest 发送请求可以使用 XMLHttpRequest.abort()
1
2
3
4
5
6
7
8
9
const xhr = new XMLHttpRequest(),
method = "GET",
url = "https://developer.mozilla.org/";
xhr.open(method, url, true);

xhr.send();

// 取消发送请求
xhr.abort();

如果使用 fetch 发送请求可以使用 AbortController

  1. 发送请求时使用一个 signal 选项控制 fetch 请求
  2. control.abort() 用以取消请求发送
  3. 取消请求发送之后会得到异常 AbortError
1
2
3
4
const controller = new AbortController();
const signal = controller.signal;
fetch("https://somewhere", { signal });
controller.abort();

如果使用 axios,取消原理同 fetch

1
2
3
4
5
6
7
8
var CancelToken = axios.CancelToken;
var source = CancelToken.source();

axios.get('https://somewhere', {
cancelToken: source.token
}

source.cancel()

而其中的原理可分为两部分

浏览器端: 基于 XHR,xhr.abort(),见源码 axios/lib/adapters/xhr.js(opens new window)
Node 端: 基于 http/https/follow-redirects,使用 request.abort(),见源码 axios/lib/adapters/http.js

textarea 如何禁止拉伸

1
2
3
textarea {
resize: none;
}

在 Canvas 中如何处理跨域的图片

1
img.setAttribute("crossOrigin", "anonymous");

HTML 标签有哪些行内元素

  • a
  • img
  • picture
  • span
  • input
  • textarea
  • select
  • label

什么是 URL 编码 (URL Encode)

encodeURI 用来编码 URI,其不会编码保留字符:;,/?😡&=+$

encodeURIComponent 用来编码 URI 参数,除了字符:A-Z a-z 0-9 - _ . ! ~ * ' ( ),都将会转义

浏览器 dom api

设置和删除 cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> 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
< ""

如何判断当前环境是移动端还是 PC 端

判断 navigator.userAgent,对于 Android/iPhone 可以匹配以下正则

1
2
3
4
5
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

1
2
3
import isMobile from "ismobilejs";

const mobile = isMobile();

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
isPc() {
var userAgentInfo = navigator.userAgent;
var Agents = new Array(
"Android",
"iPhone",
"SymbianOS",
"Windows Phone",
"iPad",
"iPod"
);
var flag = true;
flag = !Agents.some((ele) => {
return userAgentInfo.indexOf(ele) > 0;
});
return flag;
}

CSP 是干什么用的了

CSP 只允许加载指定的脚本和样式,最大限度的防止 xss 攻击。

设置 CSP,设置响应头:Content Security Policy

  1. 外部脚本可以通过指定域名来限制:Content-Security-Policy: script-src 'self',self 代表只加载当前域名
  2. 如果网站必须加载内联脚本 (inline script) ,则可以提供一个 nonce 才能执行脚本,攻击者则无法注入脚本进行攻击。Content-Security-Policy: script-src 'nonce-xxxxxxxxxxxxxxxxxx'

github 的 CSP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Content-Security-Policy: default-src 'none';
base-uri 'self';
block-all-mixed-content;
connect-src 'self' uploads.github.com www.githubstatus.com collector.githubapp.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events wss://alive.github.com;
font-src github.githubassets.com;
form-action 'self' github.com gist.github.com;
frame-ancestors 'none';
frame-src render.githubusercontent.com;
img-src 'self' data: github.githubassets.com identicons.github.com collector.githubapp.com github-cloud.s3.amazonaws.com *.githubusercontent.com;
manifest-src 'self';
media-src 'none';
script-src github.githubassets.com;
style-src 'unsafe-inline' github.githubassets.com;
worker-src github.com/socket-worker.js gist.github.com/socket-worker.js

常用指令集

指令值

使用:

  1. 通过 HTTP Header 来定义 :
    “Content-Security-Policy:” 策略集
  2. 通过 html meta 标签使用
1
<meta http-equiv="content-security-policy" content="策略集">

总结
CSP 是内容安全策略,用于阻止外部脚本的注入和运行,有效防止 xss 攻击。其中设置的方式是通过 nginx 设置,或者通过 meta 标签设置。其中指令有 default-src,script-src,style-src,img-src 等,定义某个东西的过滤策略。值有*,none,self,直接写地址等方式。来设置不被过滤的地址。

preload 和 prefetch 的区别

  1. preload 用于加载当前路由必须资源,优先级高。一般对于 bundle spliting 资源和 code spliting 资源做 preload。
  2. prefetch 优先级低,在浏览器 idle 状态加载资源。一般用来加载其他路由资源,如当页面出现 Link,可 prefetch 当前 Link 的路由资源。(next.js 默认会对 link 做懒加载+prefetch,即当某条 Link 出现页面中,即自动 prefetch 该 Link 指向的路由资源

fetch 中 crendentials 指什么意思,可以取什么值

credentials 指在使用 fetch 发送请求时是否应当发送 cookie

omit: 从不发送 cookie.
same-origin: 同源时发送 cookie (浏览器默认值)
include: 同源与跨域时都发送 cookie

当 cookie 没有设置 maxage 时,cookie 会存在多久

此时的 cookie 就是会话级别的了,浏览器一关闭就关闭了

如何找到当前页面出现次数最多的 HTML 标签

  1. document.querySelector('*')
  2. document.getElementsByTagName('*')

什么是层叠上下文 (stacking context)

我们假定用户正面向(浏览器)视窗或网页,而 HTML 元素沿着其相对于用户的一条虚构的 z 轴排开,层叠上下文就是对这些 HTML 元素的一个三维构想。众 HTML 元素基于其元素属性按照优先级顺序占据这个空间。

其中,z-index 会影响这个层级的优先性

如何实现页面文本不可复制

有 CSS 和 JS 两种方法,以下任选其一或结合使用

使用 CSS 如下:

user-select: none;
或使用 JS 如下,监听 selectstart 事件,禁止选中。

当用户选中一片区域时,将触发 selectstart 事件,Selection API 将会选中一片区域。禁止选中区域即可实现页面文本不可复制。

1
2
3
4
5
6
7
document.body.onselectstart = (e) => {
e.preventDefault();
};

document.body.oncopy = (e) => {
e.preventDefault();
};

异步加载 JS 脚本时,async 与 defer 有何区别

load 事件与 DomContentLoaded 事件的先后顺序

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载.

当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发 load 事件

vue\react 的路由实现原理

本质都是监听 url 的变化,实现方式有两种,一种是 hash 模式,一种是 history 模式,其中 hash 模式是默认的模式,兼容性好,当井号后面的哈希值变化时,通过 hashOnchange 事件监听然后页面跳转,无需刷新页面就能重新加载对应的页面。history 模式要后端配置,通过history.pushStatehistory.replaceState改变 url

前端实现文件上传

elementplus,或者原生的 input 标签,type 为 file 然后加点击事件

DOM 中如何阻止事件默认行为,如何判断事件否可阻止?

e.preventDefault阻止默认事件
e.cancelable是否可取消

什么是事件委托,e.currentTarget 与 e.target 有何区别

事件委托指的是大量事件触发的时候,将事件监听器绑在父元素进行监听。

e.currentTarget总是指向事件绑定的元素,e.target指的是触发的元素

浏览器中 cookie 有哪些字段

  • Domain
  • Path
  • Expire/MaxAge
  • HttpOnly
  • Secure
  • SameSite

SameSite Cookie 有哪些值,是如何预防 CSRF 攻击的

None:任何情况发
Lax:导航到第三方的 get 连接会发,跨域的图片,iframe,form 表单不会发
Strict:都不会发

目前主流浏览器的模式是 Lax,能够预防大部分的 CSRF

sessionStorage 与 localStorage 有何区别

localStorage 生命周期是永久除非自主清除 sessionStorage 生命周期为当前窗口或标签页,关闭窗口或标签页则会清除数据

他们均只能存储字符串类型的对象

不同浏览器无法共享 localStorage 或 sessionStorage 中的信息。相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享 sessionStorage 的信息。这里需要注意的是,页面及标 签页仅指顶级窗口,如果一个标签页包含多个 iframe 标签且他们属于同源页面,那么他们之间是可以共享 sessionStorage 的。

TypeArray/ArrayBuffer

https://zh.javascript.info/arraybuffer-binary-arrays

简述下 WebWorker,它如何进行通信

js 多线程通信,只能访问 navigator,setTimeout 和有限的 api

通过 onmessage 和 postmessage 通信,全局对象是 self

浏览器中监听事件函数 addEventListener 第三个参数有那些值

  • capture。监听器会在时间捕获阶段传播到 event.target 时触发。
  • passive。监听器不会调用 preventDefault()。
  • once。监听器只会执行一次,执行后移除。
  • singal。调用 abort()移除监听器。

浏览器中如何使用原生的 ESM

  • url 引入
    ···javascript
1
2
3
4
5
6
7
8
9
10
* importMap
```javascript
<script type="importmap">
{
"imports": {
"lodash": "https://cdn.skypack.dev/lodash",
"ms": "https://cdn.skypack.dev/ms"
}
}
</script>
  • importAssertion
1
2
3
4
5
<script type="module">
import data from "./data.json" assert { type: "json" };

console.log(data);
</script>