(1)概念
XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在 网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信 息如 cookie 等。
XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合 在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意 代码的执行。
攻击者可以通过这种攻击方式可以进行以下操作: 获取页面的数据,如 DOM、cookie、localStorage;
DOS 攻击,发送合理请求,占用服务器资源,从而使用户无法访问服 务器;
破坏页面结构;
流量劫持(将连接指向某网站)
(2)攻击类型
XSS 可以分为存储型、反射型和 DOM 型:
存储型指的是恶意脚本会存储在目标服务器上,当浏览器请求数据时, 脚本从服务器传回并执行。
反射型指的是攻击者诱导用户访问一个带有恶意代码的 URL 后,服 务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端, 浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成 XSS 攻击。
DOM 型指的通过修改页面的 DOM 节点形成的 XSS。
1)存储型 XSS 的攻击步骤:
1.攻击者将恶意代码提交到目标网站的数据库中。 2.用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接
在 HTML 中返回给浏览器。 3.用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
4.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行 为,调用目标网站接口执行攻击者指定的操作。
这种攻击常⻅于带有用户保存数据的网站功能,如论坛发帖、商品评 论、用户私信等。
2)反射型 XSS 的攻击步骤: 1.攻击者构造出特殊的 URL,其中包含恶意代码。
2.用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
3.用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。 4.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行
为,调用目标网站接口执行攻击者指定的操作。
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在
数据库里,反射型 XSS 的恶意代码存在 URL 里。
反射型 XSS 漏洞常⻅于通过 URL 传递参数的功能,如网站搜索、跳 转等。 由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会 结合多种手段诱导用户点击。
3)DOM 型 XSS 的攻击步骤: 1.攻击者构造出特殊的 URL,其中包含恶意代码
2.用户打开带有恶意代码的 URL。 3.用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL
中的恶意代码并执行。 4.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行
为,调用目标网站接口执行攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执 行恶意代码由浏览器端完成,属于前端JavaScript 自身的安全漏洞, 而其他两种 XSS 都属于服务端的安全漏洞。
可以看到 XSS 危害如此之大, 那么在开发网站时就要做好防御措施, 具体措施如下:
可以从浏览器的执行来进行预防,一种是使用纯前端的方式,不用服 务器端拼接后返回(不使用服务端渲染)。另一种是对需要插入到 HTML 中的代码做好充分的转义。对于 DOM 型的攻击,主要是前端脚 本的不可靠而造成的,对于数据获取渲染和字符串拼接的时候应该对 可能出现的恶意代码情况进行判断。
使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资 源可以加载和执行,从而防止恶意代码的注入攻击。
1.CSP 指的是内容安全策略,它的本质是建立一个白名单,告诉浏览 器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由 浏览器自己来实现。
2.通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式
对一些敏感信息进行保护,比如 cookie 使用 http-only,使得脚本 无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。
(1)概念
CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三 方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击 网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过 后台的用户验证,冒充用户向服务器执行一些操作。
CSRF 攻击的本质是利用 cookie 会在同源请求中携带发送给服务器 的特点,以此来实现用户的冒充。
(2)攻击类型
常见的 CSRF 攻击有三种:
GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个 请求,当用户打开这个网站的时候就会自动发起提交。
POST 类型的 CSRF 攻击,比如构建一个表单,然后隐藏它,当用户 进入页面时,自动提交这个表单。
链接类型的 CSRF 攻击,比如在 a 标签的 href 属性里构建一个请 求,然后诱导用户去点击。
CSRF 攻击可以使用以下方法来防护:
进行同源检测,服务器根据 http 请求头中 origin 或者 referer 信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当 origin 或者 referer 信息都不存在的时候,直接阻止请求。这种方 式的缺点是有些情况下 referer 可以被伪造,同时还会把搜索引擎 的链接也给屏蔽了。所以一般网站会允许搜索引擎的页面请求,但是 相应的页面请求这种请求方式也可能被攻击者给利用。(Referer 字 段会告诉服务器该网页是从哪个页面链接过来的)
使用 CSRF Token 进行验证,服务器向用户返回一个随机数 Token , 当网站再次发起请求时,在请求参数中加入服务器端返回的 token , 然后服务器对这个 token 进行验证。这种方法解决了使用 cookie 单一验证方式时,可能会被冒用的问题,但是这种方法存在一个缺点 就是,我们需要给网站中的所有请求都添加上这个 token,操作比较 繁琐。还有一个问题是一般不会只有一台网站服务器,如果请求经过 负载平衡转移到了其他的服务器,但是这个服务器的 session 中没 有保留这个 token 的话,就没有办法验证了。这种情况可以通过改 变 token 的构建方式来解决。
对 Cookie 进行双重验证,服务器在用户访问网站页面时,向请求域 名注入一个 Cookie,内容为随机字符串,然后当用户再次向服务器 发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数 中,然后服务器通过对 cookie 中的数据和参数中的数据进行比较, 来进行验证。使用这种方式是利用了攻击者只能利用 cookie,但是 不能访问获取 cookie 的特点。并且这种方法比 CSRF Token 的方法 更加方便,并且不涉及到分布式访问的问题。这种方法的缺点是如果 网站存在 XSS 漏洞的,那么这种方式会失效。同时这种方式不能做 到子域名的隔离。
在设置 cookie 属性的时候设置 Samesite ,限制 cookie 不能作为 被第三方使用,从而可以避免被攻击者利用。Samesite 一共有两种 模式,一种是严格模式,在严格模式下 cookie 在任何情况下都不可 能作为第三方 Cookie 使用,在宽松模式下,cookie 可以被请求是 GET 请求,且会发生页面跳转的请求所使用。
跨站脚本 (Cross-Site Scripting, XSS): 一种代码注入方式, 为了 与 CSS 区分所以被称作 XSS。早期常⻅于网络论坛, 起因是网站没 有对用户的输入进行严格的限制, 使得攻击者可以将脚本上传到帖 子让其他人浏览到有恶意脚本的⻚面, 其注入方式很简单包括但不 限于 JavaScript / CSS / Flash 等;
iframe 的滥用: iframe 中的内容是由第三方来提供的,默认情况下 他们不受控制,他们可以在 iframe 中运行JavaScirpt 脚本、Flash 插件、弹出对话框等等,这可能会破坏前端用户体验;
跨站点请求伪造(Cross-Site Request Forgeries,CSRF): 指攻击 者通过设置好的陷阱,强制对已完成认证的用户进行非预期的个人信 息或设定信息等某些状态更新,属于被动攻击
恶意第三方库: 无论是后端服务器应用还是前端应用开发,绝大多数 时候都是在借助开发框架和各种类库进行快速开发,一旦第三方库被 植入恶意代码很容易引起安全问题。
网络劫持分为两种:
(1)DNS 劫持: (输入京东被强制跳转到淘宝这就属于 dns 劫持)
DNS 强制解析: 通过修改运营商的本地 DNS 记录,来引导用户流量到 缓存服务器
302 跳转的方式: 通过监控网络出口的流量,分析判断哪些内容是可 以进行劫持处理的,再对劫持的内存发起 302 跳转的回复,引导用户 获取内容
(2)HTTP 劫持: (访问谷歌但是一直有贪玩蓝月的广告),由于 http 明文传输,运营商会修改你的 http 响应内容(即加广告)
(3)DNS 劫持由于涉嫌违法,已经被监管起来,现在很少会有 DNS 劫持,而http 劫持依然非常盛行,最有效的办法就是全站 HTTPS,将 HTTP 加密,这使得运营商无法获取明文,就无法劫持你的响应内容。
浏览器的渲染进程的线程总共有五种
(1)GUI 渲染线程
负责渲染浏览器页面,解析 HTML、CSS,构建 DOM 树、构建 CSSOM 树、 构建渲染树和绘制页面;当界面需要重绘或由于某种操作引发回流时, 该线程就会执行。
注意:GUI 渲染线程和 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到 JS 引擎空闲时 立即被执行。
(2)JS 引擎线程
JS 引擎线程也称为 JS 内核,负责处理 Javascript 脚本程序,解析 Javascript 脚本,运行代码;JS 引擎线程一直等待着任务队列中任 务的到来,然后加以处理,一个 Tab 页中无论什么时候都只有一个 JS 引擎线程在运行 JS 程序;
注意:GUI 渲染线程与 JS 引擎线程的互斥关系,所以如果 JS 执行的 时间过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。
(3)时间触发线程
时间触发线程属于浏览器而不是 JS 引擎,用来控制事件循环;当 JS 引擎执行代码块如 setTimeOut 时(也可是来自浏览器内核的其他线 程,如鼠标点击、AJAX 异步请求等),会将对应任务添加到事件触发 线程中;当对应的事件符合触发条件被触发时,该线程会把事件添加 到待处理队列的队尾,等待 JS 引擎的处理;
注意:由于 JS 的单线程关系,所以这些待处理队列中的事件都得排 队等待 JS 引擎处理(当 JS 引擎空闲时才会去执行);
(4)定时器触发进程
定时器触发进程即 setInterval 与 setTimeout 所在线程;浏览器定 时计数器并不是由 JS 引擎计数的,因为 JS 引擎是单线程的,如果处 于阻塞线程状态就会影响记计时的准确性;因此使用单独线程来计时 并触发定时器,计时完毕后,添加到事件队列中,等待 JS 引擎空闲 后执行,所以定时器中的任务在设定的时间点不一定能够准时执行, 定时器只是在指定时间点将任务添加到事件队列中;
注意:W3C 在 HTML 标准中规定,定时器的定时时间不能小于 4ms,如 果是小于 4ms,则默认为 4ms。
(5)异步 http 请求线程
XMLHttpRequest 连接后通过浏览器新开一个线程请求;
检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更 事件,将回调函数放入事件队列中,等待 JS 引擎空闲后执行;
孤儿进程:父进程退出了,而它的一个或多个进程还在运行,那这些 子进程都会成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所 收养,并由 init 进程对它们完成状态收集工作。
僵尸进程:子进程比父进程先结束,而父进程又没有释放子进程占用 的资源,那么子进程的进程描述符仍然保存在系统中,这种进程称之 为僵死进程。
实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。 因为标签页之间没有办法直接通信,因此我们可以找一个中介者,让标签页和中介者进行通信,然后让这个中介者来进行消息的转发。通 信方法如下:
使用 websocket 协议,因为 websocket 协议可以实现服务器推送, 所以服务器就可以用来当做这个中介者。标签页通过向服务器发送数 据,然后由服务器向其他标签页推送转发。
使用 ShareWorker 的方式,shareWorker 会在页面存在的生命周期 内创建一个唯一的线程,并且开启多个页面也只会使用同一个线程。 这个时候共享线程就可以充当中介者的角色。标签页间通过共享一个 线程,然后通过这个共享的线程来实现数据的交换。
使用localStorage 的方式,我们可以在一个标签页对 localStorage 的变化事件进行监听,然后当另一个标签页修改数据的时候,我们就 可以通过这个监听事件来获取到数据。这个时候 localStorage 对象 就是充当的中介者的角色。
使用 postMessage 方法,如果我们能够获得对应标签页的引用,就 可以使用 postMessage 方法,进行通信。
浏览器缓存的全过程:
浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源 文件,并缓存资源文件与 response header,以供下次加载时对比使 用;
下一次加载资源时,由于强制缓存优先级较高,先比较当前时间与上 一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果 浏览器不支持 HTTP1.1,则使用 expires 头判断是否过期;
如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向 服务器发送带有 If-None-Match 和 If-Modified-Since 的请求;
服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做 修改,Etag 值一致则没有修改,命中协商缓存,返回 304;如果不 一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200;
如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和 被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304; 不一致则返回新的 last-modified 和文件并返回 200;
很多网站的资源后面都加了版本号,这样做的目的是:每次升级了 JS 或 CSS 文件后,为了防止浏览器进行缓存,强制改变版本号,客户 端浏览器就会重新下载新的 JS 或 CSS 文件 ,以保证用户能够及时 获得网站的最新更新。
(1)强缓存
使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必 再向服务器发起请求。
强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性
(1)服务器通过在响应头中添加 Expires 属性,来指定资源的过期 时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发 送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存 在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用 户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的 结果。
(2)Expires 是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供 了对资源的缓存的更精确的控制。它有很多不同的值,
Cache-Control 可设置的字段:
public:设置了该字段值的资源表示可以被任何对象(包括:发送请 求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是 使用 max-age=来精确控制;
private:设置了该字段值的资源只能被用户浏览器缓存,不允许任 何代理服务器缓存。在实际开发当中,对于一些含有用户信息的 HTML, 通常都要设置这个字段值,避免代理服务器(CDN)缓存;
no-cache:设置了该字段需要先和服务端确认返回的资源是否发生了 变化,如果资源未发生变化,则直接使用缓存好的资源;
no-store:设置了该字段表示禁止任何缓存,每次都会向服务端发起 新的请求,拉取最新的资源;
max-age=:设置缓存的最大有效期,单位为秒;
s-maxage=:优先级高于 max-age=,仅适用于共享缓存(CDN),优先 级高于 max-age 或者 Expires 头;
max-stale[=]:设置了该字段表明客户端愿意接收已经过期的资源, 但是不能超过给定的时间限制。
一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方 式一起使用时,Cache-Control 的优先级要高于 Expires。
no-cache 和 no-store 很容易混淆:
no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也 就是说没有强缓存,但是会有协商缓存;
no-store 是指不使用任何缓存,每次请求都直接从服务器获取资源。
(2)协商缓存
如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如 果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会 发挥作用了。
上面已经说到了,命中协商缓存的条件有两个: max-age=xxx 过期了
值为 no-store
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发 生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如 果资源发生了修改,则返回修改后的资源。
协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 Etag 和 Last-Modified 属性。
(1)服务器通过在响应头中添加 Last-Modified 属性来指出资源最 后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加 一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性 来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做 了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本 地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种 方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精 确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,那么文件 已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中 的不准确。
(2)因为 Last-Modified 的这种可能发生的不准确性,http 中提 供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候, 在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符, 当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时, 浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值 就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值 来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改 变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式 更加精确。
当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更 高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多 个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
总结:
强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存 副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命 中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强 缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求 的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命 中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命 中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如 果协商缓存不命中,则浏览器返回最新的资源给浏览器。
点击刷新按钮或者按 F5:浏览器直接对本地的缓存文件过期,但是 会带上 If-Modifed-Since,If-None-Match,这就意味着服务器会对 文件检查新鲜度,返回结果可能是 304,也有可能是 200。
用户按 Ctrl+F5(强制刷新):浏览器不仅会对本地文件过期,而且 不会带上 If-Modifed-Since,If-None-Match,相当于之前从来没有 请求过,返回结果是 200。
地址栏回车: 浏览器发起请求,按照正常流程,本地检查是否过期, 然后服务器检查新鲜度,最后返回内容。
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。
首先解析收到的文档,根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。
然后对 CSS 进行解析,生成 CSSOM 规则树。
根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染 对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和 DOM 元素相对应,但这种对应关系不是一对一的,不可见的 DOM 元 素不会被插入渲染树。还有一些 DOM 元素对应几个可见对象,它们 一般是一些具有复杂结构的元素,无法用一个矩形来描述。
当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏 览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。 这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位 置和大小。通常这一行为也被称为“自动重排”。
布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用 UI 基础组件。
大致过程如图所示:
注意:这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会 尽可能早的将内容呈现到屏幕上,并不会等到所有的 html 都解析完 成之后再去构建和布局 render 树。它是解析完一部分内容就显示一 部分内容,同时,可能还在通过网络下载其余内容。
JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在 构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文 档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎 运行完毕,浏览器再从中断的地方恢复继续解析文档。也就是说,如 果想要首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是 都建议将 script 标签放在 body 标签底部的原因。当然在当下,并 不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。
cookies: 在 HTML5 标准前本地储存的主要方式,优点是兼容性好,
请求头自带 cookie方便,缺点是大小只有 4k,自动请求头加入cookie
浪费流量,每个 domain 限制 20 个 cookie,使用起来麻烦,需要自 行封装;
localStorage:HTML5 加入的以键值对(Key-Value)为标准的方式, 优点是操作方便,永久性储存(除非手动删除),大小为 5M,兼容 IE8+ ;
sessionStorage:与 localStorage 基本类似,区别是 sessionStorage 当⻚面关闭后会被清理,而且与 cookie、localStorage 不同,他不 能在所有同源窗口中共享,是会话级别的储存方式;
Web SQL:2010 年被 W3C 废弃的本地数据库数据存储方案,但是主流 浏览器(火狐除外)都已经有了相关的实现,web sql 类似于 SQLite, 是 真 正 意 义 上 的 关 系 型 数 据 库 ,用 s q l 进 行 操 作 ,当 我 们 用 J a v a S c r i p t 时要进行转换,较为繁琐;
IndexedDB:是被正式纳入HTML5 标准的数据库储存方案,它是 NoSQL 数据库,用键值对进行储存,可以进行快速读取操作,非常适合 web 场景,同时用JavaScript 进行操作会非常便。
事件是用户操作网页时发生的交互动作,比如 click/move, 事件除 了用户触发的动作外,还可以是文档加载,窗口滚动和大小调整。事 件被封装成一个 event 对象,包含了该事件发生时的所有相关信息 ( event 的属性)以及可以对事件进行的操作( event 的方法)。
事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现 代浏览器一共有三种事件模型:
DOM0 级事件模型,这种模型不会传播,所以没有事件流的概念,但 是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义
监听函数,也可以通过 js 属性来指定监听函数。所有浏览器都兼容 这种方式。直接在 dom 对象上注册事件名称,就是 DOM0 写法。
IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理 阶段和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听 事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则 执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个 监听函数,会按顺序依次执行。
DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一 个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播 到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有 则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模 型,事件绑定的函数是 addEventListener,其中第三个参数可以指 定事件是否在捕获阶段执行。
因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上 下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果 遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个 事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后, 再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列 可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕 后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有 就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行 完成后再去执行宏任务队列中的任务。
Event Loop 执行顺序如下所示: 首先执行同步代码,这属于宏任务
当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执 行
执行所有微任务 当执行完所有微任务后,如有必要会渲染页面 然后开始下一轮 Event Loop,执行宏任务中的异步代码