前端面试笔记

前言

这里是关于前端面试的一些题,我整理了一些经常被问到的问题,出现频率比较高的问题,以及个人经历过的问题。如有不足之处,麻烦大家指出,持续更新中…(ps:一到三颗⭐代表重要性,⭐选择性了解,⭐⭐掌握,⭐⭐⭐前端需要知道的知识)
在这里插入图片描述
Enchanted

一、HTML篇

1.语义话的目的是什么?⭐

答:用正确的标签做正确的事。

提高代码的可读性,页面内容结构化,有利于开发和维护,同时提高的用户体验,有利于SEO。

2.HTML5新特征⭐⭐⭐

  1. Canvas绘图以及SVG绘图。
  2. 拖放(Drag and drop)API
  3. 语义化标签(header、nav、footer、article、section)
  4. 音频、视频(audio、video)API
  5. 地理定位(Geolocation)
  6. 本地离线存储(localStorage),长期存储数据,关闭浏览器后不丢失。
  7. 会话储存(sessionStorage),数据在关闭浏览器后自动删除。
  8. 表单控件(calendar、date、time、email、url、search)
  9. 多任务 webworker
  10. 全双工通信协议 websocket
  11. 历史管理 history
  12. 跨窗口通信 PostMessage
  13. Form Data 对象

3.cookie与sessionStorage和localStorage的区别⭐⭐⭐

  1. 保存方式
    cookie存放在客户的浏览器上。
    session都在客户端中保存,不参与服务器通讯。

  2. 生命周期
    cookie可设置失效时间
    localStorage除非手动清除否则永久保存
    sessionStorage关闭当前页面或浏览器后失效

  3. 存储的大小
    cookie 4kb左右
    session 5M

  4. 易用性
    cookie需自己封装
    session可以接受原生接口

因为cookie每次请求都会携带在http请求中,所以它的主要用来识别用户登录,localStorage可以用来跨页面传参,sessionStorage可以用来保留一些临时数据。

关于storage使用的方式可以查看storage传值

二、CSS篇

1.css有哪些基本的选择器,执行先后顺序?⭐⭐

  1. 基础选择器

元素选择器:选择所有指定的HTML元素。

p { color: blue; }  

类选择器:选择具有特定类的元素。

.myClass { color: red; }

ID选择器:选择具有特定ID的单个元素。

#myId { color: green; }
  1. 组合选择器

后代选择器:选择某个元素内部的后代元素。

div p { color: yellow; }

子选择器:选择某个元素直接子级的元素。

ul > li { color: green; }

相邻兄弟选择器: 选择紧接在另一个元素后的元素。

h1 + p { color: red; }

一般兄弟选择器: 选择同一父元素下在指定元素之后的所有元素。

h1 ~ p { color: yellow; }
  1. 属性选择器

具有特定属性的元素:选择具有指定属性的元素。

