前端学习笔记-浏览器篇

一、输入url到显示页面发生了什么

  1. 浏览器的地址栏输入URL并按下回车;
  2. 浏览器查找当前URL是否存在缓存,并比较缓存是否过时;
  3. DNS解析URL对应的IP。先在本地hosts文件查找,若是没找到,再到DNS服务器(由中国电信,中国移动等服务商提供)查找;
  4. 根据IP创建TCP链接(三次握手);
  5. 发送 HTTP 请求报文,包括(1)请求方法URI协议/版本(2)请求头(Request Header)(3)请求正文;
  6. 服务器处理请求,返回响应报文,浏览器接收HTTP响应,包含(1)状态行(2)响应头(Response Header)(3)响应正文;
  7. 若是服务器发现这个url须要重定向,则会返回重定向的响应,这是为了seo,301永久重定向302临时重定向,若是有重定向,浏览器会从新请求重定向以后的地址;
  8. 关闭TCP链接(四次挥手);
  9. 浏览器解析html并渲染到页面;

ip和域名的对应关系:javascript

  • 任意多个域名能够解析到同一个IP,服务器根据header判断请求的是哪个域名。
  • 一个域名也能够对应多个IP,DNS服务商根据你的位置和运营商返回不一样的解析结果。

二、html解析

解析顺序

  1. 先把html下载下来;
  2. 分析文档结构,具体的是一个树型结构,是各个节点的层级关系,若是文档中有资源不符合安全策略(若有的页面不容许加载跨域的资源),则会给出警告。并对全部的资源进行优先级排序;
  3. 开启下载线程,按照排好的优先级进行资源下载,通常css和font这类文件会优先加载。浏览器对同一域名下的下载并发不超过 6 个,不一样域名的话,在浏览器设置的最大并发上限之内(默认是10个);
  4. 同时开启文档结构解析的线程。(4和3是同时进行的,算两个线程)自上而下构建dom,生成DOM Tree,按照 深度优先 的原则,将一个节点的所有子节点生成完成以后才会开始生成当前节点的兄弟节点;
  5. 浏览器会经过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。根据 Rendering Tree计算每一个元素的位置、大小,这个过程叫reflow(回流)。当元素的位置、大小肯定以后再去计算每一个元素的字体大小、颜色等,这个过程叫repaint(重绘)。

阻塞型资源:

  • 内联css
  • 内联javascript
  • 外联普通javascript
  • 外联defer javascript
  • javascript标签以前的外联css

非阻塞型资源:

  • javascript标签以后的外联css
  • image
  • iframe

外联async javascript若是下载完后,dom还没解析完毕,会直接执行,阻塞。若是下载完后dom已经解析完毕,则不算阻塞。下载不会阻塞,运行可能会阻塞php

解析dom的规则

  • 遇到 DOM 标签时,执行 DOM 构建,将该 DOM 元素添加到dom tree中。
  • 遇到内联的css,会生成cssom,和前边的cssom合并成css rule tree,浏览器解析CSS选择器是按照从右往左的顺序解析的
  • 遇到内联的javascript会直接执行阻塞后续内容的解析,直接执行js;
  • 遇到 link 标签时,不会阻塞后续内容的解析(好比 DOM 构建),检查 link 资源是否已下载,若是已下载,则构建 cssom,前边的cssom合并成css rule tree,未下载则开启线程下载。
  • 遇到 script 标签时,首先阻塞后续内容的解析,同时检查该script是否已经下载下来,若是已下载,便执行代码.若是还未下载完毕,则开启线程下载,如今完成后当即执行。
  • 遇到 script defer 标签时,先检查是否已经下载,若是已经下载,则继续往下走构建dom,若是未下载,则开启新线程下载,下载完成后,若是dom树还未构造完毕,则等待dom树构建完毕后执行。如故dom树已经构建完毕,则当即执行。在派发DOMContentLoaded事件以前
  • 遇到 script async 标签时, 先检查是否已经下载,若是已经下载,则阻塞后续内容解析,直接执行。若是还未下载,则开启线程下载,下载完成后当即执行。可能在dom树构建完成(DOMContentLoaded)以前执行,也可能在以后执行。但必定在onload以前
  • 遇到<img>或者<video>标签,不会阻塞页面,若是没有下载完毕,则进入下载线程的队列。img和video不影响DOMContentLoaded事件的派发。
  • html中每遇到< script >标签,页面就会从新渲染(浏览器会将dom tree和css rule tree合并成render tree)一次,由于要保证标签中的JS代码拿到的都是最新的样式。

