前端面试

218 阅读51分钟

数据结构

1. 排序算法

来源:github.com/hustcc/JS-S…

image.png

1.1 冒泡算法

动图

  • 相邻元素比较,每趟队尾是最大的元素。每趟有多次置换操作

1.2 选择排序

动图

  • 每次选择最小的数据,放到队首。每趟一次置换操作

1.3 插入排序

动图

  • 将数据划分为两块
    • 一块是排好的
    • 一个是待排序的
  • 每次选择待排序数据的队首,插入到已排好的数据队列中

1.4 希尔排序

动图

  • 将数据划分为两块
    • 一块是排好的
    • 一个是待排序的
  • 每次选择待排序数据的队首,插入到已排好的数据队列中

网络

浏览器

1 浏览器缓存

也称HTTP缓存。浏览器缓存是指浏览器对之前请求过的资源进行缓存,以便再次访问的时候提高页面展示的速度,如果有缓存,我们可以从缓存(本地)加载。缓存的资源一般为静态资源(HTML、JS、CSS、图片、视频)等。

  • 静态资源,不需要连接数据库和处理程序,所有用户的返回都是一样的
  • 动态资源(.jsp, .php, .asp)需要连接数据库,或者需要处理程序,不同的用户返回不同的内容。

1.1 缓存的位置

查找浏览器缓存时会按顺序查找: Service Worker-->Memory Cache-->Disk Cache-->Push Cache。

  • Service Worker:service worker是浏览器运行在后台,与网页主线程独立的另一个线程能。它运行在Web应用程序的上下文之外,并与Web页面通过postMessage通信。使用Service Worker的话,协议必须是HTTPS,因为Service Worker中涉及到请求拦截

  • Memory Cache:内存,主要包含的是当前中页面中已经抓取到的资源。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了

  • Disk Cache:它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,从而缓存数据

以mac系统为例,Google的数据存在 /Users/XXX/Library/Caches/Google/Chrome/Default/Cache文件夹下。

image.png

prefetch cache(预取缓存):prefetch是预加载的一种方式,被标记为prefetch的资源,将会被浏览器在空闲时间加载

  • Push Cache:Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令

强制缓存与协商缓存的区别:判断缓存命中时,是否需要向服务器发送请求

1.2 缓存类型

2.1.1 强缓存

查看数据是否过期,不过期直接使用,返回200的状态码

  • Expires 以分钟为单位,严重依赖本地时间,但是本地时间是可以修改的
  • cache-control 以s为单位
//设置强制缓存,在后端的响应头中添加配置
res.writeHead(200,{
   Expires:new Date("2022-6-8 00:00:10").toUTCString()   
   
   //在Http1.1 中增加了cache-control字段对expires进行优化
   //cache-control参数  
   //max-age 设置过期时间   s-maxage设置代理服务器的缓存时间
   //no-store 不缓存   no-cache 强制进行协商缓存
   //private 只能被浏览器缓存,默认   public 即可以被浏览器缓存,也可以被代理服务器缓存
   
   cache-control:"max-age=100000,public,no-cache"
})

注意:当 Cache-Control 与 expires 两者都存在时,Cache-Control 优先级更高

2.1.2 协商缓存

每次都需要向服务器发送缓存,查问资源是否有效,有效则使用本地缓存,无效重新发送数据

  • 浏览器把资源上次的修改时间发送给服务器,服务器进行比对文件最新的修改时间是否相同,若相同则命中缓存,返回状态码304,告诉浏览器可以使用缓存
  • 如果发现文件进行了修改,告诉浏览器资源最新修改的时间,并把修改后的资源返回给浏览器,状态码为200

请求字段字段中加上if-modified-since向后端发送请求,后端接收到会读取文件的最新修改时间,若相同则返回340,否则返回最新修改时间last-modified以及最新内容

  • last-modified/if-modified-since 问题:
    • 该记录的时间单位为秒,假如一个资源的修改速度非常快,在ms量级,则此时系统中记录的资源的最新修改时间与上次相同,捕捉不到修改;
    • 只是修改文件名,修改时间也发生改变了
//文件最后一次被修改的时间
const {mtime}=fs.statSync('./1.jpg')
//读取请求头中携带的时间    时间为上一次文件修改的时间
const ifModifiedSince=req.headers['if-modified-since']
//如果资源没有改变,使用缓存,
if(ifModifiedSince===mtime.toUTCString()){
    res.statusCode=304
    res.end()
    return
}

