X

514 阅读1小时+

1. HTML

1.1 W3C 标准

W3C 是万维网联盟的缩写,它是国际最著名的标准化组织。W3C 标准不是某一个标准,而是一系列标准的集合。是由 结构标准语言表现标准语言行为标准 组成。结构化标准语言主要包括 XMLXHTML ,表现标准语言主要包括 CSS ,行为标准主要包括 文档对象模型ECMAScript 等。

1.2 HTML5 的新特性

  1. 新的媒体标签
  • 音频: <audio>

  • 视频: <video>

  1. 新的语义化标签
  • <header> </header> : 定义了文档的头部区域

  • <footer> </footer> : 定义了文档的尾部区域

  • <nav> </nav> : 定义文档的导航

  • <section> </section> : 定义文档中的节(section、区段)

  • <article> </article> : 定义页面独立的内容区域

  • <aside> </aside> : 定义页面的侧边栏内容

  • <detailes> </detailes> : 用于描述文档或文档某个部分的细节

  • <summary> </summary> : 标签包含 details 元素的标题

  • <dialog> </dialog>: 定义对话框,比如提示框

  1. 新的表单输入控件类型
  • color : 生成一个颜色选择器

  • date : 限制用户输入必须为日期类型

  • datetime-local : 选择一个日期和时间

  • email : 限制用户输入必须为 email 类型

  • month : 选择年和月

  • number : 限制用户输入必须为数字类型

  • range : 一定范围内数字值的输入域

  • search : 搜索框

  • tel : 定义输入电话号码字段 :

  • time : 选择一个时间

  • url : 限制用户输入必须为 URL 类型

  • week : 选择周和年

  1. 新的表单属性
  • required : 表单拥有该属性表示内容不能为空,必填

  • placeholder : 表单的提示信息,如果存在默认值则不显示

  • autofocus : 自动聚焦属性,页面加载完成自动聚焦到指定表单

  • autocomplete : 当用户开始键入时,浏览器基于之前键入过的值,显示出字段中填写的选项。默认打开,需要放在表单内,同时加上 name 属性

  1. 绘画
  • <canvas> </canvas> : 定义使用 JavaScript 的图像绘制。

  • <svg> </svg> : 定义使用 SVG 的图像绘制。

  1. 其他功能标签
  • progress : 进度条。< progress max=“100” min=“1” value=“20”>

  • datalist : 文本域下拉提示。

  • detail : 展开菜单。

  • mark : 标注。

  • time : 数据标签,给搜索引擎使用,主要日期标签。

  • ruby : 对某些内容进行注释。

  • command : 按钮。

1.3 如何理解 HTML 语义化?

  • 语义化就是使用一些见名知意的标签,提高代码可读性,使代码的结构更清晰,便于开发和维护。

  • 在没有 CSS 样式下,页面也能呈现出很好地内容结构、代码结构。

  • 对机器友好,有利于SEO。SEO(Search Engine Optimization)是搜索引擎优化,为了让用户在搜索和网站相关的关键词的时候,可以使网站在搜索引擎的排名尽量靠前,从而增加流量。

  • 方便屏幕阅读器解析,如盲人阅读器根据语义渲染网页。

  • 但是,HTML5 语义化标签并没有广泛使用,比如京东、淘宝等,还是使用 div 元素,设置 idheader 或者 footer ,这个可能是因为 HTML5 新增的语义化标签的作用不是特别大,网站没有必要重写。

🌷 常见的语义化标签: titleheadernavsectionmainarticleasidefooteraddress 等。

2. CSS

2.1 盒模型

🌼 盒模型是什么?

当我们对一个文档进行布局的时候,浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型,将所有元素表示为一个个矩形的盒子。

一个盒子由四个部分组成:contentpaddingbordermargin

在 CSS3 中,盒模型分为两种:标准盒模型IE(怪异)盒模型。它们的区别在于盒子 width / height 的计算范围不同。

  • 标准盒模型,是浏览器默认的盒子模型,width / height 只包含内容 content,不包含 padding 和 border 值。
  • IE(怪异)盒模型的 width / height 包含了 contentpadding 和 border 值。

可以通过 box-sizing 来改变元素的盒模型类型:

  • box-sizing: content-box;  标准盒模型(默认值)。
  • box-sizing: border-box;  IE(怪异)盒模型。

2.2 浮动

🌼 浮动的作用:

  • 设置浮动的图片,可以实现文字环绕图片,这也是浮动最初设计的目的

  • 设置了浮动的块级元素可以排列在同一行

  • 设置了浮动的行内元素可以设置宽高,同时可以按照浮动设置的方向对齐排列盒子

🌼 浮动元素的特点:

  • 脱离标准流

  • 浮动的元素具有行内块元素的特性

🌼 浮动造成的影响:

在很多情况下,父级盒子不方便给高度(比如淘宝京东的商品),需要被子盒子撑开,但是子盒子浮动又不占有位置,最后父级盒子高度为 0 ,就会影响父级盒子后面的标准流盒子。如果浮动元素后面还有其他标准流兄弟元素,其他兄弟元素的布局也会受到影响。

为了解决浮动所带来的影响,我们要清除浮动:

🌼 清除浮动的方法:

清除浮动方法说明优点缺点
额外标签法(隔墙法)在浮动元素末尾添加一个空的块级元素标签,并给这个标签添加 clear: both 属性通俗易懂,书写方便添加无意义的标签造成了不必要的渲染,结构化差,所以这种方法我们都不用
父级添加 overflowoverflow:hidden代码方便简洁如果父级元素有定位 且元素超出父级,超出部分会隐藏(无法显示溢出的部分)
伪元素清除浮动给浮动元素父级增加 .clearfix::after { content: ''; display: table; clear: both; }不会新增标签,不会有其他影响,是我们最常用的清除浮动的方法兼容性问题

2.3 定位

position 属性用于指定一个元素在文档中的定位方式。

topbottomleftright 属性决定元素的最终位置。

🌼 1.静态定位 static

position: static;

静态定位是元素的默认定位方式,就是无定位,标准流的意思。

静态定位的元素不受 top、bottom、left 和 right 属性的影响。

🌼 2.相对定位 relative

position: relative;

相对定位的元素相对于其自身位置进行定位。

相对定位的元素不会脱离标准流。

🌼 3.绝对定位 absolute

position: absolute;

绝对定位的元素相对于它最近一级有定位的祖先元素进行定位,如果没有祖先元素或者祖先元素没有定位,则以浏览器(Document 文档)为准定位。

绝对定位是脱离标准流的。

🌼 4.固定定位 fixed

position: fixed;

固定定位的元素是相对于浏览器可视区定位的,这意味着可以在浏览器页面滚动时保持元素在视口中的位置不变。

固定定位也是脱标的。

🌼 5.粘性定位 sticky

position: sticky;

粘性定位可以被认为是相对定位和固定定位的混合。

粘性定位的元素根据用户的滚动位置进行定位。

粘性定位的元素根据滚动位置在相对定位和固定定位之间切换。起先它会被相对定位,直到在视口中遇到给定的偏移位置为止,然后固定定位在适当的位置。

2.4 CSS 选择器和优先级

2.4.1 CSS选择器:

一、基础选择器:(4个)

  • 标签选择器

  • 类选择器

  • id选择器

  • 通配符选择器

二、复合选择器:(7个)

  • 后代选择器

  • 子选择器

  • 并集选择器

  • 伪类选择器

  • 伪元素选择器

  • 属性选择器

  • 相邻兄弟选择器(.one+.two),选择紧接在.one之后的所有.two元素

还有一些使用频率相对没那么多的选择器: CSS 选择器参考手册

2.4.2 优先级规则:

CSS样式的优先级可以分成五大类:

  • 第一类 !important ,无论引入方式是什么,选择器是什么,它的优先级都是最高的。所以使用 !important 要谨慎,一定要优先考虑使用样式规则的优先级来解决问题而不是 !important ,只有在需要覆盖全站或外部 CSS 的特定页面中使用。

  • 第二类 引入方式,行内样式的优先级要高于嵌入和外链,嵌入和外链如果使用的选择器相同就看他们在页面中插入的顺序,在后面插入的会覆盖前面的。

  • 第三类 选择器,选择器优先级:id选择器 >(类选择器 | 伪类选择器 | 属性选择器 ) > (标签选择器 | 伪元素选择器 ) > (子选择器 | 相邻兄弟选择器 | 后代选择器) > 通配符选择器 。

  • 第四类 继承样式,是所有样式中优先级比较低的。

  • 字体系列属性
font:组合字体
font-family:规定元素的字体系列
font-weight:设置字体的粗细
font-size:设置字体的尺寸
font-style:定义字体的风格
font-variant:偏大或偏小的字体
  • 文本系列属性
text-indent:文本缩进
text-align:文本水平对刘
line-height:行高
word-spacing:增加或减少单词间的空白
letter-spacing:增加或减少字符间的空白
text-transform:控制文本大小写
direction:规定文本的书写方向
color:文本颜色
  • 元素可见性
