Q1、:设置小于12px的字体
使用缩放 transform:scale()方法即可以实现字号的缩放
注意:google浏览器的最小强制字体大小为12px,即使设置成 10px 最终都会显示成 12px
<body>
<p class="font">你好</p>
</body>
<style>
.font {
font-size: 12px;
transform: scale(0.5);
}
</style>
当我们直接对p元素执行缩放时我们发现不仅文字变小了,整个p元素都缩放了,这不是我们要的效果,所以我们需要改进
<body>
<p class="font"><span>你好</span></p>
</body>
<style>
.font span {
font-size: 12px;
transform: scale(0.5);
display: inline-block;
}
</style>
我们将需要缩小的文字用span标签包裹,由于span标签不能设置宽和高,不能实现缩放,因此我们需要将span转换成行内块元素,至此我们就可以解决小于12px的文字的设置了
这样修改后我们的p元素就不会缩放了
Q1总结:在div中包一个span,对span设置初识的font-size:12px,然后基于这个12px成比例缩放,并且注意一定要给span设置成行内块
Q2、rem、em、vw、vh 的值各是什么意思
em: 相对长度单位,相对于当前对象内文本的字体尺寸【是所在标签的字体大小】(参考物是父元素的font-size),
如当前父元素的字体尺寸未设置,则相对于浏览器的默认字体尺寸
特点:
- em的值并不是固定的,同一个页面中的em也会不同;
- em会继承父级元素的字体大小; rem: 是CSS3新增的一个相对单位,rem是相对于HTML根元素的字体大小(font-size)来计算的长度单位,如果你没有设置html的字体大小,就会以浏览器默认字体大小,一般是16px
(适用场景)
移动端开发
注:HTML根元素的含义
HTML文档的根元素是 html 元素,从 标签开始,到 标签结束。根元素的作用就是告诉浏览器,在 和 之间的内容是HTML类型,浏览器便按HTML进行解析其中的内容。
html{font-size: 62.5%} /* 10 ÷ 16 × 100% = 62.5% */
body{font-size: 1.4rem;} /* 1.4 × 10px = 14px */
/*在根元素中定义了一个基本字体大小为62.5%(也就是10px。设置这个值主要方便计算,如果没有设置,将是以“16px”为基准 )*/
注意:google浏览器的最小强制字体大小为12px,即使设置成 10px 最终都会显示成 12px,当把html的font-size设置成10px,子节点rem的计算还是以12px为基准。
优点是,只需要设置根目录的大小就可以把整个页面的成比例的调好
rem兼容性:除了IE8及更早版本外,所有浏览器均已支持rem em与rem的区别:
rem是相对于根元素(html)的字体大小,而em是相对于其父元素的字体大小
两者使用规则:
- 如果这个属性根据它的font-size进行测量,则使用em
- 其他的一切事物属性均使用rem 一般宽泛的讲是相对于父元素,但是并不是十分准确。
1、对于普通定位元素就是我们理解的父元素
2、对于position: absolute;的元素是相对于已定位的父元素
3、对于position: fixed;的元素是相对于 ViewPort(可视窗口)
Q3、:常⽤的meta标签有哪些
meta
元素往往不会引起用户的注意,但是meta
对整个网页有影响,会对网页能否被搜索引擎检索,和在搜索中的排名起着关键性的作用。
meta
有个必须的属性content
用于表示需要设置的项的值。
meta
存在两个非必须的属性http-equiv
和name
, 用于表示要设置的项。
比如<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
,设置的项是Content-Security-Policy
设置的值是upgrade-insecure-requests
。
一. http-equiv 属性
http-equiv
一般设置的都是与http
请求头相关的信息,设置的值会关联到http头部。也就是说浏览器在请求服务器获取html
的时候,服务器会将html
中设置的meta
放在响应头中返回给浏览器。常见的类型比如content-type
, expires
, refresh
, set-cookie
, window-target
, charset
, pragma
等等。
1. content-type
比如:<meta http-equiv="content-type" content="text/html charset=utf8">
可以用来声明文档类型、设字符集,目前content-type
只能在html文档中使用。
这样设置浏览器的头信息就会包含:
content-type: text/html charset=utf8
2. expires
用于设置浏览器的过期时间, 其实就是响应头中的expires属性。
<meta http-equiv="expires" content="31 Dec 2021">
expires:31 Dec 2008
3. refresh
该种设定表示5秒自动刷新并且跳转到指定的网页。如果不设置url的值那么浏览器则刷新本网页。
<meta http-equiv="refresh" content="5 url=http://www.zhiqianduan.com">
4. window-target
强制页面在当前窗口以独立页面显示, 可以防止别人在框架中调用自己的页面。
<meta http-equiv="window-target" content="_top'>
5. pragma
禁止浏览器从本地计算机的缓存中访问页面的内容
<meta http-equiv="pragma" content="no-cache">
二. name 属性
name
属性主要用于描述网页,与对应的content
中的内容主要是便于搜索引擎查找信息和分类信息用的, 用法与http-equiv
相同,name
设置属性名,content
设置属性值。
1. author
author
用来标注网页的作者
<meta name="author" content="aaa@mail.abc.com">
2. description
description
用来告诉搜素引擎当前网页的主要内容,是关于网站的一段描述信息。
<meta name="description" content="这是我的HTML">
3. keywords
keywords
设置网页的关键字,来告诉浏览器关键字是什么。是一个经常被用到的名称。它为文档定义了一组关键字。某些搜索引擎在遇到这些关键字时,会用这些关键字对文档进行分类。
<meta name="keywords" content="Hello world">
4. generator
表示当前html
是用什么工具编写生成的,并没有实际作用,一般是编辑器自动创建的。
<meta name="generator" content="vscode">
5. revised
指定页面的最新版本
<meta name="revised" content="V2,2015/10/1">
6. robots
告诉搜索引擎机器人抓取哪些页面,all / none / index / noindex / follow / nofollow
。
<meta name="robots" content="all">
all
:文件将被检索,且页面上的链接可以被查询; none
:文件将不被检索,且页面上的链接不可以被查询; index
:文件将被检索; follow
:页面上的链接可以被查询; noindex
:文件将不被检索,但页面上的链接可以被查询; nofollow
:文件将不被检索,页面上的链接可以被查询。
Q4、:script标签中defer和async的区别
当浏览器加载 HTML 并遇到<script>...</script>
标签时,它无法继续构建 DOM。它必须立即执行脚本。外部脚本<script src="..."></script>
也是如此:浏览器必须等待脚本下载,执行下载的脚本,然后才能处理页面的其余部分。
这导致一个重要问题:
- 如果页面顶部有一个庞大的脚本,它会“阻塞页面”。在下载并运行之前,用户无法看到页面内容
<p>...content before script...</p>
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!-- 以下在脚本加载完之前是不可见的 -->
<p>...content after script...</p>
有一个解决方法,就是把脚本放到最底部。
但是对于长 HTML 文档,这可能会有明显的延迟。
defer
defer
属性告诉浏览器不要等待脚本,浏览器会继续处理 HTML,构建 DOM。该脚本“在后台”加载,然后在 DOM 完全构建完成后再运行。
<p>...content before script...</p>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!-- 不等待脚本,立即显示 -->
<p>...content after script...</p>
另外,defer
脚本总是在 DOM 准备好时执行(但在DOMContentLoaded
事件之前)
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM fully loaded and parsed after defer!"));
</script>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<p>...content after scripts...</p>
- 页面内容立即显示。
DOMContentLoaded
事件处理程序等待defer脚本执行完之后执行
补充:(MDN)
当纯 HTML 被完全加载以及解析时,
DOMContentLoaded
事件会被触发,而不必等待样式表,图片或者子框架完成加载。
defer脚本保持相对顺序来执行,就像常规脚本一样
例如:我们有两个延迟脚本:long.js
和 small.js
:
<script defer src="https://javascript.info/article/script-async-defer/long.js"></script>
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>
这两个脚本会并行下载,small.js 可能会比long.js先下载完成,但是执行的时候依然会先执行 long.js
所以defer可用于对脚本执行顺序有严格要求的情况
async
async属性意味着该脚本是完全独立的:
-
浏览器不会阻止async脚本
-
其他脚本也不会等待async脚本,async脚本也不会等待其他脚本
-
DOMContentLoaded
和async脚本不会互相等待DOMContentLoaded
可能在async脚本执行之前触发(如果async脚本在页面解析完成后完成加载)- 或在async脚本执行之后触发(如果async脚本很快加载完成或在 HTTP 缓存中)
简单来说就是 async 脚本在后台加载完就立即运行
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM 完全加载以及解析"));
</script>
<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>
<p>...content after scripts...</p>
- 页面内容立即显示说明了:
async
不阻塞DOMContentLoaded
可能发生在async之前或之后- small.js先加载完就会在long.js之前执行,但如果long.js在之前有缓存,那么long.js先执行。
应用场景:将独立的第三方脚本集成到页面中时,比如计数器,广告等。
注意:async和defer属性都仅适用于外部脚本,如果script标签没有src属性,尽管写了async、defer属性也会被忽略
Q5、:什么是重绘与回流,如何减少重绘与回流
重绘
当 DOM 的修改导致了样式的变化
,并且没有影响几何属性的时候
,会导致重绘
(repaint
)。
回流
元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染
- 如添加或删除可见的DOM元素;
- 元素的位置发生变化;
- 元素的尺寸发生变化;
- 内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代);
- 页面一开始渲染的时候(这个无法避免);
- 因为回流是根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流......
第一次渲染完成后,基于JS改变了 元素的位置或者大小,浏览器需要重新计算渲染树中 每一个元素在视口中的 位置和大小
=> 这个阶段就是=> 重新布局 / 回流 / 重排 / reflow
回流
。回流
也叫重排
。简单来说,就是当我们对 DOM 结构的修改引发 DOM 几何尺寸变化
的时候,会发生回流
的过程。比如以下情况
- 一个 DOM 元素的几何属性变化,常见的几何属性有
width
、height
、padding
、margin
、left
、top
、border
等等, 这个很好理解。 - 使 DOM 节点发生
增减
或者移动
。 - 读写
offset
族、scroll
族和client
族属性的时候,浏览器为了获取这些值,需要进行回流操作。 - 调用
window.getComputedStyle
方法。
二、避免DOM的回流
1、放弃传统操作 DOM 的时代,基于 vue/react 开始数据影响视图模式
- mvvm / mvc / virtual dom / dom diff ......
vue / react 数据驱动思想 :
我们自己不操作DOM,我们只操作数据,让框架帮我们根据数据渲染视图(框架内部本身对于DOM的回流和重绘以及其它性能优化做的非常好)
2、分离读写操作(现代浏览器的渲染队列的机制)(重要)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<ul id="item">
<!-- <li>我是第1个LI</li> -->
</ul>
</body>
</html>
- 在老版本的浏览器中,我们分别改变了三次样式(都涉及了位置或者大小的改变),所以触发三次回流和重绘
// SCRIPT在DOM结构末尾导入,可以直接使用元素的ID代表这个元素对象
box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
//=> 在老版本的浏览器中,我们分别改变了三次样式(都涉及了位置或者大小的改变),所以触发三次回流和重绘
- 现代浏览器中默认增加了“渲染队列的机制”,以此来减少DOM的回流和重绘
=> 遇到一行修改样式的代码,先放到渲染队列中,继续看 下面一行代码 是否还为修改样式的,如果是继续增加到渲染队列中...直到下面的代码不再是修改样式的,而是获取样式的代码!此时不再向渲染队列中增加,把之前渲染队列中要修改的样式一次性渲染到页面中,引发一次DOM的回流和重绘
// 一次回流重绘
box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
所以我们利用这种机制来减少DOM的回流
// 三次回流和重绘
box.style.width = '200px';
console.log(box.style.width); //=>中断渲染队列,立即渲染一次,引发一次DOM回流和重绘 200px
box.style.height = '200px';
console.log(box.offsetHeight);
box.style.margin = '20px';
// 一次回流重绘
box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
console.log(box.style.width);
console.log(box.offsetHeight);
但是有的时候我们还需要浏览器多次回流:例如:先设置动画,渲染后,在去改变样式,让其有动画效果
//=> 此时动画并没有体现
box.style.transition = '.3s';
box.style.width = '200px';
box.style.height = '200px';
// 手动不分离读写的需求:先设置动画,渲染后,在去改变样式,让其有动画效果
box.style.transition = '.3s';
let AA = box.offsetHeight;
box.style.width = '200px';
box.style.height = '200px';
3、样式集中改变(不重要)
- 通过修改样式类:把样式实现写好,我们后期通过样式类修改样式
- 用cssText的方式添加想要修改的样式
//=> 通过修改样式类:把样式实现写好,我们后期通过样式类修改样式
// 一次回流重绘
box.className = 'active';
//=> 把所有想写的样式,用cssText的方式添加
// 一次回流重绘
box.style.cssText = 'width:200px;height:200px;';
4、缓存布局信息(不重要)
把要操作的内容一次都拿到,然后用变量存储,想设置的时候直接拿变量值即可,不用在重新获取了,和分离读写的原理类似
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
=> 改为
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
5、元素批量修改(重要)
DOM的增加也会引起回流重绘
/* 在动态操作DOM结构中的优化(例如:数据绑定) */
for (let i = 1; i <= 5; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
item.appendChild(liBox);
//=>每一次向页面中增加,都会触发一次DOM的回流和重绘(5次)
}
- 文档碎片:临时创建的一个存放文档的容器,我们可以把新创建的LI,存放到容器中,当所有的LI都存储完,我们统一把容器中的内容增加到页面中(只触发一次回流)
// 文档碎片:临时创建的一个存放文档的容器,我们可以把新创建的LI,存放到容器中,当所有的LI都存储完,我们统一把容器中的内容增加到页面中(只触发一次回流)
let frag = document.createDocumentFragment();
for (let i = 1; i <= 5; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
frag.appendChild(liBox);
}
item.appendChild(frag);
- 字符串拼接:项目中,有一个文档碎片类似的方式,也是把要创建的LI事先存储好,最后统一放到页面中渲染(字符串拼接)
// 项目中,有一个文档碎片类似的方式,也是把要创建的LI事先存储好,最后统一放到页面中渲染(字符串拼接)
let str = ``;
for (let i = 1; i <= 5; i++) {
str += `<li>我是第${i}个LI</li>`;
}
item.innerHTML = str;
6、动画效果应用到 position 属性为 absolute 或 fixed 的元素上(脱离文档流)
- 也会引起回流重绘,只不过从新计算过程中,因为他脱离文档流了,不会对其他元素产生影响,重新计算的过程中比较快一点
7、CSS硬件加速(GPU加速)
- 比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘:transform/opacity/filters......这些属性会触发硬件加速,不会引发回流和重绘......
- 可能会引发坑:过多使用会占用大量内存,性能消耗严重,有时候会导致字体模糊等
8、牺牲平滑度换取速度
- 每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与回流做东征。每次移动3像素可能开起来平滑度低了,但它不会导致CPU在较慢的机器中抖动
9、避免 table 布局和使用 css 的javascript表达式
Q6、:网站开发中,如何实现图片的懒加载
实现思路
-
在 img 标签上自定义一个属性 data-src,用于存放真正需要显示的图片路径,而 img 的 src 属性上放一张大小为 1 * 1px 的图片路径,或者为空。
-
当页面滚动直至某个图片出现在可视区域时,就通过 js 获取该图片的 data-src 的值,然后赋给它的 src。
方案一
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>图片懒加载</title>
<style>
img {
width: 100%;
height: 700px;
}
</style>
</head>
<body>
<!-- 网图链接若是失效,可自行替换 -->
<img src="https://pic.netbian.com/uploads/allimg/211120/005250-1637340770973a.jpg" alt="1" />
<img src="https://pic.netbian.com/uploads/allimg/210920/165135-16321278956369.jpg" alt="2" />
<img data-src="https://pic.netbian.com/uploads/allimg/211122/223735-1637591855c6b1.jpg" alt="3" />
<img data-src="https://pic.netbian.com/uploads/allimg/211021/232902-16348301422b2a.jpg" alt="4" />
<img data-src="https://pic.netbian.com/uploads/allimg/211109/221940-1636467580f68a.jpg" alt="5" />
<!-- Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
const imgs = document.querySelectorAll('img');
const imgList = Array.prototype.slice.call(imgs);
const clientHeight = document.documentElement.clientHeight; // 视口高度
const lazyLoad = () => {
imgList.forEach(item => {
// 判断图片出现在了当前视口的条件:
// img元素的 CSSOM 对象到视口顶部的距离 < 视口高度 + 100px,加上 100px 是为了提前触发图片加载
if (item.getBoundingClientRect().top < clientHeight + 100) {
if ('src' in item.dataset) {
item.src = item.dataset.src;
}
}
});
};
// 使用节流器(throttle),提高性能
document.addEventListener('scroll', _.throttle(lazyLoad, 200));
</script>
</body>
</html>
相关知识点:
- Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。
- Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库(非常强大)。这里仅使用其封装的节流方法——
throttle
,来提高性能。
若是对函数的节流与防抖比较感兴趣的话,不妨看看本人写的文章。
方案二
const imgs = document.querySelectorAll('img');
const imgList = Array.prototype.slice.call(imgs);
const clientHeight = document.documentElement.clientHeight; // 视口高度
const lazyLoad = () => {
// 滚动距离
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
imgList.forEach(item => {
// 判断图片出现在了当前视口的条件:
// 图片到浏览器顶部的距离 < 视口高度 + 滚动距离
if (item.offsetTop < clientHeight + scrollTop && !item.src) {
item.src = item.dataset.src;
}
});
};
// 使用节流器(throttle),提升性能
document.addEventListener('scroll', _.throttle(lazyLoad, 200));
同方案一相比,方案二代码主要是对 lazyLoad
函数进行了一些更改,其它基本不变。在这个函数中不在使用 getBoundingClientRect()
方法,而是通过 HTMLElement.offsetTop 和 document.documentElement.scrollTop 来实现图片的懒加载。
方案三
Intersection Observer API 提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。通过这个 API 也可以实现图片懒加载,但是它的兼容性不是很好。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>图片懒加载</title>
<style>
.img-item {
width: 100%;
height: 700px;
}
.img-item > img {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="img-item">
<img data-src="https://pic.netbian.com/uploads/allimg/211120/005250-1637340770973a.jpg" alt="1" />
</div>
<div class="img-item">
<img data-src="https://pic.netbian.com/uploads/allimg/210920/165135-16321278956369.jpg" alt="2" />
</div>
<div class="img-item">
<img data-src="https://pic.netbian.com/uploads/allimg/211122/223735-1637591855c6b1.jpg" alt="3" />
</div>
<div class="img-item">
<img data-src="https://pic.netbian.com/uploads/allimg/211021/232902-16348301422b2a.jpg" alt="4" />
</div>
<script>
const imgs = document.querySelectorAll('img');
const imgList = Array.from(imgs);
const options = {
root: null,
rootMargin: '0px',
threshold: [0.15],
};
const ob = new IntersectionObserver(imgs => {
// imgs 为目标元素集合
imgs.forEach(item => {
// isIntersecting 代表目标元素可见
if (item.isIntersecting) {
console.log(item, '显示');
const img = item.target;
img.src = img.dataset.src;
ob.unobserve(img); // 停止对一个元素的观察
}
});
}, options);
imgList.forEach(item => {
// 可见性区域被监控的元素。此元素必须是根元素的后代 (如果根元素为视窗,则该元素必须被当前文档包含)。
ob.observe(item);
});
</script>
</body>
</html>
注意,dom结构中,我们给每个 img 元素添加一个父级元素并设置宽高。其目的,是因为加载 img 元素时,若是其 src 属性是空或不存在,就会变成一个非常小的正方形图标。这样的话,所有待加载的 img 就全部出现在可视区域之中,其结果就是加载所有图片而不是进行懒加载。
看下面这段代码:
imgList.forEach(item => {
ob.observe(item);
});
这段代码就是为了监控每个出现在可视区域的元素以便执行回调,若是所有元素都出现在可视区域,那么它将性触发所有元素的回调,即加载所有的图片。
Q7、:什么是 Data URL
Q8、:a标签下载文件
给a标签加一个download属性 可以设置下载下来的文件的文件名
<a href="URL" download="文件名"> //download属性也可以设置一个值来规定下载文件的名称。所允许的值没有限制,浏览器将自动检测正确的文件扩展名并添加到文件
注意:只有 Firefox 和 Chrome 支持 download 属性。href的属性地址必须是和你前端同源情况下download才会起作用,如果涉及跨域情况下,download将不会起作用
如果涉及跨域问题,可以采用axios获取文件流下载文件
Axios.get(url,{responseType:'blob'}).then(res=>{
const blob = new Blob([res.data])
let a = document.createElement('a')
a.href=URL.createObjectURL(blob)
a.download = fileName
a.click()
})
Q9、:link和@import的区别
1)link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。
2)link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
3)link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
4)link支持使用Javascript控制DOM去改变样式;而@import不支持。
Q10、:有没有使用过 css variable,less, sass
Q11、:CSS 如何设置一行超出显示省略号
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
Q12、:CSS 如何设置多行超出显示省略号
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
Q13、:用过reset.css,主要有哪些作用
重置浏览器自带样式
Q14、:css 加载会阻塞 DOM 树的解析和渲染吗
浏览器的渲染
浏览器的渲染流程如下:
图:WebKit 主流程
图:Mozilla 的 Gecko 呈现引擎主流程(3.6)
结合上图,一个完整的渲染流程如下:
- 渲染进程解析 HTML 内容转换为能够读懂的 DOM 树结构,解析 CSS 为 CSSDOM
- 把 DOM 和 CSSOM 结合起来生成渲染树(Render Tree)
- 渲染树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标
- 把渲染树展示到屏幕上。再下一步就是绘制,即遍历渲染树,并使用UI后端层绘制每个节点。
值得注意的是:
关键的点在于上述的 4 步并不是以严格顺序执行的。渲染引擎会以最快的速度展示内容,也就是说,浏览器一边解析 HTML,一边构建渲染树,构建一部分,就会把当前已有的元素渲染出来。如果这个时候外部样式并没有加载完成,渲染出来的就是浏览器默认样式了。
其它阶段也是如此。由于浏览器会尝试尽快展示内容,所以内容有时会在样式还没有加载的时候展示出来。这就是经常发生的FOCU(flash of unstyled content)或白屏问题。
CSS 加载不会阻塞 DOM 树的解析
由浏览器的渲染流程图可知,DOM 解析和 CSS 解析是两个并行的进程,所以 CSS 加载不会阻塞 DOM 树的解析
CSS 加载会阻塞 DOM 树的渲染
Render Tree是依赖于 DOM Tree 和 CSSOM Tree 的,所以无论 DOM Tree 是否已经完成,它都必须等待到 CSSOM Tree 构建完成,即 CSS 加载完成(或 CSS 加载失败)后,才能开始渲染。
因此,CSS加载是会阻塞 DOM 树的渲染
<head>
<script>
document.addEventListener('DOMContentLoaded', () => {
var p = document.querySelector('p')
console.log(p)
})
</script>
<link rel="stylesheet" href="./static/style.css?sleep=3000">
</head>
<body>
<p>hello world</p>
</body>
案例来源:关于 JS 与 CSS 是否阻塞 DOM 的渲染和解析
CSS 的加载并没有阻塞 DOM 树的解析,p 标签是正常解析的,但 p 标签加载完后,页面是迟迟没有渲染的,是因为 CSS 还没有请求完成,在 CSS 请求完成后,hello world 才被渲染出来,所以 CSS 会阻塞页面渲染
DOMContentLoaded:只有当纯 HTML 被完全加载以及解析时,DOMContentLoaded 事件会被触发,它不会等待样式表,图片或者子框架完成加载
CSS 加载会阻塞其后的 JS 执行
由浏览器的渲染流程图可知,JS 的加载、解析与执行会阻塞 DOM 的构建,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JS,那么它会暂停构建 DOM ,将控制权移交给JS引擎,等 JS 引擎运行完毕,浏览器再从中断的地方恢复 DOM 构建。
这也是建议将 script 标签放在 body 标签底部的原因。
由浏览器的渲染流程图可知,DOM 和 CSSOM 的构建是互不影响,但如果在 JS 脚本前引入外部 CSS 文件喃?
<html>
<head>
<link href="theme.css" rel="stylesheet">
</head>
<body>
<div>hello world</div>
<script>
console.log('hello world')
</script>
<div>hello world</div>
</body>
</html>
它的执行流程:
此时 CSS 也阻塞 DOM 的生成
这是因为 JS 不只是可以改 DOM ,它还可以更改样式,也就是它可以更改 CSSOM 。而不完整的 CSSOM 是无法使用的, JS 中想访问 CSSOM 并更改它,那么在执行 JS 时,必须要能拿到完整的CSSOM。
所以就导致了一个现象,如果浏览器尚未完成 CSSOM 的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和 DOM 构建,直至其完成 CSSOM 的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建 CSSOM ,然后再执行JS脚本,最后在继续构建 DOM 。
如果也有 JS 加载喃?
<head>
<script>
document.addEventListener('DOMContentLoaded', () => {
var p = document.querySelector('p')
console.log(p)
})
</script>
<link rel="stylesheet" href="./static/style.css?sleep=3000">
<script src="./static/index.js"></script>
</head>
<body>
<p>hello world</p>
</body>
HTML 文件中包含了 CSS 的外部引用和 JS 外部文件,HTML 同时发起这两个文件的下载请求,不管 CSS 文件和 JS 文件谁先到达,都要先等到 CSS 文件下载完成并生成 CSSOM,然后再执行 JavaScript 脚本,最后再继续构建 DOM,构建布局树,绘制页面。
所以一般将 <script>
放在 <link>
标签前面
如何优化渲染流程
即如何减少白屏时间?
- 使用内联 JS、CSS ,减少 JS 、 CSS 文件的下载
- webpack 等工具对 JS、CSS 文件压缩,减少文件大小
- 使用 async 或者 defer
- 使用 CDN 等
Q15、position的属性有哪些,区别是什么,fixed
positon
定义和用法:position 属性规定元素的定位类型。
说明:这个属性定义建立元素布局所用的 定位机制 。任何元素都可以定位,不过绝对或固定元素会生成一个块级框,而不论该元素本身是什么类型。相对定位元素会相对于它在正常流中的默认位置偏移。
—— 来自 w3school
position
有以下可选值:
值 | 描述 |
---|---|
absolute | 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。 元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。 |
fixed | 生成绝对定位的元素,相对于浏览器窗口进行定位。 元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。 |
relative | 生成相对定位的元素,相对于其正常位置进行定位。 因此,"left:20" 会向元素的 left 位置添加 20 像素。 |
sticky | CSS3 新增,粘性定位,相对于最近的一个拥有“滚动机制”的祖先上(当该祖先的overflow 是 hidden, scroll, auto 或 overlay时,即不是 visible 时)。 它的行为就像 position:relative 而当页面滚动超出目标区域时,它的表现就像 position:fixed,它会固定在目标位置。 |
static | 默认值。没有定位,元素出现在正常的流中 (忽略 left、top、right、bottom 或者 z-index 声明)。 |
inherit | 规定应该从父元素继承 position 属性的值。 |
其中,CSS 定位机制:
CSS 有三种基本的定位机制:普通流、浮动和绝对定位。
除非专门指定,否则所有框都在普通流中定位。也就是说,普通流中的元素的位置由元素在 (X)HTML 中的位置决定。
块级框从上到下一个接一个地排列,框之间的垂直距离是由框的垂直外边距计算出来。
行内框在一行中水平布置。可以使用水平内边距、边框和外边距调整它们的间距。但是,垂直内边距、边框和外边距不影响行内框的高度。由一行形成的水平框称为行框(Line Box) ,行框的高度总是足以容纳它包含的所有行内框。不过,设置行高可以增加这个框的高度。
position: absolute;
绝对定位 ,绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那么它的位置相对于 <html>
:
<div class="one">one</div>
<div class="two">two</div>
<div class="three">three</div>
div {
width: 100px;
height: 100px;
}
.one {
background: red;
}
.two {
background: yellow;
position: absolute;
top: 50px;
left: 50px;
}
.three {
background: green;
}
position: fixed;
固定定位 ,与绝对定位相似,但元素的包含块为 viewport 视口。该定位方式常用于创建在滚动屏幕时仍固定在相同位置的元素。在下面的示例中,"one" 元素定位在离页面顶部 80px,离页面左侧 20px 的位置。
<div class="an">
<div class="one">one</div>
<div class="two">two</div>
</div>
.an {
width: 500px;
height: 300px;
overflow: scroll;
background: indianred;
}
.one {
position: fixed;
top: 50px;
left: 50px;
background: red;
width: 100px;
height: 100px;
color: white;
}
.two {
background: yellow;
height: 500px;
}
position: relative;
相对定位 ,相对于其正常位置进行定位,不影响其他元素的偏移。
<div class="one">one</div>
<div class="two">two</div>
<div class="three">three</div>
div {
width: 100px;
height: 100px;
color: white;
}
.one {
background: red;
}
.two {
background: yellow;
position: relative;
top: 50px;
left: 50px;
}
.three {
background: green;
}
position: sticky;
粘性定位 ,可以被认为是相对定位和固定定位的混合。元素在跨越特定阈值前为相对定位,之后为固定定位
这是一个结合了 position:relative
和 position:fixed
两种定位功能于一体的特殊定位。常见的吸顶、吸底(头部返回栏,底部切换栏等)的效果都是使用这个属性:
注意:
- 须指定
top
、right
、bottom
、left
四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。 并且top
和bottom
同时设置时,top
生效的优先级高,left
和right
同时设置时,left
的优先级高。 - 设定为
position:sticky
元素的任意父节点的overflow
属性必须是visible
,否则position:sticky
不会生效。如果position:sticky
元素的任意父节点定位设置为overflow:hidden
,则父容器无法进行滚动,所以position:sticky
元素也不会有滚动然后固定的情况。如果position:sticky
元素的任意父节点定位设置为position:relative | absolute | fixed
,则元素相对父元素进行定位,而不会相对viewport
定位。 - 达到设定的阀值,也就是设定了
position:sticky
的元素表现为relative
还是fixed
是根据元素是否达到设定了的阈值决定的。
position: static;
静态定位 ,HTML 元素默认情况下的定位方式为 static(静态),静态定位的元素不受 top、bottom、left 和 right 属性的影响,它始终根据页面的正常流进行定位
position: inherit;
inherit
值如同其他 css
属性的 inherit
值,即继承父元素的 position
值。
Q16、两个div上下排列,都设margin,有什么现象?
外边距会重合
- 两个正数的外边距取最大的边距作为外边距。
- 如果一个为正数,一个为负数,最终的外边距为两者之和。
- 如果两个值都是负数的话,最终的外边距取绝对值最大的值。
外边距重叠问题
外边距重叠只发生在块级元素上,例如 <div>
<p>
等,并不会发生在脱离文档流的元素上,例如:设置 float
属性,或者 position
的值为 absolute
时。
发生外边距重叠的情况
-
相邻兄弟元素的外边距重叠
示例代码如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>相邻兄弟元素的外边距重叠</title> <style> .box { width: 600px; height: 100px; line-height: 50px; text-align: center; color: #333; } .box1 { background-color: #f60; margin-bottom: 100px; } .box2 { background-color: #a90; margin-top: 100px; } </style> </head> <body> <div class="box box1">box1</div> <div class="box box2">box2</div> </body> </html>
如果按照我们正常的思维逻辑的话
box1
和box2
中间的边距应该是200px
,但事实并不是这样的,如下图事实上,目前就已经出现了外边距重叠问题。
-
父级与子级的外边距重叠
示例代码如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>父级与子级的外边距重叠</title> <style> .box { line-height: 50px; text-align: center; color: #333; } .parent { width: 600px; height: 100px; background-color: #f60; margin-top: 100px; } .child { width: 600px; height: 50px; background-color: #a90; margin-top: 50px; } </style> </head> <body> <div class="box parent"> <div class="box child">child</div> </div> </body> </html>
如果仅仅只看我们的代码的话,父级的
margin-top
为100px
,子级的margin-top
为50px
,,如果相加的话为150px
,但实际结果如下所示:出现外边距重叠问题,需满足以下条件中的任意一项才能实现
margin-top
重叠- 父级元素不是 BFC 元素。
- 父元素没有
border-top
属性 - 父元素没有
padding-top
属性 - 父元素与第一个子元素之间没有 行内元素 进行分割。
margin-bottom
重叠- 父级元素不是 BFC 元素。
- 父元素没有
border-top
属性 - 父元素没有
padding-top
属性 - 父元素与第一个子元素之间没有 行内元素 进行分割。
- 父元素没有高度限制
-
空
block
元素的外边距重叠示例代码如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>空 block 元素的外边距重叠</title> <style> .parent { width: 600px; height: 10px; background-color: #f60; } .child { margin: 50px 0; } </style> </head> <body> <div class="parent"> <div class="child"></div> </div> </body> </html>
代码结果如下所示:
子级元素的外边距与
body
的8px
的margin
进行了重叠。空 block 元素外边距重叠触发条件必须满足如下
- 元素没有
border
属性 - 元素没有
padding
属性 - 里面没有
inline
元素 - 没有设置高度。
- 元素没有
Q17、CSS 预处理器 less
Q18、::before 和 :after中双冒号和单冒号有什么区别?解释一下这2个伪元素的作用
两个的作用一致.都是创建伪元素
单冒号是旧语法,双冒号是新语法用来区别伪类:hover,:active,:nth-child(n)...这些
作用:
-
::before 和::after 的主要作用是在元素内容前后加上指定内容伪类与伪元素都是用 于向选择器加特殊效果
-
伪类与伪元素的本质区别就是是否抽象创造了新元素
-
伪类只要不是互斥可以叠加使用 伪元素在一个选择器中只能出现一次,并且只能出现在开始和末尾
-
伪类与伪元素优先级分别与类、标签优先级相同
Q19、使用 CSS 如何画一个三角形
Q20、如何取消请求的发送
CancelToken
Q21、bind 与 call/apply 的区别是什么
Q22、cookie 有哪些字段
Q23、数组的常用方法
filter(),push(),splice(),indexOf(),forEach(),pop()
Q24、说说==和===,&和&&、|和||的区别,
1.&&
运算符
&&
可以叫逻辑与,在其操作数中找到第一个虚值表达式并返回它,如果没有找到任何虚值表达式,则返回最后一个真值表达式。它采用短路来防止不必要的工作。
console.log(false && 1 && []);//false
//' '这里不是空字符串,里面有空格
console.log(' '&& true && 5);//5
使用if
语句
const router: Router = Router();
router.get('/endpoint', (req: Request, res: Response) => {
let conMobile: PoolConnection;
try {
//do some db operations
} catch (e) {
if (conMobile) {
conMobile.release();
}
}
});
使用&&
操作符
const router: Router = Router();
router.get('/endpoint', (req: Request, res: Response) => {
let conMobile: PoolConnection;
try {
} catch (e) {
conMobile && conMobile.release()
}
});
2.||
运算符
||
可以叫逻辑或,在其操作数中找到第一个真值表达式并返回它。这也使用了短路来防止不必要的工作。在支持 ES6 默认函数参数之前,它用于初始化函数中的默认参数值。
console.log(null || 1 || undefined); // 1
function logName(name) {
var n = name || "Neck";
console.log(n);
}
logName(); //Neck
3.!!
运算符
!!
运算符可以将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法。
首先介绍!
,!
一般是取反
console.log(!null);//true
console.log(!false);//true
console.log(!0);//true
console.log(!'');//true
console.log(!undefined);//true
console.log(!true);//false
正常判断对象不能为空
if(a!=null&&typeof(a)!=undefined&&a!=''){
//a不为空
}
!!
用法
if(!!a){
//a不为空
}
使用Boolean()
强制转换为布尔值
console.log(Boolean(null));//false
console.log(Boolean(undefined));//false
console.log(Boolean(''));//false
console.log(Boolean(0));//false
console.log(Boolean(NaN));//false
console.log(Boolean(' '));//true
console.log(Boolean({}));//true
console.log(Boolean([]));//true
console.log(Boolean(1));//true
console.log(Boolean([].length));//false
使用!!
方法强制转换为布尔值
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!''); // false
console.log(!!0); // false
console.log(!!NaN); // false
console.log(!!' '); // true
console.log(!!{}); // true
console.log(!![]); // true
console.log(!!1); // true
console.log(!![].length); // false
通过上面的对比,明显可以看出!!
运算符的简单便捷
4.==
和===
运算符
==
用于一般比较,===
用于严格比较,==
在比较的时候可以转换数据类型,===
严格比较,只要类型不匹配就返回flase。
==
运算符
强制是将值转换为另一种类型的过程。在这种情况下,==会执行隐式强制。在比较两个值之前,==需要执行一些规则。 假设我们要比较x == y
的值。
- 如果
x
和y
的类型相同,则 JS 会换成===
操作符进行比较。 - 如果
x
为null
,y
为undefined
,则返回true
。 - 如果
x
为undefined
且y
为null
,则返回true
。 - 如果
x
的类型是number
,y
的类型是string
,那么返回x == toNumber(y)
。 - 如果
x
的类型是string
,y
的类型是number
,那么返回toNumber(x) == y
。 - 如果
x
为类型是boolean
,则返回toNumber(x)== y
。 - 如果
y
为类型是boolean
,则返回x == toNumber(y)
。 - 如果
x
是string
、symbol
或number
,而y
是object类型,则返回x == toPrimitive(y)
。 - 如果
x
是object
,y
是string
,symbol
则返回toPrimitive(x) == y
。 - 剩下的 返回
false
例子
x | y | x == y | 符合条件 |
---|---|---|---|
6 | 6 | true | 条件1 类型相同 |
6 | '6' | true | 条件4 |
null | undefined | true | 条件2 |
0 | false | true | 条件7 y为boolean类型 |
'6,7' | [1,2] | true | 条件8 使用toString()方法将数组转换为字符串,该方法返回1,2 |
'[object Object]' | {} | true | 条件8 使用toString()方法将对象转换为字符串,该方法返回[object Object] |
console.log(6 == 6);//true
console.log(6 =='6');//true
console.log(null == undefined);//true
console.log(0 == false);//true
console.log('5,6' == [5,6]);//true
console.log('[object Object]' == {});//true
===
运算符
x | y | x === y | 说明 |
---|---|---|---|
6 | 6 | true | 值和类型都相同 |
6 | '6' | false | 值和类型都不相同 |
null | undefined | false | 值和类型都不相同 |
0 | false | false | 值和类型都不相同 |
'6,7' | [1,2] | false | 值和类型都不相同 |
'[object Object]' | {} | false | 值和类型都不相同 |
使用===
运算符,则第一个示例以外
的所有比较将返回false
,因为它们的类型不同,而第一个示例将返回true
,因为两者的类型和值相同。
console.log(6 === 6);//true
console.log(6 ==='6');//false
console.log(null === undefined);//false
console.log(0 ===false);//false
console.log('5,6' === [5,6]);//false
console.log('[object Object]' === {});//false
let a = { a: 1 };
let b = { a: 1 };
let c = a;
console.log(a === b); // 打印 false,即使它们有相同的属性
console.log(a === c); // true
JS 以不同的方式比较对象和基本类型。在基本类型中,JS 通过值对它们进行比较,而在对象中,JS 通过引用或存储变量的内存中的地址对它们进行比较
。这就是为什么第一个console.log
语句返回false
,而第二个console.log
语句返回true
。a
和c
有相同的引用地址,而a
和b
没有。
Q25、Json,JSON.stringify(),JSON.pares()
Q26、前端如何实现文件上传功能
<input
type="file"
ref="referenceUpload"
/>
Q27、JS 如何实现一个 sleep/delay 函数
1.目标分析
既然我们要去实现一个 sleep
函数,那么我们肯定要先有一个比较实际的场景,这样才好开展工作。
假设我们有如下一段代码:
<script>
function fnA() {
console.log('A');
}
function fnB() {
console.log('B');
}
function fnC() {
console.log('C');
}
// 实现目标
fnA(); // 1 秒后打印
fnB(); // 2 秒后打印
fnC(); // 3 秒后打印
</script>
上段代码非常简单,我们的目的就是为了让它们几个分别间隔 1
秒打印,需求非常简单,实现起来也很容易,可能有些小伙伴直接想到了 setTimeout
。
确实,setTimeout
可以实现我们的需求,比如下面的代码:
setTimeout(fnA, 1000);
setTimeout(fnB, 2000);
setTimeout(fnC, 3000);
定时器确实可以满足我们的需求,但是如果项目中到处些定时器的可能会让人很疑惑,所以我们有必要进行封装,写一个自己的 sleep
函数,大家多看几种实现方式应该就会豁然开朗了。
2.setTimeout
封装
这是大家最容易想到也是最暴力的一种方式,毕竟一提到延时执行大家都会想到定时器,所我们直接利用 setTimeout
的这个特性来实现我们的 sleep
函数。
代码如下:
<script>
function fnA() {
console.log('A');
}
function fnB() {
console.log('B');
}
function fnC() {
console.log('C');
}
// sleep 函数
function sleep(fun, time) {
setTimeout(() => {
fun();
}, time);
}
sleep(fnA, 1000); // 1 秒后输出 A
sleep(fnB, 2000); // 2 秒后输出 B
sleep(fnC, 3000); // 3 秒后输出 C
</script>
这是最原始的一种方式,其实本质就是定时器,只不过我们封装成一个函数罢了。
这种实现方式有如下优缺点:
优点:
简单易实现,兼容性好,毕竟只是用了 setTimeout
,而且非常好理解。
缺点:
我们需要传入回调函数的方式进去,如果函数里面有多回调函数可能不太好理解。另外一点就是它不会阻塞同步任务,比如下面代码的输出结果:
sleep(fnA, 1000);
console.log('E');
sleep(fnB, 2000);
console.log('G');
sleep(fnC, 3000);
输出结果:
在有些场景下我们可能需要 sleep
函数会阻塞代码,依次执行,这个时候这种封装就满足不了了。
3.Promise
封装
promise
是 ES6
提出的一种异步解决方案,它和 setTimeout
一样,都可以实现异步,区别在于 promise
解决了回调函数的问题,它可以实现链式调用,我们可以接触 promise
来实现 sleep
函数。
代码如下:
<script>
function fnA() {
console.log('A');
}
function fnB() {
console.log('B');
}
function fnC() {
console.log('C');
}
// sleep 函数--Promise 版本
function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});
}
sleep(1000).then(fnA); // 1 秒后输出 A
sleep(2000).then(fnB); // 2 秒后输出 B
sleep(3000).then(fnC); // 3 秒后输出 C
</script>
上段代码中利用 promise
实现了 sleep
函数,其实是 promise
和 setTimeout
的结合,不过上段代码中我们可以进行链式调用了,不必再往 sleep
函数中传入回调函数了。
优点:
不用再传入回调函数,采用链式调用。
缺点:
仍未解决阻塞问题,依然会先执行同步任务,代码如下:
sleep(1000).then(fnA); // 1 秒后输出 A
console.log('E');
sleep(2000).then(fnB); // 2 秒后输出 B
console.log('G');
sleep(3000).then(fnC); // 3 秒后输出 C
输出结果:
4.async/await
前面两个封装中我们一直提及阻塞问题,那么既然我们使用了 promise
,我们就很有必要将 async/await
拿出来使用,它们可以完美的阻塞我们的代码,然我们的代码依次执行。
代码如下:
<script>
function fnA() {
console.log('A');
}
function fnB() {
console.log('B');
}
function fnC() {
console.log('C');
}
// sleep 函数--Promise 版本
function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});
}
async function sleepTest() {
fnA(); // 输出 A
await sleep(1000); // 睡眠 1 秒
console.log('E'); // 输出 E
fnB(); // 输出 B
await sleep(1000); // 睡眠 1 秒
fnC(); // 输出 C
await sleep(1000); // 睡眠 1 秒
console.log('G'); // 输出 G
}
sleepTest();
</script>
输出结果:
上段代码中我们封装的 sleep
函数并没有改变,只是我们调用 sleep
函数的使用采用了 async/await
的方式调用,这就很好的解决了我们程序没有阻塞的额问题了。
总结
实现 sleep
函数其实非常简单,主要是理解 JavaScript
中异步执行情况。虽然上面的代码中使用 await sleep()
的方式看起来最像一个真正的 sleep
函数,但是凡事都有两面性,比如我们有些场景只是需要一定时间后执行某个函数,不想阻塞代码的执行,这个时候 setTimeout
和 promise
都是非常好的选择,但有时候我们就是需要阻塞代码的执行,比如后面的代码用到了前面这个函数的返回结果,这个时候 async/await
就是一个很好的选择了。