res.writeHead(200,{
	'last-modified':mtime.toUTCString(),   //last-modified使用的时间戳为秒,如果修改速度很快的话,就捕捉不到修改
    			               //last-modified,文件名字发生改变也会重新发送资源,造成不必要的浪费
    "Cache-Control":"no-cache"

为了弥补last-modified/if-modified-since的不足,可以使用Etag/if-none-match Etag标签

  • Etag是文件内容的hash值,类似文件的指纹
  • 请求头中带上if-none-match Etag向后端请求,若命中返回状态码340;否则返回最新的Etag,状态码为200 image.png
//etag对不同资源进行哈希运算,相当于文件指纹
//etag需要安装,data为读取的文件内容
const data = fs.readFileSync('./1.jpg')
const etagContent=etag(data)
//读取请求头中携带的指纹 ifNoneMatch
const ifNoneMatch=req.headers['if-none-match']
//如果资源没有改变,使用缓存
if(ifNoneMatch===etagContent()){
    res.statusCode=304
    res.end()
    return
}

res.writeHead(200,{
    "Cache-COntrol":"no-cache", 
    "etag":etagContent    //etagContnet为文件指纹,但是也有弊端,文件较大时,指纹生成较慢
})   

image.png

2. 进程和线程

一个进程就是一个程序的运行实例,启动一个程序的时候,操作系统会该程序创建一块内存,用来存放代码、运行数据和一个执行任务的主线程。

  • 一个进程包括多个线程,线程之间可以共享数据和资源,由进程来管理。进程之间资源相互隔离
  • 进程是系统分配资源的最小单位。线程是cpu调度的最小基本单位

2.1 浏览器进程

打开浏览器,会有如下进程:

  • 浏览器主线程:主要负责界面显示、用户交互、子进程管理,同时提供存储等功能
  • GPU进程:GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程
  • NetWorks Service:主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
  • Storage Service 即使页面未启用WebStorage, 该进程也会启动并获得内存空间分配, 不过此时CPU占用为0.0
  • Audio Service:负责音频
  • Dvetools:如果打开终端也会,会有此进程
  • 渲染进程:核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
  • 插件进程:主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。每个插件都会有一个进程
  • 标签页:每个标签页都是一个独立的进程。但是如果从一个页面打开了新页面,而新页面和当前页面属于同一站点时,那么新页面会复用父页面的渲染进程。官方把这个默认策略叫process-per-site-instance。以图中极客为例; 但是有些网站即使根域名相同也不会复用进程,美团网站

image.png

image.png

当打开一个Chrome标签页时, 至少有6个进程启动: 浏览器主进程GPU进程Network ServiceStorage Service渲染进程和 当前标签页进程

插件进程、扩展进程、代理进程、 压缩进程、音频进程、视频进程、 iframe、控制台进程等会视页面具体情况出现

2.2 渲染进程的线程

  • GUI线程(有且只有一个)

    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行

    注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

    小知识点:为什么JS引擎执行的时候GUI线程要被挂起?

因为JS是可以操作DOM的,而如果在修改这些元素的同时渲染界面,即当这两个线程不是互斥的时候,那么GUI渲染线程前后获得的元素数据就可能不一致。 所以JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎线程空闲时立即被执行

  • JS引擎线程(有且只有一个)
    • 也称为JS内核,负责处理Javascript脚本程序。(例如常常听到的谷歌浏览器的V8引擎,新版火狐的JaegerMonkey引擎等)
    • JS引擎线程负责解析Javascript脚本,运行代码。
    • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序

同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。这就是为什么建议将<script>标签写在body的最末端

  • 事件触发线程

    • 归属于渲染进程而不是JS引擎,用来控制事件轮询(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
    • 当JS引擎执行代码块如鼠标点击、AJAX异步请求等,会将对应任务添加到事件触发线程中
    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理任务队列的队尾,等待JS引擎的处理

    注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

  • 定时器触发线程(多个)

    • 定时器setInterval与setTimeout所在线程
    • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果任务队列处于阻塞线程状态就会影响记计时的准确)
    • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)

    W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

  • 异步http请求(多个)

    • 用于处理请求XMLHttpRequest,在连接后是通过浏览器新开一个线程请求。如ajax,是浏览器新开一个http线程
    • 将检测到状态变更(如ajax返回结果)时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入js引擎线程的事件队列中。再由JavaScript引擎执行。

3. 页面渲染

JS部分

1. commonjs和es6的module、AMD

在ES6之前,js没有模块化说法,最主要使用的是CommonJs和AMD两种

  • commonJS ,node就是这种规范

    • 每个文件都可以当作一个模块

    • 在服务器端:模块的加载和运行是同步的

    • 在浏览器端:模块需要提前编译打包好,并且浏览器不兼容CommonJs,需要进行代码转化

image.png

-   默认暴漏的是exports对象

-   输出的是一个值的拷贝
  • AMD

    • 专门针对浏览器端,模块的加载是异步的
  • es6 module

    • export import导出导入
    • ES6模块是编译时输出接口
    • 输出的是值的引用
  • 区别:

    • commonJs是动态的,esmodel是静态的

      • 加载是动态的,运行才会引入对象,esmodel是编译时引入对象
    • commonJS引入的是一个exports对象,esmodel引入的是值的引用

      • commonJs是可读可写,esmodel只是可读的
    • commonJS可以根据条件引入模块,但是import必须在最上边引入

    • commonJS是同步的,esmodel是异步的

2. 事件循环

  • JS是单线程,防止代码阻塞,任务分为:同步和异步
  • 同步代码给js引擎执行,异步代码交给宿主环境(浏览器/Node)
  • 同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队
  • 执行栈执行完毕之后,会去任务队列看是否有异步任务,有就送到执行栈中执行,反复循环查看执行,这个过程就是事件循环
  • 异步任务认为宏任务和微任务,微任务是由JS引擎发起的,宏任务是由宿主环境执行的

宏任务:整体代码script、setTimeout、setInterval、setImmediate、i/o操作(输入输出,比如读取文件操作、网络请求)、ui render(dom渲染,即更改代码重新渲染dom的过程)、异步ajax等

微任务:Promise(then、catch、finally)、async/await、process.nextTick、Object.observe(⽤来实时监测js中对象的变化)、 MutationObserver(监听DOM树的变化)

宏任务一次取出一个,每个宏任务执行完之后都要查询微任务队列,如果有微任务,先执行微任务

process.nextTick的优先级高于Promise、async/await

3. 闭包

闭包形成

当一个嵌套的内部函数(子函数)引用了嵌套的外部函数(父函数)的变量(函数)时,就产生了闭包

关键点:函数嵌套、内部函数使用外部函数变量

闭包的生命周期

  • 产生:在嵌套内部函数定义执行完时就产生了

    //因为存在变量提升,f1()执行之前,执行上下文产生时,f2()变量提升,此时闭包已经存在
    function f1(){
      var a=1
      function f2(){
        console.log(a)
      }
    }
    f1()
    ​
    ​
    //
    function f3(){
      var a=1
      var f4=function(){   //在此行函数定义才完成,这时才会产生闭包
        console.log(a)
      }
    }
    f3()
    
  • 死亡:在嵌套的内部函数变为垃圾函数

闭包的用处

  • 变量私有化,函数外部可以使用变量,但是不可以修改变量
  • 防止污染全局环境
  • 是函数内部的变量在函数执行完毕之后,仍然存活在内存中(延长了局部变量的生命周期)

闭包的缺点

  • 可能会引起内存泄漏
  • 变量占用内存的时间可能会过长

4. 内存泄漏

  • 占用的内存的没有及时释放

  • 内存泄漏积累多了就会导致内存溢出

  • 常见的内存泄漏:

    • 意外的全局变量
    • 没有及时清理计时器和回调函
    • 闭包

5. 垃圾回收

  • 栈垃圾回收

    • ESP: 记录当前执行状态的指针
    • 函数执行结束后,ESP 向下移动到下一个执行上下文中,这个过程就是栈销毁内存
  • 标记回收

    • 定期从根节点遍历可以访问的对象打上标记,在没有可用内存时,对堆内存遍历,若没有被标记的对象就回收

    • 优点:实现简单

    • 缺点:

      • 内存过于碎片化
      • 内存分配速度比较慢
  • 计数回收

    • 一个对象引用次数,引用次数+1,取消引用一次,引用次数-1 当引用次数为0时,回收

    • 优点:当引用计数为0时会立即回收,最大限度减少了程序的暂停

    • 缺点

      • 循环引用时,引用次数永远不可能为0,无法回收,内存无法释放

        image-20230409234905452

      • 内部需要一直计算计数情况,时间开销比较大

V8引擎垃圾回收

V8将内存分为新生代和老生代

  • 新生代算法:新生代中的对象一般存活时间较短

    • 将内存分为From和To两部分,当From空间写满时会立即触发,会将From中活跃的对象,复制到To中,并将From清空,交换From和To空间
    • 如果新生代中的对象经历过一次复制后,就会晋升到老生代
    • 当一个对象从From移动到To空间时,如果这个空间已经使用超过了25%,则这个对象就会直接晋升到老生代中
  • 老生代算法:存活时间比较久

    • 标记清除、标记整理两种算法
    • 标记清除:遍历所用堆中的所有对象,如果存活就标记,如果没有标记就清除。标记一次要花费好几毫秒,增量标记将GC分为更小的模块。并发标记,让GC扫描和标记对象时,同时允许JS运行
    • 当碎片化太严重时,就会启动标记整理,将存活的对象都移动到一端,清理掉另外一端所有对象

6. 原型和原型链

  • 原型:每个函数都有prototye属性 称之为原型 这个属性的值是个对象,也称为原型对象

    • 原型的作用

      • 存放一些属性和方法,共享给实例对象使用
      • 在JavaScript中实现继承
  • 原型链:每个对象都有一个__proto__属性,这个属性指向它的原型对象,原型对象也是对象,也有__proto__属性,指向原型对象的原型对象,这样一层一层形成的链式结构称为原型链,最顶层找不到则返回null

image-20230410125654578

7. 执行上下文

执行上下文可以说是js代码执行的一个环境,存放了代码执行所需的变量,变量查找的作用域链规则以及this指向等。

  • 全局执行上下文

    • 在执行全局代码前将window确定为全局执行上下文

    • 对全局数据进行预处理

      • var定义的全局变量==》undefined,添加为window属性
      • function声明的全局函数==〉赋值,添加为window的方法
      • this==》赋值为this
    • 开始执行全局代码

  • 函数执行上下文

    • 在调用函数准备执行函数之前,创建对应函数执行上下文对象

    • 对局部数据进行预处理

      • 形参变量==》赋值(实参)=〉添加为执行上下文的属性
      • arguments==》赋值(实参列表),添加为执行上下文的属性
      • var定义的全局变量==》undefined,添加为执行上下文属性
      • function声明的全局函数==〉赋值,添加为执行上下文的方法
      • this==》赋值(调用函数的对象)
    • 开始执行函数体代码

8. 数据类型判断

  • typeof 判断基本数据类型 基本数据类型:number、string、boolean、undefined、null、bigInt、Symbol

    typeof null //object
    /*这是一个bug,不同的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型, null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回“ object ”*/
    typeof array //object
    typeof function(){}   //function
    typeof undefined  //undefined
    
  • A instant B 判断B的原型对象是否在A的原型链上,即A是否是B的实例

    • 只能用于判断引用数据类型
  • Object.protptype.toString.call() 精准判断数据类型

    • Object.protptype.toString.call([])   // [object Array]
      

      使用的是Symbol.toStringTag属性值,但是在es6之后允许修改该属性值

      image-20230411183158576

      Array.prototype[Symbol.toStringTag]="lalla"
      Object.prototype.toString.call([1,2])  //'[object lalla]'
      
  • constructor函数

    • 对象通过constructor 来访问它的构造函数
    • 二是对象实例通过 constrcutor 对象访问它的构造函数,这是属性在原型对象身上
    • 需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了
  • Array.isArray() 用来判断是否是一个数组

9. new运算符

  • 创建一个普通的JS对象 var obj={}

  • 将构造函数的prototype属性设置为新对象的原型 obj.__proto__=Person.prptptype

  • 使用实参来执行构造函数,并且将对象设置为函数中的this FUnction=Function.bind(obj,...)

  • 如果构造函数返回一个非基本数据类型,则该值会作为new运算符的返回值返回

    • 如果构造函数的返回值这是一个原始值或者没有返回值,则将新对象作为返回值返回 return obj

10. 作用域

作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量

作用域: 一块代码区域, 在编码时就确定了, 不会再变化

//块作用域,在ES6之前没有   if就是一个块
if(flag){
    var a=1;
}

全局作用域与函数作用域的区别

  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定,而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后,js代码马上执行
  • 函数执行上下文环境是在调用函数时,函数体代码执行之前创建
  • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
  • 上下文环境: 动态的, 执行代码时动态创建(对象), 当执行结束消失

执行上下文与作用域的区别

  • 作用域时静态的,只要函数定义好了就一直存在
  • 执行上下文时动态的,创建函数时创建,函数调用结束就会自动释放
var x = 10
function fn() {
    console.log(x)
}
function show(f) {
    var x = 20
    f()
}
show(fn)   //输出10,函数作用域在创建时就已经确定var fn=function(){
    console.log(fn);   //输出fn函数
}
fn()
var obj={
    fn2:function(){
        console.log(fn2)   //报错,有两层作用域,该函数和全局,该层没有fn2,全局也没有
        console.log(this.obj)   //输出fn2函数
    }
}