visibility
  • 表格布局属性
caption-side:定位表格标题位置
border-collapse:合并表格边框
border-spacing:设置相邻单元格的边框间的距离
empty-cells:单元格的边框的出现与消失
table-layout:表格的宽度由什么决定
  • 列表属性
list-style-type:文字前面的小点点样式
list-style-position:小点点位置
list-style:以上的属性可通过这属性集合
  • 引用
quotes:设置嵌套引用的引号类型
  • 光标属性
cursor:箭头可以变成需要的形状
  • 第五类 浏览器默认样式 优先级最低。

注意:

优先级的比较指的是相同的样式属性,不同样式属性优先级比较失效。

2.5 CSS尺寸设置的单位:px、em、rem、vh、vw的区别及使用场景

1. px: 像素,绝对长度单位。它的大小取决于屏幕的分辨率,是开发网页中最常使用的单位。

2. em: 相对长度单位

  • font-size 中使用,是相对于父元素的字体大小;
  • 在其他属性中使用,是相对于自身的字体大小。

如当前元素的字体尺寸未设置,由于字体大小可继承的原因,可逐级向上查找,最终找不到则相对于浏览器默认字体大小。

3. rem: 相对长度单位,相对于根元素的字体大小,若根元素字体大小未设置,使用浏览器默认字体大小。

rem 可以在需要做响应式的页面中配合媒体查询或者 flexible.js 实现。

🌼 原理: 通过 媒体查询 或者 flexible.js ,能够在屏幕尺寸发生改变时,重置 html 根元素的字体大小,页面中的元素都是使用 rem 为单位设置的尺寸,因此只要改变根元素字体大小,页面中的其他元素的尺寸就自动跟着修改。

4. vw: 相对长度单位,相对于视窗宽度的 1% 。

5. vh: 相对长度单位,相对于视窗高度的 1% 。

🌼 应用: 由于 vw 被更多浏览器兼容之后,在做移动端响应式页面时,通常使用 vw 配合 rem 。

🌼 原理: 使用 vw 设置根元素 html 字体的大小,当窗口大小发生改变,vw 代表的尺寸随之修改,无需加入 媒体查询 和 flexible.js ,页面中的其他元素仍使用 rem 为单位,就可实现响应式。

🌼 总结: 他们的应用也是 实现响应式页面,主要是用来代替 媒体查询 和 flexible.js

2.6 BFC

块级格式化上下文,是页面中的一个独立渲染区域,内部的元素和外部元素互不影响。

🌼 BFC 有一套属于自己的渲染规则:

  • 内部盒子会在垂直方向上,一个接一个地放置
  • 属于同一个 BFC 的两个相邻盒子的 margin 会发生重叠,与方向无关
  • 每个盒子(行盒与块盒)的左外边距与包含块的左边界相接触(从左到右),即使浮动元素也是如此
  • BFC 的区域不会与 float 元素区域重叠
  • 计算 BFC 高度时,浮动子元素也参与计算
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然

🌼 BFC形成条件: 触发BFC的条件包括不限于:

  • 根元素,即HTML元素
  • 浮动元素:floatleftright
  • overflow 值不为 visible,为 autoscrollhidden
  • display 的值为 inline-blockinltable-celltable-captiontableinline-tableflexinline-flexgridinline-grid
  • position 的值为 absolutefixed

🌼 BFC应用:

  • 防止 margin 重叠(塌陷)
  • 自适应多栏布局
  • 清除内部浮动

🌷 BFC 的方式都能清除浮动,但是常用的清除浮动的 BFC 方式只有 overflow: hidden ,原因是使用 float 或者 position 方式清除浮动,虽然父级盒子内部浮动被清除了,但是父级本身又脱离文档流了,会对父级后面的兄弟盒子的布局造成影响。如果设置父级为 display: flex ,内部的浮动就会失效。所以通常只是用 overflow: hidden 清除浮动。

除了 BFC ,还有:

  • IFC(Inline formatting contexts):内联格式上下文。IFC 的高度由其包含行内元素中最高的实际高度计算而来(不受到竖直方向的padding/margin影响), IFC 中的 line box 一般左右都贴紧整个 IFC ,但是会因为 float 元素而扰乱。
  • GFC(GrideLayout formatting contexts):网格布局格式化上下文。当为一个元素设置 display 值为 grid 的时候,此元素将会获得一个独立的渲染区域。
  • FFC(Flex formatting contexts):自适应格式上下文。display 值为 flex 或者 inline-flex 的元素将会生成自适应容器。

2.7 元素水平垂直居中的方法

实现居中的方法存在很多,可以分成两大类:

  • 居中元素(子元素)的宽高已知
  • 居中元素宽高未知

1. 利用绝对定位

父级设置为相对定位,子级绝对定位,并且四个定位属性的值都设置了 0 ,这时候,如果子级没有设置宽高,则会被拉开到和父级一样宽高;如果子元素设置了宽高,那么这时候给它一个 margin:auto 它就可以上下左右都居中了。

用该方法实现元素水平垂直居中必须盒子有宽高

    .father {
        position: relative;
        width: 500px;
        height: 300px;
        border: 2px solid purple;
    }
    .son {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        width: 200px;
        height: 60px;
        background: plum;
        margin: auto;
    }

2. 利用绝对定位 + margin: 负值

利用绝对定位,设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 margin-left  和 margin-top  以子元素自己的一半宽高进行负值赋值。

这种方案需要知道子元素自身的宽高,但是不要求父元素的宽高,是即使父元素的宽高变化了,仍然可以保持在父元素中的水平垂直居中位置。

    .father {
        position: relative;
        width: 200px;
        height: 200px;
        background: skyblue;
    }
    .son {
        position: absolute;
        top: 50%;
        left: 50%;
        margin-left: -50px;
        margin-top: -50px;
        width: 100px;
        height: 100px;
        background: lightgray;
    }

3. 利用绝对定位 + transform

设置元素相对父级定位 position: absolute; left: 50%; top: 50%,让元素平移自身宽度和高度-50% transform: translate(-50%,-50%);,这种方式兼容性好,是被使用最广泛的一种方式。

这种方法可以说是 margin 负值的替代方案,不需要知道元素自身的宽高,而且在子元素的宽高变化的时候,依然可以实现在父元素中水平垂直居中。

    .father {
        position: relative;
        width: 200px;
        height: 200px;
        background: skyblue;
    }
    .son {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100px;
        height: 100px;
        background: lightgray;
    }

4. flex 布局

  • 设置元素的父级为弹性盒子 display: flex
  • 设置子元素在主轴居中对齐 justify-content: center;
  • 设置子元素在侧轴居中对齐 align-items: center

这种方式代码简洁,是最经典最方便的一种方法,不需要知道元素自身的宽高

    .father {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 200px;
        height: 200px;
        background: skyblue;
    }
    .son {
        width: 100px;
        height: 100px;
        background: lightgray;
    }

5. table布局

  • 设置元素的父级为表格元素 display: table-cell
  • 子元素设置为行内块 display: inline-block

利用 vertical-align: middle;text-align: center; 可以让所有的 行内块元素 水平垂直居中。这种方式兼容性较好。

    .father {
        display: table-cell;
        width: 200px;
        height: 200px;
        background: skyblue;
        vertical-align: middle;  // 垂直
        text-align: center;  // 水平
    }
    .son {
        display: inline-block;
        width: 100px;
        height: 100px;
        background: lightgray;
    }

6. grid网格布局

  • 设置元素的父级为网格元素 display: grid;
  • 设置子元素在主轴居中对齐 justify-content: center;
  • 设置子元素在侧轴居中对齐 align-items: center;
    .father {
        display: grid;
        align-items:center;
        justify-content: center;
        width: 200px;
        height: 200px;
        background: skyblue;

    }
    .son {
        width: 10px;
        height: 10px;
        border: 1px solid red
    }

2.8 如何实现两栏布局,右侧自适应?三栏布局中间自适应呢?

2.8.1 实现两栏布局,右侧自适应

两栏布局非常常见,它的实现效果就是 将页面分割成左右宽度不等的两列,宽度较小的列设置为固定宽度,剩余宽度由另一列撑满。

DOM结构如下:

    <div class="box">
        <div class="left">左边</div>
        <div class="right">右边</div>
    </div>

1. 利用浮动

  • 左边元素宽度固定,设置向左浮动。
  • 右边元素使用 margin-left 撑出内容块做内容展示。因为右边元素不设置宽度,默认为 auto ,所以会自动撑满父元素。
        .left {
            float: left;
            width: 200px;
            height: 400px;
        }
        .right {
            margin-left: 210px;
            height: 400px;
        }

2. 利用浮动 + overflow

  • 同样利用浮动,左边元素宽度固定 ,设置向左浮动。
  • 右侧元素设置 overflow: hidden; 这样右边就触发了 BFC ,BFC 的区域不会与浮动元素发生重叠。
        .left {
            float: left;
            width: 200px;
            background-color: gray;
            height: 400px;
        }
        .right {
            overflow: auto;
            height: 400px;
            background-color: lightgray;
        }

