写点什么

如何整理自己的前端面试题库

作者:loveX001
  • 2023-02-07
    浙江
  • 本文字数:10704 字

    阅读完需:约 35 分钟

一个 tcp 连接能发几个 http 请求?

如果是 HTTP 1.0 版本协议,一般情况下,不支持长连接,因此在每次请求发送完毕之后,TCP 连接即会断开,因此一个 TCP 发送一个 HTTP 请求,但是有一种情况可以将一条 TCP 连接保持在活跃状态,那就是通过 Connection 和 Keep-Alive 首部,在请求头带上 Connection: Keep-Alive,并且可以通过 Keep-Alive 通用首部中指定的,用逗号分隔的选项调节 keep-alive 的行为,如果客户端和服务端都支持,那么其实也可以发送多条,不过此方式也有限制,可以关注《HTTP 权威指南》4.5.5 节对于 Keep-Alive 连接的限制和规则。


而如果是 HTTP 1.1 版本协议,支持了长连接,因此只要 TCP 连接不断开,便可以一直发送 HTTP 请求,持续不断,没有上限; 同样,如果是 HTTP 2.0 版本协议,支持多用复用,一个 TCP 连接是可以并发多个 HTTP 请求的,同样也是支持长连接,因此只要不断开 TCP 的连接,HTTP 请求数也是可以没有上限地持续发送

深拷贝浅拷贝

浅拷贝:浅拷贝通过ES6新特性Object.assign()或者通过扩展运算法...来达到浅拷贝的目的,浅拷贝修改副本,不会影响原数据,但缺点是浅拷贝只能拷贝第一层的数据,且都是值类型数据,如果有引用型数据,修改副本会影响原数据。
深拷贝:通过利用JSON.parse(JSON.stringify())来实现深拷贝的目的,但利用JSON拷贝也是有缺点的,当要拷贝的数据中含有undefined/function/symbol类型是无法进行拷贝的,当然我们想项目开发中需要深拷贝的数据一般不会含有以上三种类型,如有需要可以自己在封装一个函数来实现。
复制代码

哪些操作会造成内存泄漏?

  • 第一种情况是由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。

  • 第二种情况是设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。

  • 第三种情况是获取一个 DOM 元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。

  • 第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。

HTTP 世界全览


  • 互联网上绝大部分资源都使用 HTTP 协议传输;

  • 浏览器是 HTTP 协议里的请求方,即 User Agent

  • 服务器是 HTTP 协议里的应答方,常用的有 ApacheNginx

  • CDN 位于浏览器和服务器之间,主要起到缓存加速的作用;

  • 爬虫是另一类 User Agent,是自动访问网络资源的程序。

  • TCP/IP 是网络世界最常用的协议,HTTP 通常运行在 TCP/IP 提供的可靠传输基础上

  • DNS 域名是 IP 地址的等价替代,需要用域名解析实现到 IP 地址的映射;

  • URI 是用来标记互联网上资源的一个名字,由“协议名 + 主机名 + 路径”构成,俗称 URL;

  • HTTPS 相当于“HTTP+SSL/TLS+TCP/IP”,为 HTTP 套了一个安全的外壳;

  • 代理是 HTTP 传输过程中的“中转站”,可以实现缓存加速、负载均衡等功能

回流与重绘的概念及触发条件

(1)回流

当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流


下面这些操作会导致回流:


  • 页面的首次渲染

  • 浏览器的窗口大小发生变化

  • 元素的内容发生变化

  • 元素的尺寸或者位置发生变化

  • 元素的字体大小发生变化

  • 激活 CSS 伪类

  • 查询某些属性或者调用某些方法

  • 添加或者删除可见的 DOM 元素


在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的 DOM 元素重新排列,它的影响范围有两种:


  • 全局范围:从根节点开始,对整个渲染树进行重新布局

  • 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局

(2)重绘

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘


下面这些操作会导致回流:


  • color、background 相关属性:background-color、background-image 等

  • outline 相关属性:outline-color、outline-width 、text-decoration

  • border-radius、visibility、box-shadow


注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。

发布订阅模式和观察者模式

1. 发布/订阅模式


  • 发布/订阅模式

  • 订阅者

  • 发布者

  • 信号中心


我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信 号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执 行。这就叫做"发布/订阅模式"(publish-subscribe pattern)


Vue 的自定义事件