11. this

我们每次调用函数时,解析器都会将一个上下文对象作为隐含的参数传递进函数。 使用this来引用上下文对象,根据函数的调用形式不同,this的值也不同。

  • 在全局对象中以函数形式调用时,永远都是window

  • 以方法的形式调用,this是调用方法的对象

    • 立即执行函数的this都是window
  • 箭头函数没有自己的this,它的this由外层作用域决定,箭头函数的this和它的调用方式无关,函数创建的时候就确定了

  • 以构造函数的形式调用时,this就是新创建的对象 var per=new Person() this就是per

  • 以call和apply调用时,this是自己指定的

12. call、apply、bind

  • call

    Function.prototype.mycall=function(content){
      console.log(this)
      if(typeof this!=='function') throw('not a function');
    ​
      // 参数
      let args=[...arguments].slice(1)
      // 判断是否传入this
      content=content||window
      // 将调用函数设为对象的方法
      content.fn=this
      // 调用函数
      result=content.fn(...args)
      delete content.fn
      return result
    }
    
  • apply

    Function.prototype.myapply=function(content){
      console.log(this)
      if(typeof this!=='function') throw('not a function');
      // 判断是否传入this
      context=content||window
      // 将调用函数设为对象的方法
      content.fn=this
      // 调用函数
      if(arguments[1])  result=content.fn(...arguemts[1])
      else result=content.fn()
      delete content.fn
      return result
    }
    
  • bind

    Function.prototype.myapply = function (content) {
      console.log(this)
      if (typeof this !== 'function') throw ('not a function');
      // 判断是否传入this
      var args=[...arguemts].slice(1),fn=this
      context = content || window
      return function Fn() {
        return fn.apply(this instanceof Fn ? this 				   :content,args.concat(...arguments))
      }
    }
    
  • fn.call(obj,1,2)
    fn.apply(obj,[1,2])
    

13. 箭头函数、普通函数

  • 箭头函数没有自己的this,由所处的作用域确定,普通函数有自己this
  • call、apply、bind 等方法不能修改this的指向
  • 箭头函数不能作为构造函数,是因为没有自己的this
  • 箭头函数没有原型对象
  • 箭头函数没有自己的arguments

14. Promise async/await

  • 两个都是处理异步请求的方式
  • async/await是基于promises实现的
  • promise的返回对象我们使用then ,catch去处理结果,并且是链式书写,容易造成代码堆叠;async是使用try catch进行捕获异常
  • async最大的优点就是让代码看起来像同步一样,只要遇到await就会立刻返回结果,然后再执行后面的操作,但是promise.then()的方式返回,会出现请求还没返回,就执行了后边的操作

15. map和object

  • map的key可以是任意类型的,object的key必须是string或Symbol
  • map 通过size获取,object 的大小只能手动获取
  • map是可以迭代遍历的,object不可以
  • map中的key是有序的,按照插入顺序,但是object无序
  • map没有原型,默认不包括任何值,但是object有原型,本身就有很多属性
  • map增删查找事件复杂度为O(1),object未做优化