3. flex弹性布局

  • 父元素设置 display: flex;
  • 左边元素固定宽度
  • 右边的元素设置 flex: 1 
        .box {
            display: flex;
        }
        .left {
            width: 200px;
            height: 400px;
        }
        .right {
            flex: 1;
        }

注意: flex 容器的一个默认属性值:align-items: stretch;

这个属性导致了列等高的效果。

2.8.2 实现三栏布局,中间自适应

1. 圣杯布局

圣杯布局的实现方案: 三个元素放在同一个父级元素中,代表中间盒子的元素放在最前面,父级盒子设置左右 padding ,三个盒子全部浮动,设置中间盒子宽度 100% ,左右盒子设置固定宽度,设置左边盒子 margin-left: -100%;,同时相对自身定位,left: 负的自身宽度; 右边盒子设置 right: 负的自身宽度; 最后设置父级盒子清除浮动,否则父级盒子的高度无法被撑开。

    <style>
        .wrap {
            padding-left: 200px;
            padding-right: 150px;
        }
        .left {
            float: left;
            width: 200px;
            height: 200px;
            background-color: gray;
            /* 父元素宽度的100% */
            margin-left: -100%;
            position: relative;
            left: -200px;
        }
        .right {
            float: left;
            width: 150px;
            height: 200px;
            background-color: lightgray;
            /* 左盒子已经移走了 */
            margin-left: -150px;
            position: relative;
            /* 与父元素的右边界相比 */
            right: -150px;
        }
        .center {
            float: left;
            width: 100%;
            height: 200px;
            background-color: lightblue;
        }
        .clearfix:after {
            content: "";
            display: block;
            height: 0;
            clear: both;
            visibility: hidden;
        }
    </style>

    <div class="wrap clearfix">
        <div class="center">我是中间</div>
        <div class="left">我是左边</div>
        <div class="right">我是右边</div>
    </div>

2. 双飞翼布局

双飞翼布局的实现方案:三个盒子对应三个元素,其中中间盒子套了两层,中间盒子内部盒子设置 margin ,三个盒子全部浮动,设置中间盒子宽度 100% ,左右盒子设置固定宽度,设置左边盒子左边距 -100% ,右边盒子设置右边距-自身宽度,最后设置父级盒子清除浮动,否则父级盒子的高度无法被撑开。

    <style>
        .main {
            float: left;
            width: 100%;
        }
        .content {
            height: 200px;
            margin-left: 200px;
            margin-right: 150px;
            background-color: lightblue;
        }
        /* 注意:需要在main后面加一个元素来清除浮动 */
        .main::after {
            content: "";
            display: block;
            height: 0;
            clear: both;
            visibility: hidden;
        }
        .left {
            float: left;
            width: 200px;
            height: 200px;
            margin-left: -100%;
            background-color: gray;
        }
        .right {
            float: left;
            width: 150px;
            height: 200px;
            margin-left: -150px;
            background-color: lightgray;
        }
    </style>

    <div class="wrap">
        <div class="main">
            <div class="content">我是中间</div>
        </div>
        <div class="left">我是左边</div>
        <div class="right">我是右边</div>
    </div>

圣杯布局:

  • 优点:不需要添加 dom 节点
  • 缺点:正常情况下是没有问题的,但是特殊情况下就会暴露此方案的弊端,当 center 部分的宽小于 left 部分时就会发生布局混乱。

双飞翼布局:

  • 优点:不会像圣杯布局那样变形,CSS样式代码更简洁
  • 缺点:多加了一层dom节点

注意:上述代码中 margin-left: -100% 相对的是父元素的 content 宽度,即不包含 paddigborder 的宽度。

关于三栏布局中 margin-left: -100% 的理解: # 圣杯布局中 margin-left: -100%的理解

3. flex弹性布局

2.9 重排 ( reflow ) 和重绘 ( repaint )

一、 重排(也叫回流)

重排:当一个元素的几何信息(元素在视口内的位置和尺寸大小)发生改变的时候,浏览器需要重新计算该元素的几何属性并且摆放到正确的位置,这个过程叫做重排。

触发时机: 一般像页面初次渲染、改变元素的尺寸 位置 内容、添加或删除可见DOM元素、浏览器窗口发生改变、激活CSS伪类(如:hover)、查询某些属性或调用某些计算方法(如offsetWidth、offsetHeight等)的时候都会触发重排。

二、 重绘

重绘:当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程就叫重绘。

触发时机: 颜色的修改、文本方向的修改、阴影的修改

重绘不一定触发重排,但重排一定会触发重绘。一般来说,重排对性能的消耗会更多一些。

🌼 如何减少重排和重绘?

  • 样式集中改变

  • 批量操作 DOM

  • 使用 absolute 或 fixed 让元素脱离文档流,这在制作复杂的动画时对性能的影响比较明显。

  • 开启 GPU 加速,利用 css 属性 transformwill-change 等,比如改变元素位置,我们使用 translate 会比使用绝对定位改变其 lefttop 等来的高效,因为它不会触发重排或重绘,transform 使浏览器为元素创建⼀个 GPU 图层,这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。

🌱 GPU的过程 :

  1. 获取DOM并将其分割成多个层(renderLayer)
  2. 将每个层栅格化,并独立的绘制进位图中
  3. 将这些位图作为纹理上传至GPU
  4. 复合多个层来生成最终的屏幕图像(最后的layer) 开启了GPU加速的元素被独立出来,不会再影响其他dom的布局,因为它改变之后,只是相当于被贴上了页面。

2.10 CSS3 新特性

🌼 1. 新增选择器:

  • 属性选择器:可以根据元素特定属性的来选择元素。

  • 结构伪类选择器:主要根据文档结构来选择元素,常用于根据父级选择里面的子元素。

  • 伪元素选择器:利用 CSS 创建新标签元素,从而简化HTML结构。

🌼 2. 边框属性

  • border-radius:创建圆角边框

  • box-shadow:为元素添加阴影

  • border-image:使用图片来绘制边框

🌼 3. 背景

  • background-clip :设置背景颜色或图片的覆盖范围,默认背景覆盖整个元素。

  • background-origin :设置背景图片对齐,默认以 padding 的左上角为原点。

  • background-size :调整背景图片的大小。

  • background-break :元素可以被分成几个独立的盒子(如使内联元素span跨越多行),background-break 属性用来控制背景怎样在这些不同的盒子中显示

  • column-count :多列布局,如 column-count: 5;

🌼 4. 文字

  • word-wrap :换行

  • text-overflow :设置当当前行超过指定容器的边界时如何显示

  • text-shadow :文字阴影

  • text-decoration :CSS3里面开始支持对文字的更深层次的渲染,具体有三个属性可供设置:

    • text-fill-color: 设置文字内部填充颜色
    • text-stroke-color: 设置文字边界填充颜色
    • text-stroke-width: 设置文字边界宽度

🌼 5.颜色

css3 新增了新的颜色表示方式 rgbahsla

  • rgba 分为两部分,rgb为颜色值,a为透明度
  • hsla 分为四部分,h为色相,s为饱和度,l为亮度,a为透明度

🌼 6. transition 过渡

元素从一种样式变换为另一种样式

语法如下:

transition: 要过渡的属性,花费时间,效果曲线(默认ease),延迟时间(默认0)

🌼 7. transform 转换

transform 属性允许平移、旋转、倾斜和缩放

transform-origin :设置旋转中心点,默认为 (x,y,z):(50%,50%,0);

使用方式:

  • transform: translate(120px, 50%):位移

  • transform: scale(2, 0.5):缩放

  • transform: rotate(0.5turn):旋转

  • transform: skew(30deg, 20deg):倾斜

🌼 8. animation 动画

  • animation-name:动画名称

  • animation-duration:动画持续时间

  • animation-timing-function:动画时间函数

  • animation-delay:动画延迟时间

  • animation-iteration-count:动画执行次数,可以设置为一个整数,也可以设置为infinite,意思是无限循环

  • animation-direction:动画执行方向

  • animation-paly-state:动画播放状态

  • animation-fill-mode:动画填充模式

🌼 9. 渐变

  • linear-gradient:线性渐变

  • radial-gradient:径向渐变

🌼 10. 弹性盒模型:

  • 弹性布局: display: flex;

  • 栅格布局: display: grid;

还有多列布局、媒体查询(@media)、混合模式等等。

2.11 flex(弹性盒布局模型)

一、是什么?

Flexible Box 简称 flex,意为“弹性布局”,可以简便、完整、响应式地实现各种页面布局。

任何一个容器都可以指定为 Flex 布局。设为 Flex 布局以后,子元素的 floatclearvertical-align 属性将失效。

采用Flex布局的元素,称为 flex 容器 container

它的所有子元素自动成为容器成员,称为 flex 项目 item