let vm = new Vue()vm.$on('dataChange', () => { console.log('dataChange')})vm.$on('dataChange', () => {   console.log('dataChange1')}) vm.$emit('dataChange')
复制代码


兄弟组件通信过程


// eventBus.js// 事件中心let eventHub = new Vue()
// ComponentA.vue// 发布者addTodo: function () { // 发布消息(事件) eventHub.$emit('add-todo', { text: this.newTodoText }) this.newTodoText = ''}// ComponentB.vue// 订阅者created: function () { // 订阅消息(事件) eventHub.$on('add-todo', this.addTodo)}
复制代码


模拟 Vue 自定义事件的实现


class EventEmitter {  constructor(){    // { eventType: [ handler1, handler2 ] }    this.subs = {}  }  // 订阅通知  $on(eventType, fn) {    this.subs[eventType] = this.subs[eventType] || []    this.subs[eventType].push(fn)  }  // 发布通知  $emit(eventType) {    if(this.subs[eventType]) {      this.subs[eventType].forEach(v=>v())    }  }}
// 测试var bus = new EventEmitter()
// 注册事件bus.$on('click', function () { console.log('click')})
bus.$on('click', function () { console.log('click1')})
// 触发事件 bus.$emit('click')
复制代码


2. 观察者模式


  • 观察者(订阅者) -- Watcher

  • update():当事件发生时,具体要做的事情

  • 目标(发布者) -- Dep

  • subs 数组:存储所有的观察者

  • addSub():添加观察者

  • notify():当事件发生,调用所有观察者的 update() 方法

  • 没有事件中心


// 目标(发布者) // Dependencyclass Dep {  constructor () {    // 存储所有的观察者    this.subs = []  }  // 添加观察者  addSub (sub) {    if (sub && sub.update) {      this.subs.push(sub)    }  }  // 通知所有观察者  notify () {    this.subs.forEach(sub => sub.update())  }}
// 观察者(订阅者)class Watcher { update () { console.log('update') }}
// 测试let dep = new Dep()let watcher = new Watcher()dep.addSub(watcher) dep.notify()
复制代码


3. 总结


  • 观察者模式是由具体目标调度,比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模 式的订阅者与发布者之间是存在依赖的

  • 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在



参考 前端进阶面试题详细解答

Cookie、LocalStorage、SessionStorage 区别

浏览器端常用的存储技术是 cookie 、localStorage 和 sessionStorage。


  • cookie: 其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。

  • sessionStorage: html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。

  • localStorage: html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享。


上面几种方式都是存储少量数据的时候的存储方式,当需要在本地存储大量数据的时候,我们可以使用浏览器的 indexDB 这是浏览器提供的一种本地的数据库存储机制。它不是关系型数据库,它内部采用对象仓库的形式存储数据,它更接近 NoSQL 数据库。

什么是 CSRF 攻击?

(1)概念

CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。


CSRF 攻击的本质是利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。

(2)攻击类型

常见的 CSRF 攻击有三种:


  • GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交。

  • POST 类型的 CSRF 攻击,比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。