16. 函数柯里化

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数的新函数

function(x){
	return function(y){
    return x+y
  }
}
  • 原理

    • 闭包函数的高阶应用
    • 作用就是通过延长变量的生命周期, 来吧一个函数传递多个参数完成的事情
    • 改变成一个函数传递一个参数的多个函数形式
  • 应用场景

    • 参数复用

    • 延迟计算

      • sum(1,2)(3); // 未真正求值
        sum(4);          // 未真正求值
        sum();           // 输出 10
        

17. 深拷贝与浅拷贝

深拷贝的实现方式:

  • 利用JSON和object的互相转化
  • 如果只是单层的对象,可以使用扩展运算符
  • 利用递归

18. 纯函数

  • 确定的输入一定会产生确定的输出

  • 函数在执行过程中,不能产生副作用

    • 副作用:对外部的变量进行修改
  • 纯函数有哪些:redux中的reducer函数

19. js实现多线程

  • 使用Web worker

    • 子线程完全受主线程控制,且不得操作DOM。

主线程

<input type="text">
 <button >计算</button>
<script>
  var worker=new Worker('分线程.js')
  worker.onmessage=function(event){
    console.log(event.data)
  }
document.querySelector('button').addEventListener('click',()=>{
  var number=document.querySelector('input').value
  worker.postMessage(number)
})
</script>

分线程 分线程的全局对象不是window,不能操作DOM

var onmessage=function(event){
   console.log('接受到数据啦')
   var res= fun(event.data)
    postMessage(res)

}
const fun=(n)=>{
    return n<=2 ?1 : fun(n-1)+fun(n-2)
}

20. Promise.all Promise.race Promise.allSettled

// 如果所有promise都成功执行,返回成功的promise,值为成功数组,与传入顺序一致
// 如果有一个失败,就返回最先失败的promise
function all(promises){
  return new Promise((resolve,reject)=>{
    let arr=[]
    for(let i=0;i<promises.length;i++){
      promises[i].then(v => {
        arr[i]=v
      },r=>{
        reject(r)
      })
    }
    resolve(arr)
  })
}

// 返回最先执行的promise的结果
function race(promises){
  return new Promise((resolve,reject)=>{
    let arr=[]
    for(let i=0;i<promises.length;i++){
      promises[i].then(v => {
        resolve(v);
      },r=>{
        reject(r)
      })
    }
  })
}

function myPromiseAllSettled(list) {
    const result = [];
    let count = 0;
    return new Promise((reslove, reject) => {
        list.forEach((promise, index) => {
            promise.then((res) => {
                const succeed = {
                    status: 'fulfilled',
                    value: res
                }
                result[index] = succeed;
                count++;
            },(err)=> {
                const error = {
                    status: 'rejected',
                    reason: err
                }
                result[index] = error;
                count++;
            })
            .finally(() => {
                if(count >= list.length) {
                    reslove(result)
                }
            })
                
        })
    })
}

21. 事件捕获和冒泡

在chrome中支持事件捕获和冒泡,ie中只支持时间冒泡

事件流类型

  • DOM0 事件流,绑定事件 btn.onclick ,默认在冒泡阶段执行

    • DOM0 注册的事件,相同的事件默认只能注册一个,多个会相互覆盖
  • DOM2 事件流,绑定事件 btn.addEventListener 有三个参数,默认为false,代表事件在冒泡阶段执行,true为在捕获阶段执行

  • IE事件流 btn.attachEvent()

阻止冒泡或者捕获

  • stopImmediatePropagation() 可以阻止冒泡与捕获
  • stopPropagation() 阻止冒泡
  • event.cancelBubble = true 在IE中,使用cancelBubble
  • 取消默认事件 event.preventDefault() return false

22. Promise方法

  • 静态方法

    • resolve 返回一个成功的Promise

    • reject 返回一个失败的Promise

    • all 等待原则,所有Promise都会执行,所有promise都成功,返回一个成功的promise,值为各个promise的返回值。如果有某一个或者多个实例进入rejected状态,Promise.all返回的实例会立即变成Rejected状态并将第一个rejected的实例返回值传递给Promise.all返回实例的回调函数。

    • race 先执行到成功就返回成功的状态,返回失败就是失败状态

    • any 有一个成功就返回成功,如果都失败,返回失败,值 AggregateError: All promises were rejected

    • allSettled 并行执行,只能返回成功的promise,值为一个数组。保存状态与值

      image-20230612235532963

  • 实例方法

    • .then
    • .catch
    • .finally promise实例无论状态是什么都会执行的函数。

23. 数组方法

  • push、pop、unshift(+)、shift (-) 都会改变原数组

  • concat、join、sort、split、splice 改变

  • reverse 、map、slice(a,b)、filter、flat 不改变

  • indexOf (item, start)lastindexOf(item,start) includes find(()=>{}) 返回第一个符合的数据 findIndex( ()=>{} ) 返回下标

  • some( (item, index,array)=>{} ) 、every ( (item, index,array)=>{} )

  • for forEach( (item, index,array)=>{} ) 返回undefined for...in... for..of.. array.reduce(function(prev, currentValue, currentIndex, arr), initialValue)

    • 对于forEach方法除非使用try/catch,否则无法中途停止循环,break或者return都无法使循环中途停止。而for…of循环可以与break,continue和return配合使用,中途停止循环:
    • 2 Array.prototype.myReduce = function (callBack, initialValue) {
      3     if (!((typeof callBack === "Function" || typeof callBack === "function") && this)) {
      4         throw new TypeError("the first canshu muse be function");
      5     }
      6 
      7     // 获取调用reduce方法的数组
      8     const array = Object(this);
      9 
      10     // 获取数组长度
      11     const len = array.length >>> 0;
      12 
      13     // 声明累加器,当前值和当前索引
      14     let accumulator, currentValue, currentIndex;
      15 
      16     // 当数组为空且没有提供初始值时应该报错,此时执行reduce方法没有意义
      17     if (!initialValue && len <= 0) {
      18         throw new Error("Reduce of empty array with no initial value");
      19     }
      20 
      21     if (initialValue) {
      22         // 如果提供了初始值,则累加器被初始化为初始值
      23         accumulator = initialValue;
      24         for (let i = 0; i < len; i++) {
      25             currentValue = array[i]; currentIndex = i;
      26             accumulator = callBack(accumulator, currentValue, currentIndex, array);
      27         }
      28     } else {
      29         // 如果没有提供初始值,则累加器被初始化为数组的第一个元素
      30         accumulator = array[0];
      31         for (let i = 1; i < len; i++) {
      32             currentValue = array[i]; currentIndex = i;
      33             accumulator = callBack(accumulator, currentValue, currentIndex, array);
      34         }
      35     }
      36 
      37     return accumulator;
      38 }
      

24. 数据类型转换

React