容器默认存在两根轴:主轴和侧轴。项目默认沿主轴排列,通过 flex-direction 来决定主轴的方向。

总的来说, flex 布局原理 就是通过给父盒子添加 flex 属性,来控制子盒子的位置和排列方式。

二、属性

关于 flex 常用的属性,我们可以划分为容器属性和项目属性。

1)容器属性

1. flex-direction 设置主轴的方向(即项目的排列方向)

    .container {   
        flex-direction: row | row-reverse | column | column-reverse;  
    } 
  • row(默认值):主轴为水平方向,起点在左端。即项目从左到右排列。
  • row-reverse:主轴为水平方向,起点在右端。
  • column:主轴为垂直方向,起点在上沿。
  • column-reverse:主轴为垂直方向,起点在下沿。

2. flex-wrap 设置容器内项目是否可换行

    .container {  
        flex-wrap: nowrap | wrap | wrap-reverse;
    }  
  • nowrap(默认值):不换行
  • wrap:换行,第一行在上方
  • wrap-reverse:换行,第一行在下方

默认情况是不换行,但这里也不会任由元素直接溢出容器,会涉及到元素的弹性伸缩(flex布局中默认让子元素在一行内显示,如果显示不开,就缩小子元素的宽度)

3. flex-flowflex-direction 属性和 flex-wrap 属性的简写形式,默认值为 row nowrap

    .box {
        flex-flow: <flex-direction> || <flex-wrap>;
    }

4. justify-content 定义项目在主轴上的对齐方式

    .box {
        justify-content: flex-start | flex-end | center | space-between | space-around;
    }
  • flex-start(默认值):从头开始排列
  • flex-end:从尾部开始排列
  • center:在主轴居中对齐
  • space-between:先两端对齐,再平分剩余空间(项目之间的间隔都相等)
  • space-around:平分剩余空间

5. align-items 定义项目在侧轴上的对齐方式(单行)

    .box {
        align-items: flex-start | flex-end | center | baseline | stretch;
    }
  • flex-start:侧轴的起点对齐
  • flex-end:侧轴的终点对齐
  • center:在侧轴居中对齐
  • baseline: 项目的第一行文字的基线对齐
  • stretch(默认值):如果项目未设置高度或设为 auto ,将占满整个容器的高度

6. align-content 设置项目在侧轴上的排列方式,并且只能用于项目出现 换行 的情况(多行),在单行下是没有效果的。

    .box {
        align-content: flex-start | flex-end | center | space-between | space-around | stretch;
    }
  • flex-start:在侧轴的头部开始排列
  • flex-end:在侧轴的尾部开始排列
  • center:在侧轴中间显示
  • space-between:先两端对齐,再平分剩余空间
  • space-around:平分剩余空间
  • stretch(默认值):设置子项元素高度平分父元素高度

2)项目属性

1. order 定义项目的排列顺序。数值越小,排列越靠前,默认为0。

    .item {
        order: <integer>;
    }

2. flex-grow 定义项目的放大比例(容器宽度 > 元素总宽度时如何伸展),默认为 0,即如果存在剩余空间,也不放大。

    .item {
        flex-grow: <number>;
    }
  • 如果所有项目的 flex-grow 属性都为 1 ,则它们将等分剩余空间(如果有的话)。
  • 如果一个项目的 flex-grow 属性为 2,其他项目都为 1,则前者占据的剩余空间将比其他项多一倍。
  • 如果弹性容器的宽度正好等于元素宽度总和,无多余宽度,此时无论 flex-grow 是什么值都不会生效。

3. flex-shrink 定义了项目的缩小比例(容器宽度 < 元素总宽度时如何收缩),默认为 1,即如果空间不足,该项目将缩小

    .item {
        flex-shrink: <number>; /* default 1 */
    }
  • 如果所有项目的 flex-shrink 属性都为 1,当空间不足时,都将等比例缩小。
  • 如果一个项目的 flex-shrink 属性为0,其他项目都为 1,则空间不足时,前者不缩小。
  • 负值对该属性无效。
  • 在容器宽度有剩余时,flex-shrink 也是不会生效的。

4. flex-basis 设置的是元素在主轴上的初始尺寸,所谓的初始尺寸就是元素在 flex-growflex-shrink 生效前的尺寸,浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为 auto,即项目的本来大小。

    .item {
        flex-basis: <length> | auto;  /* default auto */
    }

当设置为 0 时,会根据内容撑开,它可以设为跟 widthheight 属性一样的值(比如350px),则项目将占据固定空间

5. flex 属性定义项目分配剩余空间,用flex来表示占多少份数。

flex 属性是 flex-growflex-shrinkflex-basis 的简写,默认值为0 1 auto