  • 链接类型的 CSRF 攻击,比如在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。

冒泡排序--时间复杂度 n^2

题目描述:实现一个冒泡排序


实现代码如下:


function bubbleSort(arr) {  // 缓存数组长度  const len = arr.length;  // 外层循环用于控制从头到尾的比较+交换到底有多少轮  for (let i = 0; i < len; i++) {    // 内层循环用于完成每一轮遍历过程中的重复比较+交换    for (let j = 0; j < len - 1; j++) {      // 若相邻元素前面的数比后面的大      if (arr[j] > arr[j + 1]) {        // 交换两者        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];      }    }  }  // 返回数组  return arr;}// console.log(bubbleSort([3, 6, 2, 4, 1]));
复制代码

浏览器渲染优化

(1)针对 JavaScript: JavaScript 既会阻塞 HTML 的解析,也会阻塞 CSS 的解析。因此我们可以对 JavaScript 的加载方式进行改变,来进行优化:


(1)尽量将 JavaScript 文件放在 body 的最后


(2) body 中间尽量不要写<script>标签


(3)<script>标签的引入资源方式有三种,有一种就是我们常用的直接引入,还有两种就是使用 async 属性和 defer 属性来异步引入,两者都是去异步加载外部的 JS 文件,不会阻塞 DOM 的解析(尽量使用异步加载)。三者的区别如下:


  • script 立即停止页面渲染去加载资源文件,当资源加载完毕后立即执行 js 代码,js 代码执行完毕后继续渲染页面;

  • async 是在下载完成之后,立即异步加载,加载好后立即执行,多个带 async 属性的标签,不能保证加载的顺序;

  • defer 是在下载完成之后,立即异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果 DOM 树已经准备好,则立即执行。多个带 defer 属性的标签,按照顺序执行。


(2)针对 CSS:使用 CSS 有三种方式:使用 link、@import、内联样式,其中 link 和 @import 都是导入外部样式。它们之间的区别:


  • link:浏览器会派发一个新等线程(HTTP 线程)去加载资源文件,与此同时 GUI 渲染线程会继续向下渲染代码

  • @import:GUI 渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前不会继续渲染(阻碍浏览器渲染)

  • style:GUI 直接渲染


外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。所以 CSS 一般写在 headr 中,让浏览器尽快发送请求去获取 css 样式。


所以,在开发过程中,导入外部样式使用 link,而不用 @import。如果 css 少,尽可能采用内嵌样式,直接写在 style 标签中。


(3)针对 DOM 树、CSSOM 树: 可以通过以下几种方式来减少渲染的时间:


  • HTML 文件的代码层级尽量不要太深

  • 使用语义化的标签,来避免不标准语义化的特殊处理

  • 减少 CSSD 代码的层级,因为选择器是从左向右进行解析的


(4)减少回流与重绘:


  • 操作 DOM 时,尽量在低层级的 DOM 节点进行操作

  • 不要使用table布局, 一个小的改动可能会使整个table进行重新布局

  • 使用 CSS 的表达式

  • 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。

  • 使用 absolute 或者 fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素

  • 避免频繁操作 DOM,可以创建一个文档片段documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中

  • 将元素先设置display: none,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。

  • 将 DOM 的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制


浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列


浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。


将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。

节流与防抖

  • 函数防抖 是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

  • 函数节流 是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。


// 函数防抖的实现function debounce(fn, wait) {  var timer = null;
return function() { var context = this, args = arguments;
// 如果此时存在定时器的话,则取消之前的定时器重新记时 if (timer) { clearTimeout(timer); timer = null; }
// 设置定时器,使事件间隔指定事件后执行 timer = setTimeout(() => { fn.apply(context, args); }, wait); };}
// 函数节流的实现;function throttle(fn, delay) { var preTime = Date.now();
return function() { var context = this, args = arguments, nowTime = Date.now();
// 如果两次时间间隔超过了指定时间,则执行函数。 if (nowTime - preTime >= delay) { preTime = Date.now(); return fn.apply(context, args); } };}
复制代码

选择排序--时间复杂度 n^2

题目描述:实现一个选择排序


实现代码如下:


function selectSort(arr) {  // 缓存数组长度  const len = arr.length;  // 定义 minIndex,缓存当前区间最小值的索引,注意是索引  let minIndex;  // i 是当前排序区间的起点  for (let i = 0; i < len - 1; i++) {    // 初始化 minIndex 为当前区间第一个元素    minIndex = i;    // i、j分别定义当前区间的上下界,i是左边界,j是右边界    for (let j = i; j < len; j++) {      // 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 j      if (arr[j] < arr[minIndex]) {        minIndex = j;      }    }    // 如果 minIndex 对应元素不是目前的头部元素,则交换两者    if (minIndex !== i) {      [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];    }  }  return arr;}// console.log(quickSort([3, 6, 2, 4, 1]));
复制代码

代码输出结果

Promise.resolve().then(() => {  return new Error('error!!!')}).then(res => {  console.log("then: ", res)}).catch(err => {  console.log("catch: ", err)})
复制代码


输出结果如下:


"then: " "Error: error!!!"
复制代码


返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!')),因此它会被 then 捕获而不是 catch。

深拷贝

实现一:不考虑 Symbol


function deepClone(obj) {    if(!isObject(obj)) return obj;    let newObj = Array.isArray(obj) ? [] : {};    // for...in 只会遍历对象自身的和继承的可枚举的属性(不含 Symbol 属性)    for(let key in obj) {        // obj.hasOwnProperty() 方法只考虑对象自身的属性        if(obj.hasOwnProperty(key)) {            newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];        }    }    return newObj;}
复制代码


实现二:考虑 Symbol


// hash 作为一个检查器,避免对象深拷贝中出现环引用,导致爆栈function deepClone(obj, hash = new WeakMap()) {    if(!isObject(obj)) return obj;    // 检查是有存在相同的对象在之前拷贝过,有则返回之前拷贝后存于hash中的对象    if(hash.has(obj)) return hash.get(obj);    let newObj = Array.isArray(obj) ? [] : {};    // 备份存在hash中,newObj目前是空对象、数组。后面会对属性进行追加,这里存的值是对象的栈    hash.set(obj, newObj);    // Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。    Reflect.ownKeys(obj).forEach(key => {        // 属性值如果是对象,则进行递归深拷贝,否则直接拷贝        newObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key];    });    return newObj;}
复制代码

常⽤的 meta 标签有哪些

meta 标签由 namecontent 属性定义,用来描述网页文档的属性,比如网页的作者,网页描述,关键词等,除了 HTTP 标准固定了一些name作为大家使用的共识,开发者还可以自定义 name。


常用的 meta 标签:(1)charset,用来描述 HTML 文档的编码类型:


<meta charset="UTF-8" >
复制代码


(2) keywords,页面关键词:


<meta name="keywords" content="关键词" />
复制代码


(3)description,页面描述:


<meta name="description" content="页面描述内容" />
复制代码


(4)refresh,页面重定向和刷新:


<meta http-equiv="refresh" content="0;url=" />
复制代码


(5)viewport,适配移动端,可以控制视口的大小和比例:


<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
复制代码


其中,content 参数有以下几种:


  • width viewport :宽度(数值/device-width)

  • height viewport :高度(数值/device-height)

  • initial-scale :初始缩放比例

  • maximum-scale :最大缩放比例

  • minimum-scale :最小缩放比例

  • user-scalable :是否允许用户缩放(yes/no)


(6)搜索引擎索引方式:


<meta name="robots" content="index,follow" />
复制代码


其中,content 参数有以下几种:


  • all:文件将被检索,且页面上的链接可以被查询;

  • none:文件将不被检索,且页面上的链接不可以被查询;

  • index:文件将被检索;

  • follow:页面上的链接可以被查询;

  • noindex:文件将不被检索;

  • nofollow:页面上的链接不可以被查询。

浏览器的主要组成部分

  • ⽤户界⾯ 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗⼝显示的您请求的⻚⾯外,其他显示的各个部分都属于⽤户界⾯。

  • 浏览器引擎 在⽤户界⾯和呈现引擎之间传送指令。

  • 呈现引擎 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。

  • ⽹络 ⽤于⽹络调⽤,⽐如 HTTP 请求。其接⼝与平台⽆关,并为所有平台提供底层实现。

  • ⽤户界⾯后端 ⽤于绘制基本的窗⼝⼩部件,⽐如组合框和窗⼝。其公开了与平台⽆关的通⽤接⼝,⽽在底层使⽤操作系统的⽤户界⾯⽅法。

  • JavaScript 解释器。⽤于解析和执⾏ JavaScript 代码。

  • 数据存储 这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“⽹络数据库”,这是⼀个完整(但是轻便)的浏览器内数据库。


值得注意的是,和⼤多数浏览器不同,Chrome 浏览器的每个标签⻚都分别对应⼀个呈现引擎实例。每个标签⻚都是⼀个独⽴的进程。

左边定宽,右边自适应方案

float + margin,float + calc


/* 方案1 */ .left {  width: 120px;  float: left;}.right {  margin-left: 120px;}/* 方案2 */ .left {  width: 120px;  float: left;}.right {  width: calc(100% - 120px);  float: left;}
复制代码

HTTPS 是如何保证安全的?

先理解两个概念:


  • 对称加密:即通信的双⽅都使⽤同⼀个秘钥进⾏加解密,对称加密虽然很简单性能也好,但是⽆法解决⾸次把秘钥发给对⽅的问题,很容易被⿊客拦截秘钥。

  • ⾮对称加密:


  1. 私钥 + 公钥= 密钥对

  2. 即⽤私钥加密的数据,只有对应的公钥才能解密,⽤公钥加密的数据,只有对应的私钥才能解密

  3. 因为通信双⽅的⼿⾥都有⼀套⾃⼰的密钥对,通信之前双⽅会先把⾃⼰的公钥都先发给对⽅

  4. 然后对⽅再拿着这个公钥来加密数据响应给对⽅,等到到了对⽅那⾥,对⽅再⽤⾃⼰的私钥进⾏解密


⾮对称加密虽然安全性更⾼,但是带来的问题就是速度很慢,影响性能。


解决⽅案:


结合两种加密⽅式,将对称加密的密钥使⽤⾮对称加密的公钥进⾏加密,然后发送出去,接收⽅使⽤私钥进⾏解密得到对称加密的密钥,然后双⽅可以使⽤对称加密来进⾏沟通。


此时⼜带来⼀个问题,中间⼈问题:如果此时在客户端和服务器之间存在⼀个中间⼈,这个中间⼈只需要把原本双⽅通信互发的公钥,换成⾃⼰的公钥,这样中间⼈就可以轻松解密通信双⽅所发送的所有数据。


所以这个时候需要⼀个安全的第三⽅颁发证书(CA),证明身份的身份,防⽌被中间⼈攻击。 证书中包括:签发者、证书⽤途、使⽤者公钥、使⽤者私钥、使⽤者的 HASH 算法、证书到期时间等。


但是问题来了,如果中间⼈篡改了证书,那么身份证明是不是就⽆效了?这个证明就⽩买了,这个时候需要⼀个新的技术,数字签名。


数字签名就是⽤CA⾃带的 HASH 算法对证书的内容进⾏HASH 得到⼀个摘要,再⽤CA 的私钥加密,最终组成数字签名。当别⼈把他的证书发过来的时候,我再⽤同样的 Hash 算法,再次⽣成消息摘要,然后⽤CA 的公钥对数字签名解密,得到 CA 创建的消息摘要,两者⼀⽐,就知道中间有没有被⼈篡改了。这个时候就能最⼤程度保证通信的安全了。

TCP 和 UDP 的概念及特点

TCP 和 UDP 都是传输层协议,他们都属于 TCP/IP 协议族:


(1)UDP


UDP 的全称是用户数据报协议,在网络中它与 TCP 协议一样用于处理数据包,是一种无连接的协议。在 OSI 模型中,在传输层,处于 IP 协议的上一层。UDP 有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。


它的特点如下:


1)面向无连接


首先 UDP 是不需要和 TCP 一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。


具体来说就是:


  • 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了

  • 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作


2)有单播,多播,广播的功能


UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。


3)面向报文


发送方的 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文


4)不可靠性


首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。


并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。


再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。


5)头部开销小,传输数据报文时是很高效的。


UDP 头部包含了以下几个数据:


