当前位置: 首页 > news >正文

婚庆网站建设总结怎么建立网站

婚庆网站建设总结,怎么建立网站,网站建设估价,自适应网站好建们1. 浏览器安全 1.1. 什么是 XSS 攻击#xff1f; 1.1. 1. 概念 XSS 攻击指的是跨站脚本攻击#xff0c;是一种代码注入攻击。攻击者通过在网站注入恶意脚本#xff0c;使之在用户的浏览器上运行#xff0c;从而盗取用户的信息如 cookie 等。 XSS 的本质是因为网站没有对… 1. 浏览器安全 1.1. 什么是 XSS 攻击 1.1. 1. 概念 XSS 攻击指的是跨站脚本攻击是一种代码注入攻击。攻击者通过在网站注入恶意脚本使之在用户的浏览器上运行从而盗取用户的信息如 cookie 等。 XSS 的本质是因为网站没有对恶意代码进行过滤与正常的代码混合在一起了浏览器没有办法分辨哪些脚本是可信的从而导致了恶意代码的执行。 攻击者可以通过这种攻击方式可以进行以下操作 1. 获取页面的数据如DOM、cookie、localStorage 2. DOS攻击发送合理请求占用服务器资源从而使用户无法访问服务器 3. 破坏页面结构 4. 流量劫持将链接指向某网站 1.1. 2. 攻击类型 XSS 可以分为存储型、反射型和 DOM 型 1. 存储型指的是恶意脚本会存储在目标服务器上当浏览器请求数据时脚本从服务器传回并执行 存储型 XSS 的攻击步骤 (1). 攻击者将恶意代码提交到目标网站的数据库中; (2). 用户打开目标网站时网站服务端将恶意代码从数据库取出拼接在 HTML 中返回给浏览器; (3). 用户浏览器接收到响应后解析执行混在其中的恶意代码也被执行; (4). 恶意代码窃取用户数据并发送到攻击者的网站或者冒充用户的行为调用目标网站接⼝执行攻击者指定的操作; 这种攻击常用于带有用户保存数据的网站功能如论坛发帖、商品评论、用户私信等。 2. 反射型指的是攻击者诱导用户访问一个带有恶意代码的 URL 后服务器端接收数据后处理然后把带有恶意代码的数据发送到浏览器端浏览器端解析这段带有 XSS 代码的数据后当做脚本执行最终完成 XSS 攻击; 反射型 XSS 的攻击步骤 (1). 攻击者构造出特殊的 URL其中包含恶意代码; (2). 用户打开带有恶意代码的 URL 时网站服务端将恶意代码从 URL 中取出拼接在 HTML 中返回给浏览器; (3). 用户浏览器接收到响应后解析执行混在其中的恶意代码也被执行; (4). 恶意代码窃取用户数据并发送到攻击者的网站或者冒充用户的行为调用目标网站接口执行攻击者指定的操作; 反射型 XSS 跟存储型 XSS 的区别是存储型 XSS 的恶意代码存在数据库里反射型 XSS 的恶意代码存在 URL 里。 反射型 XSS 漏洞常用于通过 URL 传递参数的功能如网站搜索、跳转等。 由于需要用户主动打开恶意的 URL 才能有效攻击者往往会结合多种手段诱导用户点击。 3. DOM 型指的通过修改页面的 DOM 节点形成的 XSS; DOM 型 XSS 的攻击步骤 (1). 攻击者构造出特殊的 URL其中包含恶意代码; (2). 用户打开带有恶意代码的 URL; (3). 用户浏览器接收到响应后解析执行前端 JavaScript 取出 URL 中的恶意代码并执行; (4). 恶意代码窃取用户数据并发送到攻击者的网站或者冒充用户的行为调用目标网站接口执行攻击者指定的操作; DOM 型 XSS 跟前两种 XSS 的区别DOM 型 XSS 攻击中取出和执行恶意代码由浏览器端完成属于前端JavaScript 本身的安全漏洞而其他两种 XSS 都属于服务端的安全漏洞。 1.2. 如何防御 XSS 攻击 可以看到XSS危害如此之大 那么在开发网站时就要做好防御措施具体措施如下 1. 可以从浏览器的执行来进行预防一种是使用纯前端的方式不用服务器端拼接后返回不使用服务端渲染。另一种是对需要插入到 HTML 中的代码做好充分的转义。对于 DOM 型的攻击主要是前端脚本的不可靠而造成的对于数据获取渲染和字符串拼接的时候应该对可能出现的恶意代码情况进行判断; 2. 使用 CSP CSP 的本质是建立一个白名单告诉浏览器哪些外部资源可以加载和执行从而防止恶意代码的注入攻击; CSP 指的是内容安全策略它的本质是建立一个白名单告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则如何拦截由浏览器自己来实现。 通常有两种方式来开启 CSP一种是设置 HTTP 首部中的 Content-Security-Policy一种是设置 meta 标签的方式。 3. 对一些敏感信息进行保护比如 cookie 使用 http-only使得脚本无法获取。也可以使用验证码避免脚本伪装成用户执行一些操作; 1.3. 什么是 CSRF 攻击 1.3.1. 概念 CSRF 攻击指的是跨站请求伪造攻击攻击者诱导用户进入一个第三方网站然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态那么攻击者就可以利用这个登录状态绕过后台的用户验证冒充用户向服务器执行一些操作。 CSRF 攻击的本质是利用 cookie 会在同源请求中携带发送给服务器的特点以此来实现用户的冒充。 1.3.2. 攻击类型 常见的 CSRF 攻击有三种 1. GET 类型的 CSRF 攻击比如在网站中的一个 img 标签里构建一个请求当用户打开这个网站的时候就会自动发起提交; 2. POST 类型的 CSRF 攻击比如构建一个表单然后隐藏它当用户进入页面时自动提交这个表单; 3. 链接类型的 CSRF 攻击比如在 a 标签的 href 属性里构建一个请求然后诱导用户去点击; 1.4. 如何防御 CSRF 攻击 CSRF 攻击可以使用以下方法来防护 1. 进行同源检测 服务器根据 http 请求头中 origin 或者 referer 信息来判断请求是否为允许访问的站点从而对请求进行过滤。当 origin 或者 referer 信息都不存在的时候直接阻止请求。这种方式的缺点是有些情况下 referer 可以被伪造同时还会把搜索引擎的链接也给屏蔽了。所以一般网站会允许搜索引擎的页面请求但是相应页面的这种请求方式也可能被攻击者给利用因为Referer 字段会告诉服务器该网页是从哪个页面链接过来的。 2. 使用 CSRF Token 进行验证 服务器向用户返回一个随机数 Token 当网站再次发起请求时在请求参数中加入服务器端返回的 token 然后服务器对这个 token 进行验证。这种方法解决了使用 cookie 单一验证方式时可能会被冒用的问题但是这种方法存在一个缺点就是我们需要给网站中的所有请求都添加上这个 token操作比较繁琐。还有一个问题是一般不会只有一台网站服务器如果请求经过负载平衡转移到了其他的服务器但是这个服务器的 session 中没有保留这个 token 的话就没有办法验证了这种情况可以通过改变 token 的构建方式来解决。 3. 对 Cookie 进行双重验证 服务器在用户访问网站页面时向请求域名注入一个Cookie内容为随机字符串然后当用户再次向服务器发送请求的时候从 cookie 中取出这个字符串添加到 URL 参数中然后服务器通过对 cookie 中的数据和参数中的数据进行比较来进行验证。使用这种方式是利用了攻击者只能利用 cookie但是不能访问获取 cookie 的特点。并且这种方法比 CSRF Token 的方法更加方便并且不涉及到分布式访问的问题。这种方法的缺点是如果网站存在 XSS 漏洞的那么这种方式会失效同时这种方式不能做到子域名的隔离。 4. 设置 cookie 属性的时候设置 Samesite 限制 cookie 不能作为被第三方使用从而可以避免被攻击者利用 Samesite 一共有两种模式一种是严格模式在严格模式下 cookie 在任何情况下都不可能作为第三方 Cookie 使用在宽松模式下cookie 可以被GET方式请求且会发生页面跳转的请求所使用。 1.5. 什么是中间人攻击如何防范中间人攻击 中间人 Man-in-the-middle attack, MITM是指攻击者与通讯的两端分别创建独立的联系, 并交换其所收到的数据, 使通讯的两端认为他们正在通过一个私密的连接与对方直接对话, 但事实上整个会话都被攻击者完全控制。在中间人攻击中攻击者可以拦截通讯双方的通话并插入新的内容。 攻击过程如下 1. 客户端发送请求到服务端请求被中间人截获; 2. 服务器向客户端发送公钥; 3. 中间人截获公钥保留在自己手上。然后自己生成一个伪造的公钥发给客户端; 4. 客户端收到伪造的公钥后生成加密hash值发给服务器; 5. 中间人获得加密hash值用自己的私钥解密获得真秘钥同时生成假的加密hash值发给服务器; 6. 服务器用私钥解密获得假密钥然后加密数据传输给客户端; 1.6. 有哪些可能引起前端安全的问题? 1. 跨站脚本 XSS 一种代码注入的式, 为了与 CSS 区分所以被称作 XSS。早期常用于网络论坛起因是网站没有对用户的输入进行严格的限制 使得攻击者可以将脚本上传到帖吧让其他人浏览到有恶意脚本的内容其注的方式很简单包括但不限于 JavaScript / CSS / Flash 等 2. iframe的滥用 iframe中的内容是由第三方来提供的默认情况下他们不受控制他们可以在iframe中运行JavaScirpt脚本、Flash插件、弹出对话框等等这可能会破坏前端用户体验 3. 跨站点请求伪造CSRF指攻击者通过设置好的陷阱强制对已完成认证的用户进行预期的个人信息或设定信息等某些状态更新属于被动攻击; 4. 恶意第三方库无论是后端服务器应用还是前端应用开发绝大多数时候都是在借助开发框架和各种类库进行快速开发一旦第三方库被植入恶意代码很容易引起安全问题; 1.7. 网络劫持有哪几种如何防范 网络劫持分为两种: 1.7.1. DNS劫持 输入京东被强制跳转到淘宝这就属于dns劫持主要有以下这两种方式 1. DNS强制解析。通过修改运营商的本地DNS记录来引导用户流量到缓存服务器; 2. 302跳转的方式。通过监控网络出口的流量分析判断哪些内容是可以进行劫持处理的,再对劫持的内存发起302跳转的回复引导用户获取内容; 1.7.2. HTTP劫持 访问谷歌但是一直有贪玩蓝月的广告由于http明文传输运营商会修改你的http响应内容比如加入告。 DNS劫持由于涉嫌违法已经被监管起来现在很少会有DNS劫持而http劫持依然依常盛行最有效的办法就是全站HTTPS将HTTP加密这使得运营商无法获取明文就无法劫持你的响应内容。 2. 进程与线程 2.1. 进程与线程的概念 从本质上说进程和线程都是 CPU 工作时间片的一个描述 1. 进程描述了 CPU 在运行指令及加载和保存上下文所需的时间放在应用上来说就代表了一个程序; 2. 线程是进程中的更小单位描述了执行一段指令所需的时间; 进程是资源分配的最小单位线程是CPU调度的最小单位。 一个进程就是一个程序的运行实例。详细解释就是启动一个程序的时候操作系统会为该程序创建一块内存用来存放代码、运行中的数据和一个执行任务的主线程我们把这样的一个运行环境叫进程。进程是运行在虚拟内存上的虚拟内存是用来解决用户对硬件资源的无限需求和有限的硬件资源之间的矛盾的。从操作系统角度来看虚拟内存即交换文件从处理器角度看虚拟内存即虚拟地址空间。 如果程序很多时内存可能会不够操作系统为每个进程提供一套独立的虚拟地址空间从而使得同一块物理内存在不同的进程中可以对应到不同或相同的虚拟地址变相的增加了程序可以使用的内存。 进程和线程之间的关系有以下四个特点 1. 进程中的任意一线程执行出错都会导致整个进程的崩溃; 2. 线程之间共享进程中的数据; 3. 当一个进程关闭之后操作系统会回收进程所占用的内存 当一个进程退出时操作系统会回收该进程所申请的所有资源即使其中任意线程因为操作不当导致内存泄漏当进程退出时这些内存也会被正确回收; 4. 进程之间的内容相互隔离。 进程隔离就是为了使操作系统中的进程互不干扰每一个进程只能访问自己占有的数据也就避免出现进程 A 写入数据到进程 B 的情况。正是因为进程之间的数据是严格隔离的所以一个进程如果崩溃了或者挂起了是不会影响到其他进程的。如果进程之间需要进行数据的通信这时候就需要使用用于进程间通信的机制了。 Chrome浏览器的架构图 从图中可以看出最新的 Chrome 浏览器包括 1. 一个浏览器主进程; 2. 一个 GPU 进程; 3. 一个网络进程; 4. 多个渲染进程; 5. 多个插件进程; 这些进程的功能 1. 浏览器进程主要负责界面显示、用户交互、子进程管理同时提供存储等功能; 2. 渲染进程核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中默认情况下Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑渲染进程都是运行在沙箱模式下; 3. GPU 进程其实 GPU 的使用初衷是为了实现 3D CSS 的效果只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制这使得 GPU 成为浏览器普遍的需求。最后Chrome 在其多进程架构上也引入了 GPU 进程; 4. 网络进程主要负责页面的网络资源加载之前是作为一个模块运行在浏览器进程里面的直至最近才独立出来成为一个单独的进程; 5. 插件进程主要是负责插件的运行因插件易崩溃所以需要通过插件进程来隔离以保证插件进程崩溃不会对浏览器和页面造成影响; 所以打开一个网页最少需要四个进程1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程。如果打开的页面有运行插件的话还需要再加上 1 个插件进程。 虽然多进程模型提升了浏览器的稳定性、流畅性和安全性但同样不可避免地带来了一些问题 1. 更高的资源占用因为每个进程都会包含公共基础结构的副本如 JavaScript 运行环境这就意味着浏览器会消耗更多的内存资源; 2. 更复杂的体系架构浏览器各模块之间耦合性高、扩展性差等问题会导致现在的架构已经很难适应新的需求了; 2.2. 进程和线程的区别 1. 进程可以看做独立应用线程不能; 2. 资源进程是CPU资源分配的最小单位是能拥有资源和独立运行的最小单位线程是CPU调度的最小单位线程是建立在进程的基础上的一次程序运行单位一个进程中可以有多个线程; 3. 通信方面线程间可以通过直接共享同一进程中的资源而进程通信需要借助进程间通信; 4. 调度进程切换比线程切换的开销要大。线程是CPU调度的基本单位线程的切换不会引起进程切换但某个进程中的线程切换到另一个进程中的线程时会引起进程切换; 5. 系统开销由于创建或撤销进程时系统都要为之分配或回收资源如内存、I/O 等其开销远大于创建或撤销线程时的开销。同理在进行进程切换时涉及当前执行进程 CPU 环境还有各种各样状态的保存及新调度进程状态的设置而线程切换时只需保存和设置少量寄存器内容开销较小; 2.3. 浏览器渲染进程的线程有哪些 浏览器的渲染进程的线程总共有五种 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请求线程 (1). XMLHttpRequest连接后通过浏览器新开一个线程请求 (2). 检测到状态变更时如果设置有回调函数异步线程就产生状态变更事件将回调函数放入事件队列中等待JS引擎空闲后执行 2.4. 进程之间的通信方式 1. 管道通信 管道是一种最基本的进程间通信机制。管道就是操作系统在内核中开辟的一段缓冲区进程1可以将需要交互的数据拷贝到这段缓冲区进程2就可以读取了。 管道的特点 (1). 只能单向通信; (2). 只能血缘关系的进程进行通信; (3). 依赖于文件系统; (4). 生命周期随进程; (5). 面向字节流的服务; (6). 管道内部提供了同步机制; 2. 消息队列通信 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型接收进程可以独立地接收含有不同类型的数据结构。可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样每个数据块都有一个最大长度的限制。 使用消息队列进行进程间通信可能会收到数据块最大长度的限制约束等这也是这种通信方式的缺点。如果频繁的发生进程间的通信行为那么进程需要频繁地读取队列中的数据到内存相当于间接地从一个进程拷贝到另一个进程这需要花费时间。 3. 信号量通信 共享内存最大的问题就是多进程竞争内存的问题就像类似于线程安全问题。我们可以使用信号量来解决这个问题。信号量的本质就是一个计数器用来实现进程之间的互斥与同步。例如信号量的初始值是 1然后 a 进程来访问内存1的时候我们就把信号量的值设为 0然后进程b 也要来访问内存1的时候看到信号量的值为 0 就知道已经有进程在访问内存1了这个时候进程 b 就会访问不了内存1。所以说信号量也是进程之间的一种通信方式。 4. 信号通信 信号是Unix系统中使用的最古老的进程间通信的方法之一。操作系统通过信号来通知进程系统中发生了某种预先规定好的事件即一组事件中的一个事件它也是用户进程之间通信和同步的一种原始机制。 5. 共享内存通信 共享内存就是映射一段能被其他进程所访问的内存这段共享内存由一个进程创建但多个进程都可以访问使多个进程可以访问同一块内存空间。共享内存是最快的 IPC 方式它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制如信号量配合使用来实现进程间的同步和通信。 6. 套接字通信 上面说的共享内存、管道、信号量、消息队列他们都是多个进程在一台主机之间的通信那两个相隔几千里的进程能够进行通信吗答是必须的这个时候 Socket 这家伙就派上用场了例如我们平时通过浏览器发起一个 http 请求然后服务器给你返回对应的数据这种就是采用 Socket 的通信方式了。 2.5. 僵尸进程和孤儿进程是什么 1. 孤儿进程 父进程退出了而它的一个或多个子进程还在运行那这些子进程都会成为孤儿进程。孤儿进程将被init进程进程号为1所收养并由init进程对它们完成状态收集工作。 2. 僵尸进程 子进程比父进程先结束而父进程又没有释放子进程占用的资源那么子进程的进程描述符仍然保存在系统中这种进程称之为僵尸进程。 2.6. 死锁产生的原因 如何解决死锁的问题 所谓死锁是指多个进程在运行过程中因争夺资源而造成的一种僵局当进程处于这种僵持状态时若无外力作用它们都将无法再向前推进。 系统中的资源可以分为两类 1. 可剥夺资源 是指某进程在获得这类资源后该资源可以再被其他进程或系统剥夺CPU和主存均属于可剥夺性资源。 2. 不可剥夺资源 当系统把这类资源分配给某进程后再不能强行收回只能在进程用完后自行释放如磁带机、打印机等。 产生死锁的原因 1. 竞争资源 (1). 产生死锁中的竞争资源之一指的是竞争不可剥夺资源。例如系统中只有一台打印机可供进程P1使用假定P1已占用了打印机若P2继续要求打印机打印将阻塞; (2). 产生死锁中的竞争资源另外一种资源指的是竞争临时资源临时资源包括硬件中断、信号、消息、缓冲区内的消息等等通常消息通信顺序进行不当则会产生死锁; 2. 进程间推进顺序非法 若P1保持了资源R1P2保持了资源R2系统处于不安全状态因为这两个进程再向前推进便可能发生死锁。例如当运行到P1请求R2时将因R2已被P2占用而阻塞当运行到P2请求R1时也将因R1已被P1占用而阻塞于是发生进程死锁。 产生死锁的必要条件 1. 互斥条件进程要求对所分配的资源进行排它性控制即在一段时间内某资源仅为一进程所占用; 2. 请求和保持条件当进程因请求资源而阻塞时对已获得的资源保持不放; 3. 不剥夺条件进程已获得的资源在未使用完之前不能剥夺只能在使用完时由自己释放; 4. 环路等待条件在发生死锁时必然存在一个进程即资源的环形链; 预防死锁的方法 1. 资源一次性分配一次性分配所有资源这样就不会再有请求了即破坏请求条件; 2. 只要有一个资源得不到分配也不给这个进程分配其他的资源即破坏请保持条件; 3. 可剥夺资源即当某进程获得了部分资源但得不到其它资源则释放已占有的资源即破坏不可剥夺条件; 4. 资源有序分配法系统给每类资源赋予一个编号每一个进程按编号递增的顺序请求资源释放则相反即破坏环路等待条件; 2.7. 如何实现浏览器内多个标签页之间的通信? 实现多个标签页之间的通信本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信因此我们可以找一个中介者让标签页和中介者进行通信然后让这个中介者来进行消息的转发。通信方法如下 1. 使用 websocket 协议因为 websocket 协议可以实现服务器推送所以服务器就可以用来当做这个中介者。标签页通过向服务器发送数据然后由服务器向其他标签页推送转发; 2. 使用 ShareWorker 的方式shareWorker 会在页面存在的生命周期内创建一个唯一的线程并且开启多个页面也只会使用同一个线程。这个时候共享线程就可以充当中介者的角色。标签页间通过共享一个线程然后通过这个共享的线程来实现数据的交换; 3. 使用 localStorage 的方式我们可以在一个标签页对 localStorage 的变化事件进行监听然后当另一个标签页修改数据的时候我们就可以通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色; 4. 使用 postMessage 方法如果我们能够获得对应标签页的引用就可以使用postMessage 方法进行通信; 2.8. 对Service Worker的理解 Service Worker 是运行在浏览器背后的独立线程一般可以用来实现缓存功能。使用 Service Worker的话传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截所以必须使用 HTTPS 协议来保障安全。 Service Worker 实现缓存功能一般分为三个步骤首先需要先注册 Service Worker然后监听到 install 事件以后就可以缓存需要的文件那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存存在缓存的话就可以直接读取缓存文件否则就去请求数据。以下是这个步骤的实现 1. 注册服务 // index.js if (navigator.serviceWorker) {navigator.serviceWorker.register(sw.js).then(function (registration) {console.log(service worker 注册成功)}).catch(function (err) {console.log(servcie worker 注册失败)}) } 2. 监听事件并拦接请求 // sw.js // 监听 install 事件回调中缓存所需文件 self.addEventListener(install, e {e.waitUntil(caches.open(my-cache).then(function (cache) {return cache.addAll([./index.html, ./index.js])})) }) // 拦截所有请求事件 // 如果缓存中已经有请求的数据就直接用缓存否则去请求数据 self.addEventListener(fetch, e {e.respondWith(caches.match(e.request).then(function (response) {if (response) {return response}console.log(fetch source)})) }) 打开页面可以在开发者工具中的 Application 看到 Service Worker 已经启动了 在 Cache 中也可以发现所需的文件已被缓存 3. 浏览器缓存 3.1. 对浏览器的缓存机制的理解 浏览器缓存的全过程 1. 浏览器第一次加载资源服务器返回 200浏览器从服务器下载资源文件并缓存资源文件与 response header以供下次加载时对比使用 2. 下一次加载资源时由于强制缓存优先级较高先比较当前时间与上一次返回 200 时的时间差如果没有超过 cache-control 设置的 max-age则没有过期并命中强缓存直接从本地读取资源。如果浏览器不支持HTTP1.1则使用 expires 头判断是否过期 3.  如果资源已过期则表明强制缓存没有被命中则开始协商缓存向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求 4. 服务器收到请求后优先根据 Etag 的值判断被请求的文件有没有做修改Etag 值一致则没有修改命中协商缓存返回 304如果不一致则有改动直接返回新的资源文件带上新的 Etag 值并返回 200 5. 如果服务器收到的请求没有 Etag 值则将 If-Modified-Since 和被请求文件的最后修改时间做比对一致则命中协商缓存返回 304不一致则返回新的 last-modified 和文件并返回 200 很多网站的资源后面都加了版本号这样做的目的是每次升级了 JS 或 CSS 文件后为了防止浏览器进行缓存强制改变版本号客户端浏览器就会重新下载新的 JS 或 CSS 文件 以保证用户能够及时获得网站的最新更新。 3.2. 浏览器资源缓存的位置有哪些 资源缓存的位置一共有 3 种按优先级从高到低分别是 1. Service WorkerService Worker 运行在 JavaScript 主线程之外虽然由于脱离了浏览器窗体无法直接访问 DOM但是它可以完成离线缓存、消息推送、网络代理等功能。它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存并且缓存是持续性的。当 Service Worker 没有命中缓存的时候需要去调用 fetch 函数获取 数据。也就是说如果没有在 Service Worker 命中缓存会根据缓存查找优先级去查找数据。但是不管是从 Memory Cache 中还是从网络请求中获取的数据浏览器都会显示是从 Service Worker 中获取的内容; 2. Memory Cache Memory Cache 就是内存缓存它的效率最快但是内存缓存虽然读取高效可是缓存持续性很短会随着进程的释放而释放。一旦我们关闭 Tab 页面内存中的缓存也就被释放了 ; 3. Disk Cache Disk Cache 也就是存储在硬盘中的缓存读取速度慢点但是什么都能存储到磁盘中比之 Memory Cache 胜在容量和存储时效性上。在所有浏览器缓存中Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存哪些资源可以不请求直接使用哪些资源已经过期需要重新请求。并且即使在跨站点的情况下相同地址的资源一旦被硬盘缓存下来就不会再次去请求数; Disk Cache Push Cache 是 HTTP/2 中的内容当以上三种缓存都没有命中时它才会被使用。并且缓存时间也很短暂只在会话Session中存在一旦会话结束就被释放。其具有以下特点 1. 所有的资源都能被推送但是 Edge 和 Safari 浏览器兼容性不怎么好; 2. 可以推送 no-cache 和 no-store 的资源; 3. 一旦连接被关闭Push Cache 就被释放; 4. 多个页面可以使用相同的 HTTP/2 连接也就是说能使用同样的缓存; 5. Push Cache 中的存只能被使用一次; 6. 浏览器可以拒绝接受已经存在的资源推送; 7. 可以给其他域名推送资源; 3.3. 协商缓存和强缓存的区别 3.3.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 是指不使用任何缓存每次请求都直接从服务器获取资源。 3.3.2. 协商缓存 如果命中强制缓存我们无需发起新的请求直接使用缓存内容如果没有命中强制缓存如果设置了协商缓存这个时候协商缓存就会发挥作用了。 上面已经说到了命中协商缓存的条件有两个 max-agexxx 过期了 值为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 属性。 3.3.3. 强缓存与协商缓存总结 强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时都会向服务器发送请求来获取资源。在实际的缓存机制中强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断强缓存是否命中如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求使用协商缓存如果协商缓存命中的话则服务器不返回资源浏览器直接使用本地资源的副本如果协商缓存不命中则浏览器返回最新的资源给浏览器。 3.4. 为什么需要浏览器缓存 对于浏览器的缓存主要针对的是前端的静态资源最好的效果就是在发起请求之后拉取相应的静态资源并保存在本地。如果服务器的静态资源没有更新那么在下次请求的时候就直接从本地读取即可如果服务器的静态资源已经更新那么我们再次请求的时候就到服务器拉取新的资源并保存在本地。这样就大大的减少了请求的次数提高了网站的性能。这就要用到浏览器的缓存策略了。 所谓的浏览器缓存指的是浏览器将用户请求过的静态资源存储到电脑本地磁盘中当浏览器再次访问时就可以直接从本地加载不需要再去服务端请求了。 使用浏览器缓存有以下优点 1. 减少了服务器的负担提高了网站的性能 2. 加快了客户端网页的加载速度 3. 减少了多余网络数据传输 3.5. 点击刷新按钮或者按 F5、按 CtrlF5 强制刷新、地址栏回车有什么区别 1. 点击刷新按钮或者按 F5 浏览器直接对本地的缓存文件过期但是会带上If-Modifed-SinceIf-None-Match这就意味着服务器会对文件检查新鲜度返回结果可能是 304也有可能是 200 2. 用户按 CtrlF5强制刷新 浏览器不仅会对本地文件过期而且不会带上 If-Modifed-SinceIf-None-Match相当于之前从来没有请求过返回结果是 200 3. 地址栏回车 浏览器发起请求按照正常流程本地检查是否过期然后服务器检查新鲜度最后返回内容 4.  浏览器组成 4.1. 对浏览器的理解 浏览器的主要功能是将用户选择的 web 资源呈现出来它需要从服务器请求资源并将其显示在浏览器窗口中资源的格式通常是 HTML也包括 PDF、image 及其他格式。用户用 URIUniform Resource Identifier 统一资源标识符来指定所请求资源的位置。 HTML 和 CSS 规范中规定了浏览器解释 html 文档的方式由 W3C 组织对这些规范进行维护W3C 是负责制定 web 标准的组织。但是浏览器厂商纷纷开发自己的扩展对规范的遵循并不完善这为 web 开发者带来了严重的兼容性问题。 浏览器可以分为两部分shell 和 内核。其中 shell 的种类相对比较多内核则比较少。也有一些浏览器并不区分外壳和内核。从 Mozilla 将 Gecko 独立出来后才有了外壳和内核的明确划分。 1. shell 是指浏览器的外壳例如菜单工具栏等。主要是提供给用户界面操作参数设置等等它是调用内核来实现各种功能的 2. 内核是浏览器的核心。内核是基于标记语言显示内容的程序或模块 4.2. 对浏览器内核的理解 浏览器内核主要分成两部分 1. 渲染引擎它的职责就是渲染即在浏览器窗口中显示所请求的内容。默认情况下渲染引擎可以显示 html、xml 文档及图片它也可以借助插件显示其他类型数据例如使用 PDF 阅读器插件可以显示 PDF 格式 2. JS 引擎解析和执行 JavaScript 来实现网页的动态效果 4.3. 常见的浏览器内核比较 1. Trident 这种浏览器内核是 IE 浏览器用的内核因为在早期 IE 占有大量的市场份额所以这种内核比较流行以前有很多网页也是根据这个内核的标准来编写的但是实际上这个内核对真正的网页标准支持不是很好。但是由于 IE 的高市场占有率微软也很长时间没有更新 Trident 内核就导致了 Trident 内核和 W3C 标准脱节。还有就是 Trident 内核的大量 Bug 等安全问题没有得到解决加上一些专家学者公开自己认为 IE 浏览器不安全的观点使很多用户开始转向其他浏览器 2. Gecko 这是 Firefox 和 Flock 所采用的内核这个内核的优点就是功能强大、丰富可以支持很多复杂网页效果和浏览器扩展接口但是代价是也显而易见就是要消耗很多的资源比如内存 3. Presto Opera 曾经采用的就是 Presto 内核Presto 内核被称为公认的浏览网页速度最快的内核这得益于它在开发时的天生优势在处理 JS 脚本等脚本语言时会比其他的内核快3倍左右缺点就是为了达到很快的速度而丢掉了一部分网页兼容性 4. Webkit Webkit 是 Safari 采用的内核它的优点就是网页浏览速度较快虽然不及 Presto 但是也胜于 Gecko 和 Trident缺点是对于网页代码的容错性不高也就是说对网页代码的兼容性较低会使一些编写不标准的网页无法正确显示。WebKit 前身是 KDE 小组的 KHTML 引擎可以说 WebKit 是 KHTML 的一个开源的分支 5. Blink 谷歌在 Chromium Blog 上发表博客称将与苹果的开源浏览器核心 Webkit 分道扬镳在 Chromium 项目中研发 Blink 渲染引擎即浏览器核心内置于 Chrome 浏览器之中。其实 Blink 引擎就是 Webkit 的一个分支就像 webkit 是KHTML 的分支一样。Blink 引擎现在是谷歌公司与 Opera Software 共同研发上面提到过的Opera 弃用了自己的 Presto 内核加入 Google 阵营跟随谷歌一起研发 Blink 4.4. 常见浏览器所用内核 1. IE 浏览器内核Trident 内核也是俗称的 IE 内核 2. Chrome 浏览器内核统称为 Chromium 内核或 Chrome 内核以前是 Webkit 内核现在是 Blink内核 3.  Firefox 浏览器内核Gecko 内核俗称 Firefox 内核 4. Safari 浏览器内核Webkit 内核 5. Opera 浏览器内核最初是自己的 Presto 内核后来加入谷歌大军从 Webkit 又到了 Blink 内核 6. 60浏览器、猎豹浏览器内核IE Chrome 双内核 7. 搜狗、遨游、QQ 浏览器内核Trident兼容模式 Webkit高速模式 8. 百度浏览器、世界之窗内核IE 内核 9. 2345浏览器内核好像以前是 IE 内核现在也是 IE Chrome 双内核了 10. UC 浏览器内核这个众口不一UC 说是他们自己研发的 U3 内核但好像还是基于 Webkit 和 Trident 还有说是基于火狐内核 4.5. 浏览器的主要组成部分 1. 用户界面包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的数据外其他显示的各个部分都属于用户界面 2. 浏览器引擎 在用户界面和呈现引擎之间传送指令 3. 呈现引擎负责显示请求的内容。如果请求的内容是 HTML它就负责解析 HTML 和 CSS 内容并将解析后的内容显示在屏幕上 4. 网络用于网络调用例如 HTTP 请求。其接口与平台有关并为所有平台提供底层实现 5. 用户界面后端 用于绘制基本的窗口零部件例如组合框和窗口。其公开了与平台有关的通知接口并在底层使用操作系统的用户界面方法 6. JavaScript 解释器 用于解析和执行JavaScript 代码 7. 数据存储 这是持久层浏览器需要在硬盘上保存各种数据例如 Cookie。新的 HTML 规范HTML5定义了“网络数据库”这是一个完整但是轻便的浏览器内数据库 5. 浏览器渲染原理 5.1. 浏览器的渲染过程 浏览器渲染主要有以下步骤 1. 首先解析收到的文档根据文档定义构建一棵 DOM 树DOM 树是由 DOM 元素及属性节点组成的 2. 然后对 CSS 进行解析生成 CSSOM 规则树 3. 根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染对象渲染对象是一个包含有颜色和大小等属性的矩形渲染对象和 DOM 元素相对应但这种对应关系不是一对一的不可见的 DOM 元素不会被插入渲染树。还有一些 DOM元素对应几个可见对象它们一般是一些具有复杂结构的元素无法用一个矩形来描述 4. 当渲染对象被创建并添加到树中它们并没有位置和大小所以当浏览器生成渲染树以后就会根据渲染树来进行布局也可以叫做回流。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小通常这一行为也被称为“自动重排” 5. 布局阶段结束后是绘制阶段遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上绘制使用 UI 基础组件 大致过程如图所示 注意 这个过程是逐步完成的为了更好的用户体验渲染引擎将会尽可能早的将内容呈现到屏幕上并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容同时可能还在通过网络下载其余内容 5.2. 浏览器渲染优化 1. 针对JavaScript JavaScript既会阻塞HTML的解析也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变来进行优化 (1). 尽量将JavaScript文件放在body的最后 (2).  body中间尽量不要写script标签 (3). script标签的引入资源方式有三种有一种就是我们常用的直接引入还有两种就是使用 async 属性和 defer 属性来异步引入两者都是去异步加载外部的JS文件不会阻塞DOM的解析尽量使用异步加载这三者的区别如下 A. script 立即停止页面渲染去加载资源文件当资源加载完毕后立即执行js代码js代码执行完毕后继续渲染页面 B. async 是在下载完成之后立即异步加载加载好后立即执行多个带async属性的标签不能保证加载的顺序 C. defer 是在下载完成之后立即异步加载。加载好后如果 DOM 树还没构建好则先等 DOM 树解析好再执行如果DOM树已经准备好则立即执行。多个带defer属性的标签按照顺序执行 2. 针对CSS使用CSS有三种方式使用link、import、内联样式其中link和import都是导入外部样式。它们之间的区别 (1). link浏览器会派发一个新的线程HTTP线程去加载资源文件与此同时GUI渲染线程会继续向下渲染代码 (2). importGUI渲染线程会暂时停止渲染去服务器加载资源文件资源文件没有返回之前不会继续渲染阻碍浏览器渲染 (3). styleGUI直接渲染 外部样式如果长时间没有加载完毕浏览器为了用户体验会使用浏览器会默认样式确保首次渲染的速度。所以CSS一般写在headr中让浏览器尽快发送请求去获取CSS样式。 所以在开发过程中导入外部样式使用link而不用import。如果CSS少尽可能采用内嵌样式直接写在style标签中。 3. 针对DOM树、CSSOM树可以通过以下几种方式来减少渲染的时间 (1). HTML文件的代码层级尽量不要太深 (2). 使用语义化的标签来避免不标准语义化的特殊处理 (3). 减少CSS代码的层级因为选择器是从左向右进行解析的 4. 减少回流与重绘 (1). 操作DOM时尽量在低层级的DOM节点进行操作 (2). 不要使用table布局 一个小的改动可能会使整个table进行重新布局 (3). 使用CSS的表达式 (4). 不要频繁操作元素的样式对于静态页面可以修改类名而不是样式 (5). 使用absolute或者fixed使元素脱离文档流这样他们发生变化就不会影响其他元素 (6). 避免频繁操作DOM可以创建一个文档片段documentFragment在它上面应用所有DOM操作最后再把它添加到文档中 (7). 将元素先设置display: none操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘 (8). 将DOM的多个读操作或者写操作放在一起而不是读写操作穿插着写这得益于浏览器的渲染队列机制 浏览器针对页面的回流与重绘进行了自身的优化渲染队列。 浏览器会将所有的回流、重绘的操作放在一个队列中当队列中的操作到了一定的数量或者到了一定的时间间隔浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。 将多个读操作或者写操作放在一起就会等所有的读操作进入队列之后执行这样原本应该是触发多次回流变成了只触发一次回流。 5.3. 渲染过程中遇到 JS 文件如何处理 JavaScript 的加载、解析与执行会阻塞文档的解析也就是说在构建 DOM 时HTML 解析器若遇到了 JavaScript那么它会暂停文档的解析将控制权移交给 JavaScript 引擎等 JavaScript 引擎运行完毕浏览器再从中断的地方恢复继续解析文档。也就是说如果想要首屏渲染的越快就越不应该在首屏就加载 JS 文件这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下并不是说 script 标签必须放在底部因为你可以给 script 标签添加 defer 或者 async 属性。 5.4. 什么是文档的预解析 Webkit 和 Firefox 都做了这个优化当执行 JavaScript 脚本时另一个线程解析剩下的文档并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是预解析并不改变 DOM 树它将这个工作留给主解析过程自己只解析外部资源的引用比如外部脚本、样式表及图片。 5.5. CSS 如何阻塞文档解析 理论上既然样式表不改变 DOM 树也就没有必要停下文档的解析等待它们。然而存在一个问题JavaScript 脚本执行时可能在文档的解析过程中请求样式信息如果样式还没有加载和解析脚本将得到错误的值显然这将会导致很多问题。所以如果浏览器尚未完成 CSSOM 的下载和构建而我们却想在此时运行脚本那么浏览器将延迟 JavaScript 脚本执行和文档的解析直至其完成 CSSOM 的下载和构建。也就是说在这种情况下浏览器会先下载和构建 CSSOM然后再执行 JavaScript最后再继续文档的解析。 5.6. 如何优化关键渲染路径 为尽快完成首次渲染我们需要最大限度减小以下三种可变因素 1. 关键资源的数量 2. 关键路径长度 3. 关键字节的数量 关键资源是可能阻止网页首次渲染的资源。这些资源越少浏览器的工作量就越小对 CPU 以及其他资源的占用也就越少。同样关键路径长度受所有关键资源与其字节大小之间依赖关系图的影响某些资源只能在上一资源处理完毕之后才能开始下载并且资源越大下载所需的往返次数就越多。最后浏览器需要下载的关键字节越少处理内容并让其出现在屏幕上的速度就越快。要减少字节数我们可以减少资源数将它们删除或设为非关键资源此外还要压缩和优化各项资源确保最大限度减小传送大小。 优化关键渲染路径的常规步骤如下 1. 对关键路径进行分析和特性描述资源数、字节数、长度 2. 最大限度减少关键资源的数量删除它们延迟它们的下载将它们标记为异步等 3. 优化关键字节数以缩短下载时间往返次数 4. 优化其余关键资源的加载顺序尽早下载所有关键资产以缩短关键路径长度 5.7. 什么情况会阻塞渲染 首先渲染的前提是生成渲染树所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快你越应该降低一开始需要渲染的文件大小并且扁平层级优化选择器。然后当浏览器在解析到 script 标签时会暂停构建 DOM完成后才会从暂停的地方重新开始。也就是说如果你想首屏渲染的越快就越不应该在首屏就加载 JS 文件这也是都建议将 script 标签放在 body 标签底部的原因。 当然在当下并不是说 script 标签必须放在底部因为你可以给 script 标签添加 defer 或者 async 属性。当 script 标签加上 defer 属性以后表示该 JS 文件会并行下载但是会放到 HTML 解析完成后顺序执行所以对于这种情况你可以把 script 标签放在任意位置。对于没有任何依赖的 JS 文件可以加上 async 属性表示 JS 文件下载和解析不会阻塞渲染。 6. 浏览器本地存储 6.1. 浏览器本地存储方式及使用场景 6.1.1. Cookie Cookie是最早被提出来的本地存储方式在此之前服务端是无法判断网络中的两个请求是否是同一用户发起的为解决这个问题Cookie就出现了。Cookie的大小只有4kb它是一种纯文本文件每次发起HTTP请求都会携带Cookie。 Cookie的特性 1. Cookie一旦创建成功名称就无法修改 2. Cookie是无法跨域名的也就是说a域名和b域名下的cookie是无法共享的这也是由Cookie的隐私安全性决定的这样就能够阻止非法获取其他网站的Cookie 3. 每个域名下Cookie的数量不能超过20个每个Cookie的大小不能超过4kb 4. 有安全问题如果Cookie被拦截了那就可获得session的所有信息即使加密也于事无补无需知道cookie的意义只要转发cookie就能达到目的 5. Cookie在请求一个新的页面的时候都会被发送过去 如果需要域名之间跨域共享Cookie有两种方法 1. 使用Nginx反向代理 2. 在一个站点登陆之后往其他网站写Cookie。服务端的Session存储到一个节点Cookie存储sessionId 6.1.2. LocalStorage LocalStorage是HTML5新引入的特性由于有的时候我们存储的信息较大Cookie就不能满足我们的需求这时候LocalStorage就派上用场了。 LocalStorage的优点 1. 在大小方面LocalStorage的大小一般为5MB可以储存更多的信息 2. LocalStorage是持久储存并不会随着页面的关闭而消失除非主动清理不然会永久存在 3. 仅储存在本地不像Cookie那样每次HTTP请求都会被携带 LocalStorage的缺点 1. 存在浏览器兼容问题IE8以下版本的浏览器不支持 2. 如果浏览器设置为隐私模式那我们将无法读取到LocalStorage 3. LocalStorage受到同源策略的限制即端口、协议、主机地址有任何一个不相同都不会访问 LocalStorage的常用API // 保存数据到 localStorage localStorage.setItem(key, value);// 从 localStorage 获取数据 let data localStorage.getItem(key);// 从 localStorage 删除保存的数据 localStorage.removeItem(key);// 从 localStorage 删除所有保存的数据 localStorage.clear();// 获取某个索引的Key localStorage.key(index) LocalStorage的使用场景 1. 有些网站有换肤的功能这时候就可以将换肤的信息存储在本地的LocalStorage中当需要换肤的时候直接操作LocalStorage即可 2. 在网站中的用户浏览信息也会存储在LocalStorage中还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中 6.1.3. SessionStorage SessionStorage和LocalStorage都是在HTML5才提出来的存储方案SessionStorage 主要用于临时保存同一窗口(或标签页)的数据刷新页面时不会删除关闭窗口或标签页之后将会删除这些数据。 SessionStorage与LocalStorage对比 1. SessionStorage和LocalStorage都在本地进行数据存储 2. SessionStorage也有同源策略的限制但是SessionStorage有一条更加严格的限制SessionStorage只有在同一浏览器的同一窗口下才能够共享 3. LocalStorage和SessionStorage都不能被爬虫爬取 SessionStorage的常用API // 保存数据到 sessionStorage sessionStorage.setItem(key, value);// 从 sessionStorage 获取数据 let data sessionStorage.getItem(key);// 从 sessionStorage 删除保存的数据 sessionStorage.removeItem(key);// 从 sessionStorage 删除所有保存的数据 sessionStorage.clear();// 获取某个索引的Key sessionStorage.key(index) SessionStorage的使用场景 1. 由于SessionStorage具有时效性所以可以用来存储一些网站的游客登录的信息还有临时的浏览记录的信息。当关闭网站之后这些信息也就随之消除了。 6.2. Cookie有哪些字段作用分别是什么? Cookie由以下字段组成 1. Namecookie的名称 2. Valuecookie的值对于认证cookievalue值包括web服务器所提供的访问令牌 3. Size cookie的大小 4. Path可以访问此cookie的页面路径。 比如domain是abc.compath是/test那么只有/test路径下的页面可以读取此cookie 5. Secure 指定是否使用HTTPS安全协议发送Cookie。使用HTTPS安全协议可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该方法也可用于Web站点的身份鉴别即在HTTPS的连接建立阶段浏览器会检查Web网站的SSL证书的有效性。但是基于兼容性的原因比如有些网站使用自签署的证书在检测到SSL证书无效时浏览器并不会立即终止用户的连接请求而是显示安全风险信息用户仍可以选择继续访问该站点 6. Domain可以访问该cookie的域名Cookie 机制并未遵循严格的同源策略允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时Cookie 的上述特性非常有用然而也增加了 Cookie受攻击的危险比如攻击者可以借此发动会话定置攻击。因而浏览器禁止在 Domain 属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名以减小攻击发生的范围 7. HTTP 该字段包含HTTPOnly 属性 该属性用来设置cookie能否通过脚本来访问默认为空即可以通过脚本访问。在客户端是不能通过js代码去设置一个httpOnly类型的cookie的这种类型的cookie只能通过服务端来设置。该属性用于防止客户端脚本通过document.cookie属性访问Cookie有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是HTTPOnly的应用仍存在局限性一些浏览器可以阻止客户端脚本对Cookie的读操作但允许写操作此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头 8. Expires/Max-size  此cookie的超时时间。若设置其值为一个时间那么当到达此时间后此cookie失效。不设置的话默认值是Session意思是cookie会和session一起失效。当浏览器关闭不是浏览器标签页而是整个浏览器后此cookie失效 总结 服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。一条cookie 包括了5个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的时间domain 是域名、path是路径domain 和 path 一起限制了 cookie 能够被哪些 url 访问。secure 规定了 cookie 只能在确保安全的情况下传输HttpOnly 规定了这个 cookie 只能被服务器访问不能使用 js 脚本访问。 6.3. Cookie、LocalStorage、SessionStorage区别 1.cookie 其实最开始是服务器端用于记录用户状态的一种方式由服务器设置在客户端存储然后每次发起同源请求时发送给服务器端。cookie 最多能存储 4 k 数据它的生存时间由 expires 属性指定并且 cookie 只能被同源的页面访问共享 2. sessionStorage html5 提供的一种浏览器本地存储的方法它借鉴了服务器端 session 的概念代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据它在当前窗口关闭后就失效了并且 sessionStorage 只能被同一个窗口的同源页面所访问共享 3. localStorage html5 提供的一种浏览器本地存储的方法它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是除非手动删除它否则它不会失效并且 localStorage 也只能被同源页面所访问共享 上面几种方式都是存储少量数据的时候的存储方式当需要在本地存储大量数据的时候我们可以使用浏览器的 indexDB 这是浏览器提供的一种本地的数据库存储机制。它不是关系型数据库它内部采用对象仓库的形式存储数据它更接近 NoSQL 数据库。 6.4. 前端储存的方式有哪些 1. Cookies 在HTML5标准前本地储存的主要方式优点是兼容性好请求头中带cookie方便缺点是最大只有4k主动请求头加上cookie浪费流量每个domain限制20个cookie使用起来麻烦需要封装 2. LocalStorageHTML5的以键值对Key-Value为标准的方式优点是操作方便永久性储存除非手动删除大小为5M兼容IE8 3. SessionStorage与localStorage基本类似区别是sessionStorage当窗口关闭后会被清理而且与cookie、localStorage不同他不能在所有同源窗口中共享是会话级别的储存方式 4. Web SQL2010年被W3C废弃的本地数据库数据存储方案但是主流浏览器火狐除外都已经有了相关的实现Web SQL类似于SQLite是真正意义上的关系型数据库较为繁琐 5. IndexedDB 是被正式纳入HTML5标准的数据库储存方案它是NoSQL数据库以键值对进行储存可以进行快速读取操作非常适合web场景同时对JavaScript进行操作会非常方便 6.5. IndexedDB有哪些特点 IndexedDB 具有以下特点 1. 键值对储存IndexedDB 内部采用对象仓库object store存放数据。所有类型的数据都可以直接存入包括 JavaScript 对象。对象仓库中数据以键值对的形式保存每一个数据记录都有对应的主键主键是独一无二的不能有重复否则会抛出一个错误 2. 异步IndexedDB 操作时不会锁死浏览器用户依然可以进行其他操作这与 LocalStorage 形成对比后者的操作是同步的异步设计是为了防止大量数据的读写拖慢网页的表现 3. 支持事务IndexedDB 支持事务transaction这意味着一系列操作步骤之中只要有一步失败整个事务就都取消数据库回滚到事务发生之前的状态不存在只改写一部分数据的情况 4. 同源限制 IndexedDB 受到同源限制每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库而不能访问跨域的数据库 5. 储存空间大IndexedDB 的储存空间比 LocalStorage 大得多一般来说不少于 250MB甚至没有上限 6. 支持二进制储存IndexedDB 不仅可以储存字符串还可以储存二进制数据ArrayBuffer 对象和 Blob 对象 7. 浏览器同源策略 7.1. 什么是同源策略? 跨域问题其实就是浏览器的同源策略造成的。 同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是协议、端口号、域名必须一致。 下表给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例 同源策略protocol协议、domain域名、port端口三者必须一致。 同源政策主要限制了三个方面 1. 当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB 2. 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM 3. 当前域下 ajax 无法发送跨域请求 7.2. 如何解决跨越问题? 7.2.1. CORS 下面是MDN对于CORS的定义 跨域资源共享(CORS) 是一种机制它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain)上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时资源会发起一个跨域HTTP 请求。 CORS需要浏览器和服务器同时支持整个CORS过程都是浏览器完成的无需用户参与。因此实现CORS的关键就是服务器只要服务器实现了CORS请求就可以跨源通信了。 浏览器将CORS分为简单请求和非简单请求 简单请求不会触发CORS预检请求。若该请求满足以下两个条件就可以看作是简单请求 1. 请求方法是以下三种方法之一 (1). HEAD (2). GET (3). POST 2. HTTP的头信息不超出以下几种字段 (1). Accept (2). Accept-Language (3). Content-Language (4). Last-Event-ID (5). Content-Type只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain 若不满足以上条件就属于非简单请求了。 1. 简单请求过程 对于简单请求浏览器会直接发出CORS请求它会在请求的头信息中增加一个Origin字段该字段用来说明本次请求来自哪个源协议端口域名服务器会根据这个值来决定是否同意这次请求。如果Origin指定的域名在许可范围之内服务器返回的响应就会多出以下信息头 Access-Control-Allow-Origin: http://api.bob.com // 和Orign一起 Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值 Content-Type: text/html; charsetutf-8 // 表示文档类型 如果Origin指定的域名不在许可范围之内服务器会返回一个正常的HTTP回应浏览器发现没有上面的Access-Control-Allow-Origin头部信息就知道出错了。这个错误无法通过状态码识别因为返回的状态码可能是200。 在简单请求中在服务器内至少需要设置字段Access-Control-Allor-Origin. 2. 非简单请求过程 非简单请求是对服务器有特殊要求的请求比如请求方法为DELETE或者PUT等。非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求称为预检请求。 浏览器会询问服务器当前所在的网页是否在服务器允许访问的范围内以及可以使用哪些HTTP请求方式和头信息字段只有得到肯定的回复才会进行正式的HTTP请求否则就会报错。 预检请求使用的请求方法是OPTIONS表示这个请求是来询问的。他的头信息中的关键字段是Origin表示请求来自哪个源。除此之外头信息中还包括两个字段 Access-Control-Request-Method 该字段是必须的用来列出浏览器的CORS请求会用到哪些HTTP方法。 Access-Control-Request-Headers该字段是一个逗号分隔的字符串指定浏览器CORS请求会额外发送的头信息字段。 服务器在收到浏览器的预检请求之后会根据头信息的三个字段来进行判断如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求如果没有就是不同意这个预检请求就会报错。 服务器回应的CORS的字段如下 Access-Control-Allow-Origin: http://api.bob.com // 允许跨域的源地址 Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法 Access-Control-Allow-Headers: X-Custom-Header // 服务器支持的所有头信息字段 Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie Access-Control-Max-Age: 1728000 // 用来指定本次预检请求的有效期单位为秒 只要服务器通过了预检请求在以后每次的CORS请求都会自带一个Origin头信息字段。服务器的回应也都会有一个Access-Control-Allow-Origin头信息字段。 在非简单请求中至少需要设置以下字段 Access-Control-Allow-Origin Access-Control-Allow-Methods Access-Control-Allow-Headers 减少OPTIONS请求次数 OPTIONS请求次数过多就会损耗页面加载的性能降低用户体验度。所以尽量要减少OPTIONS请求次数可以后端在请求的返回头部添加Access-Control-Max-Agenumber。它表示预检请求的返回结果可以被缓存多久单位是秒。该字段只对完全一样的URL的缓存设置生效所以设置了缓存时间在这个时间范围内再次发送请求就不需要进行预检请求了。 CORS中Cookie相关问题 在CORS请求中如果想要传递Cookie就要满足以下3个条件 1. 在请求中设置 withCredentials 默认情况下在跨域请求浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie. // 原生 xml 的设置方式 var xhr new XMLHttpRequest(); xhr.withCredentials true; // axios 设置方式 axios.defaults.withCredentials true; 2. Access-Control-Allow-Credentials 设置为 true 3. Access-Control-Allow-Origin 设置为非 * 7.2.2. JSONP JSONP的原理就是利用script标签没有跨域限制通过script标签src属性发送带有callback参数的GET请求服务端将接口返回数据拼凑到callback函数中返回给浏览器浏览器解析执行从而前端拿到callback函数返回的数据。 1. 原生JS实现 scriptvar script document.createElement(script);script.type text/javascript;// 传参一个回调函数名给后端方便后端返回时执行这个在前端定义的回调函数script.src http://www.domain2.com:8080/login?useradmincallbackhandleCallback;document.head.appendChild(script);// 回调执行函数function handleCallback(res) {alert(JSON.stringify(res));}/script 服务端返回如下返回时即执行全局函数 handleCallback({success: true, user: admin}) 2. Vue Axios实现 this.$http axios; this.$http.jsonp(http://www.domain2.com:8080/login, {params: {},jsonp: handleCallback }).then((res) {console.log(res); }) 后端Node代码 var querystring require(querystring); var http require(http); var server http.createServer(); server.on(request, function(req, res) {var params querystring.parse(req.url.split(?)[1]);var fn params.callback;// jsonp返回设置res.writeHead(200, { Content-Type: text/javascript });res.write(fn ( JSON.stringify(params) ));res.end(); }); server.listen(8080); console.log(Server is running at port 8080...); JSONP的缺点 1. 具有局限性 仅支持get方法 2. 不安全可能会遭受XSS攻击 7.2.3. postMessage 跨域 postMessage是HTML5 XMLHttpRequest Level 2中的API且是为数不多可以跨域操作的window属性之一它可用于解决以下方面的问题 1. 页面和其打开的新窗口的数据传递 2. 多窗口之间消息传递 3. 页面与嵌套的iframe消息传递 4. 上面三个场景的跨域数据传递 postMessage(data,origin)方法接受两个参数 1. data html5规范支持任意基本类型或可复制的对象但部分浏览器只支持字符串所以传参时最好用JSON.stringify()序列化。 2. origin 协议主机端口号也可以设置为*表示可以传递给任意窗口如果要指定和当前窗口同源的话设置为/。 以下分别以两个页面举例说明 a.htmldomain1.com/a.html iframe idiframe srchttp://www.domain2.com/b.html styledisplay:none;/iframe script var iframe document.getElementById(iframe);iframe.onload function() {var data {name: aym};// 向domain2传送跨域数据iframe.contentWindow.postMessage(JSON.stringify(data), http://www.domain2.com);};// 接受domain2返回数据window.addEventListener(message, function(e) {alert(data from domain2 e.data);}, false); /script b.htmldomain2.com/b.html script// 接收domain1的数据window.addEventListener(message, function(e) {alert(data from domain1 e.data);var data JSON.parse(e.data);if (data) {data.number 16;// 处理后再发回domain1window.parent.postMessage(JSON.stringify(data), http://www.domain1.com);}}, false); /script 7.2.4. Nginx代理跨域 nginx代理跨域实质和CORS跨域原理一样通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。 1. nginx配置解决iconfont跨域 浏览器跨域访问js、css、img等常规静态资源被同源策略许可但iconfont字体文件(eot | otf  | ttf | woff  | svg)例外此时可在nginx的静态资源服务器中加入以下配置。 location / {add_header Access-Control-Allow-Origin *; } 2. nginx反向代理接口跨域 跨域问题同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议不需要同源策略也就不存在跨域问题。 实现思路通过Nginx配置一个代理服务器域名与domain1相同端口不同做跳板机反向代理访问domain2接口并且可以顺便修改cookie中domain信息方便当前域cookie写入实现跨域访问。 nginx具体配置 #proxy服务器 server {listen 81;server_name www.domain1.com;location / {proxy_pass http://www.domain2.com:8080; #反向代理proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名index index.html index.htm;# 当用webpack-dev-server等中间件代理接口访问nignx时此时无浏览器参与故没有同源限制下面的跨域配置可不启用add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时可为*add_header Access-Control-Allow-Credentials true;} } 7.2.5. Node中间件代理跨域 node中间件实现跨域代理原理大致与nginx相同都是通过启一个代理服务器实现数据的转发也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名实现当前域的cookie写入方便接口登录认证。 1. 非vue框架的跨域 使用node express http-proxy-middleware搭建一个proxy服务器。 前端代码 var xhr new XMLHttpRequest(); // 前端开关浏览器是否读写cookie xhr.withCredentials true; // 访问http-proxy-middleware代理服务器 xhr.open(get, http://www.domain1.com:3000/login?useradmin, true); xhr.send(); 中间件服务器代码 var express require(express); var proxy require(http-proxy-middleware); var app express(); app.use(/, proxy({// 代理跨域目标接口target: http://www.domain2.com:8080,changeOrigin: true,// 修改响应头信息实现跨域并允许带cookieonProxyRes: function(proxyRes, req, res) {res.header(Access-Control-Allow-Origin, http://www.domain1.com);res.header(Access-Control-Allow-Credentials, true);},// 修改响应信息中的cookie域名cookieDomainRewrite: www.domain1.com // 可以为false表示不修改 })); app.listen(3000); console.log(Proxy server is listen at port 3000...); 2. vue框架的跨域 node vue webpack webpack-dev-server搭建的项目跨域请求接口直接修改webpack.config.js配置。开发环境下vue渲染服务和接口代理服务都是webpack-dev-server同一个所以页面与代理接口之间不再跨域。 webpack.config.js部分配置 module.exports {entry: {},module: {},...devServer: {historyApiFallback: true,proxy: [{context: /login,target: http://www.domain2.com:8080, // 代理跨域目标接口changeOrigin: true,secure: false, // 当代理某些https服务报错时用cookieDomainRewrite: www.domain1.com // 可以为false表示不修改}],noInfo: true} } 7.2.6. document.domain iframe跨域 此方案仅限主域相同子域不同的跨域应用场景。实现原理两个页面都通过js强制设置document.domain为基础主域就实现了同域。 父窗口domain.com/a.html iframe idiframe srchttp://child.domain.com/b.html/iframe scriptdocument.domain domain.com;var user admin; /script 子窗口child.domain.com/a.html scriptdocument.domain domain.com;// 获取父窗口中变量console.log(get js data from parent window.parent.user); /script 7.2.7. location.hash iframe跨域 实现原理a欲与b跨域相互通信通过中间页c来实现。 三个页面不同域之间利用iframe的location.hash传值相同域之间直接js访问来通信。 具体实现A域a.html B域b.html A域c.htmla与b不同域只能通过hash值单向通信b与c也不同域也只能单向通信但c与a同域所以c可通过parent.parent访问a页面所有对象。 a.htmldomain1.com/a.html iframe idiframe srchttp://www.domain2.com/b.html styledisplay:none;/iframe scriptvar iframe document.getElementById(iframe);// 向b.html传hash值setTimeout(function() {iframe.src iframe.src #useradmin;}, 1000);// 开放给同域c.html的回调方法function onCallback(res) {alert(data from c.html res);} /script b.htmldomain2.com/b.html iframe idiframe srchttp://www.domain1.com/c.html styledisplay:none;/iframe scriptvar iframe document.getElementById(iframe);// 监听a.html传来的hash值再传给c.htmlwindow.onhashchange function () {iframe.src iframe.src location.hash;}; /script c.html( domain1.com/c.html script// 监听b.html传来的hash值window.onhashchange function () {// 再通过操作同域a.html的js回调将结果传回window.parent.parent.onCallback(hello: location.hash.replace(#user, ));}; /script ​ 7.2.8. window.name iframe跨域 window.name属性的独特之处name值在不同的页面甚至不同域名加载后依旧存在并且可以支持非常长的 name 值2MB。 a.htmldomain1.com/a.html var proxy function(url, callback) {var state 0;var iframe document.createElement(iframe);// 加载跨域页面iframe.src url;// onload事件会触发2次第1次加载跨域页并留存数据于window.nameiframe.onload function() {if (state 1) {// 第2次onload(同域proxy页)成功后读取同域window.name中数据callback(iframe.contentWindow.name);destoryFrame();} else if (state 0) {// 第1次onload(跨域页)成功后切换到同域代理页面iframe.contentWindow.location http://www.domain1.com/proxy.html;state 1;}};document.body.appendChild(iframe);// 获取数据以后销毁这个iframe释放内存这也保证了安全不被其他域frame js访问function destoryFrame() {iframe.contentWindow.document.write();iframe.contentWindow.close();document.body.removeChild(iframe);} }; // 请求跨域b页面数据 proxy(http://www.domain2.com/b.html, function(data){alert(data); }); proxy.htmldomain1.com/proxy.html 中间代理页与a.html同域内容为空即可。 b.htmldomain2.com/b.html script window.name This is domain2 data!; /script 通过iframe的src属性由外域转向本地域跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制但同时它又是安全操作。 7.2.9. window.name iframe跨域 WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信同时允许跨域通讯是server push技术的一种很好的实现。 原生WebSocket API使用起来不太方便我们使用Socket.io它很好地封装了webSocket接口提供了更简单、灵活的接口也对不支持webSocket的浏览器提供了向下兼容。 前端代码 divuser inputinput typetext/div script srchttps://cdn.bootcss.com/socket.io/2.2.0/socket.io.js/script script var socket io(http://www.domain2.com:8080); // 连接成功处理 socket.on(connect, function() {// 监听服务端消息socket.on(message, function(msg) {console.log(data from server: msg); });// 监听服务端关闭socket.on(disconnect, function() { console.log(Server socket has closed.); }); }); document.getElementsByTagName(input)[0].onblur function() {socket.send(this.value); }; /script Nodejs socket后台 var http require(http); var socket require(socket.io); // 启http服务 var server http.createServer(function(req, res) {res.writeHead(200, {Content-type: text/html});res.end(); }); server.listen(8080); console.log(Server is running at port 8080...); // 监听socket连接 socket.listen(server).on(connection, function(client) {// 接收信息client.on(message, function(msg) {client.send(hello msg);console.log(data from client: msg);});// 断开处理client.on(disconnect, function() {console.log(Client socket has closed.); }); }); 7.3. 正向代理和反向代理的区别? 7.3.1. 正向代理 客户端想获得一个服务器的数据但是因为种种原因无法直接获取。于是客户端设置了一个代理服务器并且指定目标服务器之后代理服务器向目标服务器转交请求并将获得的内容发送给客户端。这样本质上起到了对真实服务器隐藏真实客户端的目的。实现正向代理需要修改客户端比如修改浏览器配置。 7.3.2. 反向代理 服务器为了能够将工作负载分不到多个服务器来提高网站性能 负载均衡等目的当其受到请求后会首先根据转发规则来确定请求应该被转发到哪个服务器上然后将请求转发到对应的真实服务器上。这样本质上起到了对客户端隐藏真实服务器的作用。 7.3.3. 正向代理与反向代理的区别 一般使用反向代理后需要通过修改 DNS 让域名解析到代理服务器 IP这时浏览器无法察觉到真正服务器的存在当然也就不需要修改配置了。 正向代理和反向代理的结构是一样的都是 client-proxy-server 的结构它们主要的区别就在于中间这个 proxy 是哪一方设置的。在正向代理中proxy 是 client 设置的用来隐藏 client而在反向代理中proxy 是 server 设置的用来隐藏 server。 7.4. Nginx的概念及其工作原理 Nginx 是一款轻量级的 Web 服务器也可以用于反向代理、负载平衡和 HTTP 缓存等。Nginx 使用异步事件驱动的方法来处理请求是一款面向性能设计的 HTTP 服务器。 传统的 Web 服务器如 Apache 是 process-based 模型的而 Nginx 是基于event-driven模型的。正是这个主要的区别带给了 Nginx 在性能上的优势。 Nginx 架构的最顶层是一个 master process这个 master process 用于产生其他的 worker process这一点和Apache 非常像但是 Nginx 的 worker process 可以同时处理大量的HTTP请求而每个 Apache process 只能处理一个。 8. 浏览器事件机制 8.1. 事件是什么事件模型 事件是用户操作网页时发生的交互动作比如 click/move 事件除了用户触发的动作外还可以是文档加载窗口滚动和大小调整。事件被封装成一个 event 对象包含了该事件发生时的所有相关信息 event 的属性以及可以对事件进行的操作 event 的方法。 事件是用户操作网页时发生的交互动作或者网页本身的一些操作现代浏览器一共有三种事件模型 8.1.1. DOM0 级事件模型 这种模型不会传播所以没有事件流的概念但是现在有的浏览器支持以冒泡的方式实现它可以在网页中直接定义监听函数也可以通过 js 属性来指定监听函数。所有浏览器都兼容这种方式。直接在dom对象上注册事件名称就是DOM0写法。 8.1.2. IE 事件模型 在该事件模型中一次事件共有两个过程事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段冒泡指的是事件从目标元素冒泡到 document依次检查经过的节点是否绑定了事件监听函数如果有则执行。这种模型通过attachEvent 来添加监听函数可以添加多个监听函数会按顺序依次执行。 8.1.2. DOM2 级事件模型 在该事件模型中一次事件共有三个过程第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素依次检查经过的节点是否绑定了事件监听函数如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型事件绑定的函数是addEventListener其中第三个参数可以指定事件是否在捕获阶段执行。 8.2. 如何阻止事件冒泡 普通浏览器使用event.stopPropagation() IE浏览器使用event.cancelBubble true 8.3. 对事件委托的理解 8.3.1. 事件委托的概念 事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点父节点可以通过事件对象获取到目标节点因此可以把子节点的监听函数定义在父节点上由父节点的监听函数统一处理多个子元素的事件这种方式称为事件委托事件代理。 使用事件委托可以不必要为每一个子元素都绑定一个监听事件这样减少了内存上的消耗。并且使用事件代理还可以实现事件的动态绑定比如说新增了一个子节点并不需要单独地为它添加一个监听事件它绑定的事件会交给父元素中的监听函数来处理。 8.3.2. 事件委托的特点 1. 减少内存消耗 如果有一个列表列表之中有大量的列表项需要在点击列表项的时候响应一个事件 ul idlistliitem 1/liliitem 2/liliitem 3/li......liitem n/li /ul 如果给每个列表项都绑定一个函数那对于内存消耗是非常大的效率上需要消耗很多性能。因此比较好的方法就是把这个点击事件绑定到他的父层也就是 ul 上然后在执行事件时再去匹配判断目标元素所以事件委托可以减少大量的内存消耗节约效率。 2. 动态绑定事件 给上述的例子中每个列表项都绑定事件在很多时候需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素那么在每一次改变的时候都需要重新给新增的元素绑定事件给即将删去的元素解绑事件如果用了事件委托就没有这种麻烦了因为事件是绑定在父层的和目标元素的增减是没有关系的执行到目标元素是在真正响应执行事件函数的过程中去匹配的所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。 // 来实现把 #list 下的 li 元素的事件代理委托到它的父层元素也就是 #list 上 // 给父层元素绑定事件 document.getElementById(list).addEventListener(click, function (e) {// 兼容性处理var event e || window.event;var target event.target || event.srcElement;// 判断是否匹配目标元素if (target.nodeName.toLocaleLowerCase li) {console.log(the content is: , target.innerHTML);} }); 在上述代码中 target 元素则是在 #list 元素之下具体被点击的元素然后通过判断 target 的一些属性比如nodeNameid 等等可以更精确地匹配到某一类 #list li 元素之上 3. 局限性 当然事件委托也是有局限的。比如 focus、blur 之类的事件没有事件冒泡机制所以无法实现事件委托mousemove、mouseout 这样的事件虽然有事件冒泡但是只能不断通过位置去计算定位对性能消耗高因此也是不适合于事件委托的。 当然事件委托不是只有优点它也是有缺点的事件委托会影响页面性能主要影响因素有 1. 元素中绑定事件委托的次数 2. 点击的最底层元素到绑定事件元素之间的DOM层数 在必须使用事件委托的地方可以进行如下的处理 1. 只在必须的地方使用事件委托比如ajax的局部刷新区域 2. 尽量的减少绑定的层级不在body元素上进行绑定 3. 减少绑定的次数如果可以那么把多个事件的绑定合并到一次事件委托中去由这个事件委托的回调来进行分发 8.4. 事件委托的使用场景 场景给页面的所有的a标签添加click事件代码如下 document.addEventListener(click, function(e) {if (e.target.nodeName A)console.log(a); }, false); 但是这些a标签可能包含一些像span、img等元素如果点击到了这些a标签中的元素就不会触发click事件因为事件绑定上在a标签元素上而触发这些内部的元素时e.target指向的是触发click事件的元素span、img等其他元素。 这种情况下就可以使用事件委托来处理将事件绑定在a标签的内部元素上当点击它的时候就会逐级向上查找知道找到a标签为止代码如下 document.addEventListener(click, function(e) {var node e.target;while (node.parentNode.nodeName ! BODY) {if (node.nodeName A) {console.log(a);break;}node node.parentNode;} }, false); 8.5. 同步和异步的区别 同步指的是当一个进程在执行某个请求时如果这个请求需要等待一段时间才能返回那么这个进程会一直等待下去直到消息返回为止再继续向下执行。 异步指的是当一个进程在执行某个请求时如果这个请求需要等待一段时间才能返回这个时候进程会继续往下执行不会阻塞等待消息的返回当消息返回时系统再通知进程进行处理。 8.6. 对事件循环的理解 因为 js 是单线程运行的在代码执行时通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时如果遇到异步事件js 引擎并不会一直等待其返回结果而是会将这个事件挂起继续执行执行栈中的其他任务。当异步事件执行完毕后再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列当当前执行栈中的事件执行完毕后js 引擎首先会判断微任务队列中是否有任务可以执行如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。 Event Loop 执行顺序如下所示 1. 首先执行同步代码这属于宏任务 2. 当执行完所有同步代码后执行栈为空查询是否有异步代码需要执行 3. 执行所有微任务 4. 当执行完所有微任务后如有必要会渲染页面 5. 然后开始下一轮 Event Loop执行宏任务中的异步代码 8.7. 宏任务和微任务分别有哪些 微任务包括 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。 宏任务包括 script 脚本的执行、setTimeout 、setInterval 、setImmediate 一类的定时事件还有如 I/O 操作、UI 渲染等。 8.8. 什么是执行栈? 可以把执行栈认为是一个存储函数调用的栈结构遵循先进后出的原则。 当开始执行 JS 代码时根据先进后出的原则后执行的函数会先弹出栈可以看到foo 函数后执行当执行完毕后就从栈中弹出了。 平时在开发中可以在报错中找到执行栈的痕迹 function foo() {throw new Error(error) } function bar() {foo() } bar() 可以看到报错在 foo 函数foo函数又是在 bar函数中调用的。当使用递归时因为栈可存放的函数是有限制的一旦存放了过多的函数且没有得到释放的话就会出现爆栈的问题 function bar() { bar() } bar() 8.9. Node 中的 Event Loop 和浏览器中的有什么区别process.nextTick 执行顺序 Node 中的 Event Loop 和浏览器中的是完全不相同的东西。 Node 的 Event Loop 分为 6 个阶段它们会按照顺序反复运行。每当进入某一个阶段的时候都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值就会进入下一阶段。 1. Timers计时器阶段初次进入事件循环会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调包含 setTimeout 和 setInterval如果存在则会执行所有过期的计时器回调执行完毕后如果回调中触发了相应的微任务会接着执行所有微任务执行完微任务后再进入 Pending callbacks 阶段 2. Pending callbacks执行推迟到下一个循环迭代的I / O回调系统调用相关的回调 3. Idle/Prepare仅供内部使用 4. Poll轮询阶段 当回调队列不为空时会执行回调若回调中触发了相应的微任务这里的微任务执行时机和其他地方有所不同不会等到所有回调执行完毕后才执行而是针对每一个回调执行完毕后就执行相应微任务。执行完所有的回调后变为下面的情况。 当回调队列为空时没有回调或所有回调执行完毕但如果存在有计时器setTimeout、setInterval和setImmediate没有执行会结束轮询阶段进入 Check 阶段。否则会阻塞并等待任何正在执行的I/O操作完成并马上执行相应的回调直到所有回调执行完毕。 5. Check查询阶段会检查是否存在 setImmediate 相关的回调如果存在则执行所有回调执行完毕后如果回调中触发了相应的微任务会接着执行所有微任务执行完微任务后再进入 Close callbacks 阶段 6. Close callbacks执行一些关闭回调比如socket.on(‘close’, …)等 下面来看一个例子首先在有些情况下定时器的执行顺序其实是随机的。 setTimeout(() { console.log(setTimeout) },0)setImmediate(() { console.log(setImmediate) }) 对于以上代码来说setTimeout 可能执行在前也可能执行在后。 首先 setTimeout(fn,0) setTimeout(fn,1)这是由源码决定的 进入事件循环也是需要成本的如果在准备时候花费了大于 1ms 的时间那么在 timer 阶段就会直接执行 setTimeout 回调 那么如果准备时间花费小于 1ms那么就是 setImmediate 回调先执行了 当然在某些情况下他们的执行顺序一定是固定的比如以下代码 const fs require(fs) fs.readFile(__filename, () {setTimeout(() {console.log(timeout);}, 0)setImmediate(() {console.log(immediate)}) }) 在上述代码中setImmediate 永远先执行。因为两个代码写在 IO 回调中IO 回调是在 poll 阶段执行当回调执行完毕后队列为空发现存在 setImmediate 回调所以就直接跳转到 check 阶段去执行回调了。 上面都是 macrotask 的执行情况对于 microtask 来说它会在以上每个阶段完成前清空 microtask 队列下图中的 Tick 就代表了 microtask。 setTimeout(() {console.log(timer21) }, 0)Promise.resolve().then(function() {console.log(promise1) }) 对于以上代码来说其实和浏览器中的输出是一样的microtask 永远执行在 macrotask 前面。 最后来看 Node 中的 process.nextTick这个函数其实是独立于 Event Loop 之外的它有一个自己的队列当每个阶段完成后如果存在 nextTick 队列就会清空队列中的所有回调函数并且优先于其他 microtask 执行。 setTimeout(() {console.log(timer1)Promise.resolve().then(function() {console.log(promise1)}) }, 0)process.nextTick(() {console.log(nextTick)process.nextTick(() {console.log(nextTick)process.nextTick(() {console.log(nextTick)process.nextTick(() {console.log(nextTick)})})}) }) 对于以上代码永远都是先把 nextTick 全部打印出来。 8.10. 事件触发的过程是怎样的? 事件触发有三个阶段 1. window 往事件触发处传播遇到注册的捕获事件会触发 2. 传播到事件触发处时触发注册的事件 3. 从事件触发处往 window 传播遇到注册的冒泡事件会触发 事件触发一般来说会按照上面的顺序进行但是也有特例如果给一个 body 中的子节点同时注册冒泡和捕获事件事件触发会按照注册的顺序执行。 // 以下会先打印冒泡然后是捕获 node.addEventListener(click,event {console.log(冒泡) },false)node.addEventListener(click,event {console.log(捕获 ) },true) 通常使用 addEventListener 注册事件该函数的第三个参数可以是布尔值也可以是对象。对于布尔值 useCapture 参数来说该参数默认值为 false useCapture 决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说可以使用以下几个属性 1.capture 布尔值和useCapture作用一样 2.once 布尔值值为true表示该回调只会调用一次调用后会移除监听 3.passive 布尔值表示永远不会调用preventDefault 一般来说如果只希望事件只触发在目标上这时候可以使用 stopPropagation来阻止事件的进一步传播。通常认为stopPropagation是用来阻止事件冒泡的其实该函数也可以阻止捕获事件。 stopImmediateProgagation 同样也能实现阻止事件但是还能阻止该事件目标执行别的注册事件。 node.addEventListener(click,event {event.stopImmediatePropagation()console.log(冒泡) },false)// 点击 node 只会执行上面的函数该函数不会执行 node.addEventListener(click,event {console.log(捕获) },true) 9. 浏览器垃圾回收机制 9.1. V8的垃圾回收机制是怎样的? V8 实现了准确式 GCGC 算法采用了分代式垃圾回收机制。因此V8 将内存堆分为新生代和老生代两部分。 9.1.1. 新生代算法 新生代中的对象一般存活时间较短使用 Scavenge GC 算法。 在新生代空间中内存空间分为两部分分别为 From 空间和 To 空间。在这两个空间中必定有一个空间是使用的另一个空间是空闲的。新分配的对象会被放入 From 空间中当 From 空间被占满时新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换这样 GC 就结束了。 9.1.2. 老生代算法 老生代中的对象一般存活时间较长且数量也多使用了两个算法分别是标记清除算法和标记压缩算法。 先来说下什么情况下对象会出现在老生代空间中 新生代中的对象是否已经经历过一次 Scavenge 算法如果经历过的话会将对象从新生代空间移到老生代空间中。 To 空间的对象占比大小超过 25 %。在这种情况下为了不影响到内存分配会将对象从新生代空间移到老生代空间中。 老生代中的空间很复杂有如下几个空间 enum AllocationSpace {// TODO(v8:7464): Actually map this spaces memory as read-only.RO_SPACE, // 不变的对象空间NEW_SPACE, // 新生代用于 GC 复制算法的空间OLD_SPACE, // 老生代常驻对象空间CODE_SPACE, // 老生代代码对象空间MAP_SPACE, // 老生代 map 对象LO_SPACE, // 老生代大空间对象NEW_LO_SPACE, // 新生代大空间对象FIRST_SPACE RO_SPACE,LAST_SPACE NEW_LO_SPACE,FIRST_GROWABLE_PAGED_SPACE OLD_SPACE,LAST_GROWABLE_PAGED_SPACE MAP_SPACE }; 在老生代中以下情况会先启动标记清除算法 1. 某一个空间没有分块的时候 2. 空间中被对象超过一定限制 3. 空间不能保证新生代中的对象移动到老生代中 在这个阶段中会遍历堆中所有的对象然后标记活的对象在标记完成后销毁所有没有被标记的对象。在标记大型对内存时可能需要几百毫秒才能完成一次标记。这就会导致一些性能上的问题。为了解决这个问题2011 年V8 从 stop-the-world 标记切换到增量标志。在增量标记期间GC 将标记工作分解为更小的模块可以让 JS 应用逻辑在模块间隙执行一会从而不至于让应用出现停顿情况。但在 2018 年GC 技术又有了一个重大突破这项技术名为并发标记。该技术可以让 GC 扫描和标记对象时同时允许 JS 运行。 清除对象后会造成堆内存出现碎片的情况当碎片超过一定限制后会启动压缩算法。在压缩过程中将活的对象向一端移动直到所有对象都移动完成然后清理掉不需要的内存。 9.2. 哪些操作会造成内存泄漏 1. 第一种情况是由于使用未声明的变量而意外的创建了一个全局变量而使这个变量一直留在内存中无法被回收 2. 第二种情况是设置了 setInterval 定时器而忘记取消它如果循环函数有对外部变量的引用的话那么这个变量会被一直留在内存中而无法被回收 3. 第三种情况是获取一个 DOM 元素的引用而后面这个元素被删除由于我们一直保留了对这个元素的引用所以它也无法被回收 4. 第四种情况是不合理的使用闭包从而导致某些变量一直被留在内存当中
http://www.w-s-a.com/news/768050/