.item {
  flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

这里有个小问题,很多时候我们会用到 flex: 1 ,它具体包含了以下的意思:

  • flex-grow: 1 :该属性默认为 0 ,如果存在剩余空间,元素也不放大。设置为 1  代表会放大。
  • flex-shrink: 1 :该属性默认为 1 ,如果空间不足,元素缩小。
  • flex-basis: 0% :该属性定义在分配多余空间之前,元素占据的主轴空间。设置为 0% 之后,因为有 flex-growflex-shrink 的设置会自动放大或缩小。

6. align-self 允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。默认值为 auto ,表示继承父元素的 align-items 属性,如果没有父元素,则等同于 stretch

    .item {
        align-self: auto | flex-start | flex-end | center | baseline | stretch;
    }

该属性可取6个值,除了auto,其他都与align-items属性完全一致。

2.12 响应式设计(响应式布局)

响应式设计是一种页面布局,页面的设计与开发应当根据用户行为和设备环境进行相应的响应和调整。

响应式网站常见特点:

  • 同时适配PC + 平板 + 手机等

  • 标签导航在接近手持终端设备时改变为经典的抽屉式导航

  • 网站的布局会根据视口来调整模块的大小和位置

🌼 基本原理

响应式设计的基本原理是 通过检测不同的设备屏幕尺寸做处理。

为了处理移动端,页面头部必须有 meta 标签声明 viewport

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no”>

属性对应如下:

  • width=device-width:自适应手机屏幕的尺寸宽度

  • maximum-scale:缩放比例的最大值

  • inital-scale:缩放的初始化

  • user-scalable:用户是否可以缩放的操作

🌼 实现响应式布局的方式:

  • 媒体查询
  • 百分比
  • vw/vh
  • rem

1. 媒体查询

通过媒体查询,可以通过给不同的媒体类型定义不同的样式来实现响应式的布局

  • 比如我们为不同分辨率的屏幕,设置不同的背景图片

  • 比如给小屏幕手机设置@2x图,为大屏幕手机设置@3x图,通过媒体查询就能很方便的实现

2. 百分比单位 %

当浏览器的宽度或者高度发生变化时,通过百分比单位,可以使浏览器中组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。

heightwidth 属性的百分比依托于父标签的宽高,但是其他盒子属性则不完全依赖父元素:

  • 子元素的top/left和bottom/right如果设置百分比,则相对于直接非static定位(默认定位)的父元素的高度/宽度
  • 子元素的padding如果设置百分比,不论是垂直方向或者是水平方向,都相对于直接父亲元素的width,而与父元素的height无关。
  • 子元素的margin如果设置成百分比,不论是垂直方向还是水平方向,都相对于直接父元素的width
  • border-radius不一样,如果设置border-radius为百分比,则是相对于自身的宽度

可以看到每个属性都使用百分比,会照成布局的复杂度,所以不建议使用百分比来实现响应式

3. vw/vh

vw 表示相对于视图窗口的宽度,vh 表示相对于视图窗口高度。任意层级元素,在使用 vw 单位的情况下,1vw 都等于视图宽度的百分之一。

4. rem

rem 是相对于根元 htmlfont-size 属性,可以利用媒体查询,针对不同设备分辨率改变 font-size 的值,如下:

除此之外,我们还可以利用主流UI框架,如:element uiantd提供的栅格布局实现响应式

响应式设计实现通常会从以下几方面思考:

  • 弹性盒子(包括图片、表格、视频)和媒体查询等技术
  • 使用百分比布局创建流式布局的弹性UI,同时使用媒体查询限制元素的尺寸和内容变更范围
  • 使用相对单位使得内容自适应调节
  • 选择断点,针对不同断点实现不同布局和内容展示

三、总结

响应式布局优点可以看到:

  • 面对不同分辨率设备灵活性强
  • 能够快捷解决多设备显示适应问题

缺点:

  • 仅适用布局、信息、框架并不复杂的部门类型网站
  • 兼容各种设备工作量大,效率低下
  • 代码累赘,会出现隐藏无用的元素,加载时间加长
  • 其实这是一种折中性质的设计解决方案,多方面因素影响而达不到最佳效果
  • 一定程度上改变了网站原有的布局结构,会出现用户混淆的情况

3. JavaScript

3.1 JS 数据类型

JS 数据类型分为两类:

  • 一类是 基本数据类型,也叫 简单数据类型,包含7种类型,分别是 NumberStringBooleanBigIntSymbolNullUndefined
  • 另一类是 引用数据类型,也叫 复杂数据类型,通常用 Object 代表,普通对象、数组、正则、日期、Math数学函数都属于Object。

其中 SymbolBigInt 是 ES6 新增的数据类型:

  • Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。
  • BigInt 可以表示任意大小的整数。

🌼 数据分成两大类的本质区别: 基本数据类型 和 引用数据类型 它们在内存中的存储方式不同。

  • 基本数据类型的值存储在栈中
  • 引用数据类型的值存储在堆中,在栈中存放的是指向堆内存的地址

当我们把变量 赋值 给一个变量时,解析器首先要确认的就是这个值是 基本类型值 还是 引用类型值:

  • 基本类型赋值,是生成相同的值,两个变量对应不同的地址
  • 引用类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中的同一个对象

3.2 判断数据类型的方法

JavaScript 有 4 种方法判断数据类型,分别是 typeofinstanceofObject.prototype.toString.call()(对象原型链判断方法)、 constructor (用于引用数据类型)

  • typeof: 常用于判断基本数据类型,对于引用数据类型除了函数能返回 function 之外,其余全部返回 object 。注意 null 返回的也是 object

  • instanceof: 主要用于区分引用数据类型,判断一个实例是否属于某种类型,判断基本数据类型无效。检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是 true 。

🌼 instanceof 原理: 实际上就是查找目标对象的原型链,其内部运行机制是判断在其原型链中能否找到该类型的原型。

instanceof 的实现原理:验证当前类的原型 prototype 是否会出现在实例的原型链__proto__上,只要在它的原型链上,则结果都为true。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype ,找到返回true,未找到返回 false 。

  • constructor: 用于检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。 constructor还可以检测基本数据类型。

注意:

  • null 和 undefined 是无效的对象,因此是不会有 constructor 存在,所以无法根据 constructor 来判断。
  • JS对象的 constructor 是不稳定的,这个主要体现在自定义对象上,当开发者重写prototype 后,原有的 constructor 会丢失,constructor 会默认为 Object。
  • 类继承的也会出错,因为 Object 被覆盖了,检测结果就不对了。
  • Object.prototype.toString.call(): 判断类型最精准的方法,适用于所有数据类型的判断检测,检测方法是 Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。

🌼Object.prototype.toString.call()原理: Object.prototype.toString 表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果。

面试回答 🙋‍♂️🙋‍♂️🙋‍♂️

简单来说,JavaScript 中我们有四种方法来判断数据类型。

一般使用 typeof 来判断基本数据类型,不过需要注意当遇到null的问题,这里不足就是不能判断对象具体类型(typeof xjj 只是Object不能看出是person);

而在要判断一个对象的具体类型,就可以用 instanceof,但是也可能不准确,对于一些基础数据类型,数组等会被判断为object。

结合 typeof 和 instanceof 的特点,还能使用 constructor 来判断,他能判断基本类型和引用类型,但是对于 null 和 undefined 是无效的,以及 constructor 不太稳定。

最后如果需要判断准确的内置类型,就可以使用 object.prototype.toString.call,是根据原型对象上的 tostring 方法获取的,该方法默认返回其调用者的具体类型。

3.2 作用域和作用域链

1.作用域

作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。每一个变量或函数都会有自己的作用域范围。

我们一般将作用域分成:

  • 全局作用域:任何不在函数中或是大括号中声明的变量和函数,都是在全局作用域下,可以在程序的任意位置被访问,生命周期和页面的等同

  • 函数作用域:函数作用域也叫局部作用域,如果一个变量是在函数内部声明的,它就在一个函数作用域下。只能在当前函数内部被访问,生命周期随函数结束而结束销毁。

  • 块级作用域:在一对大括号中使用 letconst 声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。

2.作用域链

当在 Javascript 中使用一个变量的时候,首先会在当前作用域下去寻找该变量,如果找不到,就向它的上层作用域寻找,以此类推直到找到该变量或者是找到全局作用域。如果在全局作用域里仍然找不到该变量,就会返回 undefined,严格模式下直接报错。

3.词法作用域

词法作用域,又叫静态作用域,是指 作用域是由代码中变量声明的位置来决定的,变量被创建时就确定好了,JavaScript 遵循的就是词法作用域。

    var a = 2;
    function foo(){
        console.log(a)
    }
    function bar(){
        var a = 3;
        foo();
    }
    bar()  // 2

由于 JavaScript 遵循词法作用域,相同层级的 foo 和 bar 就没有办法访问到彼此块作用域中的变量,所以输出 2 。

3.2 执行上下文

执行上下文是指在 JavaScript 中执行代码时,每个函数、变量和对象所处的环境。它是一种抽象的概念,就是 JavaScript 的代码执行环境。它包含了当前代码的变量对象、作用域链和 this 的值。执行上下文的创建和销毁是由 JavaScript 引擎自动完成的。

执行上下文的类型分为三种:

  • 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象

  • 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文

  • Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用

执行上下文的创建过程包括以下步骤:

  1. 创建变量对象:用于存储变量、函数声明和函数参数。
  2. 创建作用域链:用于解析变量和函数的作用域。
  3. 确定 this 的值:this 引用的是当前执行上下文所属的对象。

执行上下文的销毁发生在相应的代码块执行完成后,其中的变量和函数声明将被清除,并释放内存。

总之,执行上下文是 JavaScript 中用于管理代码执行环境的重要概念,它决定了变量和函数在代码中的可见性和访问性。

3.2 this的指向

this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了 this 的指向不同,一般指向调用者。this一旦被确定了,就不可以再更改

调用方式this指向
普通函数调用window
构造函数调用实例对象 原型对象里面的方法也指向实例对象
对象方法调用该方法所属对象
事件绑定方法绑定事件对象
定时器函数window
立即执行函数window
箭头函数它所在的上下文

3.2 改变this的指向 callapplybind

修改 this 的指向的方法有三个, bindcall 以及 apply,他们的相同点都是:都能修改函数内部 this 的指向,并且都是挂载在 Function.prototype 上的,他们的第一个参数都是 this 的指向对象。

他们的不同点在于参数和执行上,callbind 的参数格式是一样的,第一个参数是 this 的指向对象,其余参数用逗号隔开,而 apply 的参数需要放到数组中。在执行中,call 和 apply 会调用函数,而 bind 不会调用函数。是返回一个新的函数,调用显函数才会执行目标函数。

  • 对于 Function.prototype.call 来说,第一个参数就是 this 的指向对象,其余参数是直接放进去,用逗号隔开就好了。call的主要应用是可以实现继承
  • 对于 Function.prototype.apply 来说,Function.apply( obj,args ) 方法能接收两个参数,obj:是 this 的指向对象。而 args:这个是数组,它将作为参数传递,也就是说 apply 的所有参数都必须放在一个数组里面传进去。apply的主要应用是对数组做一些响应的操作。
  • 对于 Function.prototype.bind 来说,第一个参数就是 this 的指向对象,其余参数是直接放进去,用逗号隔开就好了。也就是说他和 call 是基本相同的,除了是返回是一个新的函数。当我们只是想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this的指向。

注意:箭头函数不能使用这三个方法修改 this 的指向。

3.2 实现一个 bind

// 实现一个自定义的 myBind 函数
Function.prototype.myBind = function (context) {
    // 判断调用 myBind 函数的对象是否为函数,如果不是则抛出一个错误
    if (typeof this !== "function") {
        throw new Error("Type error");
    }
    // 获取参数
    const args = [...arguments].slice(1)
    // 将当前函数保存在变量 fn 中
    const fn = this;
    return function Fn() {
        return fn.apply(
            // 如果 Fn 是通过 new 关键字调用的构造函数,则将 this 指向 Fn 的实例,否则将 this 指向传入的 context 
            this instanceof Fn ? this : context,
            // 原函数的参数和新函数调用时传入的参数合并为一个新的参数数组
            args.concat(...arguments)
        );
    };
};

3.2 原型

  1. prototype 属性

该属性是 构造函数 独有的,用于指向原型对象。而原型对象的用途是所有实例对象所共享的属性和方法。

  1. _ proto _ 属性

该属性是 构造函数的实例对象 所独有的,用于指向构造函数的原型对象。也因为这个指向,所以实例中可以读取原型中所共享的属性和方法。

  1. constructor 属性

constructor 属性是位于原型对象上的,用来指向创建对象的构造函数。因为所有的实例都能访问 constructor ,所以可以使用 constructor 属性来验证实例的原型类型。

  • 每个对象的 __proto__ 都是指向它的构造函数的原型对象 prototype
person1.__proto__ === Person.prototype
  • 构造函数是一个函数对象,是通过 Function 构造器产生的
Person.__proto__ === Function.prototype
  • 原型对象本身是一个普通对象,而普通对象的构造函数都是 Object
Person.prototype.__proto__ === Object.prototype
  • 刚刚上面说了,所有的构造器都是函数对象,函数对象都是 Function 构造产生的
Object.__proto__ === Function.prototype
  • Object 的原型对象也有 __proto__ 属性指向 nullnull 是原型链的顶端
Object.prototype.__proto__ === null

总结:

  • 一切对象都是继承自 Object 对象,Object 对象直接继承根源对象 null
  • 一切的函数对象(包括 Object 对象),都是继承自 Function 对象
  • Object 对象直接继承自 Function 对象
  • Function 对象的 __proto__ 会指向自己的原型对象,最终还是继承自 Object 对象

3.2 dom.onclick 和 dom.addEventListener 的区别

dom.onclick onclick 事件会在元素被点击时发生,可以在 HTML 和 JavaScript 中使用,所有的浏览器都支持 onclick 事件。

onclick 属性可以使用在除了 < base > , < bdo >, < br >, < head >, < html >, < iframe >, < meta >, < param >, < script >, < style >, 和 < title >之外的所有 HTML 元素上。

dom.addEventListener() 用于向文档添加事件句柄,具有三个参数,分别是事件,触发事件时所执行的函数,以及在捕获还是冒泡阶段执行

早期的浏览器不支持 addEventListener() 方法,但是可以使用 attachEvent() 方法来添加事件句柄。

🌼 区别:

事件监听器(addEventListener / attachEvent)

  • addEventListener 可以给元素添加多个事件,执行顺序从上到下依次执行,不会覆盖之前相同的事件。而 onclick 只能给元素添加一个事件,如果添加多个,后面的事件会覆盖前面的事件。
  • addEventListener 可以通过 removeEventListener() 移除事件。而 onclick 解绑事件直接将值设置为 null
  • addEventListener 注册事件时不需要写 on,而 onclick 注册事件则必须加 on。
  • addEventListener 对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效。
  • addEventListener 不能在 HTML 上使用,只能在< script >中添加。而 onclick 可以作为 HTML 属性添加,也能在< script >中添加。
  • addEventListeneruseCapture 参数,可以选择事件是在捕获阶段还是在冒泡阶段执行。而 onclick 不能选择是在捕获还是在冒泡阶段执行。
  • addEventListener 分离文档结构 (HTML) 和逻辑 (JavaScript),在大型的项目中便于开发和维护。

3.2 逻辑与 && 和 逻辑或 ||

  • 当操作数是布尔值时,逻辑与需要操作都为 true 才会返回 true,而逻辑或只需要操作数中有一个为 true 就会返回 true,否则返回 false。
  • 当操作数不是布尔值时,会进行短路运算。逻辑与和逻辑或都是一种短路操作符,从左到右的运算中,如果前者满足要求,就不再执行后者了
    • && 为取假运算,从左到右依次判断,如果遇到一个假值,就返回假值,以后不再执行,否则返回最后一个真值;
    • || 为取真运算,从左到右依次判断,如果遇到一个真值,就返回真值,以后不再执行,否则返回最后一个假值。

🌼 作用:

  • 避免变量赋值为 null 或者 undefined
  • 做空值判断 ✍常用例子:callback && callback()

3.2 null 和 undefined 的区别

🌷 undefind 是全局对象的一个属性,当

  • 声明变量未赋值
  • 一个函数没有返回值
  • 访问了某个对象不存在的属性
  • 函数定义了形参但没有传递实参

这时候都是 undefined

undefined 通过 typeof 判断类型是 undefined

🌷 null 代表对象的值未设置,相当于一个对象没有设置指针地址就是 null 。

null 通过 typeof 判断类型是 Object

null == undefinednull !== undefined

undefined 的值是 null 派生来的,所以他们表面上是相等的。

undefined 表示一个变量初始状态值,而 null 则表示一个变量被人为的设置为空对象。

🌼 为什么typeof null 是 Object ?

因为 JavaScript 数据类型在底层都是以二进制的形式表示的,如果二进制的前三位为 0 ,会被 typeof 判断为 Object 类型,而 null 的二进制位全是 0 ,自然也就被误判断为 Object 类型。

这个 bug 是初版本的 JavaScript 中留下的,扩展一下其他五种标识位:

  • 000 对象
  • 1 整型
  • 010 双精度类型
  • 100 字符串
  • 110 布尔类型

3.9 闭包 ✍

闭包是由函数以及声明该函数的词法环境组合而成的,也就是在一个内层函数中可以访问到其外层函数的作用域

在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁。

得分点 变量背包、作用域链、局部变量不销毁、函数体外访问函数的内部变量、内存泄漏、内存溢出、形成块级作用域、柯里化、构造函数中定义特权方法、Vue中数据响应式Observer

一般就是一个函数A,return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的变量背包,A函数执行结束后这个变量背包也不会被销毁,并且这个变量背包在A函数外部只能通过B函数访问。

闭包形成的原理:作用域链,当前作用域可以访问上级作用域中的变量

闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。 闭包带来的问题:由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。

加分回答 闭包的应用,能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。

闭包是指有权访问另一个函数作用域中变量的函数

形成闭包的原因:内部的函数存在外部作用域的引用就会导致闭包

闭包的作用:

  • 创建私有变量
  • 延长变量的生命周期
  • 闭包作用:延伸变量的作用范围。
  • 立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的 i 变量

柯里化函数:调用具有相同参数函数的同时,又能够轻松的重用

使用闭包需要注意什么:容易导致内存泄漏。闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。

3.1 事件循环

JavaScript 是一门单线程的语言,是以同步的方式运行的,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环。

在 JavaScript 中,所有的任务都可以分为

  • 同步任务:立即执行的任务
  • 异步任务:异步执行的任务
    • 宏任务
    • 微任务

JavaScript 引擎执行代码时,同步任务进入主线程,即主执行栈,异步任务进入任务队列,当有异步任务被压入异步任务队列时候,JavaScript 会将这些异步任务分为宏任务和微任务两个新的队列。然后,在所有同步任务执行完毕之后,异步任务会优先执行所有已经存在任务队列中的微任务。在所有的微任务执行完毕之后,再去宏任务队列中执行一个(注意是一个)宏任务,执行完一个宏任务之后会再去微任务队列中检查是否有新的微任务,有则全部执行,再回到宏任务队列执行一个宏任务,以此循环。这一套流程,就是事件循环

常见的微任务有:

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)