  • 两个十六位的端口号,分别为源端口(可选字段)和目标端口

  • 整个数据报文的长度

  • 整个数据报文的检验和(IPv4 可选字段),该字段用于发现头部信息和数据中的错误


因此 UDP 的头部开销小,只有 8 字节,相比 TCP 的至少 20 字节要少得多,在传输数据报文时是很高效的。


(2)TCP TCP 的全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 是面向连接的、可靠的流协议(流就是指不间断的数据结构)。


它有以下几个特点:


1)面向连接


面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。


2)仅支持单播传输


每条 TCP 传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。


3)面向字节流


TCP 不像 UDP 一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。


4)可靠传输


对于可靠传输,判断丢包、误码靠的是 TCP 的段编号以及确认号。TCP 为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。


5)提供拥塞控制


当网络出现拥塞的时候,TCP 能够减小向网络注入数据的速率和数量,缓解拥塞。


6)提供全双工通信


TCP 允许通信双方的应用程序在任何时候都能发送数据,因为 TCP 连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP 可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于 MSS)

图片懒加载

与普通的图片懒加载不同,如下这个多做了 2 个精心处理:


  • 图片全部加载完成后移除事件监听;

  • 加载完的图片,从 imgList 移除;


let imgList = [...document.querySelectorAll('img')]let length = imgList.length
// 修正错误,需要加上自执行- const imgLazyLoad = function() {+ const imgLazyLoad = (function() { let count = 0
return function() { let deleteIndexList = [] imgList.forEach((img, index) => { let rect = img.getBoundingClientRect() if (rect.top < window.innerHeight) { img.src = img.dataset.src deleteIndexList.push(index) count++ if (count === length) { document.removeEventListener('scroll', imgLazyLoad) } } }) imgList = imgList.filter((img, index) => !deleteIndexList.includes(index)) }- }+ })()
// 这里最好加上防抖处理document.addEventListener('scroll', imgLazyLoad)
复制代码


用户头像

loveX001

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
如何整理自己的前端面试题库_JavaScript_loveX001_InfoQ写作社区