DOMContentLoaded和onload

DOMContentLoaded为页面dom构建完成,同步js的代码执行完毕。图片资源,js里引入的异步资源等不必定加载完毕了; onload在DOMContentLoaded以后,图片资源,异步的js资源都下载完毕才会触发。css

三、前端缓存

  • 后端缓存(数据库缓存,CDN缓存,代理服务器缓存等)
  • 前端缓存,按照优先级Service WorkerMemory CacheDisk Cache(也叫http chache,分为强缓存协商缓存)、Push Cache

Service Worker

  • 是运行在浏览器背后的独立线程。主要用来实现离线缓存消息推送网络代理等功能,PWA(Progressive Web App渐进式的web App)应用较多。

memory cache

  • 内存中的缓存,浏览器的 TAB 关闭后该次浏览的 memory cache 便告失效;
  • 两种资源:(1)浏览器preloader发现的资源,好比css.html.font这些会优先下载;(2)显式指定的预加载资源<link rel="preload">,也会被放入 memory cache 中
  • memory cache 机制保证了一个页面中若是有两个相同的请求 (例如两个 src 相同的 <img>,两个 href 相同的 <link>) 都实际只会被请求最多一次,避免浪费。不过在匹配缓存时,除了匹配彻底相同的 URL 以外,还会比对他们的类型,CORS 中的域名规则等。所以一个做为脚本 (script) 类型被缓存的资源是不能用在图片 (image) 类型的请求中的,即使他们 src 相等。
  • 使用 no-store即使是 memory cache 也不会存储

Disk Cache(http chache)缓存策略

强缓存

Cache-Control 的几个取值含义:

  • private: 仅浏览器能够缓存
  • public: 浏览器和代理服务器均可以缓存,用的多一些
  • max-age=xxx 过时时间(重要)
  • no-cache 不进行强缓存(重要)
  • no-store 不强缓存,也不协商缓存,真正意义上的不缓

判断该资源是否命中强缓存,就看 response 中 Cache-Control 的值,若是有max-age=xxx秒,则命中强缓存。若是Cache-Control的值是no-cache,或者max-age=0(强缓存,但已通过期了)说明没命中强缓存,走协商缓存。html

Cache-Control优先级高于Expires(http旧的版本会用到) Expires 指缓存过时的时间,会有必定弊端,好比用户修改了本地时间 简单粗暴,若是资源没过时,就取缓存,若是过时了,则请求服务器,看是走协商缓存(服务端返回304),仍是请求新的数据前端

协商缓存

  • response header中的ETagLast-Modified,request header中对应If-None-MatchIf-Modified-Since
  • ETag优先级高于Last-Modified,会先判断ETag,再判断Last-Modified

ETag是为了解决几个Last-Modified比较难解决的问题:

  • 一些文件也许会周期性的更改,可是他的内容并不改变(仅仅改变的修改时间),这个时候咱们并不但愿客户端认为这个文件被修改了,而从新GET;
  • 某些文件修改很是频繁,好比在秒如下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改没法判断(或者说UNIX记录MTIME只能精确到秒);
  • 某些服务器不能精确的获得文件的最后修改时间。

一些操做触发的缓存策略

  • 地址栏回车,页面连接跳转,新开窗口,前进后退这些操做会走强缓存
  • F5 会 跳过强缓存规则,直接走协商缓存;
  • Ctrl+F5 ,跳过全部缓存规则,和第一次请求同样,从新获取资源

vue项目较为合理的缓存方案

  1. HTML:使用协商缓存。
  2. CSS&JS&图片:使用强缓存,文件命名带上hash值。

Push Cache

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

四、浏览器存储

Cookie

发送的cookie格式:

Cookie: name1=value1 [; name2=value2]vue

  • name:一个惟一肯定的cookie名称。一般来说cookie的名称是不区分大小写的。
  • value:存储在cookie中的字符串值。最好为cookie的name和value进行url编码
  • domain:cookie对于哪一个域名下是有效的。全部向该域发送的请求中都会包含这个cookie信息。这个值能够包含子域(如:m.baidu.com),也能够不包含它(如:.baidu.com,则对于baidu.com的全部子域都有效).
  • path: 表示这个cookie影响到的路径,浏览器跟会根据这项配置,向指定域中匹配的路径发送cookie。
  • expires:过时时间,表示cookie自删除的时间戳。若是不设置这个时间戳,cookie就会变成会话Session类型的cookie,浏览器会在页面关闭时即将删除全部cookie,这个值是GMT时间格式,若是客户端和服务器端时间不一致,使用expires就会存在误差。
  • max-age: 与expires做用相同,用来告诉浏览器此cookie多久过时(单位是秒),而不是一个固定的时间点。正常状况下,max-age的优先级高于expires。
  • HttpOnly: 告知浏览器不容许经过脚本document.cookie去更改这个值,一样这个值在document.cookie中也不可见。但在http请求仍然会携带这个cookie。注意这个值虽然在脚本中不可获取,但仍然在浏览器安装目录中以文件形式存在。这项设置一般在服务器端设置。
  • secure: 安全标志,指定后只有在使用SSL(https)连接时候才会发送到服务器,若是是http连接则不会传递该值。可是也有其余方法能在本地查看到cookie

Cookie的优势

  1. cookie键值对形式,结构简单
  2. 能够配置过时时间,不须要任何服务器资源存在于客户端上,
  3. 能够弥补HTTP协议无状态的部分不足
  4. 无兼容性问题。

Cookie的缺点

  1. 大小数量受到限制,每一个domain最多只能有20条cookie,每一个cookie长度不能超过4096或8192 字节,不然会被截掉。
  2. 用户配置可能为禁用 有些用户禁用了浏览器或客户端设备接收 Cookie 的能力,所以限制了这一功能。
  3. 增长流量消耗,每次请求都须要带上cookie信息。
  4. 安全风险,黑客能够进行Cookie拦截、XSS跨站脚本攻击和Cookie欺骗,历史上由于Cookie被攻击的网站用户不在少数,虽然能够对Cookie进行加密解密,但会影响到性能。

localStorage/sessionStorage

  1. 存储数据量大,5MB。
  2. 不会随http请求一块儿发送,有效的减小了请求大小
  3. sessionStorage只做用于当前窗口,不能跨窗口读取其余窗口的SessionStorage数据库信息,浏览器每次新建、关闭都是直接致使当前窗口的数据库新建和销毁。
  4. localStorage除非手动清理掉,不然会一直存在,不论浏览器,页面标签是否关闭。

indexedDB

  1. 浏览器内置的数据库,和NoSQL很像,与service work搭配,实现离线访问;
  2. 数据储存量无限大(只要你硬盘够),Chrome规定了最多只占硬盘可用空间的1/3;
  3. 操做大量数据的时候,可能存在性能上的消耗。
  4. 兼容性问题,只有ie11以上支持。

五、http版本

HTTP/1.1

  • 线头阻塞 (Head Of Line Blocking) 问题
  • TCP 链接数限制.对于同一个域名,浏览器最多只能同时建立 6~8 个 TCP 链接 (不一样浏览器不同)。为了解决数量限制,出现了 域名分片 技术,其实就是资源分域,将资源放在不一样域名

HTTP/2

  • HTTP/2解决了HTTP的队头阻塞问题,可是并无解决TCP队头阻塞问题
  • 二进制分帧,多路复用
  • 服务端推送

HTTP/3

  • Google开发QUIC协议,基于UDP实现,减小了 RTT
  • 解决了队头阻塞问题

HTTP状态码

  • 1xx:表示目前是协议的中间状态,还须要后续请求
  • 3xx:301永久重定向,302临时重定向。304协商缓存
  • 4xx:404资源未找到

六、请求实现的方式

XMLHttpRequest

  • 低版本IE的实现为ActiveXObject
  • JQuery的$ajax是对XHR的封装,是针对mvc的编程模式,不太适合目前mvvm的编程模式。
  • axios也是对原生XHR的一种封装,不过是Promise实现版本

fetch

  • 写法简单,是Promise的实现
  • 没法取消请求,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行。
  • fetch不支持同步请求
  • fetch默认不会带cookie,须要添加配置项
  • fetch没有办法原生监测请求的进度,而XHR能够