常见的宏任务有:

  • script (可以理解为外层同步代码)
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setImmediate、I/O(Node.js)

3.10 数组的常用方法

一、操作方法

数组基本操作可以归纳为 增、删、改、查,需要留意的是哪些方法会对改变原数组,哪些方法不会。

1)增

  • push(e1,e2...) 向数组的末尾添加一个或多个元素,返回新的数组长度,改变原数组。
  • unshift(e1,e2...) 向数组的开头添加一个或多个元素,返回新的数组长度,改变原数组。
  • splice()
  • arr1.concat(arr2,arr3...) 连接两个或多个数组,返回连接后的新数组,不影响原数组。

2)删

  • pop() 删除数组的最后一个元素,返回被删除的元素,改变原数组。
  • shift() 删除数组的第一个元素,返回被删除的元素,改变原数组。
  • splice()
  • slice(begin,end) 提取数组,包含 begin 不包含 end,返回一个新数组,不改变原数组。

3)改

  • splice(开始位置,要删除的元素数量,插入的元素) 可以对数组进行增删改操作。返回由被删除的元素组成的一个数组,改变原数组。

4)查

  • indexOf(searchElement,[fromIndex]) 返回要查找的元素在数组中的第一个位置,如果没找到则返回 -1。
  • includes(searchElement,[fromIndex]) 判断数组中是否包含指定元素,返回一个布尔值。
  • find(callbackFn) 返回数组中第一个满足回调函数的元素,没有符合条件的元素则返回 undefined
  • findIndex() 返回满足测试函数的第一个数组元素的位置。

二、排序方法

  • reverse() 翻转数组,改变原数组。
  • sort() 冒泡排序,改变原数组。

三、转换方法

  • join() 接收字符串分隔符为参数,返回包含所有项的字符串。

四、迭代方法

均不改变原数组!!!

  • some() 对数组的每一项都执行一次测试函数,如果有至少1个元素满足测试函数,则这个方法返回 true

  • every() 对数组的每一项都执行一次测试函数,如果数组内的所有元素是否都能满足测试函数,则这个方法返回 true

  • forEach() 对数组的每一项都执行一次测试函数,没有返回值。

  • filter() 过滤数组,将满足测试函数的元素组成新数组之后返回。

  • map() 对数组的每一项都执行一次测试函数,返回由每次函数调用的结果构成的新数组。

  • reduce() 对数组的每一项都按序执行一次测试函数,每一次运行都会将上一次的计算结果作为参数传入,最后将结果汇总为单个返回值。

5. ES6 语法

  1. Let 和 Const
  2. 模板字符串
  3. 箭头函数
  4. 扩展运算符
  5. for...of 和 for...in
  6. Symbol
  7. Map 和 Set
  8. Class
  9. Promise
  10. async / await
  11. 剩余参数