1. 对react的理解

  • React是一个构建网页UI的JS库,通过组件化的方式快速响应大型的web应用程序

  • 核心设计思想:声明式、组件化、通用性

    • 声明式的优势在于直观与组合

      • 声明式渲染 我们告诉程序我们想要什么效果,其他的交给浏览器来做

      • 命令式渲染 命令我们的程序去做什么,程序就会跟着命令一步一步做

        // 声明式
        let root=document.getElementById('root');
        ReactDom.render(<h1>123</h1>,root)
                        
        // 命令式
        let h1=document.createElement('h1')
        h1.innerHTML='123'
        root.appendChild(h1)
        
    • 组件化的优势在于视图的拆分与模块复用,更容易做到高内聚低耦合

    • 通用性在于一次学习,随处编写,比如React Native

  • 优点

    • 开发团队和社区团队非常强大
    • API比较简洁
    • 一次学习,到处编写
  • 缺点

    • 没有官方系统解决方案,vue的状态管理vuex react可选的非常多,有mobx redux
  • 扩展

    • 跨平台的原理:虚拟DOM
    • 声明式:JSX实现的

2. JSX

JSX是javascript的一种语法扩展,它和模版语言很接近,但他充分具备javascript的能力

JSX---》javascript 使用babel转换

image-20230501195408517

3. 虚拟DOM

<div>
        <div>123</div>
        <div>456</div>
</div>

image-20230502202937834

  • 虚拟DOM上方法及属性较少,真实DOM上方法以及属相较多

  • 虚拟DOM,本质上JS和DOM之间的一个映射缓存,在形态上表现为一个能够描述DOM结构及其属性信息的JS对象

    • 挂载阶段

      React将结合JSX的描述,构建出虚拟DOM树,然后通过ReactDOM.render实现虚拟DOM到真实DOM的映射

    • 更新阶段

      页面的变化会先做用于虚拟DOM,虚拟DOM将在JS层借助diff算法先对比出具体有哪些DOM需要被改变,然后再将这些改变做用于真实DOM

  • 优点

    • 处理了浏览器的兼容性问题,在react 里边都是onclick 但是chrome浏览器不支持onClick
    • 内容经过XSS处理,可以防止xss攻击
    • 跨平台开发
    • 更新的时候可以实现差异化更新,减少真实DOM的操作
  • 有了虚拟DOM一定会加快吗?

    • 不一定
    • 第一次渲染的时候,不快因为还要创建虚拟dom
    • 如果在后续,如果页面结构比较复杂,获取要修改的节点内容比较多,也不一定加快,因为虚拟dom和真实dom要对比

4. 函数组件和类组件

  • 源码中isReactComponent变量区分函数组件与类组件,类组件的原型有这个对象,函数组件没有

  • 不同点

    • 编程思想不同:类组件需要创建实力,是基于面向对象的方式编程,而函数式组件不需要创建实力,返回输出

    • 内存占用:类组件需要保存实例,会占用一定的内存

    • 捕获特性函数组件具有值捕获特性,类组件实例上只有,使用了闭包的特性

      • 实例是同一个,状态对象也是同一个,永远是最新的值
      • 函数组件会捕获render内部的状态
    • 可测试性:函数式更方便编写单元测试

    • 状态:类组件有自己的实例,可以定义状态state,而且可以修改state更新状态

    • 生命周期:类组件有生命周期,函数没有

    • 逻辑复用:类组件可以继承实现逻辑的复用,官方推荐高阶函数;函数组件可以自定义Hooks实现复用

    • 跳过更新:类组件可以通过shouldComponentUpdatePureComponent来跳过复用,函数式组件可以通过React.memo跳过

    image-20230502125335584

5. 渲染流程

1. 设计理念

  • 跨平台渲染==》虚拟DOM
  • 快速响应==〉异步可中断,增量更新

2. 帧

  • JS执行任务过程

    • 浏览器刷新频率为60HZ,大概16.6毫秒一次,而JS线程和渲染线程是互斥的,如果JS线程的执行时间超过16.6ms,就会出现掉帧,引入Fiber,把任务分割为一小片
    • 主线任务不是特别特别忙的时候,会把一帧变为50ms
  • 每个帧的开头包括样式计算、布局和绘制

  • image-20230502142147858

    image-20230502134535130

  • window.requestAnimationFrame 每帧都会执行的函数

告诉浏览器你希望执行一个动画,并且告诉浏览器下次重绘之前调用指定的回调函数更新动画,该回调函数会在浏览器下一次重绘之前执行

如果你想在浏览器重绘之前继续更新下一帧动画,回调函数自身必须再次调用window.requestAnimationFrame

function animate(time){
    console.log(time)
    window.requestAnimationFrame(animate)
}
window.requestAnimationFrame(animate)
  • window.requestIdleCallback 每次刷新不一定执行

window.requestIdCallback()方法插入一个函数,这个函数将在浏览器空闲时期被调用

6. fiber

  • fiber其实是指一种数据结构,他可以用一个纯JS对象来表示

  • fiber树是根据虚拟dom构建的,是链表结构

  • vdom是在createElement创建的,fiber是在render里创建的

  • fiber是一个执行单元,每次执行完一个执行单元,React就会检查现在还剩多少时间,如果没有时间就将控制权让出去

  • fiber关键特性

    • 增量渲染
    • 不同更新的优先级
    • 暂停、终止、复用渲染任务
    • 并发方面新的基础能力

Fiber执行阶段

每次渲染有两个阶段:Reconciliation(协调render阶段)和Commit(提交阶段)

  • 协调的阶段:可以认为是Diff阶段,这个阶段可以被终止,这个阶段会找出所有节点变更,例如节点新增、删除、属性变更等等,这些变更React称之为副作用
  • 提交阶段:将上一阶段计算出来的需要处理的副作用(effects)一次性执行了,这个阶段必须同步,不能被打断

7. hooks

  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

1. 解决了什么问题

  • 状态逻辑复用变得简单
  • 难以理解的class
  • 解决业务逻辑内容难以理解 ===》 可以写多了useEffect 原来的所有操作放到同一个生命周期中

2. 具体钩子

  • useLayoutEffect 是在页面还没有渲染时就将数据更新了,但是有可能会阻塞渲染。useEffect页面挂载时,会出现闪烁,是因为useEffect在页面渲染完成后去更新数据

  • useLayoutEffect是在react完成DOM更新后马上执行的代码,这是DOM树还在聂村中,useEffect是在整个页面染完才会去调用的代码

    • 如果在useEffect中操作dom,建议使用useLayoutEffect,只有一次回流、重绘的代价
    • 官方建议优先使用useEffect
  • useCallback 组件每次重新渲染,导致方法被重新创建,起到缓存作用,只有第二个参数变化了,才重新声明一次

  • useMemo 返回函数的执行值 useCallback将函数返回

     const sum=useCallback((a)=>{
        console.log(name)
      },[name])
     
     //不传入依赖,会出现问题
     
     
     //返回函数的执行结果,也就是null
     const sum=useMeno((a)=>{
        console.log(name)
     },[name])
    
  • useRef

    • 保存变量时,myRef.current++不会render
    // 标记组件
    const myRef=useRef()
    const handleDOmething=()=>{
      cosole.log(myRef.current.value)
    }
    <input ref={myRef}></input>
    
    //保存变量,函数每次render的时候不会
    const myRef=useRef(0)
    
    <button onCLick={()=>myRef.current++}>add</button>
    <span>myRef.current</sapn>
    