相关文章:

  • 江苏建站速度忿先进的网站建设
  • 广州天河建站公司com域名注册多少钱
  • 成都网站建设推广好vs2013如何做网站
  • 茶叶网站建设模板企业网站备案要多少钱
  • 怎么查网站找谁做的win主机伪静态规则 wordpress
  • 轻云服务器菁英版 多个网站北京it外包服务商
  • 售后服务 网站建设阳江seo优化
  • 网站建设后怎么赚钱wordpress调用导航栏
  • 特产网站设计六色网站
  • 服务器网站备案做网站公司如何赚钱
  • 怎样进行站点优化荣成市有做网站的吗
  • 合肥建设工会网站芜湖做网站建设公司
  • 玉林市住房和城乡建设局网站网站开发百灵鸟
  • 网站怎么做双机房切换建设部网站2015年第158号
  • 郑州服务设计公司网站色块的网站
  • 网站设计所用到的技术做网站添加mp3
  • 凡科做的微网站怎样连接公众号seo李守洪排名大师
  • 温州网站开发网站的制作东莞寮步伟易达电子厂
  • 北京网站设计制作关键词优化微信小程序开发推广网站建设优化规划书
  • 杭州临平网站建设开发公司将购房款划给总公司的法律责任
  • 广东外贸网站推广分类wordpress
  • 聚美优品网站建设方案商城和营销型网站建设
  • 比较著名的seo网站如何建设网站?
  • 如何做商业网站最火wordpress主题
  • 建设网站需要哪些软硬件条件wordpress文章页标题优化
  • 网站建设功能需求文档wordpress 1g1核1m
  • 学做窗帘要下载哪个网站用户反馈数据分析软件园
  • 宁晋网站建设多少钱产品宣传推广方式有哪些
  • delphi做网站阿里巴巴官网首页登录入口
  • 游戏网站怎么建设新建wordpress模板