navigator.sendBeacon

  • 只能发送post请求;
  • 页面关闭,仍能够继续发送完;
  • 通常用于window的unload事件里,但不只限于此;
  • 更多请参考高程
window.addEventListener('unload', function(){
    navigator.sendBeacon('/post1', '{foo: "bar"}')
})
复制代码

WebSocket

由于HTTP 协议作不到服务器主动向客户端推送信息。WebSocket能够作到双向会话。java

  • var s = new WebSocket('ws://www.a.com/s.php') // 必须传入绝对URL,能够是任何网站
  • 链接成功后,会有101状态码,表示切换请求协议,从HTTP切换到WebSocket

6.一、三种Content-Type

application/x-www-form-urlencoded

普通的表单提交,将键值对的参数用&链接起来,若是有空格,将空格转换为+加号;有特殊符号,将特殊符号转换为ASCII HEX值node

JQuery的ajax默认值是这个ios

传入一个json对象后,通常须要通过qs.stringify()处理,真实的传入是web

key1=val1&key2=val2&key3=val3
复制代码

multipart/form-data

不会对参数编码,使用的boundary(分割线)。上传文件的操做,用到的是这种 上传一个FormDate对象时,content-type是这个值,真实的可能长这样

multipart/form-data; boundary=----WebKitFormBoundaryAqr2Zs4BNE8Z8FWR
复制代码

application/json

上传json格式的数据,适合层级较深的数据,会进行JSON.stringify。

咱们日常在使用axios的时候,有时会用到qs.stringify()这个方法。注意区别

var a = {name:'hehe',age:10};
 qs.stringify(a)
// 'name=hehe&age=10'
JSON.stringify(a)
// '{"name":"hehe","age":10}'
复制代码

由于get请求须要把参数拼接在连接后面,因此能够用qs.stringify()简单处理

XSS和CSRF

XSS

Cross-Site Scripting,跨站脚本攻击,解决方法:

  • cookie设置httpOnly
  • url,搜索参数等进行转义
  • 输入内容,危险字符过滤,长度限制

CSRF

Cross-site request forgery跨站请求伪造

八、跨域

同源策略