8. 合成事件

  • 抹平了不同浏览器之间的兼容性差异

  • 当给组件绑定事件之后

    • react会先对事件进行注册,将事件统一注册到document上(17之后绑定在root上)
    • 根据组件唯一标识key来对事件函数进行存储
  • 统一的指定dispatchEvent回调函数

  • 事件对象可能会被频繁创建和回收,统一绑定在root上,减少内存的消耗

  • 在17(不包括)之间,先执行原生事件,在执行react事件

9. 路由传参

router6

  • search参数

    • 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
    • 路由注册 <Route path="/demo/test" element=<User/> />
    • 接受参数 在User中组件中声明接收
      const [search,setSearch] = useSearchParams()
      const name = search.get('name')
      const age = search.get('age')
      
  • params参数

    • 路由链接(携带参数):<Link to='/demo/test/tom'}>详情</Link>
    • 路由注册 <Route path="/demo/test/:name" element=<User/> />
    • 在User组件中声明接收
      let { name } = useParams();
      
  • state参数

    • 路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
    • 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    • 在Test组件中接受参数
      
      let location=useLocation()
      const {name,age}=location.state
      

10. Vue 与React 的区别

  • vue使用模版语言,react使用JSX
  • vue的生态比较好,vue打包使用vite,状态管理vuex redux可选的比较多,redux/mbox webpack

11.

优化部分

1. 防抖、节流

  • 防抖:连续触发事件但是在设定的一段时间内只执行最后一次 使用定时器实现,使用场景:文本框输入、实时保存

    var timer=null
    document.querySelctor('.opt').onkeyup=function(){
      if(timer) clearTimeout(timer)
      timer=setTimeOut(()=>{
        //执行函数
      },1000)
    }
    
  • 节流:连续触发事件但是在设定的一段时间内只执行一次函数,中间执行执行直接关闭 使用场景:快速点击、鼠标滑动、下拉加载

    var timer=null
    document.querySelctor('.opt').onmouseover=function(){
      if(timer==null){
          timer=setTimeOut(()=>{
          //执行函数
            timer=null
        },1000)
      }
    }
    

2. 懒加载

懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片的数据,是一种较好的网页性能优化的方式。这样使得网页的加载速度加快,减少了服务器的负载。适用于图片较多,页面列表较长的场景中。

  • 减少无用资源的加载

  • 提升用户体验

  • 防止加载过多图片而影响其他资源的加载

    window.addEventListener('scroll',function(){
      // 窗口的高度
      let client=document.documentElement.clientHeight
      // 所用盒子
      var div = document.querySelectorAll('div')
      // 最后一个盒子显示出50px就要重新添加一个新的盒子
      if(client-div[div.length-1].getBoundingClientRect().top>50){
        var div1=document.createElement('div')
        div1.style.backgroundColor=randomColor()
        div1.innerText=new Date().toLocaleString()
        body.append(div1)
      }
    },false)
    
     window.addEventListener('scroll',function(){
       var height=window.innerHeight
       imgs.forEach(item=>{
         var top=item.getBoundingClientRect().top
         // 只要最后一张照片已经显示,就要加载新图片
         if(top<height)
           var dataSrc=item.getAttribute('data-src')
           dataSrc? item.setAttribute('src',dataSrc):''
      		}
      })
    })
    

    以上方法会导致,鼠标滚动会触发所用图片事件

var imgs=document.querySelectorAll('img')
       
const callback=(entries)=>{
  console.log(entries)
  entries.forEach(item=>{
    // 如果一张图片出现在页面了
    if(item.isIntersecting) {
      // 请求图片
      const target=item.target
      var dataSrc=target.getAttribute('data-src')
      dataSrc? target.setAttribute('src',dataSrc):''
      // 卸载监视
      obeserver.unobserve(target)
      console.log('123')
    }
  })
}