5.1 var 、let 、const

都是用来声明变量的

🌼 var

  • 使用 var 声明的变量存在变量提升的情况

  • 使用 var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明

  • 在函数中使用使用 var 声明变量时候,该变量是局部的,而如果在函数内不使用 var,该变量是全局的

🌼 let

  • 使用 let 声明的变量,只在所在的代码块内有效(具有块级作用域)

  • 只要块级作用域内存在 let 命令,这个区域就不再受外部影响,也就是大家常说的 “暂时性死区”

  • 不存在变量提升

  • let 不允许在相同作用域中重复声明同一变量

🌼 const

  • const 声明一个只读的常量,一旦声明,常量的值就不能改变。

  • const 一旦声明变量,就必须立即初始化,否则会报错。

  • 如果之前用 varlet 声明过变量,再用 const 声明同样会报错。

  • 对于复杂类型的数据,const只能保证变量指向的内存地址是固定的,并不能确保该变量的结构不变。

  • 其它情况,constlet 一致。

🌱 暂时性死区

使用 let 声明变量会存在块级作用域,它所声明的变量就 “绑定”这个区域,不再受外部的影响,如果在 letconst 在声明前调用,就会报错,语法上就叫做暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

5.2 模板字符串

模板字符串 是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能。

用一对 ` 包裹,然后在需要插入变量的地方,把变量放到 ${} 里面就可以了。

5.3 箭头函数

箭头函数相当于匿名函数,简化了函数定义。

箭头函数有两种写法,当函数体是单条语句的时候可以省略 {}return。另一种是包含多条语句,不可以省略 {}return

🌼 箭头函数的特点:

  • 没有自己的 this,所以 this 从外部获取,继承外部的执行上下文中的 this

  • 没有 prototype 属性,没有原型和 super,所以箭头函数也不能作为构造函数,也不能使用 new

  • 通过 call()apply() 方法调用箭头函数时,只能传递参数,不能绑定 this,第一个参数会被忽略。

  • 不能使用 yield 关键字,因此箭头函数不能用作 Generator 函数。

🌼 箭头函数的不适用场景:

  • 定义对象上的方法:当调用 dog.jumps 时,lives 并没有递减。因为 this 没有绑定值,而继承父级作用域。 var dog = { lives: 20, jumps: () => { this.lives--; } }

  • 不适合做事件处理程序:此时触发点击事件,this不是button,无法进行class切换 var button = document.querySelector('button'); button.addEventListener('click', () => { this.classList.toggle('on'); });

🌼 箭头函数函数适用场景:

  • 简单的函数表达式,内部没有this引用,没有递归、事件绑定、解绑定,适用于map、filter等方法中,写法简洁 var arr = [1,2,3]; var newArr = arr.map((num)=>num*num)

  • 内层函数表达式,需要调用this,且this应与外层函数一致时 let group = { title: "Our Group", students: ["John", "Pete", "Alice"], showList() { this.students.forEach( student => alert(this.title + ': ' + student) ); } }; group.showList();

5.4 扩展运算符

扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转变为参数序列。 ES6扩展运算符

5.5 for...in 和 for...of 的区别

for … in

  • 适合遍历对象,遍历数组的时候会出现奇奇怪怪的问题,可能按照随机顺序遍历数组元素。

  • 会遍历所有的可枚举属性,包括原型链上的,要判断一个属性是否是对象自身拥有的,而不是继承得到的,可以用 hasOwnProperty() 方法来判断

for … of

  • 适合遍历所有拥有迭代器的数据结构,如数组、字符串、映射、节点列表等。

5.6 Symbol

Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。

symbol 的出现就是为了解决全局变量名冲突的问题。

现在我们往往是多人协作,模块化的开发,如果有一天,你需要修改一个对象,而需要给这个对象添加一个属性 aaa ,就会出现如果你没有好好读之前的代码,而在其他的 js 文件也添加属性 aaa ,你就可能出现添加了一个同名的属性 aaa。就出现冲突,覆盖了。

除了作为属性名之外,Symbol 还可以:

  • 为对象定义一些非私有的、但又希望只用于内部的方法

  • 作为唯一值

🌼 特点:

  • 不能使用 new 来构建。

  • 不能使用点操作符,作为对象属性 key 时,在获取其值的时候,只能使用 ['xxx'] 形式获取。

  • 不能被 for...in 迭代,也不能用 Object.getOwnPropertyNames() 返回对象的属性,只能使用 Object.getOwnPropertySymbols() 得到它们。

5.7. Set、Map 两种数据结构

  • Set 是一种叫做集合的数据结构。无序的,不重复的。
  • Map是一种叫做字典的数据结构。键值对的有序列表,而键和值都可以是任意类型。

它们本身都是一个构造函数,用来生成 Set/Map 数据结构

🌼 Set方法:

  • add() :添加值,返回 Set 结构本身。当添加实例中已经存在的元素,set不会进行处理添加。

  • delete() :删除某个值,返回一个布尔值,表示删除是否成功。

  • has() :返回一个布尔值,判断该值是否存在

  • clear() :清除所有成员,没有返回值

🌼 Map属性和方法:

  • size 属性返回 Map 结构的成员总数。

  • set ( key , value ) :添加值,返回整个 Map 结构。如果添加的是已经存在的值,就会覆盖

  • get ( key ) :get 方法读取 key 对应的 value值,如果找不到 key,返回 undefined

  • has() :返回一个布尔值,判断该值是否存在

  • delete() :删除某个值,返回一个布尔值,表示删除是否成功。

  • clear() :清除所有成员,没有返回值

5.8 Class

5.9 Promise

Promise 是异步编程的一种解决方案,在以前我们处理多层异步操作就很容易造成回调地狱,现在有了 promise ,因为 promise 支持链式调用,所以我们就只需要.then.then 再.catch 就可以了。

promise 对象仅有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

状态只能从 pending 变为 fulfilled 和从 pending 变为 rejected 一旦状态改变,就不会再变。

Promise 对象是一个构造函数,用来生成 Promise 实例

const promise = new Promise(function(resolve, reject) {});

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolvereject

  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”

Promise构建出来的实例存在以下方法:

  • then() then是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数 then方法返回的是一个新的Promise实例
  • catch() 用于指定发生错误时的回调函数
  • finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

Promise 构造函数存在以下方法:

  • all() 方法用于将多个 Promise实例,包装成一个新的 Promise实例,只有所有的都成功,他才会成功
  • race() 任意一个有结果了就执行
  • allSettled() 方法所有的promise都有结果了就执行,无论成功还是失败
  • resolve()
    • 参数是一个 Promise 实例,promise.resolve将不做任何修改、原封不动地返回这个实例
    • 参数是一个thenable对象,promise.resolve会将这个对象转为 Promise对象,然后就立即执行thenable对象的then()方法
    • 参数不是具有then()方法的对象,或根本就不是对象,Promise.resolve()会返回一个新的 Promise 对象,状态为resolved
    • 没有参数时,直接返回一个resolved状态的 Promise 对象
  • reject()
  • any() 任意一个成功了就执行

5.10 async / await

5.11 剩余参数

4. Vue

4.1 v-model的实现原理

v-model本质上只是一颗语法糖,真正的实现靠的还是v-bind和oninput事件。

  1. v-bind:绑定响应式数据
  2. 通过触发oninput事件传递数据

Vue监视数据的原理:

			1. vue会监视data中所有层次的数据。

			2. 如何监测对象中的数据?
							通过setter实现监视,且要在new Vue时就传入要监测的数据。
								(1).对象中后追加的属性,Vue默认不做响应式处理
								(2).如需给后添加的属性做响应式,请使用如下API:
												Vue.set(target,propertyName/index,value) 或 
												vm.$set(target,propertyName/index,value)

			3. 如何监测数组中的数据?
								通过包裹数组更新元素的方法实现,本质就是做了两件事:
									(1).调用原生对应的方法对数组进行更新。
									(2).重新解析模板,进而更新页面。

			4.在Vue修改数组中的某个元素一定要用如下方法:
						1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
						2.Vue.set() 或 vm.$set()
			
			特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

v-for 遍历,能否用 key 做数组下标?

可以,但不推荐。

key 的作用主要是为了高效的更新 虚拟DOM,使用 v-for 更新已渲染的元素列表时,默认 就地复用 策略,列表数据修改的时候,会根据 key 值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的真实DOM。

如果我们使用了数组的下标来作为 key,而数据又恰好只在数组末尾新增元素的话,不会有影响;但是如果对数据进行了破坏顺序的操作,就会使后面没有被修改的数据反复更新,就会很没效率,还有可能引发一些奇怪的错误。

所以我们要尽可能在使用 v-for 时使用 key 来给每个节点做一个唯一标识,Diff算法 就可以正确的识别此节点,在新增节点的同时复用原来的节点。

			面试题:react、vue中的key有什么作用?(key的内部原理)
					
					1. 虚拟DOM中key的作用:
									key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 
									随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
									
					2.对比规则:
								(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
											①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
											②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

								(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
											创建新的真实DOM,随后渲染到到页面。
											
					3. 用index作为key可能会引发的问题:
										1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
														会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

										2. 如果结构中还包含输入类的DOM:
														会产生错误DOM更新 ==> 界面有问题。

					4. 开发中如何选择key?:
										1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
										2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
											使用index作为key是没有问题的。
	-->

为什么 vue2 不用 proxy?

vue2之前之所以不用 Proxy,是因为 Proxy 是 es6 提供的新特性,兼容性不好,最主要的是这个属性无法用 polyfill 来兼容。Polyfill 指的是用于实现浏览器并不支持的原生 API 的代码。

比如说 querySelectorAll 是很多现代浏览器都支持的原生 Web API, 但是有些古老的浏览器并不支持,那么假设有人写了一段代码来实现这个功能. 使这些浏览器也支持了这个功能,那么这就可以成为一个 Polyfill。

  • vue2 的数据双向绑定原理使用的是 es5 的数据劫持 object.defineProperty,如果读取数据就会触发 get,修改数据就会触发 set,达到数据和视图的响应和更新,但是因为 js 固有的特性,不能动态添加和删除属性,需要调用 $set$delete 这些方法来实现动态添加双向绑定属性。

  • vue3 的数据双向绑定使用的是 es6Proxy 配合 Reflect 实现的,他比起数据劫持可以监听到对象添加属性和删除属性。

为什么vue2.0不用proxy

proxy的优势 Proxy 与 Object.defineProperty 优劣对比

  • 响应式是惰性的。
  • 在 Vue2 中,对于一个深层属性嵌套的对象,要劫持它内部深层次的变化,就需要递归遍历这个对象,执行 Object.defineProperty 把每一层对象数据都变成响应式的,这无疑会有很大的性能消耗。

  • 在 Vue3 中,使用 Proxy API 并不能监听到对象内部深层次的属性变化,因此它的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部属性才会变成响应式,简单的可以说是按需实现响应式,减少性能消耗。

  • Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的

为什么要用 Proxy 替代 defineProperty ?

  1. defineProperty API 的局限性最大原因是它只能针对单例属性做监听。 Vue2.x中的响应式实现正是基于defineProperty中的descriptor,对 data 中的属性做了遍历 + 递归,为每个属性设置了 getter、setter。这也就是为什么 Vue 只能对 data 中预定义过的属性做出响应的原因。

  2. Proxy API的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作, 这就完全可以代理所有属性,将会带来很大的性能提升和更优的代码。 Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

5.浏览器

5.1 从浏览器地址栏输入 url 到请求返回发生了什么

  1. 输入 URL 后解析出协议、主机、端口、路径等信息,并构造一个 HTTP 请求。

  2. DNS 域名解析。

  3. TCP 连接。

  4. 发送 HTTP 请求。

当建立 TCP 连接之后,就可以在这基础上进行通信,浏览器发送 HTTP 请求到目标服务器

请求的内容包括:

  • 请求行
  • 请求头
  • 请求主体
  1. 服务器响应请求并返回 HTTP 报文。

当服务器接收到浏览器的请求之后,就会进行逻辑操作,处理完成之后返回一个 HTTP 响应消息,包括:

  • 状态行
  • 响应头
  • 响应正文

在服务器响应之后,由于现在 HTTP 默认开始长连接 keep-alive,当页面关闭之后,TCP 链接则会经过四次挥手完成断开

  1. 浏览器渲染页面。

当浏览器接收到服务器响应的资源后,首先会对资源进行解析:

  • 查看响应头的信息,根据不同的指示做对应处理,比如重定向,存储cookie,解压gzip,缓存资源等等
  • 查看响应头的 Content-Type的值,根据不同的资源类型采用不同的解析方式

🌱 关于页面的渲染过程如下:

  • 解析HTML,构建 DOM 树
  • 解析 CSS ,生成 CSS 规则树
  • 合并 DOM 树和 CSS 规则,生成 render 树
  • 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算
  • 绘制 render 树( paint ),绘制页面像素信息
  • 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成,显示在屏幕上

5.2 性能优化:

  1. 减少 HTTP 请求:合并和压缩静态资源,减少页面中的资源引用,使用雪碧图或字体图标等技术来减少请求次数。

  2. 使用缓存:通过设置适当的缓存策略,使得浏览器可以缓存页面的静态资源,减少重复请求。

  3. 压缩传输数据:使用压缩算法(如 Gzip)压缩传输的数据,减小文件大小,加快传输速度。

  4. 使用 CDN 加速:将静态资源部署到 CDN(内容分发网络)上,使得用户可以从离其较近的服务器获取资源,提高访问速度。

  5. 使用异步加载:将非关键资源使用异步加载的方式加载,避免阻塞页面的渲染。

  6. 优化图片:使用适当的图片格式,压缩图片大小,使用懒加载或按需加载等技术来控制图片的加载。

  7. 前端代码优化:减少不必要的重绘和回流,优化 JavaScript 的执行性能,避免使用过多的全局变量等。

  8. 使用 HTTP/2:HTTP/2 支持多路复用,减少了请求的延迟,提高了传输效率。

5.1 什么是跨域请求,如何解决跨域问题?

(1)什么是跨域请求

跨域指的是浏览器不能执行其他网站的脚本,从一个域名的网页去请求另一个域名的资源时,协议、域名、端口任一不同,都是跨域。跨域是由浏览器的同源策略造成的,是浏览器对 JavaScript 施加的安全限制。

(2)跨域请求的解决方法包括:

  1. CORS(跨域资源共享)

这是一种标准的解决跨域问题的方法。通过在服务器响应中添加 CORS 头部,允许指定的域名访问资源。服务器端设置 CORS 头部后,浏览器会允许跨域请求,前提是请求的源 (Origin) 在服务器允许的白名单中。

  1. JSONP (JSON with Padding)

JSONP是一种通过动态创建 <script> 标签来加载跨域脚本的方法。它利用了浏览器对 <script> 标签的跨域访问不受同源策略限制的特性。但 JSONP 只支持 GET 请求,且存在一定的安全风险,因为它可以执行任意的 JavaScript 代码。

  1. 代理服务器

可以在同一域下设置一个代理服务器,将跨域请求发送到目标服务器,并将响应返回给客户端。这样,客户端只会与代理服务器进行通信,而代理服务器会处理跨域请求。

  1. WebSocket

WebSocket 是一种双向通信协议,可以用于跨域通信。WebSocket 连接不受同源策略的限制,并且可以在客户端和服务器之间实时传输数据。

5.2 defer 和 async 的区别?

defer 和 async 是 script 标签的两个属性,用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。

🌼 为什么需要?

因为浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的 script 标签,就会暂停解析,先发送网络请求获取并执行该 JS 脚本,执行完毕后才会继续HTML的解析。这就会出现阻塞的问题,如果获取 JS 脚本的网络请求迟迟得不到响应,或者 JS 脚本执行时间过长,都会导致白屏/页面卡顿,给用户带来不好的体验。为了解决这个问题,我们可以在 script 标签中写入 deferasync 这两个属性。

async script :在解析 HTML 过程中进行脚本的异步下载,下载成功立马执行,有可能会阻断 HTML 的解析。并且脚本之间的网络请求是异步的,谁先下载好谁执行,也就是后面发起的请求可能会比先发起的请求先执行。

defer script:也是在解析 HTML 过程中进行脚本的异步下载,当文档解析完成之后再按照顺序执行脚本。完全不会阻碍 HTML 的解析,往往用在页面的解析不依赖于脚本的情况。

🌼 页面的加载和渲染过程:

  1. 首先浏览器发送请求,获取 HTML 文档,开始自上而下解析并构建DOM

  2. 构建DOM的过程中,如果遇到外联样式声明或者脚本声明,会暂停文档解析,创建新的网络请求,获取外联样式或者脚本文件

  3. 样式文件下载完成后,构建 CSSOM;脚本文件下载完成后,解析并执行,之后才会继续解析文档构建DOM

  4. 完成文档解析后,将 DOM 和 CSSOM 进行关联和映射,最后将视图渲染到浏览器窗口

5.3 防抖和节流

  • 防抖:事件触发 n 秒后才执行,若在 n 秒内被重复触发,则重新计时,在新触发的事件时间的 n 秒后执行总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行
  • 节流:n 秒内只执行一次,若在 n 秒内重复触发事件,只有一次生效。

一、节流

关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。 1.

防抖在连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

节流在间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 搜索框,搜索联想功能

参考文章 / 网站

# 牛客网面试题库

# 做了一份前端面试复习计划,保熟~

# 前端10W+字八股+半年实习经历+400道算法+两年学校创新创业团队开发也无法上岸的经历~之前端我不干了!

# web前端面试 - 面试官系列