协议,域名(子域名和主域名),端口三个都相同被认为是同源。若是当前网址(http://news.a.com),如下都不是同源

  • https://news.a.com 不一样协议
  • http://news.a.com:8080 不一样端口
  • http://home.a.com 不一样域名

同源策略的约束

  1. Cookie、LocalStorage 和 IndexDB 没法读取。
  2. JavaScript 的 API 中的一些引用,没法得到。如获取iframe里的dom节点,进行操做是不被容许的
  3. AJAX 请求不能发送。(也就是没法使用XMLHttpRequest)

解决跨域的方法

跨域资源共享(CORS)

  • 在发送请求时会有个Origin头表示请求页面的源信息, 若是服务器返回的Access-Control-Allow-Origin中有相同的源信息或是* 那么就能够跨域请求信息
  • 请求和响应都不包含cookie

options预检请求

在跨域的状况下,在浏览器发起"复杂请求"时会主动发起options预检请求,获知服务端是否容许该跨域请求。服务器确认容许以后,才发起实际的 HTTP 请求。

简单请求与复杂请求

简单请求不会触发options预检,只有复杂请求会

简单请求
  • 请求方法为GET、HEAD、POST时发的请求
  • 人为设置了规范集合以内的首部字段,如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
  • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;
  • 请求中没有使用 ReadableStream 对象。
复杂请求
  • 使用了下面任一 HTTP 方法,PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH
  • 人为设置了如下集合以外首部字段,即简单请求外的字段
  • Content-Type 的值不属于下列之一,即application/x-www-form-urlencoded、multipart/form-data、text/plain

参考 面试官:说说你对 options 请求的理解

图片探测

let img = new Image();
img.onload = img.onerror = function() {
    console.log('done');
};
img.src = 'http://****/test?name=abc';
复制代码

图片探测只能发送get请求,没法获取服务器响应的内容。用图片探测只能与服务器单向通讯

jsonp

  • jsonp方法主要是动态建立script标签来得到数据;
  • jsonp只能进行get请求,由于script中的src属于静态文件。
  • 利用了js文件下载后会直接执行,相似eval()执行的特色。在src后面拼接上?callback=fn传入回调函数方法名,后端返回函数的调用。

document.domain

浏览器容许经过设置document.domain共享 Cookie,来达成效果。可是,两个网页一级域名相同,只是二级域名不一样才能够设置。

A网页:http://w1.cs.com/a.html 在这个网页地址中,w1.cs.com这部分统称为域名

  • 一级域名是由一个合法的字符串+域名后缀组成,因此,cs.com这种形式的域名才是一级域名,cs是域名主体,.com、.net也是域名后缀。
  • 二级域名实际就是一级域名下面的主机名,顾名思义,它是在一级域名前面加上一个字符串,好比w1.cs.com,w2.cs.com

网页中设置document.domain和cookie能够这样

document.domain = 'cs.com';
document.cookie = "key1=value1";
复制代码

服务器也能够在设置Cookie的时候,指定Cookie的所属域名为一级域名,好比.example.com

Set-Cookie: key=value; domain=.example.com; path=/
复制代码

window.postMessage

九、js执行顺序,宏任务和微任务

一个进程的运行,固然须要不少个线程互相配合, JS是单线程的,onclick回调、setTimeout、Ajax这些是由于浏览器或node(宿主环境)是多线程的,即浏览器搞了几个其余线程去辅助JS线程的运行。

浏览器线程

  1. GUI 渲染线程(DOM的渲染)
  2. JS 引擎线程
  3. 定时器触发线程 (setTimeout)
  4. 浏览器事件线程 (onclick)
  5. http 异步线程
  6. 轮询处理线程event loop

event loop执行顺序

  1. 一开始整个脚本做为一个宏任务执行;
  2. 执行过程当中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列;
  3. 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到所有执行完;
  4. 执行浏览器UI线程的渲染工做;
  5. 检查是否有Web Worker任务,有则执行;
  6. 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空;

tips:

  • 宏任务必然是在微任务以后才执行的(由于微任务其实是宏任务的其中一个步骤)
  • DOM Tree的修改是实时的,而修改的Render到DOM上才是异步的
  • new Promise执行自己时是属于同步代码,只有.then才是微任务
  • async/await本质上仍是基于Promise的一些封装,而Promise是属于微任务的一种。因此在使用await关键字与Promise.then效果相似

微任务

  1. Promise.then()catch();
  2. Promise为基础开发的其它技术,好比fetch API;
  3. Node独有的process.nextTick;
  4. V8的垃圾回收过程;
  5. MutationObserver;

宏任务

  1. setTimeoutsetIntervalsetImmediate(node环境)、
  2. script、
  3. UI rendering、
  4. I/O、
  5. 网络请求等等

requestAnimationFrame既不属于宏任务,也不属于微任务

十、垃圾回收和内存泄漏

垃圾回收:

JavaScript代码运行时,须要分配内存空间来储存变量和值。当变量不在参与运行时,就须要系统收回被占用的内存空间,这就是垃圾回收。

回收机制

  • Javascript 具备自动垃圾回收机制,会按期对那些再也不使用的变量、对象所占用的内存进行释放,原理就是找到再也不使用的变量,而后释放掉其占用的内存。
  • JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程当中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量再也不被使用,它们所占有的空间就会被释放。
  • 不过,当局部变量被外部函数使用时,其中一种状况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,因此不会回收。

垃圾回收的方式

  • 标记清楚(用的最多)
  • 引用计数

致使内存泄漏的状况

  • 意外的全局变量: 因为使用未声明的变量,而意外的建立了一个全局变量,而使这个变量一直留在内存中没法被回收。
  • 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,若是循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而没法被回收。
  • 脱离 DOM 的引用: 获取一个 DOM 元素的引用,然后面这个元素被删除,因为一直保留了对这个元素的引用,因此它也没法被回收。
  • 闭包: 不合理的使用闭包,从而致使某些变量一直被留在内存当中。

减小垃圾回收

虽然浏览器能够进行垃圾自动回收,可是当代码比较复杂时,垃圾回收所带来的代价比较大,因此应该尽可能减小垃圾回收。

  • 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],可是与此同时会建立一个新的空对象,能够将数组的长度设置为0,以此来达到清空数组的目的。
  • 对object进行优化: 对象尽可能复用,对于再也不使用的对象,就将其设置为null,尽快被回收。
  • 对函数进行优化: 在循环中的函数表达式,若是能够复用,尽可能放在函数的外面。