// 声明一个时口监视器
const obeserver=new IntersectionObserver(callback)
imgs.forEach(item=>{
	// 监视器监视每一张图片  
  obeserver.observe(item)
}

3. 重排和重绘

重排:当页面中节点删除、添加、大小、获取某些节点的属性事件

重绘:是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。比如改变某个元素的背景色、文字颜色、边框颜色

4. CDN

概念

CDN是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片或其他静态资源发送给用户,有CDN服务器集群分担元站点服务器集群的压力,避免网络堵塞,加快访问速度。

三个组成部分

  • 分发服务系统:边缘服务器基于就近原则将资源发送给用户,并保持边缘服务器的内容与原站点内容一致。

  • 负载均衡:负责对对所用发起请求的用户进行访问调度,确定给用户的最终实际访问地址。分为全局负载均衡和本地负载均衡。

    • 全局负载均衡给用户定向至最近的CDN节点
    • 节点内部的设备均衡
  • 运营管理系统:

作用

CDN一般会用来托管Web资源,使用CDN来加速这些资源的访问

  • 性能

    • 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
    • 部分资源请求分配给了CDN,,减少了服务器的负载
  • 安全

    • 针对DDos:通过监控分析异常流量,限制其请求频率
    • 针对MITM:从原服务器到CDN节点到ISP,全链路HTTPS通信

DNS域名解析过程:根据域名找到CDN服务器的IP地址

  • 首先host文件找,看看本地有没有缓存

  • 如果没有像本地DNS服务器中找,首先查询DNS服务器缓存中有没有,若没有

    • 查询根域名服务器,找到.com的IP地址
    • 查询com域名服务器,找到.bilibili.com的IP地址
    • 查询.bilibili.com域名服务器,找到www..bilibili.com的域名服务器

image-20230424131551986

原理

使用场景

5. xss和csrf攻击 、IMTM

XSS

  • xss 跨站脚本攻击,攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在浏览网页时,对用户浏览器进行控制或者获取隐私数据的一种攻击方式。

  • 将一些隐私数据像cookie、session发送给攻击者,将受害者重定向到一个有攻击者控制的网站

  • 分为三种类型:

    • 反射性

      • 发出请求时,xss代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,xss代码随响应内容一起传回给浏览器,最后浏览器解析执行xss代码
    • 存储性

      • 具有攻击性的脚本保存到了服务器端,并且可以被普通用户完整的从服务中取得并执行,从而获得了在网络上传播的能力
      • 商品评论、发帖
    • 基于DOM

      • 攻击者构建了特殊的URL,用户打开网站后,js脚本从URL中获取数据,从而导致了恶意代码的执行
  • 防御措施

    • 输入过滤,对输出进行转义 过滤掉一些script、iframe等一些特殊字符,onclick、onerror、onfoucs等js事件

    • 使用CSP:建立一个白名单,告诉浏览器哪些外部资源自已加载和执行,从而防止恶意代码的注入攻击

      • 设置 HTTP 首部中的 Content-Security-Policy
      • 设置 meta 标签的方式 <meta http-equiv="Content-Security-Policy">
    • 限制输入长度

    • cookie设置为http-only等,http-only防止脚本读取cookie信息

csrf

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

  • 攻击类型

    • GET类型

      • 比如在img标签中构建一个请求,打开网站会自动发送请求
    • POST类型

      • 构建一个表单,然后隐藏,自动提交表单
    • 链接类型

      • 比如在a标签的href里构建一个请求,然后诱导用户点击
  • 防御

    • 尽量使用post
    • referer:跟踪来源,请求发起的网页;但是可以随意更改referer
    • cookie http-only
    • 加入验证机制
    • 验证referer
    • token

计算机网络

1. 计算机网络结构

image-20230423121922006

image-20230423122025421

2. TCP协议

http是应用层的协议

是一种面向连接的、可靠的、基于字节流的传输层通信协议,仅支持一对一场景,比如邮件收发、文件传输、远程登录

三次握手

  • 标志位含义
    SYN建立连接
    FIN关闭连接
    ACK响应 , ACK确认序号有效,确认收到新消息,只有ACK标志位为1时,确认序号ack才会有效

seq代表当前端数据发送的位置,刚开始是随机生成的

  • 为什么不从0开始?

    • 数据冲突,防止不同链接之间发生冲突。若第一次链接我们使用seq=0, 在传输seq=200时丢失,当该链接关闭,从新建立一个新链接又从seq=0开始,此时我们可能接受到原来的seq=200的数据;
    • 序列号预测统计,安全原因,第三方中间人有可能预测到,劫持tcp包

ack代表当前端数据接受的位置

四次挥手

image-20230423132644633

如何保证包传输的有序可靠

通过ACK回复和超时重发这两个机制保证

3.UDP协议与TCP的区别

  • TCP是面向连接的,而UDP是面向无连接的

  • TCP仅支持单播传输,UDO提供了单播、多播和广播的功能

  • UDP的头部开销比TC P的更小,数据传输效率更高,实时性更好

    • UDP 分组首部开销小,TCP 首部开销 20 字节,UDP 的首部开销小,只有 8 个字节

4. http1、http1.1、http2

http协议是超文本传输协议,默认使用80端口

特点:

  • 无连接:无连接是指限制每次连接只处理一个请求
  • 无状态:每次请求都是相互独立的,每次请求都需要发送完整的信息。
  • 解决无状态的:cookie、session、token、jwt

http1

客户端每次请求资源都需要重新TCP连接,无法复用上一次的TCP连接

http1.1

  • 头部添加了connection:keep-alive字段,允许长连接但是在同一个TCP连接中,

  • 所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。队头堵塞问题

    • 管线化技术:允许一次发送多个请求,但是接受数据的顺序必须与请求顺序相同,如果其中一个数据丢失了,后边的数据就会补上,这样请求的内容就错乱了
  • 在1.1新增加了host字段,用来指定服务器的域名 一台服务器上可以存在多个虚拟主机,并且他们共享一个IP地址

  • 相较于1.0 ,增加了PUT、HEAD、OPTION

  • 缓存:1.0 使用if-modidied-sice/exprires 1.1 使用entity tag

http2

  • 采用二进制的格式

    • 1.1报文中的头信息必须是文本,数据体可以是二进制
    • 2.0中,请求头和请求体二进制
  • 头部压缩

    • 头信息使用gzip或者compress压缩,并且重复字段不需要重复发送,发送索引号就可以了
  • 多路复用:多个请求可能在同一个连接上并行执行。某个请求任务阻塞 ,不会影响到其他连接的正常执行

  • 服务器推送

    • http2允许服务器未经请求就将一些可能用到的一些资源提前发送给客户端

http3

  • https/3基于UDP协议实现了类似于TCP的多路复用数据流、传输可靠性等功能,这套功能称为QUIC协议

5. http和https

  • http: 是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。

  • https:是以安全为目标的 HTTP 通道,即 HTTP 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性。

    • 内容加密:采用混合加密技术,中间者无法直接看到明文内容
    • 验证身份:通过证书认证客户端访问的是自己的服务器
    • 保护数据的完整性:防止传输的内容被中间人冒充或者篡改
  • 在通信刚开始的时候使用非对称算法,首先解决秘钥交换的问题,然后用随机数产生对称算法使用的“会话密钥”(session key),再用公钥加密。

    image-20230424141546802

区别

  • https 默认端口443 http 80
  • http是明文传输,https是数据加密后传输
  • http连接简单,是无状态的。 https是SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议
  • https协议需要ca证书,费用更高,功能越强大的证书费费高
  • https缓存不如http高效,会增加数据开销

6. 输入网址之后发生了什么

  • 把URL解析为IP地址,DNS解析
  • 建立TCP连接(三次握手)
  • 发送请求报文
  • 服务器接收请求,处理请求、返回响应报文
  • 浏览器解析、执行、渲染

7. http状态码

8. http缓存

强制缓存与协商缓存的区别:判断缓存命中时,是否需要向服务器发送请求

强制缓存: 查看数据是否过期,不过期直接使用,返回200的状态码

//设置强制缓存,在后端的响应头中添加配置
res.writeHead(200,{
    Expires:new Date("2022-6-8  00:00:10").toUTCString()   //设置过期时间,严重依赖本地时间
 	//在Http1.1 中增加了cache-control字段对expires进行优化
    //cache-control参数  
    //max-age 设置过期时间   s-maxage设置代理服务器的缓存时间
    //no-store  不缓存   no-cache 强制进行协商缓存
    //private 只能被浏览器缓存,默认   public 即可以被浏览器缓存,也可以被代理服务器缓存
    
    cache-control:"max-age=100000,public,no-cache"
})

协商缓存:每次都需要向服务器发送缓存,查问资源是否有效,有效则使用本地缓存,无效重新发送数据

  • 浏览器把资源上次的修改时间发送给服务器,服务器进行比对文件最新的修改时间是否相同,若相同则命中缓存,返回状态码304,告诉浏览器可以使用缓存
  • 如果发现文件进行了修改,告诉浏览器资源最新修改的时间,并把修改后的资源返回给浏览器,状态码为200
  • last-modified/if-modified-since 问题:该记录的时间单位为秒,加入一个资源的修改速度非常快,在ms量级,则此时系统中记录的资源的最新修改时间与上次捕捉不到修改,修改前后时间相同;并且文件只是修改文件名,修改时间也发生改变了
//文件最后一次被修改的时间
const {mtime}=fs.statSync('./1.jpg')
//读取请求头中携带的时间    时间为上一次文件修改的时间
const ifModifiedSince=req.headers['if-modified-since']
//如果资源没有改变,使用缓存,
if(ifModifiedSince===mtime.toUTCString()){
    res.statusCode=304
    res.end()
    return
}

res.writeHead(200,{
	'last-modified':mtime.toUTCString(),   //last-modified使用的时间戳为秒,如果修改速度很快的话,就捕捉不到修改
    									//last-modified,文件名字发生改变也会重新发送资源,造成不必要的浪费
    "Cache-Control":"no-cache"
})

为了弥补last-modified的不足,可以使用ETag标签

  • Etag/if-none-match Etag是文件内容的hash值

image-20230409221125362

//etag对不同资源进行哈希运算,相当于文件指纹
//etag需要安装,data为读取的文件内容
const data = fs.readFileSync('./1.jpg')
const etagContent=etag(data)
//读取请求头中携带的时间    时间为上一次文件修改的时间
const ifNoneMatch=req.headers['if-none-match']
//如果资源没有改变,使用缓存
if(ifNoneMatch===mtime.toUTCString()){
    res.statusCode=304
    res.end()
    return
}

res.writeHead(200,{
    "Cache-COntrol":"no-cache", 
    "etag":etagContent    //etagContnet为文件指纹,但是也有弊端,文件较大时,指纹生成较慢
})   

image-20230409221311492

9. 请求头与响应头

10. 请求方式

  • GET

    • 从给定的服务器中检索信息,参数附加在URL后,不安全,可以直接读到
    • get请求有url长度限制,http协议本身不限制,请求长度限制是由浏览器和web服务器决定和设置
    • get请求的是静态资源,则会缓存,如果是数据,则不会缓存
    • GET请求只能发送ASCII字符
  • POST

    • 向服务发送数据
    • 不会被缓存,可以发更大的数据
    • POST能发送更多的数据类型,可以发送各种类型的数据
  • PUT

  • HEAD

  • DELETE

  • OPTIONS

    • 当发生跨域请求时,并且发送复杂请求时,会自动发送该请求

      • 简单请求:三个条件

        • 请求方式:GET、POST、HEAD
        • HTTP请求头只能有以下字段:认为设置规范集合之内的首部字段,如Accept/Accept-Language/Content-Language/Content-Type/DPR/Downlink/Save-Data/Viewport-Width/Width
        • Content-Type 的值仅限于下列三者之一,即application/x-www-form-urlencoded、multipart/form-data、text/plain
    • option请求头字段

      image-20230612200404051

    • 响应头

      image-20230612200451244

    • 如何优化,减少option请求

      • 使用缓存

浏览器

1. 进程和线程

一个进程就是一个程序的运行实例,启动一个程序的时候,操作系统会该程序创建一块内存,用来存放代码、运行数据和一个执行任务的主线程。

  • 一个进程包括多个线程,线程之间可以共享数据和资源,由进程来管理。进程之间资源相互隔离
  • 进程是系统分配资源的最小单位。线程是cpu调度的最小基本单位

浏览器架构图

  • 一个浏览器包含: 一个主进程:主要负责页面显示、用户交互、子进程管理、同时提供存储 一个网络进程:主要负责页面的网络资源加载 一个GPU进程:3D绘制,现在chrome的UI界面都选择GPU来绘制 多个渲染进程:主要作用为页面渲染,脚本执行,事件处理等 多个插件进程:主要负责插件的运行

  • 渲染进程包括哪些线程

    • GUI线程

      • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
      • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
      • 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
    • JS引擎线程

      • 也称为JS内核,负责处理Javascript脚本程序。(例如常常听到的谷歌浏览器的V8引擎,新版火狐的JaegerMonkey引擎等)
      • JS引擎线程负责解析Javascript脚本,运行代码。
      • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
      • 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
    • 事件触发线程

      • 归属于渲染进程而不是JS引擎,用来控制事件轮询(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
      • 当JS引擎执行代码块如鼠标点击、AJAX异步请求等,会将对应任务添加到事件触发线程中
      • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理任务队列的队尾,等待JS引擎的处理
      • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
    • 定时器触发线程

      • 定时器setInterval与setTimeout所在线程
      • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果任务队列处于阻塞线程状态就会影响记计时的准确)
      • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
      • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
    • 异步http请求

      • 用于处理请求XMLHttpRequest,在连接后是通过浏览器新开一个线程请求。如ajax,是浏览器新开一个http线程
      • 将检测到状态变更(如ajax返回结果)时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入js引擎线程的事件队列中。再由JavaScript引擎执行。

2. 跨域

跨域产生的原因:浏览器的安全策略-同源策略

同源策略:协议、域名、端口

  • JSONP 原理:利用了script脚本不受同源策略限制,动态生成script标签,但是只支持get请求

    //添加<script>标签的方法
    function addScriptTag(src) {
      var script = document.createElement('script');
      script.setAttribute("type", "text/javascript");
      script.src = src;
      document.body.appendChild(script);
    }
    
    window.onload = function () {
      // 请求成功会调用result函数
      addScriptTag("https://www.baidu.com?&callback=result");
    }
    function result(data) {
      console.log(data)
    }
    
  • CORS跨域资源共享,后端添加响应头

  • 中间服务器代理

    • 正向代理,代理客户端,对客户端负责,同时也可以隐藏客户端
    • 反向代理,代理的服务端,对客户端负责,同时隐藏服务端
  • postMessage 向HTML5中的API,且为数不多可以夸与操作的window的属性之一 postMessage(data,origin)方法接受两个参数

  • document.domain iframe 主域相同,子域不同

3. 浏览器缓存

  • cookie

    • 默认当前浏览器窗口关闭之间有效,可以通过expires/max-age属性修改

    • cookie的大小一般是有限制的,一般最大为4kb

    • 所有同源窗口之间是共享的

    • 字段值

      image-20230511232358561

    • 字段名含义
      name名称
      value
      domain可以访问此cookie的域名
      path访问此cookie的页面路径
      expires/max-agecookie的过期时间
      sizecookie的大小
      HttpOnlytrue:只有在请求头会携带cookie,但是不能通过document.cookie访问
      Secure是否只能通过https来传递该cookie
      Partition Key
      Priority优先级,当Cookie数量超出时,低优先级的Cookie会被优先清除
  • sessionStroge

    • 存储容量为5MB
    • 关闭当前会话页或者浏览器后就会失效
    • sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面
  • localStroge

    • 存储容量为5MB
    • 长期有效,除非手动删除
    • 所有同源窗口中都是共享的

4. 会话控制

nodejs

1. process.nextTick()

webpack

1. 构建流程

从入口文件开始,根据依赖关系构建一个关系依赖树,递归遍历依赖树,根据loader的配置加载各种资源,将结果打包到bundle.js中。

2. webpack简介

webpack是一种前端资源构建工具,一种静态模块打包器。

在webpack看来,前端的所有资源文件(js/json/css/html....)都会作为模块处理

webpack默认只能处理js文件

五大核心

entry:入口文件,以那个文件为入口开始打包,分析构建内部依赖图

output:输出,打包后的文件输出到哪里

loader:让js具备处理非js文件的能力

plugins:可以用于执行更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量

mode:development、production