• 2023Web前端开发面试手册


    ​​​​​​​​在这里插入图片描述
    在这里插入图片描述

    HTML基础

    1. HTML 文件中的 DOCTYPE 是什么作用?

    HTML超文本标记语言: 是一个标记语言, 就有对应的语法标准

    DOCTYPE 即 Document Type,网页文件的文档类型标准。

    主要作用是告诉浏览器的解析器要使用哪种 HTML规范XHTML规范 来解析页面。

    DOCTYPE 需要放置在 HTML 文件的 标签之前,如:

    DOCTYPE html>
    <html>
      ...
    html> (目前主流)
    
    • 1
    • 2
    • 3
    • 4
    DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
      ...
    html> (早期)
    
    • 1
    • 2
    • 3
    • 4

    2. HTML、XML、XHTML 之间有什么区别?

    它们都属于标记语言

    语言中文名说明
    HTML4超文本标记语言主要用于做界面呈现。HTML 是先有实现,后面才慢慢制定标准的,导致HTML⾮常混乱和松散,语法非常的不严谨。
    XML可扩展标记语言主要⽤于存储数据和结构。语法严谨,可扩展性强。由于 JSON 也有类似作⽤但更轻量⾼效, XML 的市场变得越来越⼩。
    XHTML可扩展超文本标记语言属于加强版 HTML,为解决 HTML 的混乱问题而生,在语法方面变得和 XML 一样严格。另外,XHTML 的出现也催生了 HTML 5,让HTML向规范化严谨化过渡。
    HTML5超文本标记语言在HTML的基础上进行拓展,用于页面呈现 (目前标准)

    XML的要求会比较严格:

    1. 有且只能有一个根元素

    2. 大小写敏感

    3. 正确嵌套

    4. 必须双引号

    5. 必须闭合标签

    
    
      
        小张
        小王
      
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    XHTML 和 HTML5 的历史延展: https://www.cnblogs.com/my-freedom/p/5796915.html

    番外: 所以 HTML5 是HTML的新一代标准, 所谓的 H5 工程师这一词, 其实是国产词, 泛指新一代的web开发工程师,

    具体H5工程师, 做什么工作方向, 还是要看需求 (比如: 移动端开发, PC端网页开发, H5小游戏开发…)

    3. 前缀为 data- 开头的元素属性是什么?

    这是一种为 HTML 元素添加额外数据信息的方式,被称为 自定义属性

    我们可以直接在元素标签上声明这样的数据属性:

    <div id="mydiv" data-message="Hello,world" data-num="123">div>
    
    • 1

    也可以使用 JavaScript 来操作元素的数据属性:

    let mydiv = document.getElementById('mydiv')
    
    // 读取
    console.log(mydiv.dataset.message)
    
    // 写入
    mydiv.dataset.foo = "bar!!!"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意:在各种现代前端框架出现后,这种原生的自定义属性已经变得不太常用了, 以前的使用频率非常高, 所以我们知道即可。

    例如: vue实现删除功能时需要 id, 可以直接传值

    
      张三
      18
      体育好
      
        
        
      
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4. 谈谈你对 HTML 语义化的理解?

    考察核心点: 语义化的好处 (利于SEO, 可阅读性更好)

    语义化之前:

    在提倡语义化之前,你可能会使用各种各样的标签去实现同样的功能,比如:

    • 使用 span、div、p、a 等做文字, 做按钮
    • 使用 div 做一切

    从功能方面来说,这确实是不存在什么问题的,完全可以达到功能效果。但这不符合直觉,非常不友好:

    • 对人不友好:阅读代码的人不能一眼看出代码的功能
    • 对机器不友好:解析代码的程序不能很好的对这些元素进行分类处理

    语义化之后:

    为了解决上面说的问题,HTML5 规范提倡语义化标签,即使⽤恰当语义的 HTML 标签让⻚⾯具有良好的结构与含义,⽐如:

    • 标签就代表段落

    • 代表正⽂内容
    • 代表头部
    • 等等…

    语义化的好处:

    对开发者的好处对机器/程序的好处
    使⽤了语义化标签的程序,可读性明显增强,开发者可以比容易和清晰地看出⽹⻚的结构;这也更利于整个开发团队的协作开发和后续维护工作带有语义的网页代码在⽂字类应用上的表现⼒丰富,利于搜索引擎爬⾍程序来爬取和提取出有效的信息;语义化标签还⽀持读屏软件,根据⽂章可以⾃动⽣成⽬录等,方便特殊人群无障碍的使用这些网页程序。

    语义化的适用性:

    语义化适合内容型的网站来使用(如简书、知乎),对其⽹站内容的传播有很⼤帮助。

    5. HTML5 对比 HTML4 有哪些不同之处?

    考察点: 是否了解 html5 新增的一些新特性

    不同点备注说明
    只有一种 DOCTYPE ⽂件类型声明(统一标准)
    增加了一些新的标签元素(功能, 语义化)section, video, progress, nav, meter, time, aside,
    canvas, command, datalist, details, embed, figcaption,
    figure, footer, header, hgroup…
    input 支持了几个新的类型值date, email, url 等等
    新增了一些标签属性charset(⽤于 meta 标签);async(⽤于 script 标签)
    新增的全域属性contenteditable, draggable…
    hidden…
    新增API本地存储, 地理定位, Canvas绘图, 拖拽API, 即时通信WebSocket…

    获取地理定位: navigator.geolocation.getCurrentPosition(successCallback, errorCallback) (为了安全, 需要在 https 网站使用)

    记忆角度: 更标准, 新增标签, 新增type表单属性, 新增全域属性, 新增API…

    6. meta 标签有哪些常用用法?

    标签的具体功能一般由 name/http-equiv 和 content 两部分属性来定义。

    • 如果设置 name 属性,则它描述的是网页文档的信息(例如:作者、⽇期和时间、⽹⻚描述、 关键词)
    • 如果设置 http-equiv 属性,则它描述的相当于是 HTTP 响应头信息(例如:网页内容信息, 网页缓存等)

    一些常用的功能及写法:

    1. 设置网页关键词 (SEO)
    <meta name="keywords" content="电商,好货,便宜">
    
    • 1
    1. 设置网页视口(viewport)控制视⼝的⼤⼩、缩放和⽐例等 (移动端开发)
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    
    • 1
    1. 设置 http 响应头:Content-Type 网页内容类型 (字符集)
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    
    
    <meta charset="utf-8">
    
    • 1
    • 2
    • 3
    • 4

    7. img 标签的 srcset 的作用是什么?

    考察点: 处理响应式图片的方式 (css媒体查询换的是背景图片, 而不是 img 标签的 src)

    开发者和设计师们竞相寻求 处理响应式图片 的方法。这的确是一个棘手的问题 ,因为我们对同一个网站在众多设备宽度下,

    使用同一图像源。你愿意在一个大显示屏上显示模糊地、马赛克状的图像?

    你愿意在你的手机上加载一个巨大的(虽然更漂亮的)图像?这个问题令人左右为难。 博客链接

    其实通过使用 img 标签的 srcset 属性,可定义一组额外的图片集合,让浏览器根据不同的屏幕状况选取合适的图片来显示。

    也就是图片的响应式处理能力。

    如果你的响应式需求比较简单,只需要针对屏幕的不同 dpr (device pixel ratio,设备像素比)来决定图片的显示的话,

    dpr 设备像素比, 越高, 能够显示的越清晰 (dpr: 2, dpr: 3)

    那么就只要这么写:

    <img srcset="320.png 1x, 640.png 2x, 960.png 3x" />
    
    • 1

    对于可变宽度的图像,我们使用srcset搭配w描述符以及sizes属性 。

    • w描述符告诉浏览器列表中的每个图象的宽度。

    • sizes属性需要至少包含两个值,是由逗号分隔的列表。

    根据最新规范,如果srcset中任何图像使用了w描述符,那么必须要设置sizes属性。

    sizes属性有两个值:

    1. 第一个是媒体查询条件;

    2. 第二个是图片对应的尺寸值,

      在特定媒体条件下,此值决定了图片的宽度。

      需要注意是,源图尺寸值不能使用百分比,如果要用100%, vw是唯一可用的CSS单位。

    <img alt="img元素srcset属性浅析"
      srcset="
        320.png 320w, 
        480.png 480w, 
        640.png 640w"
      sizes="
        (max-width: 320px) 100vw, 
        (max-width: 360px) 320px,
        (max-width: 480px) 360px,
        (max-width: 640px) 480px,
        640px"
      src="640.png"
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    为 img 定义以上属性后,浏览器的工作流程如下:

    1. 检查设备的实际宽度
    2. 检查 img 标签的 sizes 属性中定义的媒体查询条件列表,并计算哪个条件最先匹配到
    3. 得到图片此时的响应式宽度
    4. 加载 srcset 中最接近, 最适合媒体查询匹配到的宽度的图片

    注意: 测试时, 清除缓存测试, 因为一旦加载了高清图, 就不会也没有必要, 回过去再用小图替换了

    且我们无法确定究竟显示哪张图像,因为每个浏览器根据我们提供的信息挑选适当图像的算法是有差异的。

    (译者注:srcset和size列表是对浏览器的一个建议(hint),而非指令。由浏览器根据其能力、网络等因素来决定。)

    8. 响应式图片处理优化: Picture 标签

    考察点: 响应式图片处理

    picture元素就像是图像和其源的容器。浏览器仍然需要img元素,用来表明需要加载的图片

    下可放置零个或多个标签、以及一个标签,为不同的屏幕设备和场景显示不同的图片。

    如果source匹配到了, 就会优先用匹配到的, 如果没有匹配到会往下继续找

    使用picture元素选择图像,不会有歧义。

    浏览器的工作流程如下:

    • 浏览器会先根据当前的情况,去匹配和使用提供的图片

    • 如果未匹配到合适的,就使用标签提供的图片

    <picture>
      <source srcset="640.png" media="(min-width: 640px)">
      <source srcset="480.png" media="(min-width: 480px)">
      <img src="320.png" alt="">
    picture>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    9. 在 script 标签上使用 defer 和 async 的区别是什么?

    明确: defer 和 async 的使用, 可以用于提升网页性能

    script标签存在两个属性,defer和async,因此 script标签 的使用分为三种情况:

    1. 没有defer或async属性,浏览器会立即加载并执行相应的脚本。

      不等待后续加载的文档元素,读到就开始加载和执行,此举会阻塞后续文档的加载

    2. 有了async属性,表示后续文档的加载和渲染与js脚本的加载和执行是并行进行的,即异步执行;

    3. 有了defer属性,加载后续文档的过程和js脚本的加载是并行进行的(异步),此时的js脚本仅加载不执行, js脚本的执行需要等到文档所有元素解析完成之后,DOMContentLoaded事件触发执行之前。

    下图是使用了 defer、async、和未使用时的运行情况对比:

    【上图的图例说明】

    绿线:HTML 的解析时间

    蓝线:JS 脚本的加载时间

    红色:JS 脚本的执行时间

    从图中我们可以明确一下几点:

    1.defer和async在网络加载过程是一致的,都是异步执行的;(放在页面顶部, 也不会阻塞页面的加载, 与页面加载同时进行)

    2.两者的区别, 脚本加载完成之后, async是立刻执行, defer会等一等 (等前面的defer脚本执行, 等dom的加载)

    所以, js脚本加上 async 或 defer, 放在头部可以减少网页的下载加载时间, 如果不考虑兼容性, 可以用于优化页面加载的性能

    
    
    
    • 1
    • 2

    10. 前端做本地数据存储的方式有哪些?

    1. Cookies
    2. localStorage
    3. sessionStorage
    4. Web SQL
    5. IndexedDB

    11. 以上几种前端存储的区别是什么?

    方式名称标准说明功能说明
    CookiesHTML5 前加入1.会为每个请求自动携带所有的Cookies数据,比较方便,但是也是缺点,浪费流量;
    2.每个domain(站点)限制存储20个cookie;
    3.容量只有4K
    4.浏览器API比较原始,需要自行封装操作。 (js-cookie)
    localStorageHTML5 加入1.兼容IE8+,操作方便;
    2.永久存储,除非手动删除;
    3.容量为5M
    sessionStorageHTML5 加入1.功能基本与 localStorage 相似,但当前页面关闭后即被自动清理;
    2.与Cookies、localStorage 不同点是不能在所有同源窗口间共享,属于会话级别的存储
    Web SQL非标准功能1.2010年已被废弃,但一些主流浏览器中都有相关的实现;
    2.类似于 SQLite 数据库,是一种真正意义上的关系型数据库,⽤SQL进⾏操作;
    IndexedDBHTML5 加入1.是一种 NoSQL 数据库,⽤键值对进⾏储存,可进⾏快速读取操作;
    2.适合复杂 Web存储场景,⽤JS操作⽅便 (前端大量存数据的场景较少, 如果有, 可以用)
    3.存储空间容量, 大于等于 250MB,甚至没有上限

    CSS基础

    1. CSS选择器的优先级是怎么样的?

    CSS选择器的优先级顺序:

    内联样式 > ID选择器 > 类选择器 > 标签选择器

    优先级的计算:

    优先级是由 A、B、C、D 四个值来决定的,具体计算规则如下

    • A={如果存在内联样式则为1,否则为0}
    • B={ID选择器出现的次数}
    • C={类选择器、属性选择器、伪类选择器出现的总次数}
    • D={标签选择器、伪元素选择器出现的总次数}

    计算示例:

    样式一:

    /*
      A=0   不存在内联样式
      B=0   不存在ID选择器
      C=1   有一个类选择器
      D=3   有三个标签选择器
    
      最终计算结果:{0,0,1,3}
    */
    div ul li .red { ... }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    样式二:

    /*
      A=0   不存在内联样式
      B=1   有一个ID选择器
      C=0   不存在类选择器
      D=0   不存在标签选择器
    
      最终计算结果:{0,1,0,0}
    */
    #mydiv { ... }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    计算完成后,我们通过从A到D的顺序进行值的大小比较,权重由A到D从高到低,只要比较出最大值即可。例如上面的两个样式:

    1. 样式一的A=0,样式二的A=0  【相等,继续往下比较】
    2. 样式一的B=0 < 样式二的B=1 【样式二的大,不继续往下比了,即认为样式二的优先级更高】
    
    • 1
    • 2

    2. 通过 CSS 的哪些方式可以实现隐藏页面上的元素?

    方式说明
    opacity: 0通过将元素的透明度设置为0,实现看起来隐藏的效果;但是依然会占用空间并可以进行交互
    visibility: hidden与透明度为0的方案非常类似,会占据空间,但不可以进行交互
    overflow: hidden只会隐藏元素溢出的部分;占据空间且不可交互
    display: none可以彻底隐藏元素并从文档流中消失,不占据空间也不能交互,且不影响布局
    z-index: -9999通过将元素的层级置于最底层,让其他元素覆盖住它,达到看起来隐藏的效果
    transform: scale(0,0)通过将元素进行缩放,缩小为0;依然会占据空间,但不可交互
    left: -9999px通过将元素定位到屏幕外面,达到看起来看不到的效果

    3. px、em、rem之间有什么区别?

    考察点: 相对单位, 绝对单位, 以及适配问题

    单位名称说明
    px绝对单位。代表像素数量,页面会按照给出的精确像素进行展示
    em相对单位。默认的基准点为父元素的字体大小,而如果自身定义了字体大小则按自身的来算。所以即使在同一个页面内,1em可能不是一个固定的值。
    rem相对单位。可以理解为 root em,即基准点为根元素的字体大小。rem是CSS3中新增单位,Chrome/FireFox/IE9+都支持, 一般用于做移动端适配

    正常开发 px 使用率较高, 如果要做 rem 适配, 会用到 rem 单位!

    rem布局的原理:

    1. 使用 rem 为单位
    2. 动态的设置 html font-size (媒体查询, js设置, 插件设置都可以)

    webpack有工具, 可以写 px, 自动转 rem https://youzan.github.io/vant/#/zh-CN/advanced-usage

    4. 让元素水平居中的方法有哪些?

    方法一:使用 margin

    通过为元素设置左右的 margin 为 auto,实现让元素居中。

    <div class="center">本内容会居中div>
    
    • 1
    .center {
       height: 500px;
       width: 500px;
       background-color: pink;
       margin: 0 auto;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    方式二: 转成行内块, 给父盒子设置 text-align: center

    <div class="father">
    	<div class="center">我是内容盒子div>
    div>
    
    • 1
    • 2
    • 3
    .father {
      text-align: center;
    }
    .center {
      width: 400px;
      height: 400px;
      background-color: pink;
      display: inline-block;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    方法三:使用 flex 布局

    使用 flex 提供的子元素居中排列功能,对元素进行居中。

    <div class="father">
    	<div class="center">我是内容盒子div>
    div>
    
    • 1
    • 2
    • 3
    .father {
      display: flex;
      background-color: skyblue;
      justify-content: center;
      align-items: center;
    }
    .center {
      width: 400px;
      height: 400px;
      background-color: pink;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    方式四: 使用定位布局

    <div class="father">
      <div class="center">我是内容盒子div>
    div>
    
    • 1
    • 2
    • 3
    .father {
      background-color: skyblue;
      position: relative;
      height: 500px;
    }
    .center {
      width: 400px;
      height: 400px;
      background-color: pink;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    【更多方式参考】实现水平居中垂直居中

    5. 在 CSS 中有哪些定位方式?

    也就是 position 样式的几个属性。

    static 正常文档流定位

    • 此时设置 top、right、bottom、left 以及 z-index 都无效

    • 块级元素遵循从上往下纵向排列,行级元素遵循从左到右排列

    relative 相对定位

    这个 “相对” 是指相对于正常文档流的位置。

    absolute 绝对定位

    当前元素相对于 **最近的非 static 定位的祖先元素 **来确定自己的偏移位置。

    例如,当前为 absolute 的元素的父元素、祖父元素都为 relative,则当前元素会相对于父元素进行偏移定位。

    fixed 固定定位

    当前元素相对于屏幕视口 viewport 来确定自己的位置。并且当屏幕滚动时,当前元素的位置也不会发生改变。

    sticky 粘性定位

    这个定位方式有点像 relative 和 fixed 的结合。当它的父元素在视口区域、并进入 top 值给定的范围内时,当前元素就以 fixed 的方式进行定位,否则就以 relative 的方式进行定位。

    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .header {
        width: 100%;
        height: 100px;
        background-color: orange;
      }
      .nav {
        width: 100%;
        height: 200px;
        background-color: pink;
        position: sticky;
        top: 0px;
      }
      .main {
        width: 100%;
        height: 100px;
        background-color: skyblue;
      }
    
    style>
    
    <div class="header">我是头部div>
    <div class="nav">我是导航div>
    <div class="container">
        <div class="main">我是主体部分1div>
        <div class="main">我是主体部分2div>
        <div class="main">我是主体部分3div>
        <div class="main">我是主体部分4div>
        <div class="main">我是主体部分5div>
        <div class="main">我是主体部分6div>
        <div class="main">我是主体部分7div>
        <div class="main">我是主体部分8div>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    6. 如何理解 z-index?

    可以将它看做三维坐标系中的z轴方向上的图层层叠顺序。

    元素默认的 z-index 为 0,可通过修改 z-index 来控制设置了postion 值的元素的图层位置。

    可以将这种关系想象成一摞书本,通过 z-index 可以改变一本书在这摞书中的上下位置。

    z-index的小坑, 如果父辈元素有定位, 且配置了z-index, 优先按照父辈元素的定位的z-index进行比较层级

    <style>
      .father {
        width: 100%;
        height: 200px;
        position: relative;
        background-color: skyblue;
        z-index: 1;
      }
      .son {
        position: absolute;
        width: 100px;
        height: 100px;
        background-color: red;
        left: 0;
        top: 0;
        z-index: 999;
      }
      .box2 {
        position: absolute;
        width: 100px;
        height: 100px;
        background-color: blue;
        left: 0;
        top: 0;
        z-index: 100;
      }
    style>
    
    <div class="father">
      <div class="son">div>
    div>
    
    <div class="box2">div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    7. 如何清除浮动 ?

    考察: css基本功

    可以有以下几种方式:

    1. 定高法

    2. 使用一个空的div,并设置样式

    <div style="clear:both">div>
    
    • 1
    1. 为父元素添加 overflow: hidden

    2. 定义一个 clearfix 样式类

    .clearfix:after {
      content: "";  			/*设置内容为空*/
      height: 0;    			/*高度为0*/
      line-height: 0;			/*行高为0*/
      display: block;			/*将文本转为块级元素*/
      visibility: hidden;	/*将元素隐藏*/
      clear: both; 				/*清除浮动*/
    }
    
    .clearfix {
      zoom: 1; 				/*为了兼容IE*/
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    说明:当前 flex 已成为主流布局方式,适应性强, 且稳定, 所以浮动使用率目前已逐步降低。

    8. 谈谈你对 BFC 的理解?

    什么是 BFC:

    BFC 的全称是 Block Formatting Context,块级格式化上下文。这是一个用于在盒模型下布局块级盒子的独立渲染区域,

    将处于BFC区域内和区域外的元素进行互相隔离。

    何时会形成 BFC:

    满足下列条件之一就可触发BFC:

    • HTML根元素
    • position 值为 absolutefixed
    • float 值不为 none
    • overflow 值不为 visible
    • display 值为 inline-blocktable-celltable-caption

    BFC 的应用场景:

    1. 场景一:防止两个相邻块级元素的上下 margin 发生重叠 (上下margin合并问题)

    属于同一 BFC 的, 两个相邻块级子元素的上下 margin 会重叠,如果想让它们不重叠,可通过让这两个相邻块级子元素分属于不同的BFC。

    以下示例代码中的两个盒子的上下外边距会重合(即它们都设置了10px的外边距,我们期望它们之间的间距是 20px,但实际效果却只有 10px):

    <style>
      .box1 {
        width: 200px;
        height: 100px;
        background-color: red;
        margin-bottom: 10px; /* 下外边距为 10px */
      }
    
      .box2 {
        width: 200px;
        height: 100px;
        background-color: green;
        margin-top: 10px;  /* 上外边距为 10px */
      }
    style>
    
    <div class="box1">div>
    <div class="box2">div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    下面我们让其中一个盒子触发BFC,从而达到间隔 20px 的期望效果:

    .box2 {
      width: 200px;
      height: 100px;
      background-color: green;
      margin-top: 10px;
      display: inline-block; /* 通过设置 display 为 inline-block 可以触发 BFC */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1. 场景二:清除浮动

    以下示例代码中, 容器元素 box1 的高度会没有高:

    <style>
      .box1 {
        width: 200px;
        background-color: red;
      }
    
      .box2 {
        float: left;
        background-color: green;
      }
    style>
    
    <div class="box1">
      <div class="box2">Hello,worlddiv>
      <div class="box2">Hello,worlddiv>
      <div class="box2">Hello,worlddiv>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    而通过为 box1 添加 BFC 触发条件,可以让它的高度变回正常状态:

    .box1 {
      width: 200px;
      background-color: red;
      overflow: hidden;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 场景三:实现自适应布局, 防止元素被浮动元素覆盖(左边固定, 右边自适应)

    以下示例中,box2 会被设置了浮动的 box1 覆盖:

    <style>
      .box1 {
        float: left;
        width: 300px;
        background-color: red;
        height: 400px;
      }
    
      .box2 {
        background-color: blue;
        height: 600px;
      }
    style>
    
    <div class="box1">div>
    <div class="box2">div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    要避免这种覆盖行为,可以让 box2 触发 BFC, 实现布局效果, 左边固定右边自适应:

    .box2 {
      background-color: blue;
      height: 600px;
      overflow: hidden; /* 将 overflow 设置为非 visible 值可触发 BFC */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参考文章:深入理解BFC

    9. 什么是CSS Sprites以及它的好处?

    考察: 性能优化的方案

    CSS Sprites,俗称雪碧图、精灵图。这是一种CSS图片合并技术,就是将CSS中原先引用的一些较小的图片,合并成一张稍大的图片后再引用的技术方案。它可以减少请求多张小图片带来的网络消耗(因为发起的HTTP请求数变少了),并实现提前加载资源的效果。

    操作方式:

    可以手工使用图片编辑软件(如Photoshop),将多张小图片合并编辑变成一张大图片,并针对这张大图片,编写CSS样式来引用这张大图片中对应位置的小图片(涉及到的样式:background-image、background-position、background-size)。然后在HTML元素中使用这些样式即可。

    https://img.alicdn.com/tfs/TB1eiXTXlTH8KJjy0FiXXcRsXXa-24-595.png

    缺点:

    • CSS Sprites中任意一张小图的改动,都需要重新生成大图;并且用户端需要重新下载整张大图,这就降低了浏览器缓存的优势
    • 随着HTTP2的逐渐普及,HTTP2的多路复用机制可以解决请求多个小图片所创建多个HTTP请求的消耗,让CSS Sprites存在的价值降低了
    • 图片如果放大, 是会失真

    目前其他主流的处理图片的方案: iconfont 字体图标, svg矢量图…

    10. 你对媒体查询的理解是什么样的?

    考察点: 响应式适配, 根据不同的屏幕尺寸, 显示不同的效果 (设置盒子的样式)

    媒体查询是自 CSS3 开始加入的一个功能。它可以进行响应式适配展示。

    媒体查询由两部分组成:

    • 一个可选的媒体类型(如 screen、print 等)
    • 零个或多个媒体功能限定表达式(如 max-width: 500px、orientation: landscape 等)

    这两部分最终都会被解析为 true 或 false 值,然后整个媒体查询值为 true,则和该媒体查询关联的样式就生效,否则就不生效。

    使用示例:

    /* 在css样式表的定义中直接使用媒体查询 */ 
    .container {
      width: 600px;
      height: 200px;
      background-color: pink;
      margin: 0 auto;
    }
    @media screen and (max-width: 767px) {
      .container {
        width: 100%;
      }
    }
    @media screen and (min-width: 768px) and (max-width: 991px) {
      .container {
        width: 750px;
      }
    }
    @media screen and (min-width: 992px) and (max-width: 1199px) {
      .container {
        width: 980px;
      }
    }
    @media screen and (min-width: 1200px) {
      .container {
        width: 1170px;
      }
    }
    @media screen and (width: 1200px) {
      .container {
        background-color: skyblue;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    参考文章:深入理解CSS媒体查询

    11. 你对盒子模型的理解是什么样的?

    浏览器的渲染引擎在对网页文档进行布局时,会按照 “CSS 基础盒模型” (CSS Basic Box Model)标准,将文档中的所有元素都表示为一个个矩形的盒子,再用 CSS 去决定这些盒子的大小尺寸、显示位置、以及其他属性(如颜色、背景、边框等)。

    下图就是盒模型示意图,它由几部分组成:

    • 内容(content)
    • 内边距(padding)
    • 边框(border)
    • 外边距(margin)

    12. 标准盒模型和怪异盒模型有哪些区别?

    两者的区别主要体现在元素尺寸的表示上。

    盒模型的指定:

    在CSS3中,我们可以通过设置 box-sizing 的值来决定具体使用何种盒模型:

    • content-box 标准盒模型
    • border-box 怪异盒模型

    标准盒模型:

    box-sizing: content-box; (默认值)

    在标准盒模型下,元素的宽(width)和高(height)值即为盒模型中内容(content)的实际宽高值。

    因此,计算一个元素宽度的公式如下(不考虑margin, margin是外边距, 如果是计算占用页面的空间, 就要带上margin):

    盒子宽度 = border-left + padding-left + width + padding-right + border-right

    占据页面宽度 = margin-left + border-left + padding-left + width + padding-right + border-right + margin-right

    怪异盒模型:

    box-sizing: border-box; (目前主流常用值)

    在怪异盒模型下,元素的 width 和 height 值却不是 content 的实际宽高,而是去除 margin 后剩下的元素占用区域的宽高,即:

    因此,计算一个元素占用了页面总宽度的公式如下:

    盒子宽度 = width

    盒子占据页面宽度 = margin-left + width + margin-right

    参考文章:深入理解盒模型

    13. 说说伪类和伪元素的区别?

    什么是伪类?

    伪类(pseudo-class)是以冒号:为前缀,可被添加到⼀个选择器的末尾的关键字。

    它用于让样式在元素的特定状态下才被应用到实际的元素上。比如::checked:hover:disabled:first-child等。

    :hover

    :nth-child(1)

    :nth-child(2)

    :checked

    注意: 伪类, 虽然是写法比较特殊, css选择器的权重, 和类一致的

    什么是伪元素?

    :before / :after

    伪元素⽤于创建⼀些并不在 DOM 树中的元素,并为其添加样式。伪元素的语法和伪类类似,可以一个冒号或两个冒号为前缀。

    ⽐如,可以通过 :before:after 来在⼀个元素前、后增加⼀些额外的⽂本并为它们添加样式;

    并且,虽然⽤户可以看到这些⽂本,但其实它们并不在 DOM 树中。(坑: 伪元素是无法注册事件的, 所以不要通过js控制伪元素)

    两者的区别

    虽然它们在语法上是一致的,但是它们的功能区别还是非常明显的。

    • 伪类是用来匹配元素的特殊状态的
    • 伪元素是用来匹配元素的隶属元素的,这些隶属元素可以在界面中展示,但在 DOM 中不体现

    参考文章:伪类与伪元素

    14. 谈谈你对 flex 的理解?

    在真实的应用场景中,通常会遇到各种各样不同尺⼨和分辨率的设备,为了能在所有这些设备上正常的布局我们的应用界面,就需要响应式的界⾯设计方式来满⾜这种复杂的布局需求。

    flex 弹性盒模型的优势在于开发⼈员只需要声明布局应该具有的⾏为,⽽不需要给出具体的实现⽅式,浏览器负责完成实际布局,当布局涉及到不定宽度,分布对⻬的场景时,就要优先考虑弹性盒布局。

    你能联想到的flex语法有哪些呢?

    flex-direction: 调整主轴方向

    row:主轴方向为水平向右
    column:主轴方向为竖直向下
    row-reverse:主轴方向为水平向左
    column-reverse:主轴方向是竖直向上。
    
    • 1
    • 2
    • 3
    • 4

    justify-content主要用来设置主轴方向的对齐方式

    flex-start: 弹性盒子元素将向起始位置对齐
    flex-end: 弹性盒子元素将向结束位置对齐。
    center: 弹性盒子元素将向行中间位置对齐
    space-around: 弹性盒子元素会平均地分布在行里
    space-between:第一个贴左边,最后一个贴右边,其他盒子均分,保证每个盒子之间的空隙是相等的。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    align-items用于调整侧轴的对齐方式

    flex-start: 元素在侧轴的起始位置对齐。 
    flex-end: 元素在侧轴的结束位置对齐。
    center: 元素在侧轴上居中对齐。
    stretch: 元素的高度会被拉伸到最大(不给高度时, 才拉伸)。
    
    • 1
    • 2
    • 3
    • 4

    flex-wrap属性控制flex容器是单行或者多行,默认不换行

    nowrap: 不换行(默认),如果宽度溢出,会压缩子盒子的宽度。
    wrap: 当宽度不够的时候,会换行。
    
    • 1
    • 2

    align-content用来设置多行的flex容器的排列方式

    flex-start: 各行向侧轴的起始位置堆叠。 
    flex-end: 各行向弹性盒容器的结束位置堆叠。
    center: 各行向弹性盒容器的中间位置堆叠。
    space-around: 各行在侧轴中平均分布。 
    space-between: 第一行贴上边,最后一个行贴下边,其他行在弹性盒容器中平均分布。 
    stretch:拉伸,不设置高度的情况下。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可参考 flex布局教程


    JavaScript基础

    1. 解释下什么是变量声明提升?

    变量提升(hoisting),是负责解析执行代码的 JavaScript 引擎的工作方式产生的一个特性。

    JS引擎在运行一份代码的时候,会按照下面的步骤进行工作:

    1. 首先,对代码进行预解析,并获取声明的所有变量

    2. 然后,将这些变量的声明语句统一放到代码的最前面

    3. 最后,开始一行一行运行代码

    我们通过一段代码来解释这个运行过程:

    console.log(a) 
    
    var a = 1 
    
    function b() { 
      console.log(a) 
    }
    
    b() // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上⾯这段代码的实际执⾏顺序为:

    1. JS引擎将 var a = 1 分解为两个部分:变量声明语句 var a = undefined 和变量赋值语句 a = 1
    2. JS引擎将 var a = undefined 放到代码的最前面,而 a = 1 保留在原地

    也就是说经过了转换,代码就变成了:

    var a = undefined
    
    console.log(a) // undefined 
    
    a = 1
    
    function b() { 
      console.log(a) 
    }
    
    b() // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    变量的这一转换过程,就被称为变量的声明提升。

    而这是不规范, 不合理的, 我们用的 let 就没有这个变量提升的问题

    2. JS 的参数是以什么方式进行传递的?

    基本数据类型和复杂数据类型的数据在传递时,会有不同的表现。

    基本类型:是值传递

    基本类型的传递方式比较简单,是按照 值传递 进行的。

    let a = 1
    
    function test(x) { 
      x = 10  // 并不会改变实参的值
      console.log(x)
    }
    
    test(a) // 10 
    console.log(a) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    复杂类型: 传递的是地址! (变量中存的就是地址)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    来看下面的代码:

    let a = {
      count: 1 
    }
    
    function test(x) { 
      x.count = 10
      console.log(x)
    }
    
    test(a) // { count: 10 }
    console.log(a) // { count: 10 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    从运行结果来看,函数内改变了参数对象内的 count 后,外部的实参对象 a 的内容也跟着改变了,所以传递的是地址。

    思考题:

    let a = {
      count: 1 
    }; 
    
    function test(x) { 
      x = { count: 20 };
      console.log(x); 
    }
    
    test(a); // { count: 20 }
    console.log(a); // { count: 1 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    我们会发现外部的实参对象 a 并没有因为在函数内对形参的重新赋值而被改变!

    因为当我们直接为这个形参变量重新赋值时,其实只是让形参变量指向了别的堆内存地址,而外部实参变量的指向还是不变的。

    下图展示的是复杂类型参数传递后的状态:

    下图展示的是重新为形参赋值后的状态:

    3. JavaScript垃圾回收是怎么做的?

    JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。

    正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题,

    但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况。

    3.1 内存的生命周期

    JS环境中分配的内存, 一般有如下生命周期:

    1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存

    2. 内存使用:即读写内存,也就是使用变量、函数等

    3. 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存

      全局变量一般不会回收, 一般局部变量的的值, 不用了, 会被自动回收掉

    内存分配:

    // 为变量分配内存
    let i = 11
    let s = "ifcode"
    
    // 为对象分配内存
    let person = {
        age: 22,
        name: 'ifcode'
    }
    
    // 为函数分配内存
    function sum(a, b) {
        return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.2 垃圾回收算法说明

    所谓垃圾回收, 核心思想就是如何判断内存是否已经不再会被使用了, 如果是, 就视为垃圾, 释放掉

    下面介绍两种常见的浏览器垃圾回收算法: 引用计数 和 标记清除法

    3.3 引用计数

    IE采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。

    如果没有任何变量指向它了,说明该对象已经不再需要了。

    // 创建一个对象person, person指向一块内存空间, 该内存空间的引用数 +1
    let person = {
        age: 22,
        name: 'ifcode'
    }
    
    let p = person   // 两个变量指向一块内存空间, 该内存空间的引用数为 2
    person = 1       // 原来的person对象被赋值为1,对象内存空间的引用数-1,
                     // 但因为p指向原person对象,还剩一个对于对象空间的引用, 所以对象它不会被回收
    
    p = null         // 原person对象已经没有引用,会被回收
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    由上面可以看出,引用计数算法是个简单有效的算法。

    但它却存在一个致命的问题:循环引用。

    如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。

    function cycle() {
        let o1 = {}
        let o2 = {}
        o1.a = o2
        o2.a = o1 
        return "Cycle reference!"
    }
    
    cycle()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    3.4 标记清除算法

    现代的浏览器已经不再使用引用计数算法了。

    现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。

    标记清除法:

    • 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。

    • 简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。

    • 凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

    从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)。

    根据这个概念,上面的例子可以正确被垃圾回收处理了。

    参考文章:JavaScript内存管理

    4. 谈谈你对 JavaScript 作用域链的理解?

    JavaScript 在执⾏过程中会创建一个个的可执⾏上下⽂。 (每个函数执行都会创建这么一个可执行上下文)

    每个可执⾏上下⽂的词法环境中包含了对外部词法环境的引⽤,可通过该引⽤来获取外部词法环境中的变量和声明等。

    这些引⽤串联起来,⼀直指向全局的词法环境,形成一个链式结构,被称为作⽤域链。

    简而言之: 函数内部 可以访问到 函数外部作用域的变量, 而外部函数还可以访问到全局作用域的变量,

    这样的变量作用域访问的链式结构, 被称之为作用域链

    let num = 1
    
    function fn () {
      let a = 100
      function inner () {
        console.log(a)
        console.log(num)
      }
      inner()
    }
    fn()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    下图为由多个可执行上下文组成的调用栈:

    • 栈最底部为全局可执行上下文
    • 全局可执行上下文 之上有多个 函数可执行上下文
    • 每个可执行上下文中包含了指向外部其他可执行上下文的引用,直到 全局可执行上下文 时它指向 null

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    js全局有全局可执行上下文, 每个函数调用时, 有着函数的可执行上下文, 会入js调用栈

    每个可执行上下文, 都有者对于外部上下文词法作用域的引用, 外部上下文也有着对于再外部的上下文词法作用域的引用

    => 就形成了作用域链

    5. 谈谈你对闭包的理解?

    这个问题想考察的主要有两个方面:

    • 对闭包的基本概念的理解
    • 对闭包的作用的了解

    什么是闭包?

    MDN的官方解释:

    闭包是函数和声明该函数的词法环境的组合

    更通俗一点的解释是:

    内层函数, 引用外层函数上的变量, 就可以形成闭包

    需求: 定义一个计数器方法, 每次执行一次函数, 就调用一次进行计数

    let count = 0
    function fn () {
      count++
      console.log('fn函数被调用了' + count + '次')
    }
    fn()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这样不好! count 定义成了全局变量, 太容易被别人修改了, 我们可以利用闭包解决

    闭包实例:

    function fn () {
      let count = 0
    
      function add () {
        count++
        console.log('fn函数被调用了' + count + '次')
      }
    
      return add
    }
    const addFn = fn()
    addFn()
    addFn()
    addFn()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    闭包的主要作用是什么?

    在实际开发中,闭包最大的作用就是用来 变量私有

    下面再来看一个简单示例:

    function Person() {
      // 以 let 声明一个局部变量,而不是 this.name
      // this.name = 'zs'     =>  p.name
      let name = 'hm_programmer' // 数据私有
      
      this.getName = function(){ 
        return name
      }
      
      this.setName = function(value){ 
        name = value
      }
    }
    
    // new:
    // 1. 创建一个新的对象
    // 2. 让构造函数的this指向这个新对象
    // 3. 执行构造函数
    // 4. 返回实例
    const p = new Person()
    console.log(p.getName()) // hm_programmer
    
    p.setName('Tom')
    console.log(p.getName()) // Tom
    
    p.name // 访问不到 name 变量:undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在此示例中,变量 name 只能通过 Person 的实例方法进行访问,外部不能直接通过实例进行访问,形成了一个私有变量。

    6. JavaScript中数据类型的隐式转换规则(了解)

    在if语句、逻辑语句、数学运算逻辑、== 等情况下都可能出现隐式类型转换。

    下表展示了一系列原始值,通过隐式转换成数字、字符串、布尔类型后所得到的值:

    坑: 判断时, 尽量不要用 = = , 要用 = = = ( 两个等号判断, 如果类型不同, 默认会进行隐式类型转换再比较)

    7. 谈谈你对原型链的理解?

    要讲清楚这个问题,主要着重这几个方面:

    • 什么是原型对象
    • 构造函数, 原型对象, 实例的三角关系图
    • 原型链如何形成

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    原型对象

    在 JavaScript 中,除去一部分内建函数,绝大多数的函数都会包含有一个叫做 prototype 的属性,指向原型对象,

    基于构造函数创建出来的实例, 都可以共享访问原型对象的属性。

    例如我们的 hasOwnProperty, toString ⽅法等其实是 Obejct 原型对象的方法,它可以被任何对象当做⾃⼰的⽅法来使⽤。

    hasOwnProperty 用于判断, 某个属性, 是不是自己的 (还是原型链上的)

    来看一段代码:

    let person = { 
      name: "Tom", 
      age: 18, 
      job: "student"
    }
    
    console.log(person.hasOwnProperty("name")) // true 
    console.log(person.hasOwnProperty("hasOwnProperty")) // false 
    console.log(Object.prototype.hasOwnProperty("hasOwnProperty")) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以看到,hasOwnProperty 并不是 person 对象的属性,但是 person 却能调用它。

    那么 person 对象是如何找到 Object 原型中的 hasOwnProperty 的呢?这就要靠原型链的能力了。

    需求: 简单绘制原型三角关系图!

    原型链

    在 JavaScript 中,每个对象中都有一个 __proto__ 属性,这个属性指向了当前对象的构造函数的原型。

    对象可以通过自身的 __proto__属性与它的构造函数的原型对象连接起来,

    而因为它的原型对象也有 __proto__,因此这样就串联形成一个链式结构,也就是我们称为的原型链。

    6. 谈谈对于继承的理解

    为什么要学习继承 ?

    写的构造函数, 定义了一个类型 (人类), 万一项目非常大, 又有了细化的多个类型 (老师, 工人, 学生)

    学习继承, 可以让多个构造函数之间建立关联, 便于管理和复用

    什么是继承 ?

    继承: 从别人那里, 继承东西过来 (财产, 房产)

    代码层面的继承: 继承一些属性构造的过程和方法

    6.1 继承 - 原型继承

    原型继承: 通过改造原型链, 利用原型链的语法, 实现继承方法!

    分析需求:

    ​ 人类, 属性: name, age

    ​ 学生, 属性: name, age, className

    ​ 工人, 属性: name, age, companyName

    无论学生, 还是工人, => 都是人类, 所以人类原型上有的方法, 他们都应该要有

    // 1. 定义Person构造函数
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.say = function () {
      console.log('人类会说话')
    }
    
    // 2. 定义Student构造函数
    function Student (name, age, className) {
      this.name = name
      this.age = age
      this.className = className
    }
    // 3. 原型继承: 利用原型链, 继承于父级构造函数, 继承原型上的方法
    // 语法: 子构造函数.prototype = new 父构造函数()
    Student.prototype = new Person()
    Student.prototype.study = function() {
      console.log('学生在学习')
    }
    
    let stu = new Student('张三', 18, '80期')
    stu.say()
    console.log(stu)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    6.2 继承 - 组合继承

    组合继承有时候也叫伪经典继承,指的是将原型链 和 借用构造函数 call 技术组合到一块,

    从而发挥二者之长的一种继承模式,其背后的思路: 是使用原型链实现对原型属性和方法的继承 (主要是方法),

    而通过借用构造函数来实现对实例属性构造的继承。这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它的自己的属性。

    // 1. 定义Person构造函数
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.say = function () {
      console.log('人类会说话')
    }
    
    // 2. 定义Student构造函数
    function Student (name, age, className) {
      Person.call(this, name, age) // 实现构造属性的继承
      this.className = className
    }
    
    // 3. 原型继承: 利用原型链, 继承于父级构造函数, 继承原型上的方法
    // 语法: 子构造函数.prototype = new 父构造函数()
    Student.prototype = new Person()
    Student.prototype.study = function() {
      console.log('学生在学习')
    }
    
    let stu = new Student('张三', 18, '80期')
    stu.say()
    console.log(stu)
    
    // 方法通过 原型继承
    // 属性通过 父构造函数的.call(this, name, age)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    6.3 继承 - 寄生组合继承

    student实例上有 name age, 而原型 __proto__上不需要再有这些属性, 所以利用 Object.create 改装下

    Object.create(参数对象),

    1. Object.create 会创建一个新对象,
    2. 并且这个新对象的__proto__ 会指向传入的参数对象
    // 1. 定义Person构造函数
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.say = function () {
      console.log('人类会说话')
    }
    
    // 2. 定义Student构造函数
    function Student (name, age, className) {
      Person.call(this, name, age)
      this.className = className
    }
    
    // 3. 原型继承: 利用原型链, 继承于父级构造函数, 继承原型上的方法
    // 语法: 子构造函数.prototype = new 父构造函数()
    Student.prototype = Object.create(Person.prototype)
    Student.prototype.study = function() {
      console.log('学生在学习')
    }
    
    let stu = new Student('张三', 18, '80期')
    stu.say()
    console.log(stu)
    
    
    // 总结:
    // Object.create() 以参数的对象, 作为新建对象的__proto__属性的值, 返回新建的对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    6.4 es6 - class 实现继承 extends

    // 继承关键字 => extends
    class Person {
      constructor (name, age) {
        this.name = name
        this.age = age
      }
      jump () {
        console.log('会跳')
      }
    }
    
    class Teacher extends Person {
      constructor (name, age, lesson) {
        super(name, age) // extends 中, 必须调用 super(), 会触发执行父类的构造函数
        this.lesson = lesson
        console.log('构造函数执行了')
      }
      sayHello () {
        console.log('会打招呼')
      }
    }
    
    let teacher1 = new Teacher('zs', 18, '体育')
    console.log(teacher1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    8. 如何判断是否是数组?

    方法一:使用 toString 方法

    function isArray(arg) {
    	return Object.prototype.toString.call(arg) === '[object Array]'
    }
    
    let arr = [1,2,3]
    isArray(arr)  // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    方法二:使用 ES6 新增的 Array.isArray 方法

    let arr = [1,2,3]
    Array.isArray(arr) // true
    
    • 1
    • 2

    9. 谈谈你对this的理解?

    this 是一个在运行时才进行绑定的引用,在不同的情况下它可能会被绑定不同的对象。

    默认绑定 (指向window的情况) (函数调用模式 fn() )

    默认情况下,this 会被绑定到全局对象上,比如在浏览器环境中就为window对象,在node.js环境下为global对象。

    如下代码展示了这种绑定关系:

    message = "Hello"; 
    
    function test () { 
      console.log(this.message); 
    }
    
    test() // "Hello"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    隐式绑定 (谁调用, this指向谁) (方法调用模式 obj.fn() )

    如果函数的调用是从对象上发起时,则该函数中的 this 会被自动隐式绑定为对象:

    function test() {
    	console.log(this.message); 
    }
    
    let obj = {
      message: "hello,world",
      test: test
    }
    
    obj.test() // "hello,world"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    显式绑定 (又叫做硬绑定) (上下文调用模式, 想让this指向谁, this就指向谁)

    硬绑定 => call apply bind

    可以显式的进行绑定:

    function test() {
    	console.log(this.message); 
    }
    
    let obj1 = {
      message: "你好世界123"
    }
    
    let obj2 = {
      message: "你好世界456"
    }
    
    test.bind(obj1)() // "你好世界123"
    test.bind(obj2)() // "你好世界456"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    new 绑定 (构造函数模式)

    另外,在使用 new 创建对象时也会进行 this 绑定

    当使用 new 调用构造函数时,会创建一个新的对象并将该对象绑定到构造函数的 this 上:

    function Greeting(message) {
    	this.message = message;
    }
    
    var obj = new Greeting("hello,world")
    obj.message // "hello,world"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    小测试:

    let obj = {
        a: {
            fn: function () {
                console.log(this)
            },
            b: 10
        }
    }
    obj.a.fn()
    let temp = obj.a.fn;
    temp()
    
    // -------------------------------------------------------------
    
    function Person(theName, theAge){
        this.name = theName
        this.age = theAge
    }
    Person.prototype.sayHello = function(){ // 定义函数
        console.log(this)
    }
    
    let per = new Person("小黑", 18)
    per.sayHello()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    10. 箭头函数中的this指向什么?

    箭头函数不同于传统函数,它其实没有属于⾃⼰的 this

    它所谓的 this 是, 捕获其外层 上下⽂的 this 值作为⾃⼰的 this 值。

    并且由于箭头函数没有属于⾃⼰的 this ,它是不能被 new 调⽤的。

    我们可以通过 Babel 转换前后的代码来更清晰的理解箭头函数:

    // 转换前的 ES6 代码
    const obj = { 
      test() { 
        return () => { 
          console.log(this === obj)
        }
      } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    // 转换后的 ES5 代码
    var obj = { 
      test: function getArrow() { 
        var that = this
        return function () { 
          console.log(that === obj)
        }
      } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里我们看到,箭头函数中的 this 就是它上层上下文函数中的 this

    11. Promise 的静态方法

    promise的三个状态: pending(默认) fulfilled(成功) rejected(失败)

    1. resolve函数被执行时, 会将promise的状态从 pending 改成 fulfilled 成功
    2. reject函数被执行时, 会将promise的状态从pending 改成 rejected 失败

    Promise.reject()

    new Promise((resolve, reject) => {
    	reject()
    })
    
    • 1
    • 2
    • 3

    Promise.resolve()

    new Promise((resolve, reject) => {
    	resolve()
    })
    
    • 1
    • 2
    • 3

    Promise.all([promise1, promise2, promise3]) 等待原则, 是在所有promise都完成后执行, 可以用于处理一些并发的任务

    // 后面的.then中配置的函数, 是在前面的所有promise都完成后执行, 可以用于处理一些并发的任务
    Promise.all([promise1, promise2, promise3]).then((values) => {
      // values 是一个数组, 会收集前面promise的结果 values[0] => promise1的成功的结果
    })
    
    • 1
    • 2
    • 3
    • 4

    Promise.race([promise1, promise2, promise3]) 赛跑, 竞速原则, 只要三个promise中有一个满足条件, 就会执行.then(用的较少)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    12. 宏任务 微任务 是什么

    小例题:

    console.log(1)
    
    setTimeout(function() {
    	console.log(2)
    }, 0)
    
    console.log(3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    宏任务: 主线程代码, setTimeout 等属于宏任务, 上一个宏任务执行完, 才会考虑执行下一个宏任务

    微任务: promise .then .catch的需要执行的内容, 属于微任务, 满足条件的微任务, 会被添加到当前宏任务的最后去执行

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    事件循环队列 eventLoop

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    例题1:

      console.log(1)
    
      setTimeout(function() {
        console.log(2) // 宏任务
      }, 0)
    
      const p = new Promise((resolve, reject) => {
        resolve(1000)
      })
      p.then(data => {
        console.log(data)  // 微任务
      })
    
      console.log(3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    例题2:

    async function fn () {
      console.log(111)
    }
    fn()
    console.log(222)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    例题3:

    async function fn () {
      const res = await 2
      console.log(res)
    }
    fn()
    console.log(222)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    例题4:

    async function fn () {
      console.log('嘿嘿')
      const res = await fn2()
      console.log(res)  // 微任务
    }
    async function fn2 () {
      console.log('gaga')
    }
    fn()
    console.log(222)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    考察点: async 函数只有从 await 往下才是异步的开始

    13. async/await是什么?

    ES7 标准中新增的 async 函数,从目前的内部实现来说其实就是 Generator 函数的语法糖。

    它基于 Promise,并与所有现存的基于Promise 的 API 兼容。

    async 关键字

    1. async 关键字用于声明⼀个异步函数(如 async function asyncTask1() {...}

    2. async 会⾃动将常规函数转换成 Promise,返回值也是⼀个 Promise 对象

    3. async 函数内部可以使⽤ await

    await 关键字

    1. await 用于等待异步的功能执⾏完毕 var result = await someAsyncCall()

    2. await 放置在 Promise 调⽤之前,会强制async函数中其他代码等待,直到 Promise 完成并返回结果

    3. await 只能与 Promise ⼀起使⽤

    4. await 只能在 async 函数内部使⽤

    14. 相较于 Promise,async/await有何优势?

    1. 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
    2. 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
    3. 调试时的阅读性, 也相对更友好

    15. 深拷贝 浅拷贝

    引用类型, 进行赋值时, 赋值的是地址

    1. 浅拷贝

      let obj = {
      	name: 'zs',
      	age: 18
      }
      let obj2 = {
          ...obj
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    2. 深拷贝

      let obj = {
      	name: 'zs',
      	age: 18,
          car: {
              brand: '宝马',
              price: 100
          }
      }
      
      let obj2 = JSON.parse(JSON.stringify(obj))
      console.log(obj2)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      当然递归也能解决, 只是比较麻烦~

    其他方案, 可以参考一些博客


    HTTP协议

    1. HTTP有哪些⽅法?

    HTTP 1.0 标准中,定义了3种请求⽅法:GET、POST、HEAD

    HTTP 1.1 标准中,新增了请求⽅法:PUT、PATCH、DELETE、OPTIONS、TRACE、CONNECT

    2. 各个HTTP方法的具体作用是什么?

    方法功能
    GET通常⽤于请求服务器发送某些资源
    POST发送数据给服务器
    HEAD请求资源的头部信息, 并且这些头部与 HTTP GET ⽅法请求时返回的⼀致。
    该请求⽅法的⼀个使⽤场景是在下载⼀个⼤⽂件前先获取其⼤⼩再决定是否要下载, 以此可以节约带宽资源
    PUT⽤于全量修改⽬标资源 (看接口, 也可以用于添加)
    DELETE⽤于删除指定的资源
    OPTIONS⽤于获取⽬的资源所⽀持的通信选项 (跨域请求前, 预检请求, 判断目标是否安全)
    TRACE该方法会 让服务器 原样返回任意客户端请求的信息内容, 用于诊断和判断
    CONNECTHTTP/1.1协议中预留给能够将连接改为管道⽅式的代理服务器
    (把服务器作为跳板,让服务器代替用户去访问其它网页, 之后把数据原原本本的返回给用户)
    PATCH⽤于对资源进⾏部分修改

    GET POST PUT PATCH DELETE

    GET/DELETE 参数是在地址栏中传递的

    PUT/PATCH/POST 参数是在请求体传递的

    3. GET方法和POST方法有何区别?

    默认的http请求的内容, 在网络中传输, 明文的形式传递的 (https 对内容加密)

    GET方法POST方法
    数据传输⽅式通过URL传输数据 (地址栏拼接参数)通过请求体传输
    数据安全数据暴露在URL中,可通过浏览历史记录、缓存等很容易查到数据信息数据因为在请求主体内,
    所以有⼀定的安全性保证
    数据类型只允许 ASCII 字符⽆限制
    GET⽆害刷新、后退等浏览器操作是⽆害的可能会引起重复提交表单
    功能特性安全且幂等(这⾥的安全是指只读特性,就是使⽤这个⽅法不会引起服务器状态变化。
    幂等的概念是指同⼀个请求⽅法执⾏多次和仅执⾏⼀次的效果完全相同)
    ⾮安全(会引起服务器端的变化)、⾮幂等

    4. HTTP请求报文是什么样的?

    HTTP 请求报⽂的组成:请求⾏、请求头、(空⾏)、请求体。

    实际请求报文实例:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    请求行

    包含了请求⽅法、URL、HTTP 协议版本,它们之间⽤空格进行分隔。例如:

    GET http://www.abc.com/articles HTTP/1.1
    
    • 1

    请求头

    请求头由键值对组成,每⾏⼀对,键值之间⽤英⽂冒号:进行分隔。例如:

    Content-Type: application/json
    Host: www.abc.com
    
    • 1
    • 2

    请求体

    请求体中放置 POST、PUT、PATCH 等请求方法所需要携带的数据。

    5. HTTP响应报文是什么样的?

    HTTP 响应报⽂的组成: 响应⾏、响应头、空⾏、响应体。

    响应行

    响应行由协议版本、状态码、状态码的原因短语3个内容组成,中间以空格分隔。例如:

    HTTP/1.1 200 OK
    
    • 1

    响应头

    响应头由键值对组成,每⾏⼀对,键值之间⽤英⽂冒号:进行分隔。例如:

    Content-Length: 1024
    Content-Type: application/json
    
    • 1
    • 2

    响应体

    服务器发送过来的数据。

    小结:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    6. 你了解的HTTP状态码有哪些?

    成功(2XX)

    状态码原因短语说明
    200OK表示从客户端发来的请求在服务器端被正确处理
    201Created请求已经被实现,⽽且有⼀个新的资源已经依据请求的需要⽽建⽴
    通常是在POST请求,或是某些PUT请求之后创建了内容, 进行的返回的响应
    202Accepted请求服务器已接受,但是尚未处理,不保证完成请求
    适合异步任务或者说需要处理时间比较长的请求,避免HTTP连接一直占用
    204No content表示请求成功,但响应报⽂不含实体的主体部分
    206Partial Content进⾏的是范围请求, 表示服务器已经成功处理了部分 GET 请求
    响应头中会包含获取的内容范围 (常用于分段下载)

    重定向(3XX)

    状态码原因短语说明
    301Moved Permanently永久性重定向,表示资源已被分配了新的 URL
    比如,我们访问 http😕/www.baidu.com 会跳转到 https😕/www.baidu.com
    302Found临时性重定向,表示资源临时被分配了新的 URL, 支持搜索引擎优化
    首页, 个人中心, 遇到了需要登录才能操作的内容, 重定向 到 登录页
    303See Other对于POST请求,它表示请求已经被处理,客户端可以接着使用GET方法去请求Location里的URI。
    304Not Modified自从上次请求后,请求的网页内容未修改过。
    服务器返回此响应时,不会返回网页内容。(协商缓存)
    307Temporary Redirect对于POST请求,表示请求还没有被处理,客户端应该向Location里的URI重新发起POST请求。
    不对请求做额外处理, 正常发送请求, 请求location中的url地址

    因为post请求, 是非幂等的, 从302中, 细化出了 303 和 307

    简而言之:

    • 301 302 307 都是重定向
    • 304 协商缓存

    客户端错误(4XX)

    状态码原因短语说明
    400Bad Request请求报⽂存在语法错误((传参格式不正确)
    401UnAuthorized权限认证未通过(没有权限)
    403Forbidden表示对请求资源的访问被服务器拒绝
    404Not Found表示在服务器上没有找到请求的资源
    408Request Timeout客户端请求超时
    409Confict请求的资源可能引起冲突

    服务端错误(5XX)

    状态码原因短语说明
    500Internal Sever Error表示服务器端在执⾏请求时发⽣了错误
    501Not Implemented请求超出服务器能⼒范围,例如服务器不⽀持当前请求所需要的某个功能,
    或者请求是服务器不⽀持的某个⽅法
    503Service Unavailable表明服务器暂时处于超负载或正在停机维护,⽆法处理请求
    505Http Version Not Supported服务器不⽀持,或者拒绝⽀持在请求中使⽤的 HTTP 版本

    问到状态码: 304 400 401??

    当前端看到控制台报出 400 时, 请问一定是后台的问题么? 如何排查错误? (前端先检查传参格式是否有误)

    7. HTTP1.x 的 keep-alive 是什么作用?

    作用:使客户端到服务器端的连接持续有效(长连接),当出现对服务器的后继请求时,

    Keep-Alive功能避免了建立或者重新建立连接。

    早期 HTTP/1.0 在每次请求的时候,都要创建⼀个新的连接,⽽创建连接的过程需要消耗资源和时间,

    为了减少资源消耗、缩短响应时间,就需要复⽤已有连接。

    在后来的 HTTP/1.0 以及 HTTP/1.1 中引⼊了复⽤连接的机制,也就是在请求头中加⼊Connection: keep-alive,

    以此告诉对⽅这个请求响应完成后不要关闭连接,下⼀次还⽤这个请求的连接进行后续交流。

    协议规定,如果想要保持连接,则需要在请求头中加上 Connection: keep-alive。

    keep-alive 的优点 (复用连接)

    • 较少的 CPU 和内存的占⽤(因为要打开的连接数变少了, 复用了连接)
    • 减少了后续请求的延迟(⽆需再进⾏握⼿)

    缺点: 因为在处理的暂停期间,本来可以释放的资源仍旧被占用。请求已经都结束了, 但是还一直连接着也不合适

    解决:Keep-Alive: timeout=5, max=100

    • timeout:过期时间5秒(对应httpd.conf里的参数是:KeepAliveTimeout),

    • max是最多一百次请求,强制断掉连接。

      就是在timeout时间内又有新的连接过来,同时max会自动减1,直到为0,强制断掉。

    8. 为什么需要HTTPS?

    HTTP协议是网络通信的基石, 基于HTTP协议, 完成了很多的网页应用功能, 但是HTTP协议是明文传输数据的! 太不安全了!

    HTTPS 是安全版的 HTTP。

    HTTP 协议在传输数据时采用的是明⽂方式传递,因此,⼀些敏感信息的传输就变得很不安全。

    而 HTTPS 就是为了解决 HTTP 的不安全⽽产⽣的。

    9. HTTPS是如何保证安全的?

    HTTPS 在传输数据的过程中会对数据进行加密处理,保证安全性。

    那HTTPS采用的什么样的加密方式呢?我们来了解下一些加密的基本概念。

    目前常见的加密算法可以分成三类,对称加密算法非对称加密算法Hash算法

    1. 对称加密算法: 相同密钥加密解密, 可逆的! 可以用于加密解密传输数据

      想使用对称加密算法, 一定要保证密钥不被泄漏 (且进行密钥的传输约定时, 一定要保证安全)

    2. 非对称加密算法: 有两把钥匙, 公钥, 私钥, 可逆的, 可以用于 https 的初步交换密钥

    3. Hash算法: 不可逆的, 根据一段内容, 生成一段唯一标识, 一般用于验证数据是否被修改! (md5)

    9.1 什么是对称加密?

    对称加密的特点是文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥,

    这种方法在密码学中叫做对称加密算法,对称加密算法使用起来简单快捷,密钥较短,且破译困难

    **通信的双⽅都使⽤同⼀个秘钥进⾏加密, 解密。**⽐如,两个人事先约定的暗号,就属于对称加密。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    对称加密的特点是:

    • 优点:

      计算量小、加密速度快、加密效率高。

    • 缺点:

      在数据传送前,发送方和接收方必须商定好秘钥,然后双方保存好秘钥。

      如果一方的秘钥被泄露,那么加密信息也就不安全了

      最不安全的地方, 就在于第一开始, 互相约定密钥的时候!!! 传递密钥!

    使用场景:本地数据加密、https通信、网络传输等

    常见算法:AES、DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6 (全球公开, 通过了层层筛选, 层层检验) 没有被破解

    全球有专门做算法设计的加密算法设计师, 但经过公开检验的算法才是安全的 (很多公开的算法, 虽然可以用撞库的方式破解, 但是尝试破解的成本会非常高 (甚至几百年))

    9.2 什么是⾮对称加密?

    而加密和解密其实可以使用不同的规则,只要这两种规则之间存在某种对应关系即可,

    这样就避免了直接传递之前的相同的密钥。这种新的加密模式被称为"非对称加密算法"。

    通信的双方使用不同的秘钥进行加密解密,即秘钥对(私钥 + 公钥)。

    特征: 私钥可以解密公钥加密的内容, 公钥可以解密私钥加密的内容

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    非对称加密的特点是:

    • 优点:非对称加密与对称加密相比其安全性更好

    • 缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

    使用场景:https会话前期、CA数字证书、信息加密、登录认证等

    常见算法:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)

    9.3 HTTPS 加密解决⽅案

    结合了两种加密⽅式:

    • 对称加密的密钥(一串约定的字符串) ⽤非对称加密的公钥, 进⾏加密并发送出去,接收⽅使⽤私钥解密得到 对称加密密钥

    • 双⽅沟通时使⽤ 对称加密密钥 进⾏

    可以看到,只有在发送秘钥阶段才使用非对称加密,而后续的通信都使用对称加密,这样解决了性能问题。

    HTTPS 目前所使用的 TLS或SSL协议, 就是目前采用的加密通道的规范协议

    它利用对称加密、(公私钥)非对称加密, 以及其密钥交换算法,可完成可信任的信息传输

    1. 利用 非对称加密 加密传输 对称加密所约定的密钥 (保证了密钥传输的安全)
    2. 后续, 利用对称加密, 有效便捷的进行数据传输!!

    9.4 数字证书

    为了安全性, 一般还需要签发数字证书!

    客户端 和 服务器端要初步互通消息时, 客户端发送请求可以拿到公开的公钥信息

    进而进行非对称加密, 使用公钥, 加密对称加密密钥, 传递给服务器, 后续通信都使用对称加密!

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    问题是: 初步互通消息时, 如果请求拿到的公钥信息, 就是假的, 或者不安全的! 那么后续的所有操作, 都将是不安全的!

    如何保证公钥的安全性呢?? 说白了, 需要证明公钥是安全可靠的!!! 要证明网站是安全可靠的!!!

    所以, 就需要有数字证书(CA证书), 一般是CA机构(互联网的机构, 登记网站和公钥的)颁发的, 证明这个公钥是安全可靠的!

    CA证书中心会对你网站的公钥, 网站的域名地址, 证书到期时间, 等一些相关信息一起加密签发数字证书, 保证你网站的安全性

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    当公司申请了 CA 证书后, 就应该在响应时, 将数字证书一起发送给客户端

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    而客户端, 接收到消息后, 就可以查看证书 , 权威CA机构都可以

    1. 如果正在访问的网站 和 证书记载的网址 不一致, 说明不安全, 可能被冒用, 浏览器就会发出警告!!!

    2. 如果签发证书的机构, 不权威, 发出警告

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    3. 如果证书过期了, 浏览器也会发出警告

      因为一旦证书过期了, CA机构, 不会继续实时检测网站的安全有效性!

    9.5 数字签名

    但这还是有问题:如果证书被篡改了怎么办?

    这时就需要用⼀个技术:数字签名。 (根据证书内容, 生成的一个唯一标识)

    数字签名就是先⽤ CA ⾃带的 Hash 算法来计算出证书内容的⼀个摘要,然后使⽤ CA 私钥进行加密,组成数字签名。

    当别⼈把他的证书发过来时,接收方⽤同样的算法再次⽣成摘要,⽤ CA 公钥解密后得到CA生成的摘要,两者进行对⽐后,

    就能确定中间是否被⼈篡改。这样就能最⼤程度的保证通信的安全了。

    博客参考


    简要小结:

    1. 为什么需要 HTTPS ? 因为HTTP是明文传输数据的, 不安全, 而 HTTPS 是会对内容加密的

    2. HTTPS 的加密策略是什么 ?

      先用 非对称加密, 传递对称加密的密钥 (保证了密钥传输的安全)

      后续 使用对称加密, 进行交流 (保证了传输数据安全)

    3. 问题: 就算是第一次交流用非对称加密, 公钥也是要在网络中传输的!

      如何证明公钥是可靠的? 如何证明网站是可靠的 ? (CA机构认证, 网站需要申请 数字证书 )

      请求时, 网站就会将数字证书给到浏览器, 浏览器默认就会检测证书的可靠性!

      (1) 是否是权威机构发布的!

      (2) 看证书中记录的地址 和 当前访问的网站的地址, 是否一致, 只有一致, 才可靠!

      (3) 看证书是否过期

    4. 如何保证证书不被篡改 => 数字签名, 可以根据证书的所有的内容, 生成一个唯一标识!!! (Hash加密算法)

      一旦内容如果被修改了, 再次生成唯一标识时, 和之前生成的唯一标识就不一样! 检测是否被修改!

    10. HTTP/2 和 HTTP1.x 比,有什么优势和特点?(了解)

    HTTP/2 的升级, 对于用户来说, 是跨时代的! 基于HTTP/2, 用户访问网页的速度会非常快!!! (充分利用带宽)

    HTTP/2: 淘宝, 天猫, 京东等, 已做升级 …

    1. HTTP/2 采⽤⼆进制格式来传输数据,⽽⾮ HTTP 1.x 的⽂本格式,⼆进制协议解析起来更⾼效
    2. HTTP/2 采用一些头部压缩技术,减少在请求和响应头中重复携带的数据,降低网络负担
    3. HTTP/2 采⽤服务器推送方式,主动向客户端推送资源,提高页面加载效率
    4. HTTP/2 采⽤多路复用机制,减少需要创建的连接数量,降低资源占用和性能消耗

    下面是一些与之关联的技术知识。

    ⼆进制格式分帧

    帧:HTTP/2 数据通信的最⼩单位消息,是指 HTTP/2 中逻辑上的 HTTP 消息(例如请求、响应等)。消息由⼀个或多个帧组成

    流:存在于连接中的⼀个虚拟通道,它可以承载双向消息,且每个流都有唯⼀的整数ID

    头部压缩

    在 HTTP1.x 中,请求和响应中会重复携带一些不常改变、冗⻓的头数据,给⽹络带来额外负担。

    在 HTTP/2 中,客户端和服务端使⽤ “⾸部表” 来跟踪和存储之前发送过的键值对,

    相同的数据不再随着每次请求和响应发送。⾸部表在连接存续期间始终存在,由客户端和服务器共同渐进更新。

    每个新的⾸部键值对,要么被追加到当前表的末尾,要么替换表中已存在的键值对。

    可以简单的理解为:只发送差异数据,⽽不是全部发送,从⽽减少头部的信息量

    下图为首部表的更新示意图:

    服务器推送

    服务端可以在发送⻚⾯ HTML 内容时,再主动推送一些其它资源,⽽不⽤等到浏览器解析到相应的位置时发起请求后再作响应。

    例如,服务端可以主动把 JS 和 CSS ⽂件推送给客户端,⽽不需要客户端解析 HTML 时再发送这些请求。

    不过,服务端的主动推送行为,客户端有权利选择是否要接收。

    如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送 RST_STREAM 帧来拒收。

    多路复用

    在 HTTP 1.x 中如果想并发多个请求的话,必须使⽤多个 TCP 链接,但浏览器为了控制资源,

    会对单个域名有 6-8 个 TCP 链接的数量限制。而在 HTTP/2 中:

    • 同域名下的所有通信,都在单个连接上完成
    • 单个连接可以承载任意数量的双向数据流
    • 数据流以消息的形式发送,⽽消息⼜由⼀个或多个帧组成(多个帧可以乱序发送,因为可以根据帧⾸部的流标识来重新组装)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    参考文章:HTTP/2特性及其在实际应用中的表现

    小结:

    HTTP1.X 同一时间, 只能并发建立 6-8 个 TCP 连接, 一个连接同时只能一个请求 (虽然可以 keep-alive复用, 但也得一个个来)

    (建立连接的成本比较高, 不让一次性建立太多连接)

    而新版本 HTTP/2 建立一次连接, 就可以并发很多个请求!

    所以 HTTP/2 的升级, 大大提升了页面加载的效率!

    11. http缓存控制

    11.1 基本认知

    Web 服务缓存 大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 服务器缓存)、浏览器缓存。

    浏览器缓存 也包含很多内容: HTTP 缓存、indexDB、cookie、localstorage 等等。这里我们只讨论 HTTP 缓存相关内容

    HTTP缓存: (优化页面加载的效率, 如果没有缓存策略, 每次重新加载页面, 会非常慢!)

    • 强缓存
    • 协商缓存

    在具体了解 HTTP 缓存之前先来明确几个术语:

    • 缓存命中率:从缓存中得到数据的请求数 与 所有请求数的比率。理想状态是越高越好。

      (看所有的请求中, 多少从缓存中读的)

    • 过期内容:超过设置的有效时间,被标记为“陈旧”的内容。

    • 验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。

    • 失效:失效就是把内容从缓存中移除。

    浏览器缓存主要是 HTTP 协议定义的缓存机制。

    浏览器缓存, HTTP缓存分类

    浏览器缓存分为强缓存 协商缓存,浏览器加载一个页面的简单流程如下:

    1. 浏览器先根据这个资源的 http头信息判断是否命中强缓存

      如果命中则直接加载在缓存中的资源,并不会将请求发送到服务器。(强缓存)

    2. 如果未命中强缓存,则浏览器会将资源加载请求发送到服务器。

      服务器来判断浏览器本地缓存是否失效。

      若可以使用,则服务器并不会返回资源信息,浏览器继续从缓存加载资源。(协商缓存)

    3. 如果未命中协商缓存,则服务器会将完整的资源返回给浏览器,浏览器加载新资源,并更新缓存。(新的请求)

    11.2 强缓存 (验证缓存是否过期)

    (进行判断缓存是否有效, 就是判断资源是否过期, 如果未过期, 直接用缓存)

    强缓存

    命中强缓存时,浏览器并不会将请求发送给服务器。

    在Chrome的开发者工具中看到http的返回码是200,但是在Size列会显示为(from cache)。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    强缓存是利用http的返回的响应头中的Expires或者Cache-Control (优先级更高) 两个字段来控制的,用来表示资源的缓存时间。

    Expires: 指定一个具体时间(2020年12月12日 17:00), 到了这个时间了, 缓存过期了, 在时间内, 都是有效的, 可以直接读

    Cache-Control : 指定一个过期时间 (3600s), 这个资源你加载到后, 可以用 3600s

    Expires

    缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。但在上面我们提到过,cache-control的优先级更高。

    Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    该字段会返回一个时间,比如Expires: Wed, 23 Nov 2050 16:00:01 GMT 。这个时间代表着这个资源的失效时间,也就是说在xx年xx月xx日时间之前都是有效的,即命中缓存。

    这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当 服务器与客户端 时间偏差很大 以后,就会导致缓存混乱。于是发展出了Cache-Control。

    Cache-Control

    Cache-Control是一个相对时间,例如Cache-Control:max-age 3600,代表着资源的有效期是3600秒。

    由于是相对时间,并且都是与客户端时间比较,所以服务器与客户端时间偏差也不会导致问题。

    Cache-Control与Expires可以在服务端配置同时启用或者启用任意一个,同时启用的时候Cache-Control优先级高。

    Cache-Control 可以由多个字段组合而成,主要有以下几个取值:

    1. max-age 指定一个时间长度,在这个时间段内缓存是有效的,单位是s。

      例如设置 Cache-Control:max-age=31536000,也就是说缓存有效期为(31536000 / 24 / 60 / 60)天,

      第一次访问这个资源的时候,服务器端也返回了 Expires 字段,并且过期时间是一年后。

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

      在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。

    2. no-cache 强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证的请求到服务器, 问服务器是否可以读缓存。

      不是字面意思上的不缓存。

    3. no-store 禁止缓存,每次请求都要向服务器重新获取数据。

    注意: 如果命中强缓存, 在有效期内, 使用了本地浏览器的缓存, 请求该资源是不会向服务器发送请求的! (大大减轻了服务器压力)

    11.3 协商缓存 (强缓存未命中-发送请求进行协商)

    看看过期时间, 食品没过期, 直接吃 (直接读缓存, 不发请求) 命中强缓存!

    食品过期时间过了, 能不能吃呢? 问问专家(服务器), 专家瞅了一眼, 还能吃, 不会死人, 重新标了个过期时间(有科学依据)

    (响应304, 不返回内容) , 可以用 (协商缓存)

    如果问过专家(服务器), 专家瞅了一眼, 呀真不能用了, 原来的不要了, 我重新给你发一个 (响应200, 并返回内容)

    协商缓存

    若未命中强缓存(强缓存过期了),则浏览器会将请求发送至服务器。

    服务器根据http头信息中的Last-Modify/If-Modify-SinceEtag/If-None-Match来判断是否命中协商缓存。

    如果命中,则http返回码为304 (你本地之前加载的资源是有效的),浏览器从缓存中加载资源。

    Last-Modify/If-Modify-Since

    浏览器第一次请求一个资源的时候, 服务器返回的header中会加上Last-Modify,

    Last-modify是一个时间标识该资源的最后修改时间,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    当浏览器再次请求该资源时(进行协商请求时),发送的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify

    服务器收到If-Modify-Since后,根据实际服务器的资源的最后修改时间, 进行判断是否命中缓存。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    如果命中缓存,则返回 http304,并且不会返回资源内容,并且不会返回Last-Modify。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    由于对比的是服务端的修改时间,所以就算客户端与服务端时间差距, 也不会有问题。

    但是有时候通过最后修改时间来判断资源是否修改还是不太准确(资源变化了最后修改时间也可以一致)。

    比如: 最后修改只能精确到秒级, 一秒进行了多次修改, 就不行了, 于是出现了ETag/If-None-Match。

    ETag/If-None-Match

    与Last-Modify/If-Modify-Since (最后修改时间)不同的是,Etag/If-None-Match返回的是一个校验码(ETag: entity tag)。

    ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。

    ETag值的变更则说明资源状态已经被修改。

    服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    ETag生成靠以下几种因子

    1. 文件的i-node编号,是Linux/Unix用来识别文件的编号。

    2. 文件最后修改时间

    3. 文件大小

    生成Etag的时候,可以使用其中一种或几种因子,使用抗碰撞散列函数来生成。生成一个标记文件的唯一值

    既生 Last-Modified 何生 Etag ?

    你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?

    Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

    1. Last-Modified标注的最后修改只能精确到秒级

      如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

    2. 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

    Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加 准确的控制缓存。

    不会仅仅只根据最后的修改时间判断是否进行使用缓存

    Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,

    最后才决定是否返回304。

    小结:

    • 强缓存: 检查过期时间, 判断缓存是否失效, 如果不失效, 直接用, 不发请求

      大大的减少了 服务器的请求次数, 在过期时间内, 直接从客户端内存中读

    • 协商缓存: 强缓存命中失效了, 超过过期时间了, 拿着标识(最后的修改时间, 唯一标识etag), 去问服务器, 是否真的过期了

      如果验证通过, 服务器会直接响应 304, 且不会返回资源

    不太会变的资源 => 图片, 非常的适合应用强缓存 (过期时间也可以设置的很长)

    如果是一些很可能会变的资源, 也希望能缓存 => 过期时间设置短一些, 一旦过期, 协商缓存

    实际工作两者相互配合

    11.4 整体请求缓存流程

    浏览器第一次请求

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    浏览器第二次请求

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


    TCP协议

    1. TCP协议是什么?

    TCP(Transmission Control Protocol 传输控制协议) 是一种面向连接(连接导向) 的、可靠的、 基于IP的传输层协议。

    TCP 使⽤校验、确认和重传机制来保证可靠传输

    而 HTTP协议 就是建立在 TCP / IP 协议 之上的一种应用。

    TCP: 三次握手, 四次挥手~

    2. 一次完整的HTTP服务过程是什么

    当我们在web浏览器的地址栏中输入:www.baidu.com,具体发生了什么?

    1. www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址
    2. 根据这个IP,找到对应的服务器,发起TCP的三次握手
    3. 建立TCP连接后, 发起HTTP请求
    4. 服务器响应HTTP请求,浏览器得到html代码
    5. 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等)(先得到html代码,才能去找这些资源)
    6. 浏览器对页面进行渲染呈现给用户
    7. 服务过程完毕, 关闭TCP连接, 四次挥手

    注:

    1.DNS怎么找到域名的?

    DNS域名解析采用的是递归查询的方式,过程是,先去找DNS缓存->缓存找不到就去找根域名服务器->根域名又会去找下一级,这样递归查找之后,找到了,给我们的web浏览器

    2.为什么HTTP协议要基于TCP来实现?

    TCP是一个端到端的可靠面相连接的协议,HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误时,可以重传)

    3.最后一步浏览器是如何对页面进行渲染的?

    a)解析html文件构成 DOM树
    b)解析CSS文件构成渲染树
    c)边解析,边渲染
    d)JS 单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载

    3. 什么是DNS 解析

    DNS解析(域名解析服务器) 将 域名 转换成 ip地址 (一个域名和ip的映射关系, 具体登记在哪里, 看我们如何申请关联的!)

    假定请求的是 www.baidu.com

    a)首先会搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)

    b)如果浏览器自身的缓存里面没有找到,那么浏览器会搜索系统自身的DNS缓存

    c)如果还没有找到,那么尝试从 hosts 文件里面去找 (一个系统电脑的文件, 可以编辑, 可以存 域名 和 ip 的对应关系)

    d)在前面三个过程都没获取到的情况下,就递归地去域名服务器去查找(就近查找),具体过程如下

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    DNS优化两个方面:DNS缓存、DNS负载均衡 (准备多台dns服务器, 进行dns解析)

    4. TCP 三次握手理解 (双方确认)

    TCP是一个端到端的 可靠 面相连接的协议,

    HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误时,可以重传)

    根据这个IP,找到对应的服务器,发起TCP的三次握手 (tcp 三次握手四次挥手 )

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    为什么要3次握手

    我们假定第一次发送的请求, 因为网络延迟很慢才到达服务端,

    然后客户端以为这服务器居然不理睬我,然后默默的关闭的等待连接的请求,走开了(好比追女神);

    但事实呢?女神(服务器)是因为各种各样的原因,很晚才看到,然后说我接受你了, 同意你的要求咱们两结婚吧!

    但是,A早已经远走高飞,这个请求A完全不会收到(在第二次握手,服务端打开连接,等待客户端的响应),

    那么女生呢,以为对方收到了,就会一直等待,这样B的资源就会被浪费的(创建连接的时候,空间浪费以及端口消耗);

    而三次握手, 就不会发生,服务端同意连接了,但是A缺一直没有下一步的动作,导致资源浪费;

    5. 关闭TCP连接四次挥手的理解 (客气挽留)

    目标: 关闭连接(四次挥手)

    不能直接一次性断开连接(双方知晓), 万一还有什么数据没有传完, 造成数据的丢失!

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    这和有礼貌的好友道别一样:(a:客户端 b:服务端)
    1、一开始A想要回家离开,但是呢?怕B还有事情要交代,那么呢?只好先向B打招呼,我要走了,请求停止交谈(请求断开连接)

    ​ (此时,a到B的连接没有断开,依旧可以进行通信);
    2、同意A的请求,说好的,但是我这里可能还有一些话(数据)没说完。我检查看看, 你等等, 等我说完你再走。
    3、B确实没啥要补充的了,就告知你我可以撤了
    4、A说好的,知道了,88;(B得知A走开了,关闭了自己的连接 )

    完整的一次 http 请求流程才算结束


    DOM

    1. DOM的事件流是什么?

    事件流

    ⼜称为事件传播,是⻚⾯中接收事件的顺序。DOM2级事件规定的事件流包括了3个阶段:

    • 事件捕获阶段(capture phase)
    • 处于⽬标阶段(target phase)
    • 事件冒泡阶段(bubbling phase)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    如上图所示,事件流的触发顺序是:

    1. 事件捕获阶段,为截获事件提供了机会
    2. 实际的⽬标元素接收到事件
    3. 事件冒泡阶段,可在这个阶段对事件做出响应

    事件冒泡(Event Bubbling)

    事件开始由最具体的元素(⽂档中嵌套层次最深的那个节点)接收到后,开始逐级向上传播到较为不具体的节点。

    <html>
      
      <head> 
        <title>Documenttitle> 
      head>
      
      <body> 
        <button>按钮button> 
      body> 
      
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果点击了上面页面代码中的

  • 相关阅读:
    Git-快速笔记(持续更新)
    ROC-RK3588-PC 八核8K人工智能开源主板
    Codeforces 1009F 长链剖分优化dp做法
    Java基础之-OOP面向对象
    【Hadoop】Hadoop概述与核心组件
    秋日有感之秋诉-于光
    【移动开发】最新uniapp开发的多端影视APP+后端对接的苹果CMS源码(可打包多端app)
    【计算机毕业设计】2.酒店预订管理系统
    MFC_常见的消息,宏替换分析MFC消息映射
    Android的PendingIntent.getBroadcast()PendingIntent.FLAG_IMMUTABLE问题
  • 原文地址:https://blog.csdn.net/upgrade_bro/article/details/132785666