学习总结2

214 阅读22分钟

Q1、:设置小于12px的字体

使用缩放 transform:scale()方法即可以实现字号的缩放
注意:google浏览器的最小强制字体大小为12px,即使设置成 10px 最终都会显示成 12px

<body>
    <p class="font">你好</p>
</body>
<style>
    .font {
        font-size: 12px;
        transform: scale(0.5);
    }
</style>

image.png 当我们直接对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元素就不会缩放了

image.png Q1总结:在div中包一个span,对span设置初识的font-size:12px,然后基于这个12px成比例缩放,并且注意一定要给span设置成行内块

Q2、rem、em、vw、vh 的值各是什么意思

em: 相对长度单位,相对于当前对象内文本的字体尺寸【是所在标签的字体大小】(参考物是父元素的font-size),

如当前父元素的字体尺寸未设置,则相对于浏览器的默认字体尺寸

特点:

  1. em的值并不是固定的,同一个页面中的em也会不同;
  2. 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-equivname, 用于表示要设置的项。

比如<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, charsetpragma等等。

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>
  1. 页面内容立即显示。
  2. DOMContentLoaded事件处理程序等待defer脚本执行完之后执行

补充:(MDN)

当纯 HTML 被完全加载以及解析时,DOMContentLoaded 事件会被触发,而不必等待样式表,图片或者子框架完成加载。

defer脚本保持相对顺序来执行,就像常规脚本一样

例如:我们有两个延迟脚本:long.jssmall.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)。

image.png

回流
元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染

  • 如添加或删除可见的DOM元素;
  • 元素的位置发生变化;
  • 元素的尺寸发生变化;
  • 内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代);
  • 页面一开始渲染的时候(这个无法避免);
  • 因为回流是根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流......

第一次渲染完成后,基于JS改变了 元素的位置或者大小,浏览器需要重新计算渲染树中 每一个元素在视口中的 位置和大小
=> 这个阶段就是=> 重新布局 / 回流 / 重排 / reflow

回流回流也叫重排。简单来说,就是当我们对 DOM 结构的修改引发 DOM 几何尺寸变化的时候,会发生回流的过程。比如以下情况

  1. 一个 DOM 元素的几何属性变化,常见的几何属性有widthheightpaddingmarginlefttopborder 等等, 这个很好理解。
  2. 使 DOM 节点发生增减或者移动
  3. 读写 offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作。
  4. 调用 window.getComputedStyle 方法。

image.png

二、避免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>

相关知识点:

  1. Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。
  2. 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.offsetTopdocument.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 主流程

图:WebKit 主流程

图:Mozilla 的 Gecko 呈现引擎主流程(3.6)

图: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 像素。
stickyCSS3 新增,粘性定位,相对于最近的一个拥有“滚动机制”的祖先上(当该祖先的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 两种定位功能于一体的特殊定位。常见的吸顶、吸底(头部返回栏,底部切换栏等)的效果都是使用这个属性:

注意:

  • 须指定 toprightbottomleft 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。 并且 topbottom 同时设置时,top 生效的优先级高,leftright 同时设置时,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,有什么现象?

外边距会重合

  1. 两个正数的外边距取最大的边距作为外边距。
  2. 如果一个为正数,一个为负数,最终的外边距为两者之和。
  3. 如果两个值都是负数的话,最终的外边距取绝对值最大的值。

外边距重叠问题

外边距重叠只发生在块级元素上,例如 <div> <p> 等,并不会发生在脱离文档流的元素上,例如:设置 float 属性,或者 position 的值为 absolute 时。

发生外边距重叠的情况

  1. 相邻兄弟元素的外边距重叠

    示例代码如下所示:

    <!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>
    

    如果按照我们正常的思维逻辑的话 box1box2 中间的边距应该是 200px,但事实并不是这样的,如下图

    事实上,目前就已经出现了外边距重叠问题。

  2. 父级与子级的外边距重叠

    示例代码如下所示:

    <!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-top100px ,子级的 margin-top50px,,如果相加的话为 150px,但实际结果如下所示:

    image-20210421115256601

    出现外边距重叠问题,需满足以下条件中的任意一项才能实现

    margin-top 重叠

    1. 父级元素不是 BFC 元素。
    2. 父元素没有 border-top 属性
    3. 父元素没有 padding-top 属性
    4. 父元素与第一个子元素之间没有 行内元素 进行分割。

    margin-bottom 重叠

    1. 父级元素不是 BFC 元素。
    2. 父元素没有 border-top 属性
    3. 父元素没有 padding-top 属性
    4. 父元素与第一个子元素之间没有 行内元素 进行分割。
    5. 父元素没有高度限制
  3. 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>
    

    代码结果如下所示:

    image-20210421120925608

    子级元素的外边距与 body8pxmargin 进行了重叠。

    空 block 元素外边距重叠触发条件必须满足如下

    1. 元素没有 border 属性
    2. 元素没有 padding 属性
    3. 里面没有 inline 元素
    4. 没有设置高度。

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的值。

  1. 如果xy的类型相同,则 JS 会换成===操作符进行比较。
  2. 如果xnull, yundefined,则返回true
  3. 如果xundefinedynull,则返回true
  4. 如果x的类型是number, y的类型是string,那么返回x == toNumber(y)
  5. 如果x的类型是string, y的类型是number,那么返回toNumber(x) == y
  6. 如果x为类型是boolean,则返回toNumber(x)== y
  7. 如果y为类型是boolean,则返回x == toNumber(y)
  8. 如果xstringsymbolnumber,而y是object类型,则返回x == toPrimitive(y)
  9. 如果xobjectystringsymbol则返回toPrimitive(x) == y
  10. 剩下的 返回 false

例子

xyx == y符合条件
66true条件1 类型相同
6'6'true条件4
nullundefinedtrue条件2
0falsetrue条件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
  • ===运算符
xyx === y说明
66true值和类型都相同
6'6'false值和类型都不相同
nullundefinedfalse值和类型都不相同
0falsefalse值和类型都不相同
'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语句返回trueac有相同的引用地址,而ab没有。

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 封装

promiseES6 提出的一种异步解决方案,它和 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 函数,其实是 promisesetTimeout 的结合,不过上段代码中我们可以进行链式调用了,不必再往 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 函数,但是凡事都有两面性,比如我们有些场景只是需要一定时间后执行某个函数,不想阻塞代码的执行,这个时候 setTimeoutpromise 都是非常好的选择,但有时候我们就是需要阻塞代码的执行,比如后面的代码用到了前面这个函数的返回结果,这个时候 async/await 就是一个很好的选择了。