[type="text"] { border: 1px solid black; }
input[type="email"] { border: 1px solid #ccc; }

包含特定值的属性:选择包含特定值的属性。

[href^="https"] { color: orange; } /* 以"https"开头的href属性 */
  1. 伪类选择器
  • 状态伪类:用于选取元素的不同状态(hover、active、focus、visited、link、checked、disabled、enabled、required、optional)

:hover:选择鼠标悬停时的元素。

a:hover { color: red; }
  • 结构伪类:用于选取文档中特定的元素结构位置(first-child、last-child、nth-child(n)、nth-of-type(n)、first-of-type、last-of-type、only-child、only-of-type)。

:first-child: 选取属于其父元素的第一个子元素

ul li:first-child {
   color: red;
}

:nth-child(n):选择第n个子元素。

li:nth-child(2) { color: blue; }
  • 动态伪类:基于元素的动态状态来应用样式。

:not(selector): 选取不匹配指定选择器的元素。

div:not(.special) {
  background-color: #f0f0f0;
}

:root: 选取文档的根元素,通常是 。

:root {
  --main-color: #000000;
}

:empty: 选取没有子元素的元素(包括文本节点)。

div:empty {
  display: none;
}
  1. 伪元素选择器

::before 和 ::after:在元素内容前或后插入内容。

p::before { content: "UZI"; color: red; }

!important > 内联样式(<div style="color: red;") >ID选择器 > 类选择器 > 标签选择器和伪元素选择器 > 通配符选择器(*)

CSS选择器的优先级(或称为权重)规则用于决定当多个规则适用于同一元素时,哪个规则将最终应用。
优先级计算规则
CSS优先级是根据选择器的类型来计算的,优先级值由四个部分组成:a、b、c、d。这些值分别代表以下内容:

  1. a: 具有 ID 选择器的数量。
  2. b: 具有类选择器、属性选择器和伪类选择器的数量。
  3. c: 具有元素选择器和伪元素选择器的数量。
  4. d: 通配符选择器 *、类型选择器和伪元素选择器的数量(在某些CSS实现中,可能仅表示元素选择器)。

优先级计算过程

  1. ID选择器:每个ID选择器增加一个点数。例如,#header的优先级是(1,0,0,0)。
  2. 类选择器、属性选择器和伪类选择器:每个增加一个点数。例如,.button的优先级是(0,1,0,0)。
  3. 元素选择器和伪元素选择器:每个增加一个点数。例如,p的优先级是(0,0,1,0)。
  4. 内联样式:如果样式是直接在HTML元素上用style属性定义的(例如:
    ),其优先级最高,为(1,0,0,0)。

计算优先级的步骤

  1. 计算每个选择器的优先级值
    #header → (1,0,0,0)
    .button → (0,1,0,0)
    p → (0,0,1,0)
  2. 比较选择器的优先级
    如果选择器具有相同的优先级,后定义的规则会覆盖先定义的规则(即后出现的规则优先)。

2.垂直居中DIV⭐⭐⭐

请看这里前端CSS布局问题

3.两栏布局左边固定右边自适应⭐

请看这里前端CSS布局问题

3.三栏布局左右固定中自适应⭐

请看这里前端CSS布局问题

4.常用的块与行属性内标签有哪些?有什么特征⭐⭐

块标签:div、h1~h6、ul、li、table、p、br、form。
特征:独占一行,换行显示,可以设置宽高,可以嵌套块和行
设置margin时
相邻的块级元素之间的垂直外边距会合并,即如果两个块级元素相邻,它们的margin会合并较大的一个值,而不是简单相加(塌陷)。
行标签:span、a、img、textarea、select、option、input。
特征:只有在行内显示,内容撑开宽、高,不可以设置宽、高(img、input、textarea等除外)。
设置margin时
上下外边距不会生效
左右外边距有效

5.清除浮动⭐⭐⭐

  1. 父级div定义overflow:hidden(如果父级元素有定位元素超出父级,超出部分会隐藏,)
  2. 给浮动元素父级增加标签(由于新增标签会造成不必要的渲染,不建议使用)
  3. 伪元素清除浮动:给浮动元素父级增加 .clearfix::after(content: ‘’; display: table; clear: both;)(不会新增标签,不会有其他影响,)

6.CSS3新特征⭐⭐⭐

  1. 圆角(border-radius)
  2. 阴影(box-shadow)
  3. 文字特效(text-shadow)
  4. 线性渐变(gradient)
  5. 变换(transform)
  6. 更多的CSS选择器
  7. 更多背景设置(background)
  8. 色彩模式(rgba)
  9. 伪元素(::selection)
  10. 媒体查询(@media)
  11. 多栏布局(column)
  12. 图片边框(border-image)

7.介绍一下盒模型⭐⭐

答:

  1. 盒模型由内容(content)、内边距(padding)、边框(border)、外边距(margin)组成。
  2. 盒模型分为IE盒模型和W3C标准盒模型。
  3. W3C标准盒模型又叫content-box,元素宽度/高度由border+padding+content组成。
    (属性width,height只包含内容content,不包含border和padding)
  4. IE盒模型又叫border-box,元素宽度/高度由content组成。
    (属性width,height包含border和padding,指的是content+padding+border。)

PS:盒模型这个东西需要多理解。。。

8.CSS中有哪些长度单位?⭐

  1. 绝对长度单位:px
  2. 根据父元素的大小来计算:百分比: %
  3. 相对父元素字体大小单位: em
  4. 相对于根元素字体大小的单位: rem
  5. 相对于视口*宽度的百分比(100vw即视窗宽度的100%): vw
  6. 相对于视口*高度的百分比(100vh即视窗高度的100%): vh

9.display:none和visibility:hidden的区别⭐

display:none:隐藏元素,在文档布局中不在给它分配空间(从文档中移除),会引起回流(重排)。
visibility:hidden: 隐藏元素,但是在文档布局中仍保留原来的空间(还在文档中),不会引起回流(重绘)。

10. 用CSS 实现长宽为浏览器窗口一半的正方形⭐

  1. 已知父元素宽高用%
                width: 50%;
                padding-top: 50%;
                background-color: red;
  • 用vw
                width: 50vw;
                height: 50vh;
                background-color: red;

11. 用CSS 实现高度为0.5像素的线条⭐

这个可以用伪类来实现

       .line::before {
            display: block;
            content: "";
            height: 1px;
            left: -50%;
            position: absolute;
            background-color: #333333;
            width: 200%; //设置为插入元素的两倍宽高
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
            box-sizing: border-box;
        }

12. 用CSS 实现三角形⭐

向上

                width:0;
                height:0;   
                border-left:30px solid transparent;   
                border-right:30px solid transparent;   
                border-bottom:30px solid red;

13. 伪类和伪元素的区别⭐⭐

伪类
在这里插入图片描述
伪元素
在这里插入图片描述
区别

  • 伪类只能使用“”,伪元素既可以使用“:”,也可以使用“::”
  • 伪元素其实相当于伪造了一个元素,伪类没有伪造元素,例如first-child只是给子元素添加样式而已。(本质区别就是是否抽象创造了新元素

13. 重绘和重排是什么?如何避免?⭐⭐

重排:当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
重绘:当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,所以重绘跳过了创建布局树和分层的阶段。

重排需要重新计算布局树,重绘不需要,重排必定发生重绘,但是涉及到重绘不一定要重排 。涉及到重排对性能的消耗更多一些。

触发重排的方法: 页面初始渲染、添加/删除可见的DOM元素、改变元素位置、改变元素尺寸、改变元素内容、改变元素字体大小、改变浏览器窗口尺寸、设置 style 属性的值等。
避免重排的方式:样式集中改变、使用 absolute 或 fixed 脱离文档流。

14. gird⭐

详情可以看详细解析gird布局教程

15. flex⭐⭐⭐

详情可以看详细解析flex布局教程

15. 什么是BFC?⭐

全称:Block Formatting Context(块级格式化上下文)
含义:独立的渲染区域,规定了在该区域中,常规流块盒的布局。
创建 BFC:

  • 根元素
  • 浮动元素(元素的 float 不是 none)
  • 绝对定位元素(元素的 position 为 absolute 或 fixed)
  • 行内块元素(元素的 display 为 inline-block)
  • overflow 不为 visible 的块级盒子

BFC 内部的元素布局不受外部影响,即使两个相邻的元素浮动,它们也不会重叠。但是,在同一个 BFC 中的两个元素之间可能会发生盒子重叠问题。
BFC 还具有很多其他特性,例如可以包含浮动元素,防止父元素高度塌陷。常用的解决外边距塌陷的方法也是通过创建 BFC 来实现。

16. 元素显示与隐藏的方式⭐

  1. 使用CSS控制显示与隐藏
 主要使用的属性有 display: none; 和 visibility: hidden;
  1. 使用JS控制显示与隐藏

通过JS动态地控制元素的显示和隐藏,通常结合DOM操作来实现。

// 隐藏元素
document.getElementById('elementId').style.display = 'none'; 
// 显示元素
document.getElementById('elementId').style.display = 'block'; // 或者 'inline', 'inline-block',根据需要选择

通过添加或移除CSS类名来控制元素的显示和隐藏,这种方法结合了CSS和JS的优势,尤其适合于复杂的动态交互。

// 隐藏元素
document.getElementById('elementId').classList.add('hidden');
// 显示元素
document.getElementById('elementId').classList.remove('hidden');
/* CSS中定义隐藏的样式 */
.hidden {
    display: none;
}
  1. CSS动画控制显示与隐藏
.element {
    opacity: 0; 
} 
.element .show {
    opacity: 1;
}

17. 图片懒加载和预加载⭐

图片懒加载仅当图片即将进入用户视野(即将显示在屏幕上)时才加载图片资源,这种方法有助于减少页面初始加载时的资源请求和提升页面加载速度

工作原理
页面初始加载时,图片的 src 属性可以设置为一个占位符(如一个小尺寸的透明图片)或者空字符串。
当用户滚动页面时,监测图片是否进入了视口(浏览器可见区域)。
当图片即将进入视口时,动态设置图片的 src 属性为实际的图片路径,从而触发图片的加载。

优点
减少了页面初始加载时的资源请求,提升了页面的加载速度和用户体验。
节省了带宽,特别是对于长页面或者包含大量图片的页面效果更为显著。
可以有效地管理和控制页面上的图片资源加载,优化网络性能。

注意事项
对于SEO(搜索引擎优化),确保搜索引擎能够正确索引页面上的图片内容。
考虑兼容性,尽量使用现代浏览器支持的 IntersectionObserver API 等来实现懒加载效果。

实现demo

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lazy Loading Demo</title>
<style> 
  .placeholder {
    background-color: #f0f0f0;
    width: 100%;
    height: 300px; /* 或者根据图片尺寸设定高度 */
  }
</style>
</head>
<body>
  <div class="container">
    <div class="image-wrapper">
      <div class="placeholder"></div>
      <img class="lazy" data-src="image.jpg" alt="Lazy-loaded image">
    </div>
    <!-- 更多图片 -->
  </div>

<script>
  document.addEventListener("DOMContentLoaded", function() {
    let lazyImages = document.querySelectorAll('.lazy');

    function lazyLoad() {
      lazyImages.forEach(function(image) {
        if (image.getBoundingClientRect().top < window.innerHeight && !image.src) {
          image.src = image.dataset.src;
          image.onload = function() {
            image.classList.add('loaded');
          };
        }
      });
    }

    // 初始加载一次
    lazyLoad();

    // 滚动时加载
    window.addEventListener('scroll', lazyLoad);
  });
</script>
</body>
</html>

图片预加载在需要访问特定图片时能够立即展示,而不需要等待加载时间

工作原理
在页面加载或者在需要的时候,通过 JavaScript 动态创建 元素或者通过 CSS 背景图片的方式,提前加载图片资源。
使用预加载技术,浏览器会优先下载这些图片资源,而不需要用户显式请求加载。

优点
提升用户体验,当用户需要查看图片时,可以立即展示,避免了等待加载的时间。
对于视觉内容较重要的页面或者应用,可以显著减少用户感知的加载延迟。

注意事项
预加载大量的图片资源可能会增加页面的初始加载时间和带宽消耗,需要权衡资源的预加载优先级。
对于移动设备和低带宽环境,预加载需要特别小心,以免影响页面加载性能。

实现demo

const imagesToPreload = [
  'image1.jpg',
  'image2.jpg',
  'image3.jpg'
];

function preloadImages(images) {
  images.forEach(function(imageUrl) {
    const img = new Image();
    img.src = imageUrl;
  });
}

preloadImages(imagesToPreload);

三、JS篇

1.ES6新特性?⭐⭐⭐

  1. 新增块级作用域let定义变量和const定义常量 详情可以参考var、let、const的区别
  2. 变量的解构赋值
  3. 模板字符串 (‘${}’)
  4. 默认参数(key=value)
  5. 箭头函数(=>)
  6. 扩展运算符(…)
  7. 模块(import/export)
  8. 类(class/extends)
  9. Promise
  10. Proxy
  11. Symbol
    了解关于es6的更多知识可以看阮一峰——ES6 入门教程

2.ES7、ES8新特性 ⭐

es7

  • 新增了includes
    (includes)数组中是否存在
  • 取幂运算符 **
    base ** exponent
    其中,base表示底数,exponent表示指数。例如,3的4次方可以表示为3 ** 4 = 81。

es8

3.闭包的理解⭐⭐

理解:主要是为了设计私有的方法和变量。
优点:可以避免全局变量造成污染。
缺点:闭包会常驻内存,增加内存使用量,使用不当会造成内存泄漏。
特征:(1)函数嵌套函数。(2)在函数内部可以引用外部的参数和变量。(3)参数和变量不会以垃圾回收机制回收。

4.call()、apply()、bind()的区别⭐

详情请看call()、apply()、bind()重新定义this的区别

5.原型,原型链⭐⭐⭐

原型用于实现对象的继承和共享属性
继承:允许一个对象继承另一个对象的属性和方法。通过原型链,可以实现对象之间的继承关系。
共享方法和属性:所有实例可以共享原型上的属性和方法,节省内存。

  1. prototype
    在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。

  2. proto
    这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。

  3. constructor
    每个原型都有一个constructor属性,指向该关联的构造函数。

  4. 实例与原型
    当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

  5. 创建对象

1  const obj = {}
2  const obj = new Object();
3  function PersonName(name){
      this.name = name
  }

原型对象通过 Object 构造函数生成的
6. 原型链
基本思想就是通过原型链继承多个引用类型的属性和方法。简单的回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。 ——摘自《javascript高级程序设计(第四版)》

javascript——原型与原型链

6.JS基本数据类型⭐⭐⭐

  1. 基本数据类型
  • Number:数值,包括整型和浮点型。
  • String:字符型。
  • Undefined:未定义,声明变量时未赋值。
  • Null:定义为空或者不存在。
  • Boolean:布尔值,true or false。
  • Symbol:独一无二的值。
  • BigInt(s11新增) :任意精度的整数。
  1. 引用数据类型
  • Object: 包含了ArrayFunctionDateError等…

基本数据类型是直接存储在中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据。栈是存储基 本类型值和执行代码的空间。

引用数据类型是存储在内存中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆 中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。

两种数据类型的区别:

  1. 堆比栈空间大,栈比堆运行速度快。
  2. 堆内存是无序存储,可以根据引用直接获取。
  3. 基础数据类型比较稳定,而且相对来说占用的内存小。
  4. 引用数据类型大小是动态的,而且是无限的。

注: Object.prototype.toString.call() 适用于所有类型的判断检测

关于Object的方法可以看这个JS中Object方法大全

7.export和export default的区别⭐

  1. 均可导出常量、函数、文件、模块等。
  2. 在一个文件或模块中,export、import可以有多个。export default仅有一个。
  3. 通过export方式导出,在导入时要加{ },export default则不需要。

8.箭头函数和普通函数的区别⭐⭐⭐

  1. 箭头函数中this在定义时就决定,没有自己的this,一般this指向外层第一个普通函数的this,不能通过call、apply、bind来改变其this
  2. 箭头函数不能使用new(不能作为构造函数)
  3. 箭头函数没有原型
  4. 箭头函数没有arguments对象
  5. 语法更加简洁、清晰,=>()
const obj = {
    a: function() {
        console.log(this)
    },
    b:() => {
        console.log(this)
    }
}
obj.a()   //obj
obj.b()   //window

9.GET和POST的区别⭐

表面区别

  • 后退/刷新:GET无害,POST数据会被重新提交。
  • 书签:GET产生的URL地址可以被收藏为书签,而POST不可以。
  • 数据:GET一般是用来获取数据,POST提交数据。
  • 数据类型:GET只允许ASCII字符,POST无限制。
  • 数据大小:GET大小有限制(一般来说1024字节),POST理论上来说没有大小限制。
  • 安全性:GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
  • 可见性:GET参数通过URL传递对所有人可见,POST数据不可见。
  • 历史保留:GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

10.forEach和map的区别⭐⭐

forEach没有返回值,不能链式调用。
map返回新的数组,可以链式调用其他方法。

map创建新数组(不会修改原来数组),forEach不修改原数组。

map:将原数组中的每个元素通过回调函数转换成一个新的元素,然后组成一个新的数组。
forEach将每个元素传递给指定的回调函数,并对每个元素执行特定的操作。

11.JS基本数据类型的比较(==)⭐⭐

黄色表示ture

12.遍历方式for in、for of、forEach的区别⭐⭐

for…in 遍历对象的可枚举属性,包括对象原型链上的属性。它通过遍历对象的键来实现迭代,一般用于遍历对象属性。如果遍历数组则返回的是索引。
注意,使用 for…in 遍历时,还需要使用 hasOwnProperty() 方法来判断属性是否来自对象本身,并避免遍历原型链上的属性。

        let object = { a: 1, b: 2, c: 3 }
        let array = [1, 2, 3, 4, 5]
        for (const key in object) {
            console.log(key);     //a, b, c
        } for (const key in array) {
            console.log(key);     //0, 1, 2, 3, 4
        }

for…of 遍历支持迭代协议的数据结构(数组、字符串、Set、Map 等),而不包括/对象
它通过遍历可迭代的对象的值来实现迭代,一般用于遍历数组、集合等迭代器对象。

    let array = [1, 2, 3, 4, 5]
    for (const key of array) {
        console.log(key); //1, 2, 3, 4, 5
    }
    //如果遍历对象则会报错
    let obj = { a: 1, b: 2, c: 3 }
    for (const item of array) {
        console.log(item); //obj is not iterable
    }

forEach 需要传入一个回调函数,用于对每个元素进行操作。for…in 和 for…of不用。
forEach 不支持 break 和 return 语句跳出循环,如果需要跳出循环可以使用 some() 或 every() 方法。

13.简述一下你理解的面向对象⭐

面向对象是基于万物皆对象这个哲学观点. 把一个对象抽象成类,具体上就是把一个对象的静态特征和动态特征抽象成属性和方法,也就是把一类事物的算法和数据结构封装在一个类之中,程序就是多个对象和互相之间的通信组成的。

面向对象具有封装性,继承性,多态性

封装:隐藏实现细节,使得代码模块化;
继承:扩展已存在的代码模块(类),它们的目的都是为了——代码重用。
多态:相同的事物,调用其相同的方法,参数也相同时,但表现的行为却不同。多态分为两种,一种是行为多态与对象的多态

14. == 和 ===的区别⭐⭐

==!=:在比较前会进行强制类型转换,再确定操作符是否相等。
规则:

  • 如果是Boolean,将其转换为数值再比较是否相等,false转换为0,true转换为1
  • 如果是String,将其转换为数值
  • 如果是{},则调用对象的**valueOf()**取得其原始值,再比较
  • null == undefined
  • null和undefined不能转换为其他类型值再比较
  • 如果有一个操作数为NaN,则==返回false,!=返回true,NaN不等于NaN
  • 如果都是对象,则比较是不是同一个对象,如果都指向同一个对象,则返回true,否则false。

==只比较不比较类型
=== 会判断类型

 '1' === 1(false)     undefined === null(false)

15. 数组有哪些方法⭐⭐

详细可以看数组一些常用的方法

16. 普通的数组去重⭐⭐

在不涉及去重对象、NaN等情况下。

  1. IndexOf()
  2. 双重for循环
  3. […new Set()]
  4. filter()
  5. sort()

注 :如果有多维数组如 [1,[2],[3,[2,3,4,5]] ] 先扁平化再去重,
Array.flat(Infinity)实现扁平化。

17. Promise⭐⭐⭐

含义:异步编程的一种解决方案,它通过链式调用 then 、 catch 、finally方法来处理异步调用的结果。。
三种状态pending(进行中)、resolved (已成功)和reject(已失败) (Promise对象的状态改变,只有两种可能:从pending变为resolve和从pending变为reject。

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

resolve:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved)。
reject:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected)。

Promise 可以通过链式调用的方式,让多个异步请求形成一个顺序执行的队列。

then: Promise 实例添加状态改变时的回调函数。
可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。
catch : 用于指定发生错误时的回调函数。
finally: 不管 Promise 对象最后状态如何,都会执行的操作。

其他方法
Promise.all():将多个 Promise 实例,包装成一个新的 Promise 实例(所有实例都改变状态,值就改变)。
Promise.race():将多个 Promise 实例,包装成一个新的 Promise 实例(有一个实例率先改变状态,值就改变)。

缺点: 无法取消Promise,一旦新建它就会立即执行,无法中途取消。如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。

更多详情请看Promise 对象

18.promise 和 async/await 的区别 ⭐

Promise 和 async/await 都是用于处理异步任务的方式

相同点:
Promiseasync/await 的目的都是处理异步任务。
Promiseasync/await 都可以避免回调地狱。

不同点:
处理异步调用的结果方法:
Promise 通过链式调用 then 方法和 catch 方法来处理异步调用的结果。
而 async/await 通过 await 关键字和 try…catch 语句来处理异步调用的结果。
异步处理方式
Promise 是一种基于回调函数的异步处理方式。
async/await 是一种基于生成器函数的异步处理方式。
创建Promise方法
Promise 可以直接使用静态方法 Promise.resolve() 和 Promise.reject() 来创建 Promise 对象。
async/await 则需要借助于 Promise 对象创建。
是否可以阻塞
Promise 是非阻塞的。
async/await 是可以阻塞执行的(注意:这里说的阻塞是指异步等待结束后再继续执行后续代码,但不会阻塞线程)。

19.JS中new操作符有什么用?⭐⭐

  • 创建临时对象,并将this指向临时对象
  • 将构造函数的原型属性方法挂载到新对象的__proto__(原型指针)上
  • return 临时对象

20.JS获取HTML DOM元素的方法⭐

  • 通过ID获取(getElementById)
  • 通过name属性(getElementsByName)
  • 通过标签名(getElementsByTagName)
  • 通过类名(getElementsByClassName)
  • 获取html的方法(document.documentElement)
  • 获取body的方法(document.body)
  • 通过选择器获取一个元素(querySelector)
  • 通过选择器获取一组元素(querySelectorAll)
    用法以及防坑可看JS获取HTML DOM元素的方法

21.事件捕获和事件冒泡⭐⭐

事件冒泡:如果一个元素的事件被触发,那么他的所有父级元素的同名事件也会被依次触发
元素->父元素->body->html->document->window
事件捕获:从最顶级的父元素一级一级往下找子元素触发同名事件,直到触发事件的元素为止

  • 事件捕获是由外向内;而事件冒泡则是由内向外。
  • event.stopPropagation() 可以阻止事件流的进一步传播(冒泡和捕获都能被阻止)。
  • 事件一共有三个阶段:事件的执行顺序 1、捕获阶段 ,2、目标阶段 ,3、冒泡阶段 。
  • 采用事件冒泡(事件委托)的方式,能够节省内存消耗,对于动态改变子元素的时候,也非常有利,避免了很多麻烦的步骤,比如重新绑定事件。(把子元素的事件委托给父元素来处理)
    事件委托举例:
    <ul id="container">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
    <script>
        const container = document.getElementById('container')
        container.addEventListener('click', (e) => { 
            if (e.target.tagName === 'LI') {
               //dosomething…
            }
        });
    </script>

22.以下运行结果是什么?⭐⭐

结果放在23了

  1. ["2","3","4"].map(parseInt) 
  2. [] + {}
  3. {} + []
  4. 1 + {}
  5. [1,2,3] + [4,5,6]
  6. [true,false] + [true,false]
  7. Boolean({}) + []

23. 22题的结果⭐⭐

  1. [2, NaN, NaN]
    //原因:[“2”,“3”,“4”].map(parseInt) 相当于执行的是[parseInt(‘2’,0),parseInt(‘3’,1),parseInt(‘4’,2)]
  2. “[object Object]”
    加法会进行隐式类型转换,规则是调用其 valueOf() 或 toString() 以取得一个非对象的值(primitive value)。如果两个值中的任何一个是字符串,则进行字符串串接,否则进行数字加法。
    [] 和 {} 的 valueOf() 都返回对象自身,所以都会调用 toString(),最后的结果是字符串串接。[].toString() 返回空字符串,({}).toString() 返回“[object Object]”。
    最后的结果就是“[object Object]”
  3. 0
    {} + []相当于+[]语句,也就是相当于强制求出数字值的Number([])运算,相当于Number(“”)运算,最后得出的是0数字
    :所以如果第一个(前面)是{}时,后面加上其他的像数组、数字或字符串,这时候加号运算会直接变为一元正号运算,也就是强制转为数字的运算。
  4. ‘1[object Object]’
    {} 视为一个对象,而不是直接将其强制类型转换。
  5. ‘1,2,34,5,6’
    JS 将数组 [1, 2, 3] 和 [4, 5, 6] 分别转换为它们的字符串表示形式:“1,2,3” 和 “4,5,6”,
    然后,它使用 + 运算符将这两个字符串连接起来,得到最终结果 “1,2,34,5,6”。
  6. ‘true,falsetrue,false’
    同上,依然因为JS 中 + 运算符在数组,会将数组转换成字符串表示,然后连接两个字符串。
  7. “true”
    Boolean({}) 返回 true,[] 是一个空数组,在上下文中被强制转换为字符串时,会变成一个空字符串 “”。
    JavaScript 中,字符串和布尔值相加会将布尔值隐式转换为字符串。因此,true 被转换为字符串 “true”。

24.数组操作方法会改变原数组⭐⭐

会改变:push(),pop(),shift(),unshift() ,splice(),sort(),reverse()。
不变:concat(),split(),slice()。

25.JS有几种方法判断变量的类型?⭐⭐⭐

  1. typeof
    判断基本数据类型,
typeof(null) 'object'   typeof(undefined) 'undefined'  typeof([]) 'object'   typeof({}) 'object'
typeof(0)  'number'     typeof('0') 'string'       typeof(true)  'boolean'   typeof(Symbol()) 'symbol'

对于引用数据类型除了function返回’function‘,其余全部返回’object’。
可以返回类型 number、string、boolean、undefined、object、function、symbol。
2. instanceof
区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true,不太适合用于简单数据类型的检测,检测过程繁琐且对于简单数据类型中的undefined, null, symbol检测不出来。
3. constructor
检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。
4. Object.prototype.toString.call()
适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。(举例:字符串返回的是[object String])

instanceof的实现原理:验证当前类的原型prototype是否会出现在实例的原型链__proto__上,只要在它的原型链上,则结果都为true。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,找到返回true,未找到返回false。
Object.prototype.toString.call原理:Object.prototype.toString 表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果

26.如何判断一个对象是否存在?⭐

直接!XXX 这样会报错,因为没有定义
在这里插入图片描述
建议使用typeof运算符,判断XXX是否有定义。
在这里插入图片描述

27.undefined和null 区别⭐

undefined:一个变量已经声明但未被赋值,或者一个对象属性不存在
null: 一个变量或对象属性被明确地赋值为 null,表示该变量或属性的值为空。

  • undefined 表示缺少值,而 null 表示有一个值,但这个值是空的。
  • typeof检测 null为object, undefined为undefined
  • Number.null转数组为0,undefined转数值为NaN
  • JSON会将undefined对应的key删除,null则不会

以下场景会出现undefined

  • 声明了一个变量,但没有赋值
  • 访问对象上不存在的属性
  • 函数定义了形参,但没有传递实参
  • 使用 void 对表达式求值

28.break 和 return 的区别⭐

作用对象不同
break 用于跳出当前的循环或者 switch 语句。
return 用于结束当前函数并返回一个值。
结束方式不同
break 只是提前结束当前的循环或者 switch 语句,然后继续执行后续代码
return 则会直接结束函数的执行,并将一个指定的值返回给调用处。
使用场景不同
break 通常用于跳出不满足条件或者特定情况的循环结构,避免出现死循环。
return 主要用于结束函数执行,返回一个执行结果或者错误信息等。

29. 排序方式⭐

  1. 冒泡排序:比较所有相邻元素,如果第一个比第二个大,则交换它们。(复杂度 O(n^2))
  2. 选择排序:找到数组中的最小值,选中它并将其放置在第一位。(复杂度 O(n^2))
  3. 插入排序:从第二个数开始往前比,比它大就往后排。(复杂度 O(n^2))
  4. 归并排序:把数组劈成两半,再递归地对数组进行“分”操作,直到分成一个个单独的数。(复杂度 O(nlog(n)))
  5. 快速排序:从数组中任意选择一个基准,所有比基准小的元素放到基准前面,比基准大的元素放到基准的后面。(复杂度 O(nlog(n)))
  6. 计数排序:使用一个用来存储每个元素在原始数组中出现次数的临时数组。在所有元素都计数完成后,临时数组已排好并可迭代以构建排序后的数组。(复杂度 O(n+k))
  7. 桶排序:将元素分为不同的桶(较小的数组),再使用简单的排序来对每个桶进行排序,然后将每个桶合并为数组。(复杂度 O(n))
  8. 基数排序:根据数字的有效位或基数将整数分布到桶中。基数是基于数组中的记数制。 (复杂度 O(n))在这里插入图片描述

三、计算机网络与其他知识篇

1.HTTP与HTTPS⭐⭐⭐

HTTP 和 HTTPS 都是网络上传输数据使用的协议。
HTTP:客户端与服务器之间数据传输的格式规范,表示“超文本传输协议”。
HTTPS:在HTTP与TCP之间添加的安全协议层。
默认端口号:HTTP:80,HTTPS:443
传输方式:http是明文传输,https则是具有安全性的ssl加密传输l协议。
连接方式:http的是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
URL:HTTP :http://, HTTPS: https://。

2.HTTPS为什么比HTTP更安全⭐

  1. 加密数据传输
    HTTPS 使用 SSL/TLS 协议对数据进行加密传输。这意味着通过 HTTPS 发送的数据在传输过程中会被加密,第三方无法直接查看或篡改数据内容。而HTTP传输的数据是明文的,容易被窃听和篡改。
  2. 身份验证与数据完整性
    HTTPS 不仅加密了数据,还提供了身份验证和数据完整性验证。通过SSL/TLS证书,客户端可以验证服务器的身份。同时,传输的数据在传输过程中会被检查,以确保它没有被篡改。
  3. 信任与SEO因素
    HTTPS 使用数字证书来验证网站的身份,这增强了用户对网站真实性的信任。此外,现代浏览器对于使用HTTPS的网站给予积极的安全标识,这有助于提升用户体验和信任度。另外,搜索引擎(如Google)也倾向于将使用HTTPS的网站排名更高,这是因为它们认为HTTPS是一个重要的网站安全指标。
  4. 防止窃听和篡改
    HTTPS 的加密机制有效防止了数据在传输过程中被窃听和篡改。尤其是对于用户登录、支付交易等涉及敏感信息的场景,使用HTTPS能够极大地降低信息泄露和被篡改的风险。

总体来说,HTTPS通过加密传输、身份验证和数据完整性保护等多重安全措施,大大提升了网站和用户数据的安全性,使得用户能够更加安全和放心地在互联网上进行数据传输和交流。

3.HTTP缓存有哪些⭐

HTTP缓存可分为强制缓存和协商缓存。

强制缓存的优先级大于协商缓存。

强制缓存直接使用客户端缓存,不从服务器拉取新资源,也不验证缓存资源是否过期。返回的状态码为200(OK)

强制缓存是利用响应头中的 Expires Cache-Control 字段来控制的。它告诉浏览器在一定时间内,可以直接从本地缓存中获取资源而不需要再向服务器发起请求。这段时间内,浏览器不会再次请求服务器验证缓存的有效性。
例如, Cache-Control: max-age=3600 表示资源在接下来的 3600 秒(1 小时)内有效,浏览器可以直接从缓存中获取而无需发起新的请求。

协商缓存通过服务器验证资源有效性,资源有效则返回304(Not Modified),资源失效则返回最新的资源文件

协商缓存是利用 Last-Modified / If-Modified-Since ETag / If-None-Match 字段来控制的。浏览器会在发起请求时发送相应的条件头字段,如果服务器判断资源未被修改,则返回 304 Not Modified,浏览器可以继续使用缓存副本。
例如,当资源过期(如 Cache-Control: max-age=0)或者浏览器主动检查缓存是否仍然有效时,会发起条件请求,如发送 If-Modified-Since If-None-Match 头字段,服务器根据这些字段判断是否返回新的资源或者告知浏览器继续使用缓存。

参考:http缓存

4.HTTP1.0、HTTP1.1、HTTP2.0 的区别⭐⭐

HTTP 不同版本之间主要区别在于性能、功能和安全性方面:

HTTP 1.0
单连接: 每个请求需要建立一个新的连接,这样每发送一个请求都需要消耗时间进行连接建立。
无头信息压缩: 头信息不进行压缩,会增加数据传输量。
安全性较弱: 缺乏安全机制,容易受到攻击。

HTTP 1.1
持久连接: 保持连接,可以减少一次连接建立的时间成本,提高效率。
头部信息压缩: 通过使用压缩算法减少头信息的大小,降低数据传输量。
更完善的状态码: 提供了更多状态码,更加精准地描述请求结果。

HTTP 2.0
多路复用 (Multiplexing): 多个请求可以共享一个连接,并以独立的流进行传输,提高并发性和性能。
头信息压缩: 使用 HPACK 算法进行头信息压缩,更有效地减少数据传输量。
二进制格式: 传输数据采用二进制格式,比文本格式更简洁高效。
服务器推送: 服务器可以主动推送资源给客户端,提高页面的加载速度。

5.TCP与UDP的区别⭐

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
  2. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
  3. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  4. TCP首部开销20字节;UDP的首部开销小,只有8个字节。
  5. TCP提供可靠的服务。UDP适用于一次只传少量数据、对可靠要求不高的环境。

6.三次握手四次挥手⭐

原因:TCP是面向连接的,三次握手就是用来建立连接的,四次握手就是用来断开连接的。

关于三次握手先看草图(看图理解)
请添加图片描述

最开始客户端和服务端都处于关闭状态,客户端主动打开连接。
第一次握手:客户端向服务端发送SYN报文,并处于SYN_SENT状态。
第二次握手:服务端收到SYN后应答,并返回SYN+ACK报文,表示已收到,并处于SYN_RECEIVE状态。
第三次握手:客户端收到服务端报文后,再像服务端发送ACK包表示确认。此时双方进入ESTABLISHED状态。(这次报文可以携带数据

为什么不是两次握手
原因:是因为如果只有两次,在服务端收到SYN后,向客户端返回一个ACK确认就进入ESTABLISHED状态,万一这个请求中间遇到网络情况丢失而没有传给客户端,客户端一直是等待状态,后面服务端发送的信息客户端也接受不到了,所以要客户端再次确认。

关于四次挥手先看草图(看图理解)
请添加图片描述
最开始客户端和服务端都处于ESTABLISHED状态,客户端主动关闭连接。
第一次挥手:客户端准备关闭连接,向服务端发送一个FIN报文,进入FIN_WAIT1状态。
第二次挥手:服务端收到后,向客户端发送ACK确认报文,进入CLOSE_WAIT状态,
第三次挥手:客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。等待服务端处理完数据后,向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
第四次挥手:客户端收到FIN+ACK包后,再向服务端发送ACK包,
等待两个周期后再关闭连接。服务器收到了 ACK 应答报文后,关闭连接。

客户端在要等2周期再关闭
为的是确认服务器端是否收到客户端发出的 ACK 确认报文,当客户端发出最后的 ACK 确认报文时,并不能确定服务器端能够收到该段报文。服务端在没收收到ACK报文之前,会不停的重复发送FIN包而不关闭,所以得等待两个周期。

7.HTTP常见的状态码⭐

HTTP常见的状态码

8.HTTP 传输过程⭐

含义:从建立连接到断开连接一共七个步骤,就是三次握手四次挥手

  1. TCP 建立连接
  2. 浏览器发送请求命令
  3. 浏览器发送请求头
  4. 服务器应答
  5. 服务器回应信息
  6. 服务器发送数据
  7. 断开TCP连接

9.如何解决跨域⭐⭐⭐

什么是跨域?
跨域是因为浏览器的同源策略(Same Origin Policy)限制导致的。
浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域
常见的:
1、JSONP跨域
JSONP 可以跨域传递数据,基本原理是通过前端动态创建一个 <script> 标签,其中的 src 属性指向一个跨域 API 的 URL,该 URL 带有一个参数 callback,跨域 API 返回一段特定格式的 JavaScript 代码,其中 callback 函数的参数就是前端传回去的数据,前端获得结果后可以在本地执行回调函数。

2、跨域资源共享(CORS)
服务器端设置 HTTP 响应头部,使得浏览器可以跨域访问,服务器返回的响应头中包含 Access-Control-Allow-Origin 字段,指定可访问该资源的域名白名单,浏览器在接收响应时自动识别该字段判断该响应是否可跨域访问。

3、代理跨域 API 请求
使用自己的后端服务器作为代理服务器,把 API 请求发给服务器,服务器将请求转发到目标域名 API 并从服务器返回数据给前端,此时,前端 AJAX 请求的就是同域的 API,不存在跨域请求的问题了。

4、WebSocket协议跨域
基于 WebSocket 协议进行实时双向数据传输,但是需要服务端和客户端同时支持该技术。WebSocket 允许跨域使用。
5、proxy
前端配置一个代理服务器(proxy)代替浏览器去发送请求:因为服务器与服务器之间是可以通信的不受同源策略的影响。
所有的请求都发送到同一个本地后端 API,后端服务器再将请求转发到真正的目标域名服务器上。

10.网页从输入url到页面加载发生了什么⭐⭐

  1. DNS解析
  2. TCP连接
  3. 发送HTTP请求
  4. 服务器处理请求并返回HTTP报文
  5. 浏览器解析并渲染页面————>1.解析文档构建dom树。2.构建渲染树。3.布局与绘制渲染树。
  6. 连接结束

11.浏览器渲染原理?⭐⭐

含义: 从接收 HTML、CSS 和 JavaScript,到最终显示页面内容。

  1. 解析
    浏览器将 HTML 文档解析为 DOM(文档对象模型)树,
    将CSS文件解析为 CSSOM(CSS 对象模型)树。
  2. 构建渲染树
    浏览器将 DOM 和 CSSOM 结合,构建渲染树(Render Tree) 。
  3. 布局
    浏览器根据渲染树中的元素的样式信息计算每个元素的位置和尺寸,
  4. 绘制
    浏览器将渲染树中的每个元素绘制到屏幕上。
  5. 合成
    如果页面包含多个图层(例如,使用 CSS3 动画或变换),浏览器会将这些图层合成到一起,以形成最终的页面图像。

注:
浏览器会执行 JavaScript 脚本,这可能会修改 DOM 或 CSSOM,导致重新布局或重新绘制。
在JS操作后,可能会触发重排(Reflow)或重绘(Repaint),重新计算布局或重新绘制部分内容。

12.深拷贝,浅拷贝⭐⭐⭐

浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

总而言之,浅拷贝改动拷贝的数组原数组也会变(慎用!项目中很多地方共用的数组都会变)。深拷贝修改新数组不会改到原数组。
实现方法
浅拷贝

  1. Object.assign()
  2. 函数库lodash的 _.clone 方法
  3. es6的扩展运算符 (只能浅拷贝数组或对象中的引用类型的元素)
  4. Array.prototype.concat()
  5. Array.prototype.slice()
            let arr=[{name:"uzi"}]
 -             let arr1= Object.assign({}, arr);   arr1[0].name="xiaoming"
 -             let arr2= _.clone(arr);              arr2[0].name="mlxg"
 -             let arr4 = arr.concat()              arr4[0].name="zitai"
 -             let arr5 = arr.slice();              arr5[0].name="clearLove"
               console.log(arr[0].name==arr[1].name==arr[2].name==……);
               //true  arr[0].name="clearLove"

深拷贝

  • JSON.parse(JSON.stringify())
  • 函数库lodash的 _.cloneDeep 方法
  • **jQuery.extend()**方法
  • 手写递归方法(转)
  • 扩展运算符…(只是一层的数组或者对象)
var $ = require('jquery'); 
            let arr=[{name:"theShy",age:"21"}]
 -             let arr1= JSON.parse(JSON.stringify(arr));   arr1[0].name="rookie"
 -             let arr2= _.cloneDeep(arr);                  arr2[0].name="ning"
 -             let arr3= $.extend(true, {}, arr);           arr3[0].name="baolan"
                console.log(arr[0].name==arr[1].name==arr[2].name==……);
               //fales arr1[0].name="rookie" arr2[0].name="ning"

JSON.parse(JSON.stringify())使用限制:

  • 不支持函数
  • 不支持undefined(支持null)
  • 不支持Symbol
  • 不支持循环引用,比如 a = {name: ‘a’}; a.self = a; a2 = JSON.parse(JSON.stringify(a)),会出现 TypeError 错误
  • 不支持Date,会变成 ISO8601 格式的字符串
  • 不支持正则表达式
  • 对象属性的顺序可能会被改变。可以使用 ES6 中的 Map 或者使用 Object.keys 方法获取对象属性的顺序
    (对象包含 undefined、函数、Symbol 类型时,转化为 JSON 字符串时会被忽略或者转化为 null)

下面是一个简单的手写递归方法

function deepCopy(obj) {
  if (typeof obj !== "object" || obj === null) {
    return obj;
  }

  let newObj = Array.isArray(obj) ? [] : {};

  for (let key in obj) {
    newObj[key] = deepCopy(obj[key]);
  }

  return newObj;
}

对于扩展运算符的深拷贝和浅拷贝不同情况可以看数组一些常用的方法

13.防抖与节流⭐⭐⭐

防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间(多次执行变为最后一次执行
应用场景:
提交按钮用户注册时候的手机号验证邮箱验证

节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率(多次执行变成每隔一段时间执行
应用场景:
window对象的resize、scroll事件
拖拽时候的mousemove
射击游戏中的mousedown、keydown事件
文字输入、自动完成的keyup事件

vue中使用详情可以看vue中使用防抖和节流

14.性能优化⭐⭐

前端性能优化手段从以下几个方面入手:加载优化、执行优化、渲染优化、样式优化、脚本优化

  • 加载优化:减少HTTP请求、缓存资源、压缩代码、无阻塞、首屏加载、按需加载、预加载、压缩图像、减少Cookie、避免重定向、异步加载第三方资源

  • 执行优化:CSS写在头部,JS写在尾部并异步、避免img、iframe等的src为空、尽量避免重置图像大小、图像尽量避免使用DataURL

  • 渲染优化:设置viewport、减少DOM节点、优化动画、优化高频事件、GPU加速

  • 样式优化:避免在HTML中书写style、避免CSS表达式、移除CSS空规则、正确使用display:display、不滥用float等

  • 脚本优化:减少重绘和回流、缓存DOM选择与计算、缓存.length的值、尽量使用事件代理、尽量使用id选择器、touch事件优化

15.webpack是怎么打包的,babel又是什么⭐

Webpack:把所有依赖打包成一个 bundle.js文件,通过代码分割成单元片段并按需加载。Webpack是以公共JS的形式来书写脚本的,但对AMD/CMD的支持也很全面,方便旧项目进行代码迁移。
把项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

babel将es6、es7、es8等语法转换成浏览器可识别的es5或es3语法。

16. webpack构建流程⭐

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  3. 确定入口:根据配置中的 entry 找出所有的入口文件;
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

17.vite和webpack的区别⭐⭐

  • 构建速度
    Vite 通过 ES Module 原生支持和开箱即用的特性,可以快速实现客户端和服务端的热更新,开发时的启动速度快,在初次构建和增量构建时性能均优于 Webpack。
    Webpack 需要通过分析所有的模块之后进行构建,而 Vite 利用浏览器支持的 ESM 原生支持的特性,通过直接加载文件来进行编译和构建,速度较 Webpack 更快。

  • 打包方式
    Vite 支持基于 ES Module 的原生打包,可以直接使用浏览器原生支持的 ES Module 特性进行代码打包和分发。
    Webpack 对开发者使用的方式相对更为多样化,例如支持 CommonJS、AMD 等 JavaScript 模块系统,更加灵活。

  • 插件生态
    相比于 ViteWebpack 社区相对更成熟和庞大,拥有更多的第三方插件和工具支持,可以满足更为复杂的打包需求。

  • 应用场景
    Vite 仅支持现代浏览器,因此仅适用于现代浏览器、轻量级应用场景和小型项目。
    Webpack 支持更为广泛,适用于大型项目和复杂应用场景。

总结:Vite 在性能和速度方面上优于 Webpack,特别是在开发环境下。
如果较大的项目,建议使用 Webpack 进行管理和构建。
当项目较小,且需要快速启动和热更新时,可以使用 Vite 来开发项目,更加高效。

18.require和import区别⭐

  • 调用时间
    require 运行时 调用,理论上可以运用在代码任何地,甚至不需要赋值给某个变量之后再使用。
    lmport是 编译时 调用,必须放在文件开头,而且使用格式也是确定的。
  • 遵循规范
    require 是 AMD规范引入方式
    import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法
  • 本质
    require是赋值过程,其实require 的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量。
    import是解构过程

通过require 引入基础数据类型时,属于复制该变量。
通过require 引入复杂数据类型时,数据浅拷贝该对象。
出现模块之间的循环引用时,会输出已经执行的模块,而未执行的模块不输出(比较复杂)。CommonJS模块默认export的是一个对象,即使导出的是基础数据类型。

ES6 模块语法是 JavaScript 模块的标准写法,坚持使用这种写法,取代 Node.js 的 CommonJS 语法。
使用import取代require()。

// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// ES6 的写法
import { func1, func2 } from 'moduleA';

使用export取代module.exports。

// commonJS 的写法
var React = require('react');
var Breadcrumbs = React.createClass({
  render() {
    return <nav />;
  }
});
module.exports = Breadcrumbs;

// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {
  render() {
    return <nav />;
  }
};
export default Breadcrumbs;

19.事件循环(Event Loop)⭐⭐⭐

原因:JavaScript是单线程,所有任务需要排队,前一个任务结束,才会执行后一个任务。

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务:不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

同步和异步任务分别进入不同的执行环境, 先执行同步任务,把异步任务放入循环队列当中挂起,等待同步任务执行完,再执行队列中的异步任务。异步任务先执行微观任务,再执行宏观任务。一直这样循环,反复执行。

微任务:Promise.then、catch、finally、async/await。
宏任务:整体代码 Script、UI 渲染、setTimeout、setInterval、Dom事件、ajax事件。

如果使用同步的方式,有可能造成主线程阻塞,从而导致消息队列中很多其他任务无法执行。这样一来,一方面会导致主线程白白浪费时间,另一方面导致页面无法及时更新,给用户造成卡死。
所以浏览器采用异步的方式来避免,从最大限度的保证了单线程的流畅运行。

20.什么是单页面应用(SPA)⭐

一个系统只加载一次资源,之后的操作交互、数据交互是通过路由、ajax来进行,页面并没有刷新。
在一个页面上集成多种功能,甚至整个系统就只有一个页面,所有的业务功能都是它的子模块,通过特定的方式挂接到主界面上。
优点

  • 前后端分离
  • 良好的交互体验——用户不用刷新页面,页面显示流畅
  • 减轻服务器压力——服务器只出数据
  • 共用一套后端代码——多个客户端可共用一套后端代码
  • 加载速度快,内容的改变不需要重新加载整个页面,对服务器压力小

缺点

  • SEO难度高——数据渲染在前端进行
  • 页面初次加载比较慢,页面复杂提高很多

多页面:一个应用多个页面,页面跳转时整个页面都刷新,每次都请求一个新的页面
优点:SEO效果好
缺点:页面切换慢,每次切换页面需要选择性的重新加载公共资源

详情可以参考构建单页Web应用

21.axios与fetch的区别?⭐

axios 和浏览器自带的 fetch 都是用于发起 HTTP 请求的工具

  1. 语法与使用方式
    axios: 语法相对简单,易于使用。它可以直接返回 JSON 数据,无需手动解析。
    fetch: 默认返回的是一个 Promise 对象,需要手动解析为 JSON。
  2. 请求和响应拦截
    axios: 支持请求和响应拦截器,方便进行全局拦截处理(例如添加Authorization头)。
    fetch: 不支持拦截器,需要手动实现。
  3. 兼容性
    axios: 兼容性好,支持 IE 11 及更早版本。
    fetch: 不支持 IE 11,部分旧版浏览器可能不支持。
  4. 默认的 Content-Type
    axios: 当发送 JSON 数据时,默认将 Content-Type 设置为 application/json 。
    fetch: 默认不设置 Content-Type,需要自己手动设置。
  5. 错误处理
    axios: 只在 HTTP 响应状态码不为 2xx 时才会触发 catch。
    fetch: 只会在网络错误时触发 catch,仍需对 response.ok 进行判断。
  6. 数据处理
    axios: 会自动将响应数据转换为 JSON。
    fetch: 需要手动调用 .json() 方法。

22.浏览器内存泄漏?⭐⭐

概念:程序中己动态分配的堆内存由于某种原因未释放或无法被浏览器所回收,造成系统内存占用越来越大,最终导致程序运行缓慢甚至系统崩溃等严重后果。
原因:

  • 滥用全局变量,使这个变量一直留在内存中无法被回收
  • 不合理的使用闭包,从而导致某些变量一直被留在内存当中。
  • 没有清除定时器。
  • 未被销毁的事件监听。
  • 某些DOM操作(使用Dom对象绑定事件时,Dom对象消失后,还保留着没有及删除)。

23. iframe的优点、缺点⭐

优点:

  • iframe能够原封不动的把嵌入的网页展现出来。
    如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
  • 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
  • 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。

缺点:

  • iframe会阻塞主页面的onload事件;
  • iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。会产生很多页面,不容易管理。
  • iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
  • 代码复杂,无法被一些搜索引擎索引到,iframe会不利于搜索引擎优化(SEO)。
  • 很多的移动设备无法完全显示框架,设备兼容性差。
  • iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。

四、TS篇

1.TS相比JS的有哪些优点⭐

支持静态类型定义提高了代码的可维护性和可读性,减少了代码错误。
提高了开发成本降低维护成本。

2.TS的类型 ⭐⭐⭐

  • JS的数据类型
  • 新增类型
  1. any:表示任意类型。
  2. unknown:表示未知类型,但在使用前必须强行先进行类型检查。
  3. never:表示任何值都不是,不能直接限制变量,通常用于表示永远不会抛出异常或永远不会有返回值的函数的类型。
  4. void:用于表示没有任何返回值的函数的类型,与undefined相比,如果是函数不应该去调用或使用其返回值。
  5. tuple(元组类型):一种特殊的数组类型,可以存储固定数量的元素,并且每一个元素的类型是已知的且可以不同
  6. enum(枚举):可以定义一堆命名常量,它能增强代码可读性,也让代码更好维护
  • 两个用于自定义类型的方式
  1. type:可以为任何类型创建别名,让代码更简洁,更方便进行类型复用和扩展。
  2. interface:一种定义结构的方式,主要为:类、对象、函数等规定的一种契约。只能定义格式,不能包含任何实现

3. type和interface的区别 ⭐⭐

相同点:都可以用于定义对象结构

区别:
interface:

  • 更专注于定义对象的结构
  • 可以使用extends继承,支持合并
  • 多个同名的interface会合并

type:

  • 可以定义类型别名联合类型(|)、交叉类型(&)
  • 多个同名的type会报错
  • 不支持继承和自动合并(可以使用 & 继承,但不推荐)
  • type定义对象有 =

4. interface和抽象类的区别 ⭐⭐

相同点:都用于定义一个类的格式
区别:
interface: 只能描述结构不能有任何实现代码,一个类可以定义多个接口
抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类

五、VUE篇

关于vue的面试题移步这篇文章
前端面试题 — — vue篇

六、REACT篇

1.React的生命周期(版本17.0.2)⭐⭐⭐

概念:每个组件都包含 “生命周期方法”,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。
挂载

  • constructor():在 React 组件挂载之前,会调用它的构造函数。(注:如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。)
  • render(): class 组件中唯一必须实现的方法。
  • componentDidMount():在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。

更新

  • render(): class 组件中唯一必须实现的方法。
  • componentDidUpdate():在更新后会被立即调用。首次渲染不会执行此方法。

卸载

  • componentWillUnmount():在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

具体可用一张图来表示
在这里插入图片描述

详情可看官网组件的生命周期

2.React如何获取组件对应的DOM元素?⭐⭐

  • ref:通过当前class组件实例的一些特定属性来直接获取子节点实例。
    注意:不能在函数组件上使用 ref 属性,因为他们没有实例。
  • findDOMNode():findDOMNode 是一个访问底层 DOM 节点的应急方案(escape hatch)。
    注意:在大多数情况下,不推荐使用该方法,因为它会破坏组件的抽象结构。严格模式下该方法已弃用。findDOMNode 不能用于函数组件。
    详情请看官网:ReactDOM

3.React中可以在render访问refs吗?为什么?⭐⭐

答:不能,因为在render阶段refs还未生成。DOM 的读取在 pre-commit 阶段,DOM的使用在 commit 阶段。
在这里插入图片描述

4.React中什么是受控组件和非控组件?⭐⭐

渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

受控组件更新state的流程:

  • 可以通过初始state中设置表单的默认值

  • 每当表单的值发生变化时,调用onChange事件处理器

  • 事件处理器通过事件对象e拿到改变后的状态,并更新组件的state

  • 一旦通过setState方法更新state,就会触发视图的重新渲染,完成表单组件的更新

对于受控组件来说,输入的值始终由 React 的 state 驱动。你也可以将 value 传递给其他 UI 元素,或者通过其他事件处理函数重置,但这意味着你需要编写更多的代码。
详情看官网受控组件

非受控组件
如果一个表单组件没有value props(单选和复选按钮对应的是checked props)时,就可以称为非受控组件。在非受控组件中,可以使用一个ref来从DOM获得表单值。而不是为每个状态更新编写一个事件处理程序。

官网解释:要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以 使用 ref 来从 DOM 节点中获取表单数据。

因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
官网非受控组件

总结: 页面中所有输入类的DOM如果是现用现取的称为非受控组件,而通过setState将输入的值维护到了state中,需要时再从state中取出,这里的数据就受到了state的控制,称为受控组件

5.谈一谈React的状态提升?⭐⭐

官网是这么解释的:

多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。

简单来说就是:将多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。对子组件操作,子组件不改变自己的状态。
可看官网的温度计数器例子

6.为什么要使用虚拟DOM?(什么是 Virtual DOM?)⭐⭐

Virtual DOM算法
用一张图片来表示
在这里插入图片描述
虚拟DOM
那就是虚拟DOM概念出现的地方,并且其性能要比真实DOM好得多。虚拟DOM只是DOM的虚拟表示。每当我们的应用程序状态更改时,虚拟DOM就会更新,而不是真实DOM。

React如何使用虚拟DOM

在React中,每个UI块都是一个组件,每个组件都有一个状态。React遵循可观察的模式,并监听状态变化。当组件的状态改变时,React更新虚拟DOM树。虚拟DOM更新后,React然后将虚拟DOM的当前版本与虚拟DOM的先前版本进行比较。此过程称为“差异化”。

一旦React知道哪些虚拟DOM对象已更改,然后React就会在真实DOM中仅 更新那些对象。与直接操作真实DOM相比,这使性能好得多。

原因:对于局部的小视图的更新,重新构造整棵 DOM没问题;但是对于大型视图,如全局应用状态变更的时候,需要更新页面较多局部视图的时候,这样的做法不可取。 Virtual DOM只是加了一些特别的步骤来避免了整棵 DOM 树变更。

简而言之就是:

  • 频繁的DOM操作昂贵且性能沉重。
  • 虚拟DOM是真实DOM的虚拟表示。
  • React使用虚拟DOM来增强其性能。
  • JS 和 DOM 之间的缓存。

更深入可看深度剖析:如何实现一个 Virtual DOM 算法

后言

其他常问的非技术问题可以看这

面试经常会问的问题

总结

每天多学习一点,更进步一点
祝:大家早日找到理想的工作~
码字不易,持续更新中…

哪里有不足之处,麻烦指出,谢谢~~
请添加图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