关于浏览器:如何在浏览器中调试你的代码

前言在没接触worktile我的项目的时候,个别都是用console.log打断点,当初再做worktile我的项目的时候,我的项目真的很大很大,加载一次要个5分钟左右,就不能用console.log打断点了,就要在浏览器中打断点进行debug了。 Sources面板右击->查看->找到Sources。也能够应用快捷键(F12)大抵可分为三个区域,右边是文件导航,两头是文件的具体内容,左边能够统称为调试面板,次要介绍的是最左边那局部,调试面板。 先介绍几个性能按钮: 复原/暂停脚本执行 步过函数,理论体现是不遇到函数时,执行下一步。遇到函数时,不进入函数间接执行下一步。即把函数当做一条语句执行不向内开展 步入函数,体现是不遇到函数时,执行下一步。遇到到函数时,进入函数执行上下文。 当中断停留在函数外部时,点击这个按钮则会跳出函数外部,停留在函数调用的下一个语句 执行下一步 在不勾销断点标记的状况下,使得所有断点生效 监督工具 1.Watch:跟踪监督变量,点击加号,输出变量名称就能够进行监督了 2.Breakpoints:显示了所有打上的断点 3.Scope:查看以后函数作用域、全局作用域等,以及它们蕴含的变量。 4.Call Stack:记录函数调用的栈构造 5.XHR/Fetch Breakpoints:容许你在网络申请中设置断点,无论是应用 XMLHttpRequest(XHR)还是 Fetch API 进行的。当申请被发送、接管或实现时,这些断点会触发,使你可能检查和调试网络申请的细节。 6.DOM Breakpoints:容许你在DOM树产生更改时暂停代码执行。例如,你能够设置断点以在节点属性更改、子节点更改或节点删除时暂停执行,从而帮忙你追踪和调试DOM操作 7.Global Listeners:全局监听器面板容许你查看整个页面上的事件监听器 8.Event Listener Breakpoints:容许你在特定类型的事件被触发时暂停执行。你能够设置断点以在鼠标事件、键盘事件、窗口调整大小等事件产生时进行调试。 9.CSP Violation:Breakpoints内容安全策略(CSP)违规断点容许你在页面上的CSP违规产生时暂停代码执行 参考资料https://zhuanlan.zhihu.com/p/24770877

February 25, 2024 · 1 min · jiezi

关于浏览器:关于浏览器的那些事儿

对于浏览器的那些事儿七拼八凑了一些浏览器内容(^-^) 支流浏览器比照与倒退举荐一个短视频介绍浏览器/参数厂商内核JS引擎其余ChromeGoogleChromium、<br/>BlinkV8-webkit-SafariAppleWebkitJScore、<br/>SquirrelFish(Nitro)(4.0+)-webkit-FireFoxMozillaGeckoSpiderMonkey(1.0-3.0)、<br/>TraceMonkey(3.5-3.6)、<br/>JaegerMonkey(4.0+)-moz-OperaOperaSoftwarePresto、<br/>Webkit、<br/>BlinkLinear A(4.0-6.1)、<br/>Linear B(7.0-9.2)、<br/>Futhark(9.5-10.2)、<br/>Carakan(10.5-)-o-IEMicrosoftTridentJScript(IE9-)、<br/>Chakra(IE9+)-ms-EdgeMicrosoftEdgeHTMLChakra-ms-新EdgeMicrosoftChromiumV8-webkit-UC阿里巴巴U3U3集成?同红芯浏览器一样换汤不换药360/QQ/搜狗/猎豹、<br/>百度/2345/傲游/世界之窗见下文Trident(兼容模式)+Webkit(高速模式)双内核/能用就行Chrome 以前是 Chromium 内核(Chrome 内核),在 Webkit 根底上批改,但代码可读性更高,比 Webkit 更好用。目前是应用从新升级换代后的 Blink 内核。谷歌还开发了本人的 JS 引擎 V8,使 JS 运行速度极大地提高,Node.js 也是以 V8 为底层架构封装。另外咱们能够通过在地址栏输出 chrome://version/ 来查看浏览器相干信息,通过 chrome://dino 玩小游戏。Safari 的 Webkit 源自 KHTML,苹果在比拟了 Gecko 和 KHTML 后,抉择了后者来做引擎开发,是因为 KHTML 领有清晰的源码构造和极快的渲染速度。苹果与谷歌抵触又研发应用 Webkit2 内核,谷歌则研发了 Chromium 内核,Webkit 也算是苹果为业界做出的最大奉献。FireFox 的 Gecko 内核俗称 Firefox 内核,代码齐全公开,可开发水平高,全世界的程序员都可为其编写代码,减少其性能。还有一个 JS 引擎 Rhino,也是由Mozilla基金会治理,尽管最终被废除,但其凋谢源代码,齐全以Java编写。Opera 最早本人研发 Presto,前面用 Webkit,最初与谷歌一起公布应用 Blink,而后因用户体验降落逐步衰败。IE 是微软和 Spyglass 合作开发,随 Windows 绑定抢占市场,并且只能在 Windows 应用也不开源。Edge 原名叫斯巴达,后改名 Edge,2015 年 3 月公布第一个预览版。微软打算在 Windows 中齐全淘汰 Internet Explorer 后,为 Edge 增加 “IE 模式”,该模式容许用户在 Edge 内应用 IE 内核从新加载网页。新 Edge 是微软斗争下的产物,2018 年 12 月发表新 Edge 将基于 Chromium 内核开发,正式版于 2020 年 1 月公布。能够通过 edge://version/ 来查看浏览器版本信息,通过 edge://surf/ 能够玩离线小游戏。UC 浏览器的 U3 内核实质是基于开源内核 Webkit 开发,也有说是基于 Gecko 内核与 Trident 内核开发的。泛滥国产浏览器的厂商别离为360平安、Tencent、搜狗信息、豹好玩科技、Baidu、二三四五、网际傲游、凤凰工作室。这些浏览器适宜须要常常拜访那种古老零碎的用户(兼容模式) ...

September 28, 2023 · 3 min · jiezi

关于浏览器:Chrome-历史版本下载

因为测试时候会须要用到历史版本,但发现不是很好找,于是记录一下。 window/MAC/Linux:http://chromedriver.storage.googleapis.com/index.html Window:https://lanzoui.com/b138066 MAC:https://google-chrome.cn.uptodown.com/mac firefox的历史版本:http://ftp.mozilla.org/pub/firefox/releases/

March 20, 2023 · 1 min · jiezi

关于浏览器:浏览器工作原理

浏览器(也称为网络浏览器或互联网浏览器)是装置在咱们设施上的软件应用程序,使咱们可能拜访万维网。在浏览这篇文字时,你实际上正在应用一个浏览器。有许多浏览器正在被应用,截至2022年,应用最多的是:谷歌浏览器、苹果的Safari、微软的Edge和火狐。 然而,它们实际上是如何工作的,从咱们在地址栏中键入网络地址开始,到咱们试图拜访的页面显示在屏幕上,会产生什么? 对于这个问题的答案,一个极其简化的版本是: 当咱们从一个特定的网站申请一个网页时,浏览器从网络服务器检索必要的内容,而后在咱们的设施上显示该网页。很间接,对吗?是的,但在这个看似超级简略的过程中还波及更多的内容。在这个系列中,咱们将探讨导航、获取数据、解析和渲染等步骤,并心愿能使你对这些概念更清晰。 1.导航导航是加载网页的第一步。它指的是当用户通过点击一个链接、在浏览器地址栏中写下一个网址、提交一个表格等形式申请一个网页时产生的过程。 DNS 查问(解决网址问题)导航到一个网页的第一步是找到该网页的动态资源地位(HTML、CSS、Javascript和其余类型的文件)。如果咱们导航到 [](https://www.notion.so/842e8b3...)https://example.com ,HTML 页面位于 IP 地址为 93.184.216.34 的服务器上(对咱们来说,网站是域名,但对计算机来说,它们是 IP 地址)。如果咱们以前从未拜访过这个网站,就必须进行域名零碎(DNS)查问。 DNS 服务器是蕴含公共 IP 地址及其相干主机名数据库的计算机服务器(这通常被比作电话簿,因为人们的名字与一个特定的电话号码相关联)。在大多数状况下,这些服务器依照要求将这些名字解析或翻译成 IP 地址(当初有 600 多个不同的 DNS 根服务器散布在世界各地)。 因而,当咱们申请进行 DNS 查问时,咱们理论做的是与这些服务器中的一个进行对话,要求找出与https://example.com 名称绝对应的IP地址。如果找到了一个对应的 IP,就会返回。如果产生了一些状况,查找不胜利,咱们会在浏览器中看到一些错误信息。 在这个最后的查问之后,IP 地址可能会被缓存一段时间,所以下次访问同一个网站会更快,因为不须要进行 DNS 查问(记住,DNS 查问只产生在咱们第一次拜访一个网站时)。 TCP (Transmission Control Protocol) 握手一旦浏览器晓得了网站的 IP 地址,它将尝试通过 TCP 三次握手(也称为 SYN-SYN-ACK,或者更精确的说是 SYN、SYN-ACK、ACK,因为 TCP 有三个音讯传输,用于协商和启动两台计算机之间的TCP 会话),与持有资源的服务器建设连贯。 TCP 是传输控制协议的缩写,是一种通信规范,使应用程序和计算设施可能在网络上替换信息。它被设计用来在互联网上发送数据包,并确保数据和信息在网络上胜利传递。TCP 握手是一种机制,旨在让两个想要互相传递信息的实体(在咱们的例子中是浏览器和服务器)在传输数据之前协商好连贯的参数。 因而,如果浏览器和服务器是两个人,他们之间的对话会是这样的: 浏览器向服务器发送一个 SYNC 音讯,要求进行同步(同步意味着连贯) 而后,服务器将回复一个 SYNC-ACK 音讯( SYNChronization 和 ACKnowledgement) 在最初一步,浏览器将回复一个 ACK 信息 ...

February 27, 2023 · 5 min · jiezi

关于浏览器:图解-Google-V8事件循环和垃圾回收学习笔记三

这是《图解 Google V8》第三篇/共三篇:事件循环和垃圾回收 这里次要讲了 2 点: 事件循环:宏工作和微工作 什么是微工作微工作的执行机会垃圾回收 垃圾回收运行过程垃圾回收算法通过这个专栏的学习,V8 不在是个生疏的黑盒了,变成了一个相熟的黑盒,因为这个专栏让你理解了 V8 的大抵原理,面试时吹吹牛皮还是能够的,不过也就仅此而已,细节方面还须要本人去深刻 17 | 音讯队列:V8 是怎么实现回调函数的?同步回调函数是在执行函数外部被执行的异步回调函数是在执行函数内部被执行的UI 线程是运行窗口的线程,也叫主线程 当鼠标点击了页面,零碎会将该事件交给 UI 线程来解决,然而 UI 线程不能立刻响应来解决 针对这种状况,浏览器为 UI 线程提供了音讯队列,而后 UI 线程会一直的从音讯队列中取出事件和执行事件,如果以后没有任何音讯期待被解决,那么这个循环就会被挂起 setTimeout在执行 setTimeout,浏览器会将回调函数封装成一个事件,增加到音讯队列中,而后 UI 线程会不间断的从音讯队列中取出工作,执行工作,在适合的机会取出 setTimeout 的回调函数 XMLHttpRequest在 UI 线程执行 XMLHttpRequest,会阻塞 UI 线程,所以 UI 线程会将它调配给网络线程(是网络过程中的一个线程): UI 线程从音讯队列中取出工作,剖析发现是一个下载工作,就会交给网络线程去执行网络线程接到下载申请后,会和服务器建立联系,收回下载申请网络线程一直从服务器接收数据网络申请在收到数据后,会将返回的数据和回调函数封装成一个事件,放在音讯队列中UI 线程循环读取音讯队列,如果是下载状态的事件,UI 线程就会执行回调函数直到下载事件完结,页面显示下载实现18 | 异步编程(一):V8 是如何实现微工作的?宏工作是音讯队列中期待被主线程执行的事件,每个宏工作在执行的时候都会创立栈,宏工作完结,栈也会被清空 微工作是一个须要异步执行的函数,执行机会是在主函数执行完结之后,以后宏工作完结之前 微工作执行的机会: 如果当前任务中产生了一个微工作,不会再以后的函数中被执行,所以执行微工作时,不会导致栈的有限扩张微工作会在当前任务执行完结之前被执行微工作完结执行之前,不会执行其余的工作参考资料 V8 Promise 源码全面解读JavaScript Event Loop vs Node JS Event Loop19 |异步编程(二):V8 是如何实现 async/await 的?生成器 Generator带星号的函数配合 yield 能够实现函数的暂停和复原,这个叫生成器 function* getResult() { console.log("getUserID before"); yield "getUserID"; console.log("getUserName before"); yield "getUserName"; console.log("name before"); return "name";}let result = getResult();console.log(result.next().value);console.log(result.next().value);console.log(result.next().value);在生成器外部,如果遇到 yield 关键词,那么 V8 将 yield 前面的内容返回给内部,并暂停函数的执行 ...

February 10, 2023 · 2 min · jiezi

关于浏览器:深入了解现代网络浏览器第-1-部分

CPU、GPU、内存和多过程架构在这个由 4 局部组成的博客系列中,咱们将深刻理解 Chrome 浏览器,从高级架构到渲染管道的细节。如果您想晓得浏览器如何将您的代码变成功能性网站,或者您不确定为什么倡议应用特定技术来进步性能,那么本系列适宜您。 作为本系列的第 1 局部,咱们将理解外围计算术语和 Chrome 的多过程架构。 计算机的外围是 CPU 和 GPUCPU首先是中央处理器(CPU),CPU能够被视为计算机的大脑。一个 CPU 内核,在计算机硬件方面指处理器的外部外围,包装在一个元件中的独立处理单元,在这里被描绘成一个办公室工作人员,能够一个接一个地解决许多不同的工作,它能够解决从数学到艺术的所有工作,同时晓得如何回复客户电话。在过来,大多数CPU都是单芯片,但在古代硬件中,为了给您的手机和笔记本电脑提供更多的计算能力,通常会取得多个内核。如何了解处理器、CPU、多处理器、内核、多核? 图 1:4 个 CPU 内核作为办公室工作人员坐在每张办公桌前解决进来的工作 GPU图形处理器(GPU)是计算机的另一部分,与CPU不同,GPU善于矩阵计算,像素解决,它最后是为解决图形而开发的。这就是为什么在图形环境中应用GPU与疾速渲染和平滑交互无关。近年来,随着GPU减速计算,越来越多的计算在GPU上成为可能。 图 2:许多带有扳手的 GPU 内核表明它们只能解决无限的工作 当您在计算机或手机上启动应用程序时,CPU和GPU为应用程序提供能源。通常,应用程序应用操作系统提供的机制在CPU和GPU上运行。 图 3:三层计算机架构。机器硬件在底部,操作系统在两头,应用程序在顶部。 过程和线程在深入研究浏览器架构之前,须要把握的另一个概念,过程和线程。过程能够被形容为应用程序的执行程序,线程是位于过程外部并执行其过程程序的任何局部的线程。 图 4:过程作为边界框,线程作为形象的鱼在过程内游动 启动应用程序时,将创立一个过程,程序可能会创立线程来帮忙它工作,但这是可选的。操作系统为过程提供了一块“平板”内存,所有应用程序状态都保留在该公有内存空间中。敞开应用程序时,过程也会隐没,操作系统会开释内存。 图 5:应用内存空间和存储应用程序数据的过程图 一个过程能够要求操作系统启动另一个过程来运行不同的工作。产生这种状况时,会为新过程调配不同的内存局部。如果两个过程须要对话,它们能够应用过程间通信(IPC)进行对话。许多应用程序都是这样设计的,因而,如果工作过程没有响应,则能够重新启动,而不影响其余应用程序过程运行。 图 6:通过 IPC 进行通信的独立过程示意图 浏览器架构那么如何应用过程和线程构建 Web 浏览器呢?它可能是一个具备许多不同线程的单过程架构,也可能有许多不同过程的多过程架构,其中有几个线程通过 IPC 进行通信。 图 7:过程/线程图中的不同浏览器架构 这儿须要留神这些不同架构的实现细节,并没有对于如何构建 Web 浏览器的标准规范,一种浏览器的架构设计可能与另一种齐全不同。 为了这个博客系列,咱们将应用下图中形容的 Chrome 最新架构。 顶部是浏览器过程与解决应用程序不同局部的其余过程协调。对于渲染器过程,每创立一个选项卡都会一个渲染器过程。当初,Chrome 在为每个选项卡提供了一个渲染器过程的同时,也尝试为每个选项卡内嵌套的站点(iframe)提供本人的过程(请参阅站点隔离)。 图 8:Chrome 的多过程架构图。渲染器过程下显示了多个层,以示意 Chrome 为每个选项卡运行多个渲染器过程。 各类过程的职责下表形容了每个 Chrome 过程及其管制的内容: Browser Process 负责整个浏览器内行为协调,调用各个过程模块Renderer Process 负责网站选项卡内的内容显示。Plugin Process 负责解决各类浏览器插件GPU Process 负责绘图图 9:指向浏览器 UI 不同局部的不同过程 ...

September 11, 2022 · 1 min · jiezi

关于浏览器:一字一图领略浏览器方向的优化

一、写在后面再过半个月,Internet Explorer 就正式服役了,已经的浏览器霸主,退役超过25年的浏览器闭幕。它的闭幕可能有多方面因素综合的后果,但浏览器性能和用户体验不符预期,必然是它被市场和用户所“摈弃”的重要起因。市面上的浏览器很多,据统计超过 8 0种,很多你可能都没听过,例如 greenbrowser,chromeplus(枫树),Lunascape,糖果浏览器,彗星浏览器,Gomodo Dragon,蜜蜂浏览器,Slim Browser等。不论啥浏览器,也不过有多少种浏览器,浏览器性能永远是避不开的话题,也经常是各大浏览器发布会上“卖点”。至此,浏览器性能重要性显而易见了。那么接下来,就看看对于浏览器方向的优化,以及咱们具体上能做些什么。 tips:分明本文是对于介绍浏览器方向的优化,对于读懂本文并有所播种很重要。 二、高谈阔论:“一字一图”一字指的是“预”字,一图指的是上面这张概括浏览器方向优化的脑图。 痴呆的你,置信看出了一些货色。咱们晓得,不同的浏览器,它们的内核,它们的外部运行机制,可能是有所不同,这意味着在具体的优化技术上,可能要“就地取材”,能力更好的见效,是浏览器性能和用户体验失去晋升。尽管如此,从外围优化策略的角度看,也能够大抵的将针对浏览器方向的优化分为两类: 一是,文档类优化 二是,揣测类优化 也就是说,对于浏览器方向的优化,在外围优化策略上,能够分为两个方向,一是文档优化方向,二是浏览器揣测性优化方向。而在具体的技术手段上,次要分为上面这四种技术: ①页面预渲染 页面预渲染,是通过猜想你可能要拜访的指标,从而在暗藏的标签页中事后渲染整个页面。当然,如果你是首次拜访某个指标,这可能不现实。留神,这是通过咱们的一些示意,例如输出局部关键字,此时咱们还没确定拜访,也还没正式拜访,但浏览器通过一些线索,揣测咱们可能要拜访的指标,事后渲染了这些页面。当用户真正拜访浏览器猜中并提前渲染的指标页面时,置信会有一种这个浏览器或这个站点响应速度真快的“错觉”。咱们无可否认,这是一种令大多数用户称心的体现,所以,页面预渲染很棒。 ②DNS 预解析 DNS 预解析,有点页面预渲染的滋味,当然,这一步通常产生在页面预渲染的后面。它是一种通过揣测用户可能要拜访的域名,提前对这些域名进行解析,从而缩短用户感知到的消耗工夫,晋升体验的伎俩。既然是揣测提前解析,那么揣测的根据是啥呢?这可能和浏览器的标签页,鼠标悬浮指向,导航历史等无关。咱们晓得,http 申请是存在DNS提早的,而如果浏览器的揣测正确,提前进行了 DNS 解析,这种提早问题能够失去很好的解决。 ③TCP 预连贯 浏览器揣测性的提前开始 TCP 连贯,就是所说的 TCP 预连贯,它产生在 DNS 解析之后。TCP 预连贯能带来的益处是,如果浏览器的揣测正确,那么能够省下一次残缺的 TCP 握手实际。不要小瞧这一次握手的工夫,这对机器而言,能够产生很多事,尤其是在“领先占位”这种方向上。 ④资源预取 和页面相干的解析器,例如文档解析器、款式解析器、脚本解析器等,能够和网络协议层沟通,申明预加载某些资源。某些资源,当然是指那些初始化渲染必要的资源,必要而又会阻塞持续渲染的资源。 tips1:综上图文信息,一字是四种技术手段的“预”,一图是概括浏览器方向优化的脑图。 tips2:下面提到的策略和伎俩,其实浏览器自身曾经做了,或者说浏览器厂商曾经做了。所以说这有点“高谈阔论”的意思,而咱们须要分明这些机制和特点,从而做一些更具体的,一般开发人员能做的事件,从而晋升经咱们手上开发的利用的性能。 具体落地:一个 link 标签对于预加载预解析方面的技术 http 方向有,html 的 link 标签也通过 ref=“prefetch”,ref=“prerender”,ref=“dns-prefetch” 来反对。通过 link 标签 ref 提醒一些关键字,通知浏览器为咱们采纳对应的优化机制。举例 link 标签在这方面的利用:<!-- 预解析特定的域名 --><link rel="dns-prefetch" href="//example.com"><!-- 预获取某些页面要用到的要害资源 --><link rel="subresource" href="//example.com/app.js"><!-- 预获取某些未来要用的资源,例如浏览器标签上小 logo 图标等 --><link rel="prerender" href="//example.com/logo.png"><!-- 预渲染某些指定页面 --><link rel="prefetch" href="//example.com/index.html">复制代码 tips:link,HTML 内部资源链接元素,规定了以后文档与内部资源的关系。 tips:留神到了吗?这些具体落地的,应用在咱们开发的应用程序上的技术上,是不是和前述的高谈阔论“一字一图”非亲非故。

May 28, 2022 · 1 min · jiezi

关于浏览器:浏览器缓存的简单介绍和实践

前言绝对于以前的 html 开发阶段,当初的前端我的项目个别都会通过打包,而后再部署到服务器上,最初用户再拜访。打包部署过程可玩性有很大进步,引入了前端工程治理的很多内容,比方多环境反对、动静配置、打包速度、拜访减速等等。 明天这里只探讨资源文件的拜访减速,C 端大概率是要用到 CDN 减速,B 端个别将资源放在服务器上即可,这两者都会波及到浏览器缓存。 那如何利用浏览器缓存机制来实现咱们的减速呢?就须要对浏览器缓存有肯定了解。 浏览器缓存浏览器缓存(Brower Caching)是浏览器在本地磁盘对用户最近申请过的文档进行存储,当访问者再次拜访同一页面时,浏览器就能够间接从本地磁盘加载文档。 一是能缩小服务器压力和数据传输,节俭网站压力和带宽。二是能放慢客户端的应用速度,晋升零碎应用体验。 浏览器缓存次要有两类:缓存协商和彻底缓存,也有称之为「协商缓存」和「强缓存」。 浏览器在第一次申请产生后,再次申请时: 浏览器会先获取该资源缓存的 header 信息,依据其中的 Expires 和 Cache-control 判断是否命中强缓存,若命中则间接从缓存中获取资源,包含缓存的 header 信息, 本次申请不会与服务器进行通信;如果没有命中强缓存,浏览器会发送申请到服务器,该申请会携带第一次申请返回的无关缓存的 header 字段信息(Last-Modified/IF-Modified-Since、Etag/IF-None-Match),由服务器依据申请中的相干 header 信息来比照后果是否命中协商缓存,若命中,则服务器返回新的响应 header 信息更新缓存中的对应 header 信息,然而并不返回资源内容,它会告知浏览器能够间接从缓存获取;否则返回最新的资源内容。是不是能够了解强缓存就是指从本地拿缓存,如果失败就去协商缓存?强缓存与强缓存相干的 Header 字段是Cache-Control与Expires,这里重点介绍Cache-Control,它是 http1.1 呈现的 header 信息。 它的值有以下几个: max-age:重要!是一个绝对工夫,例如Cache-Control:max-age=3600,代表着资源的有效期是 3600 秒。no-cache:不应用本地缓存。须要应用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在 ETag,那么申请的时候会与服务端验证,如果资源未被更改,则能够防止从新下载。no-store:间接禁止游览器缓存数据,每次用户申请该资源,都会向服务器发送一个申请,每次都会下载残缺的资源。public:能够被所有的用户缓存,包含终端用户和 CDN 等两头代理服务器。private:只能被终端用户的浏览器缓存,不容许 CDN 等中继缓存服务器对其缓存。这下晓得 no-cache 和 no-store 的区别了吧,面试时不要答错了。Cache-Control 与 Expires可在服务端配置同时启用,同时启用的时候 Cache-Control 优先级高,倡议应用 Cache-Control 。 对于这两个 header,能够这么了解: Expires 是 http1.0 标准的内容,Cache-Control 是 http1.1 标准的内容Expires 的过期工夫点是一个本地工夫,在多时区中不靠谱,所以改为工夫长度。相似倒计时在前后端中的设计,是 unix 工夫戳+绝对工夫长度协商缓存协商缓存能够了解为通过协商机制来实现缓存同步,这个机制的外围就是成双成对的响应头和申请头。协商步骤是:第一次申请资源时,浏览器会返回响应头和其值;再次申请资源时,浏览器会增加相应的申请头和上一次响应头中的值,服务依据这个值来判断是否命中缓存和后续不同的操作,被浏览器缓存的文件会有不同的缓存存储。所以别离用不同的字段来协商,它们关系如下: 响应头(Last-Modify)/申请头(If-Modify-Since):缓存到内存中(from memory cache),其值是一个工夫如 Thu,31 Dec 2037 23:59:59 GMT。响应头(ETag)/申请头(If-None-Match):缓存到硬盘(from disk cache),其值是一个校验码。上面说说二者的具体细节和差异。 ...

May 15, 2022 · 2 min · jiezi

关于浏览器:JavaScript重写LocalStorage方法实现浏览器本地存储设置时间问题

最近遇到了用户登录信息本地存储的问题,所以须要对浏览器的localStorage的存储工夫进行设置,因而重写localStorage办法并在此记录。 浏览器几个存储总结:localStorage保留的数据(大小5M左右),以“键值对”的模式长期存在。也就是说,每一项数据都有一个键名和对应的值,所有的数据都是以文本格式保留。保留的数据没有过期工夫,直到手动去除。sessionStorage保留的数据(大小5M左右)用于浏览器的一次会话,当会话完结(通常是敞开窗口或标签页),数据被清空。sessionStorage与localStrage和Cookie不同的一点在于,即使是雷同域名下的两个页面,只有它们不在同一个浏览器窗口中关上,那么它们的sessionStorage内容便无奈共享;cookie以“键值对”的模式存在,是一些数据,存储于你电脑上的文本文件中。总结cookie、localStorage、sessionStorage异同点比照相同点:都是保留在浏览器端,且都是字符串类型的键值对。都遵循同源策略:当一个浏览器的两个tab页中别离关上来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会查看这个脚本是属于哪个页面的,即查看是否同源,只有和百度同源的脚本才会被执行。不同点:传递形式不同cookie数据始终在同源的http申请中携带(即便不须要),即cookie在浏览器和服务器间来回传递。sessionStorage和loaclStorage不会主动把数据发给服务器,仅在本地保留。数据大小不同cookie数据还有门路(path)的概念,能够限度cookie只属于某个门路下。存储大小限度也不同,cookie数据不能超过4KB,同时因为每次http申请都会携带cookie,所以cookie只适宜保留很小的数据,如会话标识。sessionStorage和localStorage尽管也有存储大小的限度,但比cookie大得多,能够达到5M或者更大。数据有效期不同cookie:只在设置cookie过期工夫之前始终无效,即便窗口或浏览器敞开;localStorage:始终无效,窗口或浏览器敞开也始终保留,除非手动删除,因而用作持久数据;sessionStorage:仅在以后浏览器窗口敞开前无效,天然也就不可能长久放弃。作用域不同cookie:在所有同源窗口中都是共享的;localStorage:在所有同源窗口中也都是共享的;sessionStorage:不在不同的浏览器窗口中共享,即便是同一个页面。因为cookie在http申请中每次都会被携带,生命周期只继续到浏览器敞开,而咱们须要让用户信息存储24小时,因而抉择手动封装LocalStorage办法。 setItem()实现思路及代码首先,changeHourToMs()办法:用于判断调用setItem办法时如果传入了小时,将其转化为毫秒,因为获取以后工夫时是毫秒级工夫。同时对于为传入expires或传入expires非法状况进行拦挡。而后,调用Object.assign()办法:将初始化的数据与传入的数组合并,更新传入的数据。其次,判断options.expires办法是确定options.expires属性存在时,须要将整个对象的数据进行格局转换存入。否则,如果options.expires属性不存在,则意味着没有工夫限度,只需存储其name和Value,而不关注存储的工夫和时常,存入即可。 setItem(params) { const changeToMs = 60 * 60 * 1000; function changeHourToMs(params) { if (!Object.prototype.hasOwnProperty.call(params, "expires")) { return; } if (!isNumber(params.expires)) { console.log("expires属性输出有误,应输出数字!") return; } params.expires = parseFloat(params.expires) * changeToMs; } const obj = { name: '', value: '', expires: 24 * 60 * 60 * 1000, startTime: new Date().getTime()// 存入缓存的工夫 } const options = {}; changeHourToMs(params); // 将obj和传进来的params合并(首先与Obj合并指定必要变量,而后与输出的params合并,如果key存在则增加value,否则增加key和value) Object.assign(options, obj, params); if (options.expires) {// 如果options.expires设置了的话,以options.name为key,options为值放进去 localStorage.setItem(options.name, JSON.stringify(options)); } else { // 如果options.expires没有设置,就判断一下value的类型 const type = Object.prototype.toString.call(options.value); // 如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去 if (type === '[object Object]') { options.value = JSON.stringify(options.value);// 转换为JSON字符串 } if (type === '[object Array]') { options.value = JSON.stringify(options.value); } localStorage.setItem(options.name, options.value); } }getItem()实现思路及代码首先通过name取到数据,并将数据尝试进行Json格局的转换。而后确保数据非null,判断是否传入了options.expires属性,如有进行下一步操作,否则将值返回。下一步操作:获取以后工夫做差,判断若超时间接清空缓存,并采纳阻塞提醒,用户信息生效,请从新登录,点击确定后跳转到登陆页面。附:残缺代码如下;import {isNumber} from "lodash";import router from "umi/router";import {Modal} from 'antd';export default class Storage { constructor(name) { this.name = 'storage'; }// 应用阐明:该类应用须要先初始化一个对象。// setItem必须传入的值为:name,vlaue.// expires为限度工夫默认为一天,如有须要可传入存储的工夫(以小时为单位)// startTime默认获取以后工夫,无需传入 setItem(params) { const changeToMs = 60 * 60 * 1000; function changeHourToMs(params) { if (!Object.prototype.hasOwnProperty.call(params, "expires")) { return; } if (!isNumber(params.expires)) { console.log("expires属性输出有误,应输出数字!") return; } params.expires = parseFloat(params.expires) * changeToMs; } const obj = { name: '', value: '', expires: 24 * 60 * 60 * 1000, startTime: new Date().getTime()// 存入缓存的工夫 } const options = {}; changeHourToMs(params); // 将obj和传进来的params合并(首先与Obj合并指定必要变量,而后与输出的params合并,如果key存在则增加value,否则增加key和value) Object.assign(options, obj, params); if (options.expires) {// 如果options.expires设置了的话,以options.name为key,options为值放进去 localStorage.setItem(options.name, JSON.stringify(options)); } else { // 如果options.expires没有设置,就判断一下value的类型 const type = Object.prototype.toString.call(options.value); // 如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去 if (type === '[object Object]') { options.value = JSON.stringify(options.value);// 转换为JSON字符串 } if (type === '[object Array]') { options.value = JSON.stringify(options.value); } localStorage.setItem(options.name, options.value); } } // 拿到缓存 getItem(name) { let item = localStorage.getItem(name); try { // 先将拿到的试着进行json转为对象的模式,不能够的话间接返回字符串 item = JSON.parse(item); } catch (error) { console.log(error); item = item; } if (item !== null) { if (item.startTime) { // 如果有startTime的值,阐明设置了生效工夫 const date = new Date().getTime(); if (date - item.startTime > item.expires) {// 判断是否超时 localStorage.removeItem(name);// 革除超时缓存 Modal.warning({ content: '用户受权已生效,请从新登录!', onOk() { router.push("/user/login"); }, }); return null; } return item.value; // 缓存未过期,返回值 } } return item; // 如果没有设置生效工夫,间接返回值 } removeItem(name) { localStorage.removeItem(name); } clear() { localStorage.clear(); }} ...

April 14, 2022 · 2 min · jiezi

关于浏览器:onbeforeunload事件之关闭浏览器之前的提示弹框

问题形容对于表单填写信息的页面,有时候会呈现用户填写了一部分不小心“来到”了这个页面,那么这个时候,产品说,须要再做一个用户提醒,问问用户是不是真的要来到这个页面。针对于这个需要,咱们把具体“来到”的形式分为两种状况 状况一 来到以后路由页面去到别的路由页面这种状况比拟好解决,就是直接判断表单填写的信息是否变动了,如果变动了,做个弹框询问一下,没变动间接放行。或者应用beforeRouteLeave钩子做管制。比方我之前的文章:https://segmentfault.com/a/11... 状况二 间接敞开浏览器tab标签页或敞开浏览器对于这种状况下(比方是误操作),那么咱们能够应用浏览器自带的onbeforeunload事件去做管制 onbeforeunload事件onbeforeunload还有两个兄弟,这里一块介绍下 onload、和onbeforeunload、和onunload介绍咱们先看一下浏览器自带的三个比拟常见的事件 onload(网页加载结束后立即执行的操作,很像vue的mounted钩子)onbeforeunload(网页卸载之前的操作,很像vue的beforeDestroy钩子)onunload(网页卸载了的操作,很像vue的destroyed钩子,与destroyed不同的是,onunload如果是刷新页面的话,onunload执行完当前,又会从新加载页面即:DOM树+CSSOM=>render tree...这样的操作)onload、onbeforeunload、onunload执行程序留神一下它们三个的执行程序 当咱们关上页面看到网页内容的时候,其实onload曾经执行结束了当咱们敞开页面的时候,会先触发onbeforeunload事件的执行当咱们刷新页面的时候,会: onbeforeunload --> onunload --> onloadvue中的onbeforeunload写法<template> <div class="wrap123"> <h2>我是表单填写页面</h2> </div></template><script>export default { mounted() { // 存一份this let _this = this; window.onbeforeunload = function(e) { // 那个路由页面须要,就把path的名字批改成那个,比方我以后页面的path是/vue if (_this.$route.path == "/vue") { // 兼容IE8和Firefox 4之前的事件对象写法(不加也行,当初少有我的项目兼容老版本浏览器了) e = e || window.event; if (e) { e.returnValue = "returnValue属性值的文字不能自定义,写不写都行的"; } // Chrome反对, Safari反对, Firefox 4版本当前反对, Opera 12版本当前反对 , IE 9版本当前反对 return "returnValue属性值的文字不能自定义,写不写都行的"; } }; }, beforeDestroy() { // 来到页面时候再革除 window.onbeforeunload = () => {}; }};</script><style lang="less" scoped>.wrap123 { width: 600px; height: 400px;}</style>浏览器效果图当咱们刷新或者敞开浏览器的时候,就会呈现下图这样的成果 ...

March 7, 2022 · 1 min · jiezi

关于浏览器:动手打造一款-canvas-排版引擎

图片起源:https://unsplash.com 本文作者:飞腾 背景在线示例 Demo 作为前端开发尤其是偏 c 端的前端开发者(如微信小程序),置信大家都碰到过分享流动图片、分享海报图相似的性能 个别这种需要的解决方案大体上能够分为以下几种: 依赖服务端,比方写一个 node 服务,用 puppeteer 拜访提前写好的网页来截图。间接应用 CanvasRenderingContext2D 的 api 或者应用辅助绘图的工具如 react-canvas 等来绘制。应用前端页面截图框架,比方 html2canvas、dom2image,用 html 将页面构造写好,再在须要的时候调用框架 api 截图计划剖析: 依赖服务端这种计划会耗费肯定的服务端资源,尤其截图这种服务,对 cpu 以及带宽的耗费都是很大的,因而在一些可能高并发或者图片比拟大的场景用这种计划体验会比拟差,等待时间很长,这种计划的长处是还原度十分高,因为服务端无头浏览器版本是确定的,所以能够确保所见即所得,并且从开发上来说,无其余学习老本,如果业务还不是很大访问量不高用这种计划是最牢靠的。这种计划比拟硬核,比拟费时费力,大量的代码来计算布局的地位,文字是否换行等等,并且当开发实现后,如果 ui 后续有一些调整,又要在茫茫代码中寻找你要批改的那个它。 这个计划的长处是细节很可控,实践上各种性能都能够实现,如果头发够用的话。这应该也是目前 web 端应用最广的一种计划了,截止目前 html2canvas star 数量曾经 25k。html2canvas 的原理简略来说就是遍历 dom 构造中的属性而后转化到 canvas 上来渲染进去,所以它必然是依赖宿主环境的,那么在一些老旧的浏览器上可能会遇到兼容性问题,当然如果是开发中就遇到了还好,毕竟咱们是万能的前端开发(狗头),能够通过一些 hack 伎俩来躲避,然而 c 端产品会运行在各种各样的设施上,很难防止公布后在其余用户设施上兼容问题,并且出了问题除非用户上报,个别难以监控到,并且在国内小程序用户量基数很大,这个计划也不能在小程序中应用。所以这个计划看似一片祥和,然而会有一些兼容的问题。在这几年不同的工作中,根本都遇到了须要分享图片的需要,尽管需要个别都不大频次不高,然而印象中每次做都不是很顺畅,下面几种计划也都试过了,多多少少都有一些问题。 萌发想法: 在一次需要评审中理解到在后续迭代有 ui 对立调整的布局,并且会波及到几个分享图片的性能,过后的业务是波及到小程序以及 h5 的。会后关上代码,看到了像山一样的分享图片代码,并且穿插着各种兼容胶水代码,如此宏大的代码只是为了生成一个小卡片的布局,如果是 html 布局,应该 100 行就能写完,过后就想着怎么来进行重构。 鉴于开发工夫还很富余,我在想有没有其余更便捷、牢靠、通用一点的解决方案,并且本人对这块也始终很感兴趣,秉持着学习的态度,于是萌发了本人写一个库的想法,通过思考后我抉择了 react-canvas 的实现思路,然而react-canvas依赖于React框架,为了放弃通用性,咱们本次开发的引擎不依赖特定web框架、不依赖 dom 的 api,能依据相似 css 的样式表来生成布局渲染,并且反对进阶性能能够进行交互。 在梳理了要做的性能后,一个繁难的 canvas 排版引擎浮现脑海。 什么是排版引擎 排版引擎(layout engine),也称为浏览器引擎(browser engine)、页面渲染引擎(rendering engine)或样版引擎,它是一种软件组件,负责获取标记式内容(如 HTML、XML 及图像文件等等)、整顿信息(如 CSS 及 XSL 等),并将排版后的内容输入至显示器或打印机。所有网页浏览器、电子邮件客户端、电子阅读器以及其它须要依据示意性的标记语言(Presentational markup)来显示内容的应用程序都须要排版引擎。摘自 Wikipedia 对浏览器排版引擎的形容,对于前端同学来说这些概念应该是比拟相熟的,常见的排版引擎比方 webkit、Gecko 等。 ...

February 8, 2022 · 4 min · jiezi

关于浏览器:V8引擎的JavaScript内存机制

对于前端攻城师来说,JS的内存机制不容忽视。如果想成为行业专家,或者打造高性能前端利用,那就必须要弄清楚JavaScript的内存机制了 先看栗子 function foo (){ let a = 1 let b = a a = 2 console.log(a) // 2 console.log(b) // 1 let c = { name: '掘金' } let d = c c.name = '沐华' console.log(c) // { name: '沐华' } console.log(d) // { name: '沐华' } } foo()能够看出在咱们批改不同数据类型的值后,后果有点不一样。 这是因为不同数据类型在内存中存储的地位不一样,在JS执行过程中,次要有三种内存空间:代码空间、栈、堆 代码空间次要就是存储可执行代码,对于这个内容有点多,能够看我另一篇文章有具体介绍 咱们先看一下栈和堆 栈和堆在JS中,每一个数据都须要一个内存空间。而不同的内存空间有什么区别特点呢?,如图 调用栈也叫执行栈,它的执行准则是先进后出,后执行的会先出栈,如图 栈: 存储根底类型:Number, String, Boolean, null, undefined, Symbol, BigInt存储和应用形式后进先出(就像一个瓶子,后放进去的货色先拿进去)主动分配内存空间,主动开释,占固定大小的空间存储援用类型的变量,但实际上保留的不是变量自身,而是指向该对象的指针(在堆内存中寄存的地址)所有办法中定义的变量存在栈中,办法执行完结,这个办法的内存栈也主动销毁能够递归调用办法,这样随着栈深度减少,JVW维持一条长长的办法调用轨迹,内存不够调配,会产生栈溢出堆: 存储援用类型:Object(Function/Array/Date/RegExp)动态分配内存空间,大小不定也不会主动开释堆内存中的对象不会因为办法执行完结就销毁,因为有可能被另一个变量援用(参数传递等)为什么会有栈和堆之分通常与垃圾回收机制无关。每一个办法执行时都会建设本人的内存栈,而后将办法里的变量一一放入这个内存栈中,随着办法执行完结,这个办法的内存栈也会主动销毁 为了使程序运行时占用的内存最小,栈空间都不会设置太大,而堆空间则很大 每创立一个对象时,这个对象会被保留到堆中,以便重复复用,即便办法执行完结,也不会销毁这个对象,因为有可能被另一个变量(参数传递等)援用,直到对象没有任何援用时才会被零碎的垃圾回收机制销毁 而且JS引擎须要用栈来维护程序执行期间上下文的状态,如果所有的数据都在栈里在,栈空间大了的话,会影响到上下文切换的效率,进而影响整个程序的执行效率 ...

October 4, 2021 · 1 min · jiezi

关于浏览器:深入理解浏览器中的进程与线程

过程和线程的分割和区别当咱们启动某个程序时,操作系统会给该程序创立一块内存(当程序敞开时,该内存空间就会被回收),用来寄存代码、运行中的数据和一个执行工作的主线程,这样的一个运行环境就叫过程 而线程是依附于过程的,在过程中应用多线程并行处理能晋升运算效率,过程将工作分成很多细小的工作,再创立多个线程,在外面并行别离执行 过程和线程的关系特点是这样的: 过程与过程之间齐全隔离,互不烦扰,一个过程解体不会影响其余过程,防止一个过程出错影响整个程序过程与过程之间须要传递某些数据的话,就须要通过过程通信管道IPC来传递一个过程中能够并发多个线程,每个线程并行执行不同的工作一个过程中的任意一个线程执行出错,会导致这个过程解体同一过程下的线程之间能够间接通信和共享数据当一个过程敞开之后,操作系统会回收该过程的内存空间晚期浏览器 2007年以前浏览器并不是多过程的构造,而是单过程的构造,一个过程中蕴含了网络、JS运行环境、渲染引擎、页面、插件等,这也导致单过程的构造引发了很多问题 一是不稳固,其中一个线程卡死,可能会导致整个程序出问题,比方关上多个标签页,其中一个标签页卡死可能会导致整个浏览器无奈失常运行二是不平安,浏览器一个过程里是能够共享数据的,那JS线程岂不是能够随便拜访浏览器过程内的所有数据,这显然不合理三是不晦涩,一个过程须要负责太多事件,会导致运行效率问题所以为了解决这些问题,才倒退出了多过程构造 咱们来看一下目前最新的Chrom有过程架构 Chrome 关上一个页面有多少过程?别离是哪些浏览器从敞开到启动,而后新开一个页面至多须要:1个浏览器过程,1个GPU过程,1个网络过程,和1个渲染过程,一共4个过程; 后续如果再关上新的标签页:浏览器过程,GPU过程,网络过程是共享的,不会重新启动,而后默认状况下会为每一个标签页配置一个渲染过程,然而也有例外,比方从A页面外面关上一个新的页面B页面,而A页面和B页面又属于同一站点的话,A和B就共用一个渲染过程,其余状况就为B创立一个新的渲染过程 所以,最新的Chrome浏览器包含:1个浏览器主过程,1个GPU过程,1个网络过程,多个渲染过程,和多个插件过程 浏览器过程: 负责管制浏览器除标签页外的界面,包含地址栏、书签、后退后退按钮等,以及负责与其余过程的协调工作,同时提供存储性能GPU过程:负责整个浏览器界面的渲染。Chrome刚开始公布的时候是没有GPU过程的,而应用GPU的初衷是为了实现3D CSS成果,只是前面网页、Chrome的UI界面都用GPU来绘制,这使GPU成为浏览器广泛的需要,最初Chrome在多过程架构上也引入了GPU过程网络过程:负责发动和承受网络申请,以前是作为模块运行在浏览器过程一时在面的,前面才独立进去,成为一个独自的过程插件过程:次要是负责插件的运行,因为插件可能解体,所以须要通过插件过程来隔离,以保障插件解体也不会对浏览器和页面造成影响渲染过程:负责管制显示tab标签页内的所有内容,外围工作是将HTML、CSS、JS转为用户能够与之交互的网页,排版引擎Blink和JS引擎V8都是运行在该过程中,默认状况下Chrome会为每个Tab标签页创立一个渲染过程咱们平时看到的浏览器呈现出页面过程中,大部分工作都是在渲染过程中实现,所以咱们来看一下渲染过程中的线程 渲染过程中的线程GUI渲染线程:负责渲染页面,解析html和CSS、构建DOM树、CSSOM树、渲染树、和绘制页面,重绘重排也是在该线程执行JS引擎线程:一个tab页中只有一个JS引擎线程(单线程),负责解析和执行JS。它GUI渲染过程不能同时执行,只能一个一个来,如果JS执行过长就会导致阻塞掉帧计时器线程:指setInterval和setTimeout,因为JS引擎是单线程的,所以如果处于阻塞状态,那么计时器就会不准了,所以须要独自的线程来负责计时器工作异步http申请线程: XMLHttpRequest连贯后浏览器开的一个线程,比方申请有回调函数,异步线程就会将回调函数退出事件队列,期待JS引擎闲暇执行事件触发线程:次要用来管制事件循环,比方JS执行遇到计时器,AJAX异步申请等,就会将对应工作增加到事件触发线程中,在对应事件合乎触发条件触发时,就把事件增加到待处理队列的队尾,等JS引擎解决说下浏览器的过程、线程模型,线程模型中的每个线程都是干嘛用的?Chrome为例,有四种过程模型,别离是 Process-per-site-instance:默认模式。拜访不同站点创立新的过程,在旧页面中关上的新页面,且新页面与旧页面属于同一站点的话会共用一个过程不会创立Process-per-site:同一站点应用同一过程Process-per-tab:每一个标签页都创立新的过程Single Process:单过程模式线程模型中的线程都是干嘛的呢? MessagePumpForIO:解决过程间通信的线程,在Chrome中,这类线程都叫做IO线程MessagePumpForUI:解决UI的线程用的MessagePumpDefault:个别的线程用到的每一个Chrome的线程,入口函数都差不多,都是启动一个音讯循环,期待并执行工作 你晓得哪些过程间通信的形式?管道通信:就是操作系统在内核中开拓一段缓冲区,过程1能够将须要交互的数据拷贝到这个缓冲区里,过程2就能够读取了音讯队列通信:音讯队列就是用户能够增加和读取音讯的列表,音讯队列里提供了一种从一个过程向另一个过程发送数据块的办法,不过和管道通信一样每个数据块有最大长度限度共享内存通信:就是映射一段能被其余过程拜访的内存,由一个过程创立,但多个过程都能够拜访,共享过程最快的是IPC形式信号量通信:比方信号量初始值是1,过程1来拜访一块内存的时候,就把信号量设为0,而后过程2也来拜访的时候看到信号量为0,就晓得有其余过程在拜访了,就不拜访了socket:其余的都是同一台主机之间的过程通信,而在不同主机的过程通信就要用到socket的通信形式了,比方发动http申请,服务器返回数据多标签之间怎么通信?没有方法间接通信,须要有一个相似中介者进行音讯的转发和接管,比方 localStorage:在一个标签页监听localStorage的变动,而后当另一个标签页批改的时候,能够通过监听获取新数据WebSocket:因为websocket能够实现实时服务器推送,所以服务器就能够来当这个中介者。标签页通过向服务器发送数据,而后服务器再向其余标签推送转发ShareWorker:会在页面的生命周期内创立一个惟一的线程,并开启多个页面也只会应用同一个线程,标签页共享一个线程postMessage: // 发送方 window.parent().pastMessage('发送的数据','http://接管的址') // 接管方 window.addEventListener('message',(e)=>{ let data = e.data })你晓得僵尸过程和孤儿过程吗?孤儿过程:故名思义,就是没爹的孩子。父过程退出了,而它的一个或多个过程还在运行,那么这些子过程都会成为孤儿过程。这些孤儿都将被init过程收养,并负责这些孤儿的当前僵尸过程:就是子过程比父过程先完结,而父过程又没有开释子过程占用的资源,那么子过程的形容还留在零碎中,这种过程就是僵尸过程结语点赞反对、手留余香、与有荣焉 参考浏览器工作原理与实际

October 1, 2021 · 1 min · jiezi

关于浏览器:浏览器工作原理及V8引擎

浏览器解析过程当浏览器加载html资源时,会进行如下的解析过程 遇见 HTML 标记,构建 DOM 树遇见 style/link 标记调用相应解析器解决CSS标记,并构建出CSS款式树遇见 script 标记 调用javascript引擎 解决script标记、绑定事件、批改DOM 树/CSS树等将 DOM树 与 CSS树 合并成一个渲染树依据渲染树来渲染,以计算每个节点的几何信息最终将各个节点绘制到屏幕上用一张十分经典的图来示意 浏览器引擎浏览器引擎分为两局部,渲染引擎和js引擎。 渲染引擎用于解析、解决html、css文件、布局绘制JavaScript引擎用于解析js文件,常见的JavaScript引擎有JavascriptCore、V8咱们晓得,javascript是一门高级语言,它须要通过编译能力被计算机辨认,那么编译的这个过程就由V8引擎来实现 V8引擎V8解决JS文件通过以下几个步骤 1、Parser模块拥护Javascript代码进行词法剖析,解析成AST(形象语法树)AST的生成如下图所示,定义一个名为name的常量,解析成左边的树结构 这样所有的代码的构造都十分对立,便于解决 2、Ignition将AST解析成bytecode(字节码),最初依据不同的操作系统/环境编译成计算机可辨认的机器码比方windows/macos,cpu架构不统一所能执行的机器指令是不一样的,字节码能够跨平台,等到执行的时候,V8引擎再将字节码解析成机器码 3、Ignition收集优化信息,通过Turbofan将bytecode编译成机器码如果一个函数会被屡次执行,AST-->bytecode-->机器码这样的过程比拟节约性能,为了进行优化,会标记此函数为热点函数,此时Ignition会收集优化信息,如函数的参数,这样间接通过Turbofan将字节码编译成机器码。如果优化信息产生的变动,比方函数入参的类型始终是 number,忽然变成了string,这时候Turbofan会将机器码反向的编译成字节码,再同Iginition解析成机器码执行。 图示如下 Parser模块Parser模块解析成AST的过程中还通过了以下步骤 Blink(Chrome浏览器内核)将源码交给V8引擎,Stream进行编码转换Scanner进行词法剖析后,将代码转成tokenPreParser(预解析),如果函数没有调用则不会被解析Parser模块解析成ASTIgnition、Toburfan再解析图示如下

September 25, 2021 · 1 min · jiezi

关于浏览器:Mozilla火狐浏览器背后神秘又伟大的开源组织|Open-Source-Prism

https://www.bilibili.com/vide...

September 20, 2021 · 1 min · jiezi

关于浏览器:前端要知道的浏览器知识

渲染从浏览器多过程到JS单线程,JS运行机制最全面的一次梳理 浏览器过程页面渲染过程复合图层EventLoop回流与重绘制 https://juejin.cn/post/684490...Tasks, microtasks, queues and schedules 译文参考:https://segmentfault.com/a/11...Tasks, microtasks具体介绍 + 运行演示网络

September 1, 2021 · 1 min · jiezi

关于浏览器:从输入URL到页面呈现超详细

解析地址栏中的信息浏览器监听用户输出的信息并尝试匹配你想要拜访的网址或关键词。以掘金为例,在浏览器地址栏中输出信息,而后回车,浏览器会进行以下判断: 判断是否是非法的 URL 链接;是。持续判断 URL 是否残缺,如果不残缺,浏览器可能会对域进行猜想,对输出的内容增加前缀、后缀、或者前后缀来补全 URL,常见的 URL 通产包含: 协定:如 http https websocket域名(主机名):可能是IP地址,也可能是域名。域名可能由根域名、顶级域名、二级域名等组成,域名的叫法是依据域名从右向左以 . 分隔进行划分,比方:juejin.cn.,. 代表根域名,.cn 代表顶级域名,juejin.cn 代表二级域名(也就是主机名)端口号:http 协定默认端口号为 80,https 协定默认端口号为 443。浏览器会自动隐藏默认端口号。门路:以 / 划分每一层目录,比方:/web/user查问:以 ? 开始,以 & 分隔键值对,如:?username="张三"&age=16哈希:以 # 开始,利用它可实现定位到以后页面的具体位置否。浏览器将输出的内容作为搜寻条件,应用用户设置的默认搜索引擎进行查问并返回后果查找强缓存浏览器过程通过过程间通信(IPC)将 URL 申请发送给网络过程,网络过程接管到URL申请后,会发动真正的申请。但在申请之前,网络过程会查找本地是否缓存了该资源。如果有缓存资源,那么间接返回资源给浏览器过程。首选,查找强缓存资源,如果有则查看强缓存资源是否过期,没过期间接应用该资源,过期则从新向服务器申请资源。强缓存波及到两个字段: Expires。即过期工夫(Expires=Wed, 21 Oct 2015 07:28:00 GMT),HTTP/1.0 采纳此字段,它存在于服务器返回的响应头中,告知浏览器在过期工夫范畴内间接应用缓存资源。但它有个很大的毛病,当服务器和客户端的工夫不统一时,那么服务器返回的工夫是不精确的,因而,HTTP/1.1 摈弃了这个字段而采纳了 Cache-Control字段Cache-Control。即过期时长(Cache-Control:max-age=3600),HTTP/1.1 采纳此字段,它也存在于服务器返回的响应头中,告知浏览器在过期时长范畴内间接应用缓存资源。它还能够设置其余指令,上面列举一些要害指令: public。浏览器和代理服务器都能够缓存资源private。只能浏览器缓存资源,代理服务器不能缓存资源no-cache。跳过强缓存阶段。向服务器发送申请,进入协商缓存阶段no-store。不缓存s-maxage。代理服务器的缓存工夫must-revalidate。一旦缓存过期,就必须回到源服务器验证扩大: 怎么设置强缓存?能够在服务端代码中设置 Cache-Control 字段以及他对应的值;强缓存资源缓存在哪儿? memory cache 或 disk cache ,也就是内存或硬盘中,个别会将图片、脚本文件、字体文件缓存在 memory cache 中;将款式文件缓存在 disk cache 中。拜访缓存的优先级?遵循三级缓存原理:先在 memory cache 中找,有则间接应用;没有再去 disk cache 中找,有则指间接应用;没有就进行网络申请,将申请返回的资源依据响应头字段信息进行缓存。DNS域名解析如果在强缓存中没有找到所需资源,那么间接进入网络申请流程。通常状况下,咱们在浏览器的地址栏中输出的都是域名,而在网络通信中是以 IP 地址确定目标主机的,所以还得通过域名找到对应的 IP 地址。 DNS 又是什么?DNS全名是 domain name system(域名零碎),它将域名和 IP 地址映射关系保留在一个分布式数据库中,所以咱们能够通过 DNS 找到对应的 IP,而这个查找的过程就是 DNS 域名解析。上面以 juejin.cn.来剖析域名的解析过程: ...

July 13, 2021 · 3 min · jiezi

关于浏览器:浏览器设备信息UserAgent查询

浏览器设施信息UserAgent查问浏览器设施信息UserAgent查问 本工具反对对用户以后浏览器设施的UserAgent,IP,设施信息等进行查问。 https://tooltt.com/ua/

June 26, 2021 · 1 min · jiezi

关于浏览器:Chrome的同源

好久不见。 又来了好久好久好久不见。但有时候我想想,人还是得学习。保持真的是个无益的能力。同源策略(主讲Chrome 91的坑)简要介绍同源(same-origin):那就是常见的三个元素,协定,主机名,端口(scheme, hostname, port)都得一样。同站(same-site):无效的顶级域名+顶级域名前的局部,都得一样。Same Origin 上图中的 scheme, hose name, port 都雷同则为 same origin ,否则为 cross origin。 Origin A(雷同的例子)Origin BExplanation of whether Origin A and B are "same-origin" or "cross-origin"https://www.example.com:443https://www.evil.com:443cross-origin: different domainshttps://www.example.com:443https://example.com:443cross-origin: different subdomainshttps://www.example.com:443https://login.example.com:443cross-origin: different subdomainshttps://www.example.com:443http://www.example.com:443cross-origin: different schemeshttps://www.example.com:443https://www.example.com:80cross-origin: different portshttps://www.example.com:443https://www.example.com:443same-origin: exact matchhttps://www.example.com:443https://www.example.comsame-origin: implicit port number (443) matchesSame Site 上图中的 eTLD+1 雷同则为 same site ,否则为 cross site。 Origin A(雷同的例子)Origin BExplanation of whether Origin A and B are "same-site" or "cross-site"https://www.example.com:443https://www.evil.com:443cross-site: different domainshttps://www.example.com:443https://login.example.com:443same-site: different subdomains don't matterhttps://www.example.com:443http://www.example.com:443same-site: different schemes don't matterhttps://www.example.com:443https://www.example.com:80same-site: different ports don't matterhttps://www.example.com:443https://www.example.com:443same-site: exact matchhttps://www.example.com:443https://www.example.comsame-site: ports don't matterSchemeful Same Site ...

June 7, 2021 · 1 min · jiezi

关于浏览器:IE-浏览器将退出历史舞台微软2022-年-Win10-不再支持-IE-11-桌面应用程序

5 月 19 日,微软发表 Windows 10 版本中的 Internet Explorer 浏览器将被 Microsoft Edge 取代,2022 年 6 月 15 日某些 Windows 10 版本将停止使用 Internet Explorer 11 桌面应用程序。微软示意,Microsoft Edge 比 IE 浏览器更快、更平安、为用户带来更古代的浏览体验,而且还解决了一个关键问题:对旧的、legacy 网站和利用具备兼容性。Microsoft Edge 内置了 Internet Explorer 模式(IE mode),用户能够间接从 Microsoft Edge 拜访基于旧版 Internet Explorer 的网站和利用。 不过,该决策对 Windows 10 LTSC、Server Internet Explorer 11 桌面应用程序、MSHTML (Trident) 引擎不会造成影响,Windows 7 和 8.1 版本也不会受到影响。 (图注)微软此次决策受影响和未受影响的 Windows 版本。 此外,微软给出了 IE 11 桌面应用程序的「服役」时间表:2021 年 8 月 17 日,Microsoft 365 等 app 将进行反对 IE 11;2022 年 6 月 15 日,进行反对 IE11 桌面应用程序。 ...

May 20, 2021 · 2 min · jiezi

关于浏览器:chrome浏览器中使用adblockplus拦截广告

adblock plus是一款能够屏蔽广告以及任何你想屏蔽元素的软件,屏蔽之后的成果如下图所示,abp主动屏蔽广告,还能够自行添加屏蔽内容,右上角红色的ABP标识就是该软件 下载地址 https://downloads.adblockplus.org/devbuilds/adblockpluschrome/ 抉择一个下载,倡议不要在chrome浏览器中,chrome浏览器中下载实现之后无奈装置到利用市场会间接删除下载的文件,亲测firefox中能够下载。 下载实现之后将文件名后缀改成 rar,并解压,在谷歌浏览器的拓展程序中加载已解压的拓展程序 加载实现之后在拓展程序中就能看到对应的插件 而后在右上角拓展程序按钮将adblock plus插件固定住 抉择拦挡元素,淡黄色框住的内容就是拦挡的内容 确定增加过滤元素 此时浏览器页面干干净净,就能够欢快的上网啦~

May 14, 2021 · 1 min · jiezi

关于浏览器:译深入了解现代web浏览器三渲染进程内部工作

渲染过程的外部工作这是咱们理解浏览器如何工作4篇博客的第3篇。之前,咱们介绍了 多过程架构 和 导航流程。在本文中,咱们将钻研渲染过程外部产生了什么。 渲染器过程波及web优化的许多方面。因为渲染过程外部产生了很多事件,因而本文只是概述。如果你想更加深刻,“web基础知识的性能优化局部”有更多的资源。 渲染过程解决web内容渲染过程负责解决tab选项卡内产生的所有事件。在一个渲染过程中,主过程解决你发送给用户的大多数代码。如果有时候你应用web worker 或者 service worker,你的局部 javascript 由woker线程解决。排版和光栅线程也在渲染过程外部运行,以便高效,晦涩的渲染页面。 渲染过程的外围工作是将html、css和javascript转换为能够与用户交互的web页面。 解析DOM结构当渲染过程接管到导航提交的信息,并且开始接管html数据,主线程开始解析文本字符(html),并且将其转换为文档对象模型(DOM)。 DOM是一个浏览器对页面的外部示意,也是web开发者能够通过javascript与之交互的数据结构和API。 解析一个HTML文档到DOM是通过HTML规范来定义的。你可能留神到将HTML放到浏览器素来没有抛出过谬误。例如,短少闭合 </p> 是一个非法的HTML。谬误的标记例如 Hi! I'm Chrome! (b标签在i标签之前敞开),它被看成是 Hi! I'm Chrome!。这是因为HTML标准定义了如何优雅的解决这些谬误。如果你关怀这些事件是如何实现的,你能够浏览HTML标准中“错误处理和解析中奇怪的例子”一节。 子资源加载一个网站通常会应用额定的资源,例如图片、css和javascript。这些文件须要从网络或者缓存中加载。主过程能够 在解析构建DOM的时候一个接一个的申请他们,但为了加快速度,“预加载扫描”会同时运行。如果在HTML外面有一些像img和link的内容,预加载会查看HTML解析生成的token,并且在浏览器过程中发送网络申请。 javascript会阻塞解析当HTML解析发现一个script标签,它进行解析HTML文档,并且去加载,解析,并且执行javascript代码。为什么?因为javascript能够扭转文档的形态,应用document.write()能够扭转整个的DOM构造(解析模型概述在HTML定义里有一个好的绘图)。这是HTML解析为什么在复原HTML文档解析之前期待javascript执行。如果你像深刻理解javascript执行产生了什么,V8团队对于此的探讨在这。 提醒浏览器如何加载资源web开发者有很多种形式能够发送提醒给浏览器按序加载资源。如果你的javascript没有应用document.write(),你能够增加 async 和 defer 属性给 <script> 标签。浏览器能够异步地加载和执行 javascript 代码,并且不会阻塞html解析。如果适宜的话你可能也会应用 javascript module。<link rel="preload">是一种告诉浏览器在以后导航须要尽可能早的下载资源的形式。你能够浏览更多的内容在这里 Resource Prioritization – Getting the Browser to Help You. 款式计算只有一个DOM是不足以晓得页面长什么样子的,因为咱们能够在css中设置页面款式。主线程解析CSS并且为每个DOM节点精确地计算出款式。这是基于CSS选择器为每个元素利用对应款式的信息。你能够在 DevTools 中的 computed 局部看到这些信息。 即便你没有提供任何的CSS,每个DOM节点也会又一个computed款式。<h1>标签比<h2>标签展现进去大,并且为每个元素都定义了外间距。这是因为浏览器有一个默认样式表。如果你想晓得chrome有哪些默认的css,请查看这个chrome默认css。 布局当初渲染过程晓得文档的构造和每个节点的款式,然而这些还不足以渲染一个页面。设想一下你在电话外面为你的敌人形容一副画。“这里有一个大红色的圆和一个小的蓝色正方形”这些信息不可能让你的敌人精确的晓得画到底是什么样子。 布局是一个寻找元素坐标的过程。主线程遍历DOM和计算款式,创立蕴含x,y坐标和边界框大小的布局树。布局树和DOM树有着类似的构造,然而它仅仅蕴含页面上可见元素的关联信息。如果利用了 display:none,那么这个元素就不是布局树的一部分(然而,一个visibility:hidden的元素在布局树中)。相似地,一个蕴含内容的伪类就像p::before{content: "Hi!"}被利用,它会蕴含在布局树中,即便它不在DOM中。 确定页面布局是一个有挑战的工作。即便最简略的页面,从上到下块布局,也必须去思考字体多大,哪里须要换行,因为这些都会影响段落的大小和形态;而且也会影响下一行的段落的内容。 CSS能够使元素浮动到一边,屏蔽溢出项,扭转书写的方向。你能够设想,这个布局阶段有一个艰巨的工作。在Chrome中,一个工程师团队都在为布局工作。如果你想理解更多他们的工作细节,请点击 演讲视频 观看乏味的记录。 绘制有了DOM,款式,布局还是不足以渲染一个页面。假如你想模拟一幅画。你晓得大小,形态,元素的地位,然而你依然须要判断绘制的程序。 例如,会为一些元素设置z-index,在上面的例子中,依照HTML的程序绘制将会呈现谬误的渲染后果。 ...

April 23, 2021 · 1 min · jiezi

关于浏览器:重绘及回流

浏览器的渲染过程解析HTML,生成DOM树,解析CSS,生成CSSOM树将DOM树和CSSOM树联合,生成渲染树(Render Tree)Layout(回流):依据生成的渲染树,进行回流(Layout),失去节点的几何信息(地位,大小)Painting(重绘):依据渲染树以及回流失去的几何信息,失去节点的相对像素Display:将像素发送给GPU,展现在页面上。为了构建渲染树,浏览器次要实现了以下工作: 从DOM树的根节点开始遍历每个可见节点。对于每个可见的节点,找到CSSOM树中对应的规定,并利用它们。依据每个可见节点以及其对应的款式,组合生成渲染树。第一步中,既然说到了要遍历可见的节点,那么咱们得先晓得,什么节点是不可见的。不可见的节点包含: 一些不会渲染输入的节点,比方script、meta、link等。一些通过css进行暗藏的节点。比方display:none。留神,利用visibility和opacity暗藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。回流后面咱们通过结构渲染树,咱们将可见DOM节点以及它对应的款式联合起来,可是咱们还须要计算它们在设施视口(viewport)内的确切地位和大小,这个计算的阶段就是回流。 重绘最终,咱们通过结构渲染树和回流阶段,咱们晓得了哪些节点是可见的,以及可见节点的款式和具体的几何信息(地位、大小),那么咱们就能够将渲染树的每个节点都转换为屏幕上的理论像素,这个阶段就叫做重绘节点。 何时产生回流回流这一阶段次要是计算节点的地位和几何信息,那么当页面布局和几何信息发生变化的时候,就须要回流。比方以下状况: 增加或删除可见的DOM元素元素的地位发生变化元素的尺寸发生变化(包含外边距、内边框、边框大小、高度和宽度等)内容发生变化,比方文本变动或图片被另一个不同尺寸的图片所代替。页面一开始渲染的时候(这必定防止不了)浏览器的窗口尺寸变动(因为回流是依据视口的大小来计算元素的地位和大小的)浏览器的优化机制古代的浏览器都是很聪慧的,因为每次重排都会造成额定的计算耗费,因而大多数浏览器都会通过队列化批改并批量执行来优化重排过程。浏览器会将批改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。然而!当你获取布局信息的操作的时候,会强制队列刷新,比方当你拜访以下属性或者应用以下办法: offsetTop、offsetLeft、offsetWidth、offsetHeightscrollTop、scrollLeft、scrollWidth、scrollHeightclientTop、clientLeft、clientWidth、clientHeightgetComputedStyle()getBoundingClientRect缩小重绘与回流CSS应用 transform 代替 top应用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(扭转了布局防止应用table布局,可能很小的一个小改变会造成整个 table 的从新布局。尽可能在DOM树的最末端扭转class,回流是不可避免的,但能够缩小其影响。尽可能在DOM树的最末端扭转class,能够限度了回流的范畴,使其影响尽可能少的节点。防止设置多层内联款式,CSS 选择符从右往左匹配查找,防止节点层级过多。将动画成果利用到position属性为absolute或fixed的元素上,防止影响其余元素的布局,这样只是一个重绘,而不是回流,同时,管制动画速度能够抉择 requestAnimationFrame,详见探讨 requestAnimationFrame。防止应用CSS表达式,可能会引发回流。将频繁重绘或者回流的节点设置为图层,图层可能阻止该节点的渲染行为影响别的节点,例如will-change、video、iframe等标签,浏览器会主动将该节点变为图层。CSS3 硬件加速(GPU减速),应用css3硬件加速,能够让transform、opacity、filters这些动画不会引起回流重绘 。 JavaScript防止频繁操作款式,最好一次性重写style属性,或者将款式列表定义为class并一次性更改class属性。防止频繁操作DOM,创立一个documentFragment,在它下面利用所有DOM操作,最初再把它增加到文档中。防止频繁读取会引发回流/重绘的属性,如果的确须要屡次应用,就用一个变量缓存起来。对具备简单动画的元素应用相对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

April 10, 2021 · 1 min · jiezi

关于前端:浏览器缓存机制强缓存和协商缓存

1、为什么须要浏览器缓存?当咱们拜访同一个页面时,申请资源、数据都是须要肯定的耗时,如果能够将一些资源缓存下来,那么从第二次拜访开始,就能够缩小加载工夫,进步用户体验,也能加重服务器的压力 2、有哪些缓存?浏览器缓存分为强缓存和协商缓存,当存在缓存时,客户端第一次向服务器申请数据时,客户端会缓存到内存或者硬盘当中,当第二次获取雷同的资源,强缓存和协商缓存的应答形式有所不同。 强缓存:当客户端第二次向服务器申请雷同的资源时,不会向服务器发送申请,而是间接从内存/硬盘两头读取 协商缓存:当客户端第二次向服务器申请雷同的资源时,先向服务器发送申请"询问"该申请的文件缓存在ben'd与服务器相比是否更改,如果更改,则更新文件,如果没有就从内存/硬盘中读取 强缓存由服务器的响应头里 cache-control 和 expires 两个字段决定,协商缓存由 last-modified 和 etag两个字段决定。 3、强缓存(1) expireshttp1.0时定义的字段,示意过期工夫,格局如 expires: Mon, 29 Mar 2021 01:03:05 GMT ,示意在这个工夫之前,如果客户端须要再次获取这个资源,不会向服务器中取,会间接在缓存里读取。 (2) cache-controlhttp1.1时的字段,示意缓存的工夫长度,格局如 cache-control: max-age=2592000,单位为秒,示意可缓存的工夫是30天。cache-contorl 还有其它一些能够设置的值no-cache,示意不进行强缓存,但不影响协商缓存no-store,既不强缓存,也不协商缓存 (3) 两者的优先级:cache-control 的优先级要高于 expires 4、协商缓存(1) last-modified 与 if-modified-sincelast-modified 示意该文件上一次被批改的工夫,格局如 last-modified: Tue, 04 Aug 2020 14:54:28 GMT,当客户端第一次向服务器第一次申请时,服务器会在响应头上带上最初批改工夫 last-modified,等到第二次客户端向服务器申请同样的资源时,客户端会在申请头上的 if-modified-since带上上一次申请的 last-modifed值,服务器对最初批改工夫进行比拟,如果工夫统一,服务器返回304状态码,客户端间接在缓存中读取数据,如果不统一,服务器返回200的状态码,并更新文件 (2) etag 与 if-none-matchetag示意文件的惟一标识,格局如 etag: "5f2976a4-17d",当客户端第一次向服务器第一次申请时,服务器会在响应头上带上文件惟一标识etag,等到第二次客户端向服务器申请同样的资源时,客户端会在申请头上的 if-none-match带上上一次申请的etag值,服务器对etag进行比拟,如果工夫统一,服务器返回304状态码,客户端间接在缓存中读取数据,如果不统一,服务器返回200的状态码,并更新文件 (3) 两者有什么区别呢?etag的呈现时为了解决last-modified所存在的一些问题① 当周期性的更改文件的工夫,然而并没有更改文件的内容时,② last-modifed只能准确到秒,如果一个文件在1秒内更改了屡次,那么无奈更新到最新的数据,而etag的精确度更高③ 某些服务器不能准确的失去文件的最初批改工夫 (4) 两者如何应用last-modified与etag是能够一起应用的,服务器会优先验证etag,统一的状况下,才会持续比对last-modified,最初才决定是否返回304

March 24, 2021 · 1 min · jiezi

关于浏览器:前端面试每日-31-第701天

明天的知识点 (2021.03.17) —— 第701天 (我也要出题)[html] 写一个select下拉分组的组件[css] 说说你对:-webkit-autofill的了解[js] 写一个办法判断浏览器的标签是否在以后页面[软技能] 在线版的代码编辑器,你感觉如何?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

March 17, 2021 · 1 min · jiezi

关于浏览器:浏览器开发者工具之网络面板一

浏览器开发者工具概览 模块1.控制器 2. 过滤器过滤筛选性能。通过过滤器模块来筛选你想要的文件类型。3. 抓图信息用来剖析用户期待页面加载工夫内所看到的内容,剖析用户理论的体验状况勾选面板上的“Capture screenshots”即可启用屏幕截图。4. 工夫线用来展现 HTTP、HTTPS、WebSocket 加载的状态和工夫的一个关系用于直观感触页面的加载过程。如果是多条竖线重叠在一起,那阐明这些资源被同时被加载。5. 具体列表具体记录了每个资源从发动申请到实现申请这两头所有过程的状态,以及最终申请实现的数据信息。通过该列表,你就能很容易地去诊断一些网络问题。6. 下载信息概要下载信息概要中,你要重点关注下 DOMContentLoaded 和 Load 两个事件,以及这两个事件的实现工夫。 DOMContentLoaded,这个事件产生后,阐明页面曾经构建好 DOM 了,这意味着构建 DOM 所须要的 HTML 文件、JavaScript 文件、CSS 文件都曾经下载实现了。Load,阐明浏览器曾经加载了所有的资源(图像、样式表等)。具体列表1. 属性 2. 详细信息 3. 单个资源的工夫线 优化工夫线上耗时我的项目 参考极客工夫-《浏览器原理》 原文地址wolai掘金

March 4, 2021 · 1 min · jiezi

关于浏览器:有道写作浏览器扩展实践

有道写作浏览器扩大作为一款为网页减少英文语法批改的辅助工具,容许用户在任意网页上绝大部分的富文本编辑器、多行文本输入框中编辑英文文本,可实时失去批改后果反馈,并自行承受倡议主动批改,实现完满写作。起源/ 有道技术团队公众号作者/ 李靖雯编辑/ 刘振宇一、背景介绍有道写作服务是有道出品的写作智能批改产品,为用户提供优质的作文拼写、语法、款式方面的批改服务。有道写作不仅仅反对浏览器扩大模式,还反对在其余平台应用:例如有道词典 APP-作文批改、Web 在线端、Word 插件、PC 词典内。欢送各位体验。 http://write.youdao.com/ 浏览器插件在浏览器外面的称说是 Browser Extension,也就是浏览器扩大,是一个扩大网页浏览器性能的插件。它次要基于 HTML、JavaScript、CSS 开发,同时因为是扩大个性,能够利用浏览器自身提供的底层 API 接口进行开发,能够给所用页面或者特定页面增加一些非凡性能而不影响本来页面逻辑。 每个反对扩大的浏览器有本人下载扩大的利用商店,能够间接在利用商店下载。有些产品本人提供浏览器扩大的 .crx 文件让用户下载并装置。 二、适配浏览器有道写作在 Windows/Mac 零碎都可装置,适配 Chrome、360平安浏览器、360极速浏览器、Edge 新版浏览器等,在以上浏览器商店中搜寻有道写作,点击装置按钮即可。 三、性能介绍&成果展现在介绍开发思路与实际之前,咱们先来直观地看一下有道写作浏览器扩大的实际效果,并对其性能进行简略的介绍。 3.1 体现形式视觉效果就是,给谬误的文本字符上面画一条横线,在 hover 的时候,能够给文本减少一个高亮的成果。在选承受倡议的时候,能够替换成咱们想要的文本数据。 3.2 实用场景>>> 在线邮件编辑: 163邮箱 Outlook 邮箱 Gmail >>> 社交动静、评论: Facebook 微博动静 评论 >>> 工具、笔记类: 有道翻译 Google 翻译 石墨文档 3.3 性能介绍>>> 实时批改: 反对一边批改一边实时提供批改反馈,展现批改谬误数量。 >>> 语法检测: ![上传中...]() >>> 加强编辑框: ...

March 3, 2021 · 2 min · jiezi

关于浏览器:浏览器的重排和重绘

参考文章:https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/浏览器的高层构造1、用户界面 - 包含地址栏、后退/后退按钮、书签菜单等。2、浏览器引擎 - 在用户界面和出现引擎之间传送指令。3、渲染引擎 - 负责显示申请的内容。4、网络 - 用于网络调用,比方http申请。5、用户界面后端 - 用于绘制根本的窗口小部件。6、javascript解释器 - 用于解析和执行javascript代码。7、数据存储 - 数据长久化。 和大多数浏览器不同,Chrome 浏览器的每个标签页都别离对应一个渲染引擎实例。每个标签页都是一个独立的过程。渲染引擎主流程1、 解析 HTML 文档,构建DOM树。解析内部 CSS 文件和行内款式,生成 CSS 规定树。2、将 DOM 树和 CSS 规定树连贯在一起创立了另一个渲染树Render Tree,它是一个带有多个视觉属性的(色彩、尺寸)的矩形,这些矩形的排列程序就是它们在屏幕上的显示程序。3、开始进入“布局”解决阶段,也就是为每个节点调配一个呈现在屏幕上的确切坐标。4、“绘制”阶段,渲染引擎遍历渲染树,由用户后端层将每个节点绘制进去。 主流程示例 出现树构建在 DOM 树构建的同时,浏览器还会构建另一个树结构:出现树。这是由可视化元素依照其显示程序而组成的树,也是文档的可视化示意。它的作用是让您依照正确的程序绘制内容。 Firefox 将出现树中的元素称为“框架”。WebKit 应用的术语是出现器或出现对象。 出现器晓得如何布局并将本身及其子元素绘制进去。 每一个出现器都代表了一个矩形的区域,通常对应于相干节点的 CSS 框,它蕴含诸如宽度、高度和地位等几何信息。 重排 or 布局渲染引擎在渲染树创立实现的时候,并不蕴含地位和大小信息。计算这些值的过程称为布局或重排。HTML采纳基于流的布局模型,这意味着大多数状况下只有一次遍历就能计算出几何信息。处于流中靠后地位元素不会影响靠前地位元素的几何特色,因而布局能够依照从左往右,从上往下的程序遍历文档。坐标系是绝对于根框架而建设的,应用的是上坐标和左坐标。布局是一个递归的过程。它从根出现器<html>开始,而后递归遍历局部或所有的框架层次结构,为每一个须要计算的出现器计算几何信息。根出现器的地位右边是0,0,其尺寸为视口(也就是浏览器窗口的可见区域)。所有的出现器都会有一个“layout”或“reflow”办法,没一个出现器都会调用须要布局的子代的layout办法。** Dirty 位零碎为防止对所有细小更改都进行整体布局,浏览器采纳了一种“dirty 位”零碎。如果某个出现器产生了更改,或者将本身及其子代标注为“dirty”,则须要进行布局。有两种标记:“dirty”和“children are dirty”。“children are dirty”示意只管出现器本身没有变动,但它至多有一个子代须要布局。 全量布局和增量布局全局布局是指触发了整个出现树范畴的布局,触发起因可能包含: 影响所有出现器的全局款式更改,例如字体大小更改。屏幕大小调整。布局能够采纳增量形式,也就是只对 dirty 出现器进行布局。 全局布局往往是同步触发的。增量布局是异步执行的,webkit有用于执行增量布局的计时器:对出现树进行遍历,并对dirty出现器进行布局。 绘制在绘制阶段,零碎会便当出现树,并调用出现树的“paint”办法,将出现树的内容显示在屏幕上。绘制也分为全局绘制和增量绘制。增量绘制中,局部出现器产生了更改,然而不会影响整个树,更改后的出现器将其在屏幕上对应的矩形区域设为有效,这导致操作系统将其视为一块“dirty区域”,并生成“paint”事件。操作系统会奇妙地将多个区域合并成一个。 绘制程序绘制的程序就是元素进入堆栈款式上下文的程序。上面是css2标准的绘制流程程序: https://drafts.csswg.org/css2/#stacking-contextWithin each stacking context, the following layers are painted in back-to-front order: ...

February 24, 2021 · 2 min · jiezi

关于浏览器:DOM-元素大小

一、偏移量 (offset dimension)元素的偏移量偏移量,包含元素在屏幕上占用的所有可见空间。元素的可见大小由其高度、宽度决定,包含内容 content 大小、内边距 padding、滚动条 scrollbar 大小、边框 border 大小,不包含外边距 margin。 通过下列 4 个属性能够获得元素的偏移量: offsetHeight :元素在垂直方向上占用的空间大小,以像素计算。包含元素的高度、(可见的)程度滚动条的高度、高低边框高度。offsetWidth :元素在程度方向上占用的空间大小,以像素计算。包含元素的宽度、(可见的)垂直滚动条的高度、左右边框的宽度。offsetLeft :元素的左外边框至蕴含元素 offsetParent 的左内边框之间的像素间隔。offsetTop :元素的上外边框至蕴含元素 offsetParent 的上内边框之间的像素间隔。其中,MDN 对 offsetParent 的定义是: HTMLElement.offsetParent 是一个只读属性,返回一个指向最近的(指蕴含层级上的最近)蕴含该元素的定位元素或者最近的 table,td,th,body元素。当元素的 style.display 设置为 "none" 时,offsetParent 返回 null。offsetParent 很有用,因为 offsetTop 和 offsetLeft 都是绝对于其内边距边界的。须要补充的是,元素的 position 为 fixed 时,offsetParent 也返回 null。元素 body 的 offsetParent 也返回 null。 元素在整个页面上的偏移量:绝对于文档的坐标想要获取元素在页面上的偏移量,即元素在页面上的相对横纵坐标,能够通过累加该元素及其各层蕴含元素的 offsetTop 和 offsetLeft 即可。示例代码如下: function getElementLeft(element) { var actualLeft = element.offsetLeft; var current = element.offsetParent; while (current !== null) { actualLeft += current.offsetLeft; current = current.offsetParent; } return actualLeft;}function getElementTop(element) { var actualTop = element.offsetTop; var current = element.offsetParent; while (current !== null) { actualTop += current.offsetTop; current = current.offsetParent; } return actualTop;}须要留神的是,对于应用表格和内嵌框架布局的页面,因为不同浏览器实现这些元素的形式不同,因而失去的值不肯定可信。另外,如果其中一层蕴含元素呈现滚动条,这两个函数计算的值也不可用。 ...

February 16, 2021 · 1 min · jiezi

关于浏览器:从前端角度看浏览器加载资源的渲染过程

欲知 渲染过程,先看浏览器的组成。如有不对,欢送斧正。我也是查看很多材料,加上集体了解得出的,没找到官网正式文档,固不保障正确,欢送大家指出谬误。。 从前的我认为是这样的从前的我认为,浏览的的组成,是这样的。 但总感觉 逻辑有点不分明。 js引擎,怎么就是浏览器引擎了? 网络 和用户界面,和js 引擎 怎么就同一个级别了? js 引擎和js 解释器是什么关系?? 3个引擎之间的关系是什么?? 改良之后的浏览器组成先理解下 过程和线程,再看组成。 过程和线程的了解! 浏览器的组成 再细细讲下渲染过程,浏览器内核: 所以浏览器加载一个资源的过程是怎么的? 实践和实际相结合晓得这些有什么用?做性能优化。如 不白屏,浏览器渲染页面很快,不卡顿,等等。 直到渲染机制,所以咱们尽量减少重排;只有浏览器要下载资源,在渲染,所以咱们网络越快越好,资源越小越少。一个tcp 能够多个http 申请,申请又一系列过程,所以申请能少则少,如何少,可应用个缓存。 不能少,返回的数据能不能小一点,多余的数据不要。。。。写出高性能,优雅的代码即写出优雅的 性能较好的代码。

February 2, 2021 · 1 min · jiezi

关于浏览器:事件循环Event-loop到底是什么

摘要:本文通过联合官网文档MDN和其余博客深刻解析浏览器的事件循环机制,而NodeJS有另一套事件循环机制,不在本文探讨范畴中。process.nextTick和setImmediate是NodeJS的API,所以本文也不予探讨。首先,先理解几个概念。 Javascript到底是单线程还是多线程语言?Javascript是一门单线程语言。置信应该有不少敌人对于Javascript是单线程语言还有些疑难(题外话:之前在某次面试中遇到一个面试官,一来就是“咱们晓得JS是一门多线程语言。。。”巴拉巴拉,过后就把我给愣住了。),不是有Web Worker能够创立多个线程吗?答案就是,Javascript是单线程的,然而他的运行环境不是单线程。要如何了解这句话,首先得从Javascript运行环境比方浏览器的多线程说起。 浏览器通常蕴含以下线程: GUI渲染线程次要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。当界面须要重绘或者因为某种操作引发回流时,将执行该线程。该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起。JS引擎线程该线程负责解决Javascript脚本,执行代码。负责执行待执行的事件,比方定时器计数完结,或者异步申请胜利并正确返回时,将顺次进入工作队列,期待JS引擎线程执行。该线程与GUI线程互斥,当JS线程执行Javascript脚本事件过长,将导致页面渲染的阻塞。定时器触发线程负责执行异步定时器一类函数的线程,如:setTimeout,setInterval。主线程顺次执行代码时,遇到定时器会将定时器交给该线程解决,当计数结束后,事件触发线程会将计数结束的事件回调退出到工作队列,期待JS引擎线程执行。事件触发线程次要负责将期待执行的事件回调交给JS引擎线程执行。异步http申请线程负责执行异步申请一类函数的线程,如:Promise,axios,ajax等。主线程顺次执行代码时,遇到异步申请,会将函数交给该线程解决,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数退出到工作队列,期待JS引擎线程执行。Web Worker是浏览器为Javascript提供的一个能够在浏览器后盾开启一个新的线程的API(相似下面说到浏览器的多个线程),使Javascript能够在浏览器环境中多线程运行,但这个多线程是指浏览器自身,是它在负责调度治理Javascript代码,让他们在失当机会执行。所以Javascript自身是不反对多线程的。 异步Javascript的异步过程通常是这样的: 主线程发动一个异步申请,异步工作承受申请并告知主线程已收到(异步函数返回);主线程继续执行后续代码,同时异步操作开始执行;异步操作执行实现后告诉主线程;主线程收到告诉后,执行异步回调函数。这个过程有个问题,异步工作各工作的执行工夫过程长短不同,执行实现的工夫点也不同,主线程如何调控异步工作呢?这就引入了音讯队列。 栈、堆、音讯队列栈:函数调用造成的一个由若干帧组成的栈。 堆:对象被调配在堆中,堆是一个用来示意一大块(通常是非结构化的)内存区域。 音讯队列:一个Javascript运行时蕴含了一个待处理音讯的音讯队列。每一个音讯都关联着一个用来解决这个音讯的回调函数。在事件循环期间,运行时会从最先进入队列的音讯开始解决,被解决的音讯会被移出队列,并作为输出参数来调用与之关联的函数。而后事件循环在解决队列中的下一个音讯。 事件循环Event loop理解了上述要点,当初回到主题事件循环。那么Event loop到底是什么呢? Event loop是一个执行模型,在不同的中央有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event loop。当初明确为什么要把NodeJS排除在外了吧?同样网上很多Event loop的相干博文一来就是Javascript的Event loop,实际上说的都是浏览器的Event loop。浏览器的Event loop是在Html5标准中定义的,大抵总结如下: 一个事件循环里有很多个工作队列(task queues)来自不同工作源,每一个工作队列里的工作(task)都是严格依照先进先出的程序执行的,然而不同工作队列的工作执行程序是不确定的,浏览器会本人调度不同工作队列。也有中央把task称之为macrotask(宏工作)。标准中还提到了microtask(微工作)的概念,以下是标准论述的过程模型: 抉择以后要执行的工作队列,抉择一个最先进入工作队列的工作,如果没有工作能够抉择,则会跳转至microtask的执行步骤;将事件循环的以后运行工作设置为已抉择的工作;运行工作;将事件循环的当前任务设置为null,将运行完的工作从工作队列中移除;microtask步骤:进入microtask检查点;更新界面渲染;返回第一步。执行进入microtask检查点时,用户代理会执行以下步骤: 设置进入microtask检查点的标记为true;当事件循环的微工作队列不为空时:抉择一个最先进入microtask队列的microtask,设置事件循环以后运行工作为此microtask;运行microtask;设置事件循环以后运行工作为null,将运行完结的microtask从microtask队列中移除;对于相应事件循环的每个环境设置对象,告诉它们哪些promise为rejected;清理indexedDB的事务;设置进入microtask检查点的标记为false。由上可总结为:在事件循环中,用户代理会一直从task队列中按程序取task执行,每执行完一个task都会查看microtask队列是否为空(执行完一个task的具体标记时函数执行栈为空),如果不为空则会一次性执行完所有microtask。而后再进入下一个循环去task队列中取下一个task执行。 task/macrotask(宏工作)script(整体代码)setTimeoutsetIntervalI/OUI renderingmicrotask(微工作)Promise.then catch finallyMutationObserver来看一个例子: console.log('script start');setTimeout(function() { console.log('setTimeout');}, 0);Promise.resolve().then(function() { console.log('promise1');}).then(function() { console.log('promise2');});console.log('script end'); 运行后果是: script startscript endpromise1promise2setTimeout 那么问题来了,不是说每个事件循环开始会从task队列取最先进入的task执行,而后再执行所有microtask吗?为什么setTimeout是task却在Promise.then这个task的后面呢?反正我一开始是有这个纳闷的,很多文章都没有说分明这个具体执行的程序,大部分都是在形容标准的时候说的是“每个事件循环开始会从task队列中取一个task执行,而后再执行所有microtask”,然而也有局部文章说的是“每个事件循环开始都是先执行所有microtask”。通过自己多方查证,标准里的形容如上的确就是每个事件循环都是先执行task,那为什么下面例子外面体现进去的是先执行所有microtask呢? script(整体代码)属于task。 来看一下下面例子的具体执行过程: 事件循环开始,task队列中只有一个script,抉择script作为事件循环的已抉择工作;script按程序执行,同步代码间接输入(script start、script end);遇到setTimeout,0ms后将回调函数放入task队列;遇到Promise,将第一个then的回调函数放入microtask队列;当所有script代码执行实现后,此时函数执行栈为空,开始查看microtask队列,队列只有第一个.then的回调函数,执行输入“promise1”,因为第一个.then返回的仍然是promise,所以第二个.then的回调会放入microtask队列继续执行,输入“promise2”;此时microtask队列空了,进入下一个事件循环,查看task队列取出setTimeout回调函数,执行输入“setTimeout”,代码执行实现。这样是不是分明了?所以实际上一开始执行script代码的时候就曾经开始事件循环了,这就解释了为什么如同每次都是先执行所有的microtask。同时,这个例子中还引申出一个要点:在执行microtask工作的时候,如果又产生了新的microtask,那么会持续增加到队列的开端,且也会在这个事件循环周期执行,直到microtask队列为空为止。

January 27, 2021 · 1 min · jiezi

关于浏览器:前端面试每日-31-第640天

明天的知识点 (2021.01.15) —— 第640天 (我也要出题)[html] HTML5的Server-Sent和WebSocket如何抉择哪一个?[css] 在Less中是如何导入的?[js] 如何监听浏览器窗口大小变动?[软技能] 批改了文件并已commit,如何吊销?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

January 15, 2021 · 1 min · jiezi

关于浏览器:10步写了个Django网站正经网站

 Django做网站只有10步,真的只有10步,不信?咱们来数数…… 明天次要解说用Pycharm编辑器搭建网站,网站性能是 实现在局域网中疾速传递大文件! 比方:共事要给你个1G的文件,你丢一个网站链接给他。他上传后,文件就在你电脑啦!演示成果如下动图:从电脑F磁盘通过浏览器上传,在D盘呈现文件,这里浏览器上传文件能够在其余任意电脑上,上传文件将存于你的D盘! 装置Pycharm专业版 留神Pycharm须要装置 专业版 Django网站搭建第一步 Pycharm关上后,新建我的项目:点击左上角File文件——New Project创立我的项目弹出方框,即第二步中的图示。 第二步 创立我的项目内容,点击右边 Django ,在上方 Locattion 中输出我的项目地址+项目名称。图中我的项目地址为:" E:PyCharm Project "项目名称为" Decrypt_Test ",用""隔开!点击 "ProjectInterpreter:Python3.7" ,会开展选项如下图。抉择" Existing interpreter "(意思是已存在的解释器)在Interpreter中输出Python环境所在地址+python.exe。能够手工输出,也能够用点击左边"三个点"控件抉择文件门路。最初点击Create创立我的项目。 也能够在开展的 "ProjectInterpreter:Python3.7" 中抉择New environment using,这个是默认抉择的,为每个我的项目独自建设一个虚拟环境。老手倡议抉择此项,节俭操作步骤! 留神装置Pycharm前须要当时装置Python环境,如果不晓得装置的小伙伴能够参照 想学Python不知如何入门,教你! 文章下半段Python环境装置教程。 第三步 第二步创立实现我的项目后,会弹出如下对话框。抉择 Open in current window ,点击 OK 第四步 点击OK后,会进入到方才创立的我的项目,如下图。默认关上了settings.py和urls.py文件,对应的就是左侧文件。 点击界面左下角图标,抉择Terminal,并在光标出输出 python manage.py startapp decrypt,按enter完结后,会在我的项目左侧看见新增了一个文件夹,如下图2: 第五步 在settings.py中的找到 INSTALLED_APPS ,在中括号内新增 'decrypt.apps.DecryptConfig', 示意注册App。如下图: 在settings.py中的找到" ALLOWED_HOSTS ",在中括号内写入 "*" ,代码任意IP地址可拜访你的网站。 ...

January 14, 2021 · 1 min · jiezi

关于浏览器:13-聊聊Chrome从输入url到页面展示

3000字长文预警~ 第一关:Chrome的多过程架构:并发与并行的辨别并发:领有解决多任务的能力并行:领有同时解决多任务的能力过程与线程的辨别:作为科班生,先分享我在OS课堂上听过的,印象最深的的说法:过程是资源分配的最小单位;线程是任务调度的最小单位。 线程必须依靠于过程存在。同一过程内的线程共享过程资源。过程内一个线程解体,则该过程解体。但不会影响到操作系统。过程间通过IPC机制通信:共享内存、socket、管道通信(通过内核)Chrome的多线程架构实现:主过程×1:用户交互、子过程治理、存储管理。网络过程×1:资源加载GPU过程×1:3D渲染渲染过程xn:负责文档解析和子资源加载(每个页面都会创立一个)插件过程×n:因为插件不稳固,须要与其余过程隔离开。第二关:TCP/IP协定四层网络模型OSI七层模型与TCP/IP四层模型比照 我的了解是:OSI 是更偏重于规范性的体系结构,而 TCP/IP 则是目前网络环境中的最佳实际。 实际上在本科的课本中是形象为五层模型来讲的: 应用层(应用层+表示层+会话层)=> 传输层=> 网络层=> 链路层=> 物理层 可见具体的分层形式并不是要害,要害的是上图两头列对应的重要性能是否失去了实现。 数据包的漂泊:这是每个本科老师都十分善于讲的故事。过后听课的我并没有因这个故事而很快了解计算机网络的根本运作,但时至今日,这个例子却很好地领导了我对于计算机网络的了解。 第三关:Domain Name System(DNS)前置内容DNS的工作:对于给定的url查问其对应的ip地址。DNS的查问形式分为两种:迭代 / 递归; 浏览器向本地DNS服务器查问个别采纳递归形式;本地DNS服务器向其余服务器查问个别采纳迭代形式;DNS采纳UDP协定进行查问:疾速高效。查问过程:浏览器将URL发送给本地DNS服务器(LDNS);本地DNS服务器查找本地缓存,若存在该URL的记录且尚未过期,则返回该ip地址,DNS查问过程完结;若本地DNS服务器不存在该URL对应的记录,则迭代查找ip地址;本地DNS服务器首先向根域名服务器发送DNS报文,根域名服务器依据报文中申请的URL返回对应的顶级域名服务器地址本地DNS服务器向顶级域名服务器发送DNS报文,顶级域名服务器依据报文中申请的URL返回对应的权威服务器地址。反复以上过程直到失去最终URL对应的IP地址。本地DNS服务器将该 [ip,url] 的记录写入缓存,将ip返回。第四关:SSL协定 — HTTPSHTTPS是什么?HTTPS (http secure)= HTTP + 混合加密 + 权威认证 + 完整性保障 因为网络中对于HTTPS的详尽介绍十分丰盛,此处不进行具体地叙述了,间接奔向论断。 SSL协定运行机制网络上蕴含两种说法(3个随机数生成密钥 or 2个随机数生成密钥),但大同小异,都采纳了对称加密+非对称加密的形式: Client向Server发送:加密套件列表 + 随机数client_randomServer向Client返回:选中的加密套件 + 随机数server_random + 数字证书(带公钥)Client对数字证书进行合法性验证,若非法:产生一个随机数pre-master,并用该证书中的公钥进行加密。将加密后的数据包发送给ServerServer应用私钥对该数据包进行解密,失去pre-master此时单方利用曾经协商好的加密套件对client_random+server_random+pre-master进行加密生成对称密钥。尔后就能够应用此对称密钥进行加密通信了 第五关:TCP协定TCP协定特点面向连贯,牢靠的传输层协定。 三次握手三次握手的基本目标:确认我方和对方的发送、接管能力是否均失常三次握手过程: Client与Server都明确本身的发送和承受能力失常,须要确定对方的发送和接管能力。 第一次握手:Client向Server发送报文:SYN=1,seq=x;第二次握手:Server接管到报文,向Client发送报文:ACK=1,ack=x+1,SYN=1,seq=y;Server确认Client的发送能力失常:Client发送了SYN报文。 第三次握手:Client接管到报文,向Server发送报文:ACK=1,seq=x+1,ack=y+1。Client确认Server的发送和接管能力失常:因为Server正确地接管并响应了Client。 Server确认Client的接管能力失常:Server接管到了Client的ACK。 为什么不应用两次握手:假如舍弃最初一次握手,则Server端无奈明确Client的接管能力是否失常。 四次挥手四次握手的目标:保障单方的数据均发送结束,再敞开连贯。四次挥手过程: 第一次挥手:Client向Server发送FIN报文,表明Client不会再向Server发送数据:FIN=1,seq=x第二次挥手:Server向Client发送ACK报文示意回应:AKC=1,seq=y,ack=x+1期间Server能够持续向Client发送数据,此时处于TCP的半连贯状态; Client还须要持续监听Server发送来的报文。 第三次挥手:Server向Client发送FIN报文,表明Server不会再向Client发送数据:FIN=1,seq=z,ack=x+1第四次挥手:Client向Server发送ACK报文示意回应:ACK=1,seq=x+1,ack=z+1。同时期待2MSL,若此期间没有接管到报文,则TCP连贯顺利断开。为什么是四次挥手而不是像建设连贯时应用三次挥手?这是因为服务端在LISTEN(监听)状态下,收到建设连贯申请的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而敞开连贯时,当收到对方的FIN报文时,仅仅示意对方不再发送数据了然而还能接收数据,己方是否当初敞开发送数据通道,须要下层利用来决定,因而,己方ACK和FIN个别都会离开发送。 为什么要期待2MSL? 第四次握手,客户端收回ACK但并不会收到回应,所以客户端无奈确认本人的ACK是否顺利达到,所以此时须要期待2MSL来给服务器重发FIN报文的机会。客户端发送ACK后的1MSL内,服务器的计时器也会到时,如果因为某种原因并没有收到ACK,则会重发FIN报文。FIN文件会在1MSL内达到客户端;此时客户端的计时器还未清零,收到FIN后重启2MSL计时器并回送ACK。第五关:ARP协定定义地址解析协定,是通过解析ip地址来寻找MAC地址的一个在网络协议包中十分重要的网络传输协定。ARP属于链路层协定。工作过程同一网段:播送查问 -> 单播回应不同网段:播送查问 -> 单播回应 -> 网关直达 -> 反复参考:https://juejin.cn/post/6890167829984149518从输出URL到页面展现产生了什么?浏览器侧:用户输出url由浏览器判断地址栏中的字符串是否合乎url命名规定。若不合乎则将该字符串交由搜索引擎解决;若正当则进入第二步。 构建HTTP报文GET url HTTP/1.1 ...

January 13, 2021 · 1 min · jiezi

关于浏览器:前端面试每日-31-第622天

明天的知识点 (2020.12.28) —— 第622天 (我也要出题)[html] 说说你对H标签在布局中的重要性的了解[css] Sass脚本反对哪些数据类型?[js] 写一个办法js将数组对象中某个属性值雷同的对象合并成一个新对象[软技能] 浏览器缓存什么时候会呈现如下状况:from disk、from memory?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

December 28, 2020 · 1 min · jiezi

关于浏览器:浏览器缓存的这些知识点你都清楚吗

一、浏览器缓存根本意识分为强缓存和协商缓存 1、浏览器在加载资源时,先依据这个资源的一些http header判断它是否命中强缓存,强缓存如果命中,浏览器间接从本人的缓存中读取资源,不会发申请到服务器。比方某个css文件,如果浏览器在加载它所在的网页时,这个css文件的缓存配置命中了强缓存,浏览器就间接从缓存中加载这个css,连申请都不会发送到网页所在服务器。 2、当强缓存没有命中的时候,浏览器肯定会发送一个申请到服务器,通过服务器端根据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个申请返回,然而不会返回这个资源的数据,而是通知客户端能够间接从缓存中加载这个资源,于是浏览器就又会从本人的缓存中去加载这个资源。 强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发申请到服务器,协商缓存会发申请到服务器。 当协商缓存也没有命中的时候,浏览器间接从服务器加载资源数据。 二、强缓存的原理2.1 介绍当浏览器对某个资源的申请命中了强缓存时,返回的http状态为200,在chrome的开发者工具的network外面size会显示为from cache,比方京东的首页里就有很多动态资源配置了强缓存,用chrome关上几次,再用f12查看network,能够看到有不少申请就是从缓存中加载的 强缓存是利用Expires或者Cache-Control这两个http response header实现的,它们都用来示意资源在客户端缓存的有效期。 Expires是http1.0提出的一个示意资源过期工夫的header,它形容的是一个相对工夫,由服务器返回,用GMT格局的字符串示意,如:Expires:Thu, 31 Dec 2037 23:55:55 GMT- 2.2 Expires缓存原理1、浏览器第一次跟服务器申请一个资源,服务器在返回这个资源的同时,在respone的header加上Expires,如 2、浏览器在接管到这个资源后,会把这个资源连同所有response header一起缓存下来(所以缓存命中的申请返回的header并不是来自服务器,而是来自之前缓存的header) 3、浏览器再申请这个资源时,先从缓存中寻找,找到这个资源后,拿出它的Expires跟以后的申请工夫比拟,如果申请工夫在Expires指定的工夫之前,就能命中缓存,否则就不行 4、如果缓存没有命中,浏览器间接从服务器加载资源时,Expires Header在从新加载的时候会被更新 Expires是较老的强缓存治理header,因为它是服务器返回的一个相对工夫,在服务器工夫与客户端工夫相差较大时,缓存治理容易呈现问题,比方随便批改下客户端工夫,就能影响缓存命中的后果。所以在http1.1的时候,提出了一个新的header,就是Cache-Control,这是一个绝对工夫,在配置缓存的时候,以秒为单位,用数值示意,如:Cache-Control:max-age=315360000- 2.3 Cache-Control缓存原理1、浏览器第一次跟服务器申请一个资源,服务器在返回这个资源的同时,在respone的header加上Cache-Control,如: 2、浏览器在接管到这个资源后,会把这个资源连同所有response header一起缓存下来 3、浏览器再申请这个资源时,先从缓存中寻找,找到这个资源后,依据它第一次的申请工夫和Cache-Control设定的有效期,计算出一个资源过期工夫,再拿这个过期工夫跟以后的申请工夫比拟,如果申请工夫在过期工夫之前,就能命中缓存,否则就不行 4、如果缓存没有命中,浏览器间接从服务器加载资源时,Cache-Control Header在从新加载的时候会被更新 Cache-Control形容的是一个绝对工夫,在进行缓存命中的时候,都是利用客户端工夫进行判断,所以相比拟Expires,Cache-Control的缓存治理更无效,平安一些。 这两个header能够只启用一个,也能够同时启用,当response header中,Expires和Cache-Control同时存在时,Cache-Control优先级高于Expires: = 三、强缓存的治理后面介绍的是强缓存的原理,在理论利用中咱们会碰到须要强缓存的场景和不须要强缓存的场景,通常有2种形式来设置是否启用强缓存1、通过代码的形式,在web服务器返回的响应中增加Expires和Cache-Control Header 2、通过配置web服务器的形式,让web服务器在响应资源的时候对立增加Expires和Cache-Control Header 比方在javaweb外面,咱们能够应用代码设置强缓存还能够通过java代码设置不启用强缓存 nginx和apache作为业余的web服务器,都有专门的配置文件,能够配置expires和cache-control,这方面的常识,如果你对运维感兴趣的话,能够在百度上搜寻nginx 设置 expires cache-control或 apache 设置 expires cache-control 都能找到不少相干的文章。 因为在开发的时候不会专门去配置强缓存,而浏览器又默认会缓存图片,css和js等动态资源,所以开发环境下常常会因为强缓存导致资源没有及时更新而看不到最新的成果,解决这个问题的办法有很多,罕用的有以下几种 解决缓存带来的问题 1、间接ctrl+f5,这个方法能解决页面间接援用的资源更新的问题 2、应用浏览器的隐衷模式开发 3、如果用的是chrome,能够f12在network那里把缓存给禁掉(这是个十分无效的办法) 4、在开发阶段,给资源加上一个动静的参数,如css/index.css?v=0.0001,因为每次资源的批改都要更新援用的地位,同时批改参数的值,所以操作起来不是很不便,除非你是在动静页面比方jsp里开发就能够用服务器变量来解决(v=${sysRnd}),或者你能用一些前端的构建工具来解决这个参数批改的问题 5、如果资源援用的页面,被嵌入到了一个iframe外面,能够在iframe的区域右键单击从新加载该页面,以chrome为例 6、如果缓存问题呈现在ajax申请中,最无效的解决办法就是ajax的申请地址追加随机数 7、还有一种状况就是动静设置iframe的src时,有可能也会因为缓存问题,导致看不到最新的成果,这时候在要设置的src前面增加随机数也能解决问题 8、如果你用的是grunt和gulp、webpack这种前端工具开发,通过它们的插件比方grunt-contrib-connect来启动一个动态服务器,则齐全不必放心开发阶段的资源更新问题,因为在这个动态服务器下的所有资源返回的respone header中,cache-control始终被设置为不缓存 四、强缓存的利用强缓存是前端性能优化最无力的工具,没有之一,对于有大量动态资源的网页,肯定要利用强缓存,进步响应速度。通常的做法是,为这些动态资源全副配置一个超时工夫超长的Expires或Cache-Control,这样用户在拜访网页时,只会在第一次加载时从服务器申请动态资源,其它时候只有缓存没有生效并且用户没有强制刷新的条件下都会从本人的缓存中加载,比方后面提到过的京东首页缓存的资源,它的缓存过期工夫都设置到了2026年 ...

November 19, 2020 · 1 min · jiezi

详解浏览器跨域的几种方法

摘要:本文针对浏览器的跨域个性,做一下深刻介绍,以便咱们在进行WEB前端开发和测试时,对浏览器跨域个性有全面的了解和把握。1 前言在WEB前端开发中,咱们常常会碰到“跨域”问题,最常见的就是浏览器在A域名页面发送B域名的申请时会被限度。跨域问题波及到WEB网页安全性问题,使用不当会造成用户隐衷泄露危险,但有时业务上又须要进行跨域申请。如何正确的应用跨域性能,既能满足业务需要,又可能满足安全性要求,显得尤为重要。 本文针对浏览器的跨域个性,做一下深刻介绍,以便咱们在进行WEB前端开发和测试时,对浏览器跨域个性有全面的了解和把握。 2 背景常识介绍2.1 同源政策1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都履行这个政策。 最后,它的含意是指,A 网页设置的 Cookie,B 网页不能关上,除非这两个网页“同源”。所谓“同源”指的是“三个雷同”: 协定雷同域名雷同端口雷同同源政策的目标,是为了保障用户信息的平安,避免歹意的网站窃取数据。 构想这样一种状况: A 网站是一家银行,用户登录当前,A 网站在用户的机器上设置了一个 Cookie,蕴含了一些隐衷信息(比方贷款总额)。用户来到 A 网站当前,又去拜访 B 网站,如果没有同源限度,B 网站能够读取 A 网站的 Cookie,那么隐衷信息就会透露。更可怕的是,Cookie 往往用来保留用户的登录状态,如果用户没有退出登录,其余网站就能够假冒用户,随心所欲。因为浏览器同时还规定,提交表单不受同源政策的限度。由此可见,同源政策是必须的,否则 Cookie 能够共享,互联网就毫无平安可言了。 以后,如果非同源,共有三种行为受到限制: Cookie、LocalStorage 和 IndexDB 无奈读取DOM 无奈取得AJAX 申请不能发送2.2 为什么要有跨域限度Ajax 的同源策略次要是为了避免 CSRF(跨站申请伪造) 攻打,如果没有 AJAX 同源策略,相当危险,咱们发动的每一次 HTTP 申请都会带上申请地址对应的 cookie,那么能够做如下攻打: 用户登录了本人的银行页面 mybank.com,mybank.com向用户的cookie中增加用户标识。用户浏览了歹意页面 evil.com。执行了页面中的歹意AJAX申请代码。evil.com向http://mybank.com发动AJAX HTTP申请,申请会默认把http://mybank.com对应cookie也...。银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回申请数据。此时数据就泄露了。而且因为Ajax在后盾执行,用户无奈感知这一过程。DOM同源策略也一样,如果 iframe 之间能够跨域拜访,能够这样攻打: 做一个假网站,外面用iframe嵌套一个银行网站 mybank.com。把iframe宽高啥的调整到页面全副,这样用户进来除了域名,别的局部和银行的网站没有任何差异。这时如果用户输出账号密码,咱们的主网站能够跨域拜访到http://mybank.com的dom节点,就能够拿到用户的输出了,那么就实现了一次攻打。所以有了跨域拜访限度之后,咱们才可能平安的上网。 3 浏览器跨域的解决方案3.1 CORS规范CORS 是一个 W3C 规范,全称是跨域资源共享(CORSs-origin resource sharing),它容许浏览器向跨源服务器,收回XMLHttpRequest申请。 其实,精确的来说,跨域机制是阻止了数据的跨域获取,不是阻止申请发送。 CORS须要浏览器和服务器同时反对。目前,所有浏览器都反对该性能,IE浏览器不能低于IE10。 https://caniuse.com/#search=cors 整个CORS通信过程,都是浏览器主动实现,不须要用户参加。对于开发者来说,CORS通信与同源的AJAX通信没有差异,代码齐全一样。浏览器一旦发现AJAX申请跨源,就会主动增加一些附加的头信息,有时还会多出一次附加的申请,但用户不会有感觉。 ...

July 13, 2020 · 2 min · jiezi

各大浏览器本地实现跨域模式

chromemac笔记本 1.彻底敞开chrome浏览器,从程序坞中右击彻底敞开。2.关上终端输出一下命令open -n -a /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --args --user-data-dir="/tmp/chrome\_dev\_test" --disable-web-security firefox装置 "CORS Everywhere"扩大程序 safari1.safari -> 偏好设置 -> 高级 -> 选中“在菜单中显示’开发‘菜单” 2.在“开发”菜单中选中“进行跨源限度”

July 9, 2020 · 1 min · jiezi

TCP-的办公室

新人拜访“您好,我是 TCP 服务的实习生,刚培训完,请问您就是 TCP 老司机吗?” 正坐在办公室悠闲喝着咖啡的我,差点一口喷出来,“哦哦,行,你终于来啦,看来几天前的资源申请通过了啊,老司机?” “浏览器老大这么叫你的,说你厉害,靠谱的很。” “好吧,既然来了,赶紧准备准备!这你的工作台。” 我放下手中的咖啡,走到了一个靠窗的工作台,指了指。 “好嘞。” 说着,刚来的小伙子走了过来。 工作台“好多工具啊,这个通道是什么,这儿还有个钟?都怎么用呀?” 小伙子满脸好奇的问道。 “这样吧,从左到右,我一一给你说说。” “首先,最左边呢,是个通道,这里会蹦出应用层需要我们发送的数据。最常见的就是 HTTP 那小子的报文了。” “了解,我们的工作就是发送应用层的数据。原来不需要我们自己去拿啊!” “旁边呢就是扫描机,你需要根据实际情况把数据进行扫描,扫描机会把数据分块,按序号,放在旁边这个盒子里。” “嗯嗯,这个我清楚,数据块大小需要根据实时的网络情况确定。” “不错。这小伙子脑子还挺灵光。看来培训的不错” 我心中暗暗赞到。 “再往右,你看到这又是一个通道,这个通道就是我们和服务器进行沟通的通道了。通道旁是两个计数器,分别用来记录我们的序号和服务器序号,这个会自动加,你不用操作。就叫 1 号计数器和 2 号计数器吧,等下会用到。” “好的,1 号计数器其实就是报文中的序号?” “嗯嗯,不错,用的时候看一眼就行,在计数器旁边有几个按钮,我给你说说。这里最好记一下。” “嗯嗯” 说着小伙子拿出了笔记本开始记录。 任务降临我指了指标着 SYN 的按钮,说道:“这个是请求连接按钮,也就是通知服务器,我们想发数据给他。” 说着,最左侧的通道突然一闪,蹦出了一个 HTTP 服务打包好的包裹,刚来的小兄弟显的有点慌乱,咽了一口气。 “刚好,那我就那这给给你做一下演示吧。” 说着,我站到了工作台正中。 “嗯。” 小兄弟紧张的说不出话来。 “首先呢,按一下 SYN 按钮,请求发送数据。” 1 号计数器跳了一下,由原本的 0 跳到了 1。“按下这个按钮工作台会发送一个请求连接报文,相信报文的内容你应该清楚吧?” 我问道。 “SYN 为 1,序号为 0。” 看了一眼 1 号计数器,小伙子自信的答道。 “嗯,不错!” 不一会计数器旁的通道蹦出了一段报文,2 号计数器直接由 0 跳到了 11。 “让我们来看看,都有什么,你看这里。” 我指了指报文中的 ACK,小伙子也靠了过来。 ...

November 4, 2019 · 2 min · jiezi

详细判断浏览器运行环境可能是最全的判断值得一看

关注Uzero公众号,更多前端小干货等着你喔! 前言看到标题,大家就能想起这个需求在很多项目上都能用到。我们部署在Web服务器上的前端应用,既可以用PC浏览器访问,也可以用手机浏览器访问,再加上现在智能设备的推广,我们甚至能在车载系统、穿戴设备和电视平台上访问。 设备的多样化让用户无处不在,有时候我们需要根据不同的浏览器运行环境做出对应的处理。浏览器是JavaScript的承载体,我们可以从浏览器上获取相关的信息,来进一步处理我们的业务逻辑。 然而浏览器品牌众多,有些浏览器使用的标准也不太一样,造就了难以统一的判断。下面我大概罗列一下常用的浏览器品牌和在什么情况下使用浏览器运行环境判断。浏览器相关统计数据可以参考这里。 国际五大浏览器品牌:按照全球使用率降序排列Google Chrome:Windows、MacOS、Linux、Android、iOSApple Safari:MacOS、iOSMozilla Firefox:Windows、MacOS、Linux、Android、iOSASA Opera:Windows、MacOS、Linux、Android、iOSMicrosoft Internet Explorer或Microsoft Edge:Windows国产常用浏览器品牌:按照国内使用率降序排列,普遍基于开源项目Chromium进行开发微信浏览器QQ浏览器UC浏览器2345浏览器搜狗浏览器猎豹浏览器遨游浏览器百度浏览器:百度在2019年04月30日宣布停止服务其他浏览器:很多很多,数不清,我就不列出来了顺便吐槽一下这个不要脸的红芯浏览器,明明就是基于Chromium进行二次开发再套多一层外壳,还非得说自己开发的浏览器是世界第五大浏览器,偷吃不抹嘴,还是被眼尖的网友发现了。详情请戳one、two、three。。。。 使用场景判断用户浏览器是桌面端还是移动端,显示对应的主题样式判断用户浏览器是Android端还是iOS端,跳转到对应的App下载链接判断用户浏览器是微信端还是H5端,调用微信分享或当前浏览器分享获取用户浏览器的内核和载体,用于统计用户设备平台分布区间获取用户浏览器的载体版本,用于提示更新信息其实还有很多使用场景,就不一一举例了原理针对处理一个这样的使用场景,其实有一个比较专业的名字,叫做浏览器指纹。我们上面谈到的需求也只是浏览器指纹方案里面的一小部分,而我们需要使用到的浏览器指纹就是UserAgent。 这个UserAgent是何方神圣呢,中文翻译过来就是用户代理。引用百度的定义,就是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU类型、浏览器载体及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。而这些信息也足够我们去判断浏览器运行环境了。 准备目前网上很多解决方法都只是针对系统是否是桌面端还是移动端,Android端还是iOS端,部分浏览器载体的判断和获取等等,没有一个比较完美或者终极的解决方案。 因此我用了很多测试平台整理出一个比较全面的解决方案。这个方案包含浏览器系统及版本、浏览器平台、浏览器内核及版本、浏览器载体及版本、浏览器外壳及版本。 而此方案也是基于navigator.userAgent获取相关浏览器信息(如下),再通过系统、平台、内核、载体、外壳的特有字段进行归类统一,整理出一个完整的浏览器运行环境。 const ua = navigator.userAgent.toLowerCase();// 输出"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"浏览器信息:权重按照以下降序排列浏览器系统:所运行的操作系统,包含Windows、MacOS、Linux、Android、iOS浏览器平台:所运行的设备平台,包含Desktop桌面端、Mobile移动端浏览器内核:浏览器渲染引擎,包含Webkit、Gecko、Presto、Trident浏览器载体:五大浏览器品牌,包含Chrome、Safari、Firefox、Opera、Iexplore/Edge浏览器外壳:基于五大浏览器品牌的内核进行开发,再套一层自研技术的外壳,如国内众多浏览器品牌获取UserAgent是否包含字段:判断是否包含系统、平台、内核、载体、外壳的特有字段const testUa = regexp => regexp.test(ua);获取UserAgent对应字段的版本const testVs = regexp => (ua.match(regexp) + "").replace(/[^0-9|_.]/ig, "").replace(/_/ig, ".");方案上述准备工作完成后,我们就按照权重(<font color="#f66">系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本</font>)根据系统、平台、内核、载体、外壳的特有字段来归类统一浏览器运行环境。 ...

October 17, 2019 · 4 min · jiezi

Chrome-快捷键概览

此文主要归纳 Chrome 中所有键盘快捷键的参考信息。一些快捷键全局可用,而其他快捷键会特定于 Chrome DevTools 中的单一面板。 它就像一本字典,可以随时查看我们需要的快捷键。 访问 DevTools访问 DevToolsMacWindows / Linux打开控制台面板Command + Option + JCtrl + Shift + J打开元素面板Command + Shift + C or Command + Option + CCtrl + Shift + C打开你上次使用的面板Command + Option+IF12 or Ctrl + Shift + IDevTools 跨面板快捷键全局快捷键MacWindows / Linux显示设置? or Function+F1? or F1显示下一个面板Command + ]Ctrl + ]显示上一个面板Command + [Ctrl + [根据历史切换 DevTools 的位置Command + Opt + [Ctrl + Shift + D打开/关闭 Device ModeCommand + Shift + MCtrl + Shift + M在当前文件或面板中搜索文本(不支持 Audits、Application、和 Security 面板)Command + FCtrl + F放大Command + Shift ++Ctrl ++缩小Command + Shift --Ctrl --恢复默认文本大小Command + 0Ctrl + 0打开/关闭 Command MenuCommand + Shift + PCtrl + Shift + P在所有加载的资源中搜索文本Command + Shift + FCtrl + Shift + F打开文件Command + OCtrl + O控制台快捷键控制台快捷键MacWindows / Linux拒绝命令提示建议EscapeEscape接受命令提示建议Right Arrow or TabRight Arrow or Tab输入历史中上/下一个命令向上键/向下键向上键/向下键聚焦到控制台Command + 反引号Ctrl + 反引号清除控制台Command + K or Option + LCtrl + L多行输入Command + ReturnShift + Enter执行ReturnEnterElements 面板快捷键Elements 面板MacWindows / Linux切换为以 HTML 形式编辑Function + F2F2撤消更改Command + ZCtrl + Z重新应用更改Command + Shift + ZCtrl + Y选择当前选定元素上方/下方的元素向上键/向下键向上键/向下键展开/折叠节点向右键/向左键向右键/向左键展开/折叠节点及其所有子节点Opt + 点击箭头图标Ctrl + Alt + 点击箭头图标编辑属性Enter / 双击属性Enter / 双击属性隐藏元素(visibility)HHStyles 边栏快捷键Styles 边栏MacWindows / Linux编辑规则点击点击插入新属性点击空格点击空格前往声明属性值的行Command + 点击属性Ctrl + 点击属性循环展示颜色值的 rbga、hsla 和 hex 形式按住 shift 键,然后单击值旁边的颜色预览框按住 shift 键,然后单击值旁边的颜色预览框编辑下一个/上一个属性Tab / Shift + TabTab / Shift + Tab以 0.1 为增量增大/减小值Opt + 向上键 / Opt + 向下键Alt + 向上键 / Alt + 向下键以 1 为增量增大/减小值向上键/向下键向上键/向下键以 10 为增量增大/减小值Shift + Up / Shift + DownShift + Up / Shift + Down以 100 为增量增大/减小值Command + Up / Command + DownCtrl + Up / Ctrl + DownSources 面板快捷键Sources 面板MacWindows / Linux暂停/继续执行脚本F8 or Command + \F8 or Ctrl + \跳过下一个函数调用F10 or Command + 'F10 or Ctrl + '进入下一个函数调用F11 or Command + ;F11 or Ctrl + ;跳出当前函数Shift + F11 or Command + Shift + ;Shift + F11 or Ctrl + Shift + ;暂停时继续执行某行代码按住 Command,然后单击代码行按住 Ctrl,然后单击代码行前往当前调用栈中当前帧下面的调用帧/帧上面的调用帧Control + . / Control + ,Ctrl + . / Ctrl + ,保存本地更改Command + SCtrl + S保存所有更改Command + Opt + SCtrl + Alt + S跳转到指定行(输入格式:单个数字)Control + GCtrl + G跳转到列(输入格式:行:列)Control + GCtrl + G在资源面板下搜索文件Command + O or Command + PCtrl + O or Ctrl + P关闭活动的标签Option + WAlt + W代码编辑器快捷键代码编辑器MacWindows / Linux跳转到匹配的括号Control + MCtrl + M从光标处删除至当前所在单词结尾Option + DeleteCtrl + Delete添加/删除行断点将光标集中在行上,然后按 Command + B将光标集中在行上,然后按 Ctrl + B切换注释Command + /Ctrl + /继续选择下一个匹配/撤消上一个选择Command + D / Command + UCtrl + D / Ctrl + U性能面板快捷键性能面板MacWindows / Linux开始/停止录制Command + ECtrl + E保存录制Command + SCtrl + S打开录制Command + OCtrl + O内存面板快捷键内存面板MacWindows / Linux开始/停止录制Command + ECtrl + E标签页和窗口快捷键标签页和窗口快捷键MacWindows / Linux打开新窗口Ctrl + N⌘ + N在无痕模式下打开新窗口Ctrl + Shift + N⌘ + Shift + N打开新的标签页,并跳转到该标签页Ctrl + T⌘ + T按标签页的关闭顺序重新打开先前关闭的标签页Ctrl + Shift + T⌘ + Shift + T跳转到下一个打开的标签页Ctrl + Tab 或 Ctrl + PgDn⌘ + Option + 向右箭头键跳转到上一个打开的标签页Ctrl + Shift + Tab 或 Ctrl + PgUp⌘ + Option + 向左箭头键跳转到特定标签页Ctrl + 1 到 Ctrl + 8⌘ + 1 到 ⌘ + 8跳转到最右侧的那个标签页Ctrl + 9⌘ + 9在当前标签页中打开主页Alt + Home⌘ + Shift + H打开当前标签页浏览记录中记录的上一个页面Alt + 向左箭头键⌘ + [ 或 ⌘ + 向左箭头键打开当前标签页浏览记录中记录的下一个页面Alt + 向右箭头键⌘ + ] 或 ⌘ + 向右箭头键关闭当前标签页Ctrl + w 或 Ctrl + F4⌘ + W关闭当前窗口Ctrl + Shift + w 或 Alt + F4⌘ + Shift + W最小化当前窗口Alt + 空格键,然后按 N 键⌘ + M最大化当前窗口Alt + 空格键,然后按 X 键⌘ + H退出 Google ChromeAlt + F,然后按 X 键⌘ + QGoogle Chrome 功能快捷键Google Chrome 功能快捷键MacWindows / Linux打开 Chrome 菜单Alt + F 或 Alt + E 显示或隐藏书签栏Ctrl + Shift + B⌘ + Shift + B打开书签管理器Ctrl + Shift + O⌘ + Option + B在新标签页中打开“历史记录”页Ctrl + H⌘ + Y在新标签页中打开“下载内容”页Ctrl + J⌘ + Shift + J打开 Chrome 任务管理器Shift + Esc 将焦点放置在 Chrome 工具栏中的第一项上Shift + Alt + T 将焦点放置在 Chrome 工具栏中最右侧的那一项上F10 将焦点移到未聚焦于的对话框(如果显示)或所有工具栏F6 打开查找栏搜索当前网页Ctrl + F 或 F3 跳转到与查找栏中搜索字词相匹配的下一条内容Ctrl + G 跳转到与查找栏中搜索字词相匹配的上一条内容Ctrl + Shift + G 打开“清除浏览数据”选项Ctrl + Shift + Delete⌘ + Shift + Delete在新标签页中打开 Chrome 帮助中心F1 使用其他帐号登录或以访客身份浏览Ctrl + Shift + M⌘ + Shift + M打开反馈表单Alt + Shift + I 地址栏快捷键地址栏快捷键MacWindows / Linux使用默认搜索引擎进行搜索输入搜索字词并按 Enter 键输入搜索字词并按 Enter 键使用其他搜索引擎进行搜索输入搜索引擎名称,然后按 Tab 键输入搜索引擎名称,然后按 Tab 键为网站名称添加 www. 和 .com,并在当前标签页中打开该网站输入网站名称并按 Ctrl + Enter 键输入网站名称并按 Control + Enter 键打开新的标签页并执行 Google 搜索输入搜索字词并按 Alt + Enter 键 跳转到地址栏Ctrl + L、Alt + d 或 F6⌘ + l从页面中的任意位置搜索Ctrl + K 或 Ctrl + E从地址栏中移除联想查询内容按向下箭头键以突出显示相应内容,然后按 Shift + Delete 键按向下箭头键以突出显示相应内容,然后按 hift + FN + Delete 键,在笔记本电脑上按 Forward Delete 或 Fn-Delete 键网页快捷键网页快捷键MacWindows / Linux打开选项以打印当前网页Ctrl + p⌘ + P打开选项以保存当前网页Ctrl + S⌘ + S重新加载当前网页F5 或 Ctrl + R 重新加载当前网页(忽略缓存的内容)Shift + F5 或 Ctrl + Shift + R⌘ + Shift + R停止加载网页Esc 浏览下一个可点击项TabTab浏览上一个可点击项Shift + TabShift + Tab显示当前网页的 HTML 源代码(不可修改)Ctrl + U⌘ + Option + U将当前网页保存为书签Ctrl + D⌘ + D将所有打开的标签页以书签的形式保存在新文件夹中Ctrl + Shift + D⌘ + Shift + D开启或关闭全屏模式F11⌘ + Ctrl + F向下滚动网页,一次一个屏幕空格键或 PgDn空格键向上滚动网页,一次一个屏幕Shift + 空格键或 PgUpShift + 空格键转到网页顶部Ctrl + Home 转到网页底部Ctrl + End 在网页上水平滚动按住 Shift 键并滚动鼠标滚轮 将光标移到文本字段中的上一个字词起始处Ctrl + 向左箭头键Option + 向左箭头键将光标移到下一个字词起始处Ctrl + 向右箭头键Option + 向右箭头键删除文本字段中的上一个字词Ctrl + BackspaceOption + Delete鼠标快捷键鼠标快捷键MacWindows / Linux在当前标签页中打开链接(仅限鼠标)将链接拖到标签页中将链接拖到标签页中在新的后台标签页中打开链接按住 Ctrl 键的同时点击链接按住 ⌘ 键的同时点击链接打开链接,并跳转到该链接按住 Ctrl + Shift 键的同时点击链接按住 ⌘ + Shift 键的同时点击链接打开链接,并跳转到该链接(仅使用鼠标)将链接拖到标签栏的空白区域将链接拖到标签栏的空白区域在新窗口中打开链接按住 Shift 键的同时点击链接按住 Shift 键的同时点击链接在新窗口中打开标签页(仅使用鼠标)将标签页拖出标签栏将标签页拖出标签栏将标签页移至当前窗口(仅限鼠标)将标签页拖到现有窗口中将标签页拖到现有窗口中将标签页移回其原始位置拖动标签页的同时按 Esc拖动标签页的同时按 Esc将当前网页保存为书签将相应网址拖动到书签栏中将相应网址拖动到书签栏中在网页上水平滚动按住 Shift 键并滚动鼠标滚轮 下载链接目标按住 Alt 键的同时点击链接按住 Option 键的同时点击链接显示浏览记录右键点击左上角“后退”/“前进”箭头,或左键点击左上角“后退”/“前进”箭头不释放右键点击左上角“后退”/“前进”箭头,或左键点击左上角“后退”/“前进”箭头不释放在最大化模式和窗口模式间切换双击标签栏的空白区域 放大网页上的所有内容按住 Ctrl 键并向上滚动鼠标滚轮 缩小网页上的所有内容按住 Ctrl 键并向下滚动鼠标滚轮 其它整理不易,难得齐全。 ...

October 17, 2019 · 4 min · jiezi

前后端开发数据大小限制

背景编程过程中在存储用户数据的时候,会遇到数据存储大小的限制。经常遇到的限制可以分为:用户侧、服务端、数据库三个方面,按照流程可以划分为5个阶段,如图所示。作为开发人员,需要了解这些限制,避免撞墙。 第一道墙-client:浏览器限制用户通过http请求提交数据,http请求本身是没有数据大小的限制,但是浏览器对URI的长度进行了限制。 浏览器限制的是URI长度,而不是你的请求参数浏览器限制大小(字符)IE2083Chrome8182curl8167汉字字符:在utf-8编码格式中,3个字节,在gbk编码中,2个字节,英文就一个字符一个字节超过限制长度就会返回错误码414 第二道墙-server:服务端限制服务端对于数据的处理能力不同,对于提交的数据限制能力也不同,不同服务器对请求的限制不同,会存在两个方面的限制: URI长度限制(以Node为例)数据包大小限制(以post请求为例)NodeNode对URI的大小有一定的大小限制, 最大值8kb(8 * 1024),一般情况下,不会触发这个限制,如果程序想单独限制一个大小,通过中间件限制下就行了 req.on('data', function(chunk){ received += chunk.length; if (received > bytes) req.destroy();});如果想修改最大的大小,只能去改变源码文件 http_parser.h 了 POST服务器限制大小(字符)apache8192IIS16384nginx8kb附加:get请求是幂等操作,所以可以用缓存来处理,post请求不是幂等的,所以无法应用缓存 第二道墙-数据库代理:DBProxy线上环境会部署几个数据库,通常情况下,会使用DBProxy进行代理,实现负载均衡、IP地址过滤、数据库分表等操作。常见的数据库代理mycat/mybuh/dbproxy等。不同的数据库代理默认的数据大小不同,比如有的公司,DBProxy的代理设置大小最大为150kb。 第三道墙-max_allowed_packet在实际读写数据库的时候,数据库本身会有容量限制,mysql中max_allow_packet,就是第三道墙。max_allow_pocket是数据库对于单个数据包的大小限制。 参数大小默认大小64M(v >= 8.0.3)/4M最大值1G第四道墙-数据库本身大小限制每一条数据在数据库中都有对应的字段对应,而每一个字段会有对应的大小限制,所以在写入数据库的时候就有对应的大小限制。下面以mysql数据库为例说明一下:mysql的数据库类型分为五类:数字类型、日期类型、字符串类型,特殊类型、JSON类型 数字类型类型存储(位)无符号范围有符号范围默认值TINYINT1-128~1270~255SMALLINT2-32768~327670~65535MEDIUMINT3-8388608~83886070~16777215INT4-2147483648~21474836470~4294967295BIGINT8-2的63方~2的63方-10~2的64方-1注意⚠️:超过范围怎么处理? 在严格模式下,会直接报错在非严格模式下,会显示范围的边界,比如要存储9223372036854775808的值,会显示9223372036854775807,因为有符号整数最大值为9223372036854775807假如就是想显示这样的值怎么办? 将值变为有符号的,或者使用字符串。其他基本的数据类型和大小,在这里就不赘述了查看详情 JSON类型对于JSON数据类型,可以通过JSON_STORAGE_SIZE,获取可以存储的JSON数据的大小 总结本文目的不是让你记住这些限制,而是让你知道哪里有数据存储的限制,当出现问题的时候,知道去哪里排查。 参考文献JSON MERGE FUNCTION 对GET和POST的理解很形象 url长度限制 如何选择合适的数据库代理 美团点评的DBProxy 常见的Mysql数据库类型 长度限制 推荐一个面试 JSON_STORAGE_SIZE()

October 15, 2019 · 1 min · jiezi

IPUDP和TCP的关系

互联网,实际上是一套理念和协议组成的体系架构。其中,协议是一套众所周知的规则和标准,如果各方都同意使用,那么它们之间的通信将变得毫无障碍。 IP:把数据包送达目的主机数据包要在互联网上进行传输,就要符合网际协议(IP)标准,互联网上不同的在线设备都有唯一的地址,地址只是一个数字,这和大部分家庭收件地址类似,你只需要知道一个家庭的具体地址,就可以往这个地址发送包裹,这样物流系统就能把物品送到目的地。 计算机的地址就称为 IP 地址,访问任何网站实际上只是你的计算机向另外一台计算机请求信息。 如果要想把一个数据包从主机 A 发送给主机 B,那么在传输之前,数据包上会被附加上主机 B 的 IP 地址信息,这样在传输过程中才能正确寻址。额外地,数据包上还会附加上主机 A 本身的 IP 地址,有了这些信息主机 B 才可以回复信息给主机 A。这些附加的信息会被装进一个叫 IP 头的数据结构里。IP 头是 IP 数据包开头的信息,包含 IP 版本、源 IP 地址、目标 IP 地址、生存时间等信息。 简化的 UDP 网络三层传输模型 UDP:把数据包送达应用程序IP 是非常底层的协议,只负责把数据包传送到对方电脑,但是对方电脑并不知道把数据包交给哪个程序,是交给浏览器还是交给王者荣耀?因此,需要基于 IP 之上开发能和应用打交道的协议,最常见的是“用户数据包协议(User Datagram Protocol)”,简称UDP。 UDP 中一个最重要的信息是端口号,端口号其实就是一个数字,每个想访问网络的程序都需要绑定一个端口号。通过端口号 UDP 就能把指定的数据包发送给指定的程序了,所以IP 通过 IP 地址信息把数据包发送给指定的电脑,而 UDP 通过端口号把数据包分发给正确的程序。和 IP 头一样,端口号会被装进 UDP 头里面,UDP 头再和原始数据包合并组成新的 UDP 数据包。UDP 头中除了目的端口,还有源端口号等信息。 简化的 UDP 网络四层传输模型 UDP 不能保证数据可靠性,但是传输速度却非常快,所以 UDP 会应用在一些关注速度、但不那么严格要求数据完整性的领域,如在线视频、互动游戏等。 ...

August 20, 2019 · 1 min · jiezi

Life-of-a-Pixel浏览器渲染流程概要

本文是 Chrome 团队新人入职学习资料《Life of a Pixel》的概要版,首发于我的博客(点此查看),欢迎关注。原文 Slides 地址:https://bit.ly/lifeofapixel中文字幕演讲视频地址:https://www.bilibili.com/vide...《Life of a Pixel》内容讲的是开发者编写的 web 内容(也就是通常所说的 HTML+CSS+JS 以及 image、video 等其他资源)渲染为图形并呈现到屏幕上的整个过程。我将其演讲内容分为以下三个部分,第一个是静态渲染过程,讲述一个完整的从 content 到 pixel 的渲染过程;第二个是动态更新过程,讲述浏览器如何高效更新页面内容。 概览首先看一下整个过程的概览。在了解详细内容前,我们也大概知道浏览器最终是通过调用 GPU 完成像素到屏幕的绘制。但这个过程中有很多的步骤。注意概览图中浏览器的渲染进程是放在沙箱进程中由 Blink 处理的,这也是其安全策略。 静态渲染过程这一页的内容对于广大前端从业者来说应该都比较熟悉。首先是 HTML 通过 HTMLDocumentParser 转换为 DOM 树,CSS 通过 CSSParser 转换为 StyleRule 集。每个 StyleRule 包含 CSSSelector 和 CSSPropertyValue,当然二者间存在对应关系。再加上浏览器提供的每种类型元素的 DefaultStyle,经过一系列的计算(这一步称为 recalc)生成所有元素包含所有 style 属性值的 ComputedStyle,如右上角的图所示。ComputedStyle 也通过开发者工具和 JS API 暴露了出来,相信大家也不陌生。 接下来一步就是 layout。layout 的功能是根据上一步得到的所有元素的 computedStyle,将所有元素的位置布局计算好。每个元素在这一步会生成一个 LayoutObject,简单来说其包含四个属性:x、y、width 及 height 用于标识其布局位置。layout 最简单的情况就是,所有的块按照 DOM 顺序从上往下排列,也就是我们常说的流。layout 也包含很复杂的情况,比如带有 overflow 属性的元素,浏览器会计算其 border-box 的长宽和实际内容的长宽。如果设置为 scroll 并且内容超出,还要为其预留滚动条的位置。此外, float、flexbox 等布局也会使得 layout 变复杂。。所以为了解决复杂性的问题,layout 阶段浏览器首先会生成一个和 DOM 树节点大致一一对应的 layout 树,然后遍历该树,将经过计算后得出的位置布局数据填入节点。对于这个过程,Chrome 团队认为没有很好地分离输入和输出,因此下一代的 layout 系统会进行重构,使得分层更加清晰。 ...

August 17, 2019 · 2 min · jiezi

页面审核工具-Chrome-Lighthouse-简介

作者:Bolaji Ayodeji翻译:疯狂的技术宅 原文:https://www.freecodecamp.org/... 未经允许严禁转载 Chrome Lighthouse 已经存在了一段时间了,但如果我要求你解释一下它能做什么,你能解释清楚吗? 我发现许多 Web 开发人员包括初学者,都没有听说过这个工具,而那些尚未尝试过的人,一点也不酷 ????。 在本文中,我将向你介绍 Chrome Lighthouse 的作用以及如何使用它。 让我们开始吧 ???? 根据维基百科,lighthouse是一座塔楼、建筑物或其他类型的结构,它用灯和镜头系统发出光线,作为海上或内陆水道船舶的导航设备。好吧,让我们把它变成一个技术术语; Lighthouse 是一个塔楼,建筑物或其他类型的结构,它在 Chrome 开发者工具的“审核”面板下的系统发出光线,并作为开发人员的指南有道理吗????? 好吧,Lighthouse 是 Google 开发的一款工具,用于分析网络应用和网页,收集现代性能指标并提供对开发人员最佳实践的意见。 可以将 Lighthouse 看作是汽车中用来检查和平衡汽车速度限制的车速表。 一般情况下 Lighthouse 与开发最佳实践和性能指标一起使用。它在 Web 应用上运行检查,并为你提供有关错误的反馈、低于标准的实践、更好的性能提示以及如何解决这些问题。 根据 Google Developers Docs 上的描述 Lighthouse 是一种开源的自动化工具,用于提高网页质量。你可以在任何网页上运行它。它能够针对性能、可访问性、渐进式 Web 应用等进行审核。你可以在 Chrome DevTools 中从命令行运行 Lighthouse,也可以作为 Node.js 模块运行。当你向 Lighthouse 提供了一个 URL 来进行审核时,它会针对该页面运行一系列审核,然后生成一个关于该页面执行情况的报告。这份报告可以作为如何改进页面的指标。每次审核都会产生一份参考文档,解释了这些审核为什么重要,以及如何解决等内容。 这几乎都与 Lighthouse 有关,它会审核 Web 应用的 URL 并根据 Web 标准和开发人员最佳实践生成一份报告,告诉你 Web 应用的糟糕程度。报告的每个部分还附有文档,说明你的应用哪些部分已经通过审核,为什么你应该改进应用的某一部分以及如何去解决它。 以下是此博客https://bolajiayodeji.com的lighthouse审核报告演示 ...

June 21, 2019 · 2 min · jiezi

如何使用Web-Share-API

翻译:疯狂的技术宅原文:https://css-tricks.com/how-to... 未经允许严禁转载 Web Share API 自从它首次在Android 版 Chrome 61中推出以来,似乎已经不再受到关注。从本质上讲,它提供了一种方法,可以直接从网站或 Web 应用中共享内容(例如链接或联系人卡片)时触发设备(如果使用 Safari 桌面也可以)的本机共享对话框。 虽然用户已经可以通过本地方式从网页共享内容,但他们必须在浏览器菜单中找到该选项,即使这样,也无法控制共享内容。此API的引入允许开发人员通过利用用户设备上的本机内容共享功能,将共享功能添加到 APP 或网站中。 与传统方法相比,这种方法具有许多优点: 向用户提供了多种共享内容的选项。可以通过取消各个社交平台的第三方脚本来改善页面加载时间。无需为不同的社交媒体网站和电子邮件添加一系列按钮。单个按钮足以触发设备的本机共享选项。用户可以在自己的设备上自定义他们的首选共享目标,而是不仅限于预定义的选项。关于浏览器支持在我们深入了解 API 的工作原理之前,先要解决浏览器支持问题。说实话,目前浏览器支持不是很好。它仅适用于 Android 版 Chrome 和 Safari(桌面版和iOS版)。 下面的浏览器支持数据来自Caniuse,其中包含更多详细信息。数字表示浏览器支持该版本及以上版本的功能。 桌面ChromeOperaFirefoxIEEdgeSafariNoNoNoNoNo12.1手机/平板电脑iOS SafariOpera MobileOpera MiniAndroidAndroid ChromeAndroid Firefox12.2NoNoNo74No但是不要让这些阻止你在自己的网站上使用此 API。正如你所看到的,在不支持的浏览器上很容易实现备用方案。 使用它的一些要求要在你自己的 Web 项目中使用这个 API ,有两件事需要注意: 你的网站必须通过 HTTPS 进行访问。为了便于本地开发,当你的站点在 localhost 上运行时,API也可以运行。为了防止滥用,只能在响应某些用户操作时(例如 click 事件)触发API。这是一个例子To demonstrate how to use this API, I’ve prepared a demo that works essentially the same as it does on my site. Here’s how it looks like: ...

June 17, 2019 · 2 min · jiezi

实现一个前端路由如何实现浏览器的前进与后退

1. 需求如果要你实现一个前端路由,应该如何实现浏览器的前进与后退 ? 2. 问题首先浏览器中主要有这几个限制,让前端不能随意的操作浏览器的浏览纪录: 没有提供监听前进后退的事件。不允许开发者读取浏览纪录,也就是 js 读取不了浏览纪录。用户可以手动输入地址,或使用浏览器提供的前进后退来改变 url。所以要实现一个自定义路由,解决方案是自己维护一份路由历史的记录,从而区分 前进、刷新、回退。 下面介绍具体的方法。 3. 方法目前笔者知道的方法有两种,一种是 在数组后面进行增加与删除,另外一种是 利用栈的后进先出原理。 3.1 在数组最后进行 增加与删除通过监听路由的变化事件 hashchange,与路由的第一次加载事件 load ,判断如下情况: url 存在于浏览记录中即为后退,后退时,把当前路由后面的浏览记录删除。url 不存在于浏览记录中即为前进,前进时,往数组里面 push 当前的路由。url 在浏览记录的末端即为刷新,刷新时,不对路由数组做任何操作。另外,应用的路由路径中可能允许相同的路由出现多次(例如 A -> B -> A),所以给每个路由添加一个 key 值来区分相同路由的不同实例。 注意:这个浏览记录需要存储在 sessionStorage 中,这样用户刷新后浏览记录也可以恢复。笔者之前实现的 用原生 js 实现的轻量级路由 ,就是用这种方法实现的,具体代码如下: // 路由构造函数function Router() { this.routes = {}; //保存注册的所有路由 this.routerViewId = "#routerView"; // 路由挂载点 this.stackPages = true; // 多级页面缓存 this.history = []; // 路由历史}Router.prototype = { init: function(config) { var self = this; //页面首次加载 匹配路由 window.addEventListener('load', function(event) { // console.log('load', event); self.historyChange(event) }, false) //路由切换 window.addEventListener('hashchange', function(event) { // console.log('hashchange', event); self.historyChange(event) }, false) }, // 路由历史纪录变化 historyChange: function(event) { var currentHash = util.getParamsUrl(); var nameStr = "router-history" this.history = window.sessionStorage[nameStr] ? JSON.parse(window.sessionStorage[nameStr]) : [] var back = false, // 后退 refresh = false, // 刷新 forward = false, // 前进 index = 0, len = this.history.length; // 比较当前路由的状态,得出是后退、前进、刷新的状态。 for (var i = 0; i < len; i++) { var h = this.history[i]; if (h.hash === currentHash.path && h.key === currentHash.query.key) { index = i if (i === len - 1) { refresh = true } else { back = true } break; } else { forward = true } } if (back) { // 后退,把历史纪录的最后一项删除 this.historyFlag = 'back' this.history.length = index + 1 } else if (refresh) { // 刷新,不做其他操作 this.historyFlag = 'refresh' } else { // 前进,添加一条历史纪录 this.historyFlag = 'forward' var item = { key: currentHash.query.key, hash: currentHash.path, query: currentHash.query } this.history.push(item) } // 如果不需要页面缓存功能,每次都是刷新操作 if (!this.stackPages) { this.historyFlag = 'forward' } window.sessionStorage[nameStr] = JSON.stringify(this.history) }, }以上代码只列出本次文章相关的内容,完整的内容请看 原生 js 实现的轻量级路由,且页面跳转间有缓存功能。 ...

June 9, 2019 · 3 min · jiezi

如何提升JSONstringify的性能

1. 熟悉的JSON.stringify()在浏览器端或服务端,JSON.stringify()都是我们很常用的方法: 将 JSON object 存储到 localStorage 中;POST 请求中的 JSON body;处理响应体中的 JSON 形式的数据;甚至某些条件下,我们还会用它来实现一个简单的深拷贝;……在一些性能敏感的场合下(例如服务端处理大量并发),或面对大量 stringify 的操作时,我们会希望它的性能更好,速度更快。这也催生了一些优化的 stringify 方案/库,下图是它们与原生方法的性能对比: 绿色部分时原生JSON.stringify(),可见性能相较这些库都要低很多。那么,在大幅的性能提升背后的技术原理是什么呢? 2. 比 stringify 更快的 stringify由于 JavaScript 是动态性很强的语言,所以对于一个 Object 类型的变量,其包含的键名、键值、键值类型最终只能在运行时确定。因此,执行JSON.stringify()时会有很多工作要做。在一无所知的情况下,我们想要大幅优化显然无能为力。 那么如果我们知道这个 Object 中的键名、键值信息呢 —— 也就是知道它的结构信息,这会有帮助么? 看个例子: 下面这个 Object, const obj = { name: 'alienzhou', status: 6, working: true};我们对它应用JSON.stringify(),得到结果为 JSON.stringify(obj);// {"name":"alienzhou","status":6,"working":true}现在如果我们知道这个obj的结构是固定的: 键名不变键值的类型一定那么其实,我可以创建一个“定制化”的 stringify 方法 function myStringify(o) { return ( '{"name":"' + o.name + '","status":' + o.status + ',"isWorking":' + o.working + '}' );}看看我们的myStringify方法的输出: ...

June 5, 2019 · 3 min · jiezi

浏览器缓存那些事

浏览器读取资源的流程浏览器在加载资源时,根据请求头的expires和cache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modified或者etag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源如果前面两者都没有命中,直接从服务器加载资源强制缓存(不发送请求)如何设置 通常我们会同时设置expires和cache-control两种,保证无论在http1还是1.1的情况下都有效 expires过期时间,如果设置了时间,则浏览器会在设置的时间内直接读取缓存,不再请求cache-control http1.1新标准,包括这些属性: (1)max-age:用来设置资源(representations)可以被缓存多长时间,单位为秒;(2)s-maxage:和max-age是一样的,不过它只针对代理服务器缓存而言;(3)public:指示响应可被任何缓存区缓存;(4)private:只能针对个人用户,而不能被代理服务器缓存;(5)no-cache:强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。服务器接收到请求,然后判断资源是否变更,是则返回新内容,否则返回304,未变更。这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control:no-cache是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。(6)no-store:禁止一切缓存(这个才是响应不被缓存的意思)。缓存的两种表现形式 memory cache来自于内存的数据,会随着进程的结束而清除,读取速度相对快(0ms)一般存放脚本,图片,字体等文件 disk cache来自于硬盘的数据,不会随着进程的结束而清除,读取速度慢于memory cache(2-10ms 硬盘读写的IO操作)一般存放css文件 根据经验情况来看:浏览器的实际处理逻辑是这样的 首次加载资源 -> 200 -> 关闭标签页再次进入 -> 200 from disk cache -> 刷新 -> 200 from memory cache(不过好像css都是from disk cache, base64都是from memory cache) 协商缓存(发送请求)客户端向服务端发送请求时候(没有命中强制缓存),服务端会检查是否有对应的标识,没有则返回200并生成一个新的标识带到header,下次在请求的时候服务端检查到对应的这个标识并做相应的校验,通过则返回304,读取缓存。 Last-modify / If-modify-since浏览器首次请求资源的时候,服务器会返回一个last-Modify到header中. Last-Modify 含义是最后的修改时间。 当浏览器再次请求的时候,request header会带上 if-Modify-Since,该值为之前返回的 Last-Modify。服务器收到if-Modify-Since后,根据资源的最后修改时间(last-Modify)和该值(if-Modify-Since)进行比较,如果相等的话,则命中缓存,返回304,否则, 则会给出200响应,并且更新Last-Modify为新的值 Etag / If-none-match(http1.1规范) ETag的原理和上面的last-modified是类似的。ETag对当前请求的资源做一个唯一的标识。该标识可以是一个字符串,文件的size,hash等。只要能够合理标识资源的唯一性并能验证是否修改过就可以了。ETag在服务器响应请求的时候,返回当前资源的唯一标识(它是由服务器生成的)。但是只要资源有变化,ETag会重新生成的。浏览器再下一次加载的时候会向服务器发送请求,会将上一次返回的ETag值放到request header 里的 if-None-Match里面去,服务器端只要比较客户端传来的if-None-Match值是否和自己服务器上的ETag是否一致,如果一致说明资源未修改过,因此返回304,如果不一致,说明修改过,因此返回200。并且把新的Etag赋值给if-None-Match来更新该值。协商缓存两种方式对比在精度上,ETag要优先于 last-modified。last-modified这种方式精度差在哪里: a. 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了 b. 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒); 在性能上,Etag要逊于Last-Modified,Last-Modified需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。在优先级上,服务器校验优先考虑Etag。

May 31, 2019 · 1 min · jiezi

吐血分享谷歌浏览器插件

在如今的浏览器市场份额中,谷歌浏览器一家独大,霸占了将近百分之六十的份额。谷歌浏览器的流行程度可见一斑,我平时用的最多的浏览器就是它了。 干净清爽是它给我的最直观的印象,访问速度快则可能是让大多数人选择的原因了。 不仅如此,大量的谷歌浏览器插件丰富了浏览器的功能,使得它更加的贴心。你可以根据自己的需求下载相应的插件,让自己的使用体验加倍提升! 今天给大家分享几个比较常用的谷歌浏览器插件。其实最近发现QQ浏览器也是支持谷歌浏览器的crx类型的扩展程序的,使用起来也很不错,安装方法类似。大家可以根据自己的需求来选择。 1.Anything to QRCode 这个插件可以将浏览器页面的文本、网页地址、图片地址、链接等等转换成二维码,同时还支持解析页面上的二维码,非常的方便好用! 2.Awesome Screenshot 这是一个截图的插件,支持多种功能,还可以录屏,支持截图之后的编辑,完全是在线的 ps! 3.LastPass 这是一个密码管理器,你可能会经常遇到登录一个网站时需要密码,你有可能会忘记了账号密码,或者懒得去输入。这个插件可以帮助你保管你的账号密码,让你一次输入之后,再也不用输入密码就能实现登录! 4.Momentum 打开一个浏览器新标签页的时候,我们总是会看到一个空白的画面,这样难免有点枯燥。这个插件可以在你新打开标签页的时候生成一张优美的背景图片,比如我今天生成的的图片是这样的: 5.OneTab 当你打开多个浏览器标签页的时候,是非常浪费内存的,同时也不方便寻找。OneTab 插件可以把你打开的标签页合成到一个页面,你可以根据需求打开相应的页面,非常的方便和直观,并且非常节省内存! 6.TamperMonkey 中文名叫做油猴脚本,是一个神器!你可以下载相应的脚本,然后可以破解视频会员,全网音乐一键下载,广告清理等等。 7.Tunnello_VPN fq 工具,每天提供 200M 免费流量,只不过速度一般,基本的搜索还是可以的。也可以花钱买旗舰版,不限量,速度很不错,价格也不贵! 8.广告终结者 网页上面一些乱七八糟的广告、弹窗有时候看着真的很烦,这个插件是实打实的广告终结者!安装之后浏览器页面干净清爽! 最后三个是开发者可能会经常使用到的插件: 9.JsonView 这是开发者经常使用到的一个插件,有时候可能会遇到浏览器上的 json 数据格式混乱的情况,这个插件可以解析 json,生成可视化视图,方便查看数据。 10.FeHelper 这是一个在线助手,提供的功能非常丰富,例如 Json 格式化,Json 美化,代码美化,二维码生成等等,堪称神器! 11.Restlet Client 这是一个接口测试工具,类似于 Postman,只不过是插件形式的,更省空间,功能和 Postman 类似,强烈推荐使用! 以上全部插件领取方式: 在我的公众号【roseduan】后台回复 谷歌 或者 插件 即可打包带走!谢谢你的关注支持!

May 14, 2019 · 1 min · jiezi

浏览器layer知识

原文:https://www.alibabacloud.com/... RenderObject(LayoutObject)会分成PaintLayer,在某些情况下PaintLayer会升级成CompositorLayer(GraphicLayer),开发工具的layers都是CompositorLayer。 一. 什么情况下会LayoutObject会生成PaintLayer包裹? (1)生成普通PaintLayer(SelfPaintingLayers)的原因: 1.document 2.非static的position属性 3.opacity小于1 4.有css filter属性 5.有css mask属性 6.css mix-blend-mod属性 7.有css transform属性 8.backface-visibility为hidden 9.有css reflection属性 10.有css column-count属性或者column-width属性 11.动画改变 opacity, transform, filter, and backdrop-filter.(2) OverflowClipPaintLayer:overflow非visible (3) NoPaintLayer:没有需要paint的LayoutObject 其他LayoutObject与最近的祖先节点分享PaintLayer 二. 什么情况下PaintLayer升级成CompositingLayer? (1)本身节点原因 1.拥有硬件加速属性节点的iframe,如果一个iframe没有CompositingLayer,则该iframe会与父document分享CompositingLayer 2.Video节点 3.Video内的控制条 4.3D或者硬件加速2D的canvas节点,getContext(‘2d’)是不会升级的 5.硬件加速的插件,如flash 6.在高DPI的设备里,fixed节点会自动升级为CompositingLayer,由于PaintLayer升级会改变font的渲染模式(测试在pc chrome fixed元素也会升级) 7.3d transform 8.backface-visibility为hidden 9.动画或者缓动改变 opacity, transform, filter, and backdrop-filter,当动画停止的时候则恢复PaintLayer. 10.will-change设置为 opacity, transform, top, left, bottom, or right.  11.position为fixed或者sticky(2)重叠原因 1.一个CompositingLayer被覆盖,则该覆盖者自动升级(squashing,该覆盖者升级的CompositingLayer是被覆盖的CompositingLayer衍生出来的,两者同级) 2.一个CompositingLayer被有filter属性的filter部分覆盖(测试没有发现有升级) 3.被transformed元素覆盖(squashing) 4.被overflow:scroll or auto节点覆盖 5.兄弟节点有动画或者缓动改变opacity, transform, filter, and backdrop-filter.(3)Layer Squashing 层级压缩 ...

May 13, 2019 · 1 min · jiezi

Chrome-74-带来的新功能

What’s new in Chrome 74翻译:疯狂的技术宅https://blog.logrocket.com/wh...本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 Chrome 74 已经发布了,虽然从用户的角度来看并没有什么令人兴奋的东西,但是对开发人员来说带来了一些好处。新版本附带了新的 Javascript 私有类字段、允许用户减少动画的媒体查询和 Windows 的深色模式等等。 公共类字段,私有类字段你可能还记得,Chrome 72 在1月份增加了对 Javascript 公共类字段语法的支持。这是一种简化语法的新方法,它允许直接在类定义中定义类字段,且不需要构造函数。 现在在 Chrome 74 中加入私有类字段,它与公有类字段的功能大致相同,但是用 # 来表示它们是私有而不是公共的,当然它们只能在类的内部访问。 先复习一下公共类字段,如下所示: class IncreasingCounter { // Public class field _publicValue = 0; get value() { return this._publicValue; } increment() { this._publicValue++; }}私有类字段添加了 #: class IncreasingCounter { // Private class field #privateValue = 0; get value() { return this.#privateValue; } increment() { this.#privateValue++; }}不那么快事实证明,有些人并不是那么喜欢现代网站上的华丽动画。实际上视差滚动、缩放和跳跃动作效果会使一些动画出问题,这并不好玩。操作系统已添加了减少这类动作的选项,在 Chrome 74 上你可以通过使用媒体查询,来减少动画中的动作。 ...

May 12, 2019 · 1 min · jiezi

译JavaScript-究竟是如何工作的第一部分

原文地址:How Does JavaScript Really Work? (Part 1)原文作者:Priyesh Patel译者:Chor 如果你是一个 JS 开发者或者是正在学习这门语言的学生,很大概率上你会遇到双字母词"V8"。在这篇文章中,我将会为你简述不同的 JS 引擎并深入探究 V8 引擎的工作机制。文章的第二部分涵盖了内存管理的概念,不久后将发布。 这篇文章是由 Bit (GitHub) 带来的。作为一个共享组件的平台,Bit 帮助每个人构建模块化的 JavaScript 应用程序,在项目和团队之间轻松地共享组件,同时实现更好&更快的构建。试试看。 编程语言是如何工作的?在开始讲解 JavaScript 之前,我们首先要理解任意一门编程语言的基本工作方式。电脑是由微处理器构成的,我们通过书写代码来命令这台小巧但功能强大的机器。但是微处理器能理解什么语言?它们无法理解 Java,Python 等语言,而只懂机器码。用机器语言或汇编语言编写企业级代码是不可行的,因此我们需要像 Java,Python 这样配带一个解释器或者编译器用于将其转换为机器码的高级语言。 编译器和解释器编译器/解释器可以用它处理的语言或任何其他语言来编写。 解释器: 一行一行地快速读取和翻译文件。这就是 JavaScript 最初的工作原理。 编译器: 编译器提前运行并创建一个文件,其中包含了输入文件的机器码转换。 有两种途径可以将 JavaScript 代码转换为机器码。编译代码时,机器对代码开始运行前将要发生的事情有更好的理解,这将加快稍后的执行速度。不过,在这个过程之前需要花费时间。 另一方面,解释代码时,执行是立即的,因此要更快,但是缺乏优化导致它在大型应用程序下运行缓慢。 创建 ECMAScript 引擎的人很聪明,他们集二者之长开发了 JIT(Just-in-time) 编译器。JavaScript 同时被编译和解释,但实际实现和顺序取决于引擎。我们将会看到 V8 团队采用的是什么策略。 从 JavaScript 到机器码就 JavaScript 而言,有一个引擎将其转换为机器码。和其他语言类似,引擎可以用任何语言来开发,因此这样的引擎不止一个。 V8 是谷歌针对 Chorme 浏览器的引擎实现。SpiderMonkey 是第一个引擎,针对网景浏览器开发,现用于驱动 FireFox。JavaScriptCore 是苹果针对 Safari 浏览器使用的引擎。还有很多,如果你想知道 Internet Explorer 背后的引擎,查看这个维基百科页面. ...

May 10, 2019 · 1 min · jiezi

前端面试题及答案-浏览器篇

这篇文章并不是最全的前端面试题(没有最全,只有更全),只是针对自己面试过程中遇到的一些难题、容易忽略的题做一个简单的笔记,方便后面有面试需要的小伙伴们借鉴,后续内容会不定时更新,有错误之处希望大家不吝指出。1. 谈谈Cookie的优劣Cookie不同之处: IE6或更低版本最多20个cookieIE7和之后的版本最后可以有50个cookie。Firefox最多50个cookiechrome和Safari没有做硬性限制优点:极高的扩展性和可用性 通过良好的编程,控制保存在cookie中的session对象的大小。通过加密和安全传输技术(SSL),减少cookie被破解的可能性。只在cookie中存放不敏感数据,即使被盗也不会有重大损失。控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。缺点: Cookie数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。例子JavaScript 中,创建 cookie 如下所示: document.cookie="username=John Doe";还可以为 cookie 添加一个过期时间(以 UTC 或 GMT 时间)。默认情况下,cookie 在浏览器关闭时删除: document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT";可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下,cookie 属于当前页面。 document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";2、浏览器本地存储 - cookie、localStorage、sessionStorage区别相同:在本地(浏览器端)存储数据不同: localStorage只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份localStorage数据。sessionStorage比localStorage更严苛一点,除了协议、主机名、端口外,还要求在同一窗口(也就是浏览器的标签页)下。localStorage是永久存储,除非手动删除。sessionStorage当会话结束(当前页面关闭的时候,自动销毁)cookie的数据会在每一次发送http请求的时候,同时发送给服务器而localStorage、sessionStorage不会。了解更多 前端面试题及答案 - HTML篇前端面试题及答案 - CSS篇前端面试题及答案 - JS篇

May 10, 2019 · 1 min · jiezi

浏览器网页链接打开本地exe客户端程序

我们经常可以看到在浏览器打开客户端的场景:浏览器打开 QQ 聊天窗口,百度网盘打开网盘客户端下载等。 我们如何使用浏览器网页链接打开本地 exe 客户端程序? <!-- more --> 步骤如下 新建注册表文件 szztClient.reg, 客户端的名称和客户端的地址可以自己定义。[HKEY_CLASSES_ROOT\szztClient]@="szztClientProtocol""URL Protocol"=""[HKEY_CLASSES_ROOT\szztClient\DefaultIcon]@="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe,1"[HKEY_CLASSES_ROOT\szztClient\shell]@=""[HKEY_CLASSES_ROOT\szztClient\shell\open]@=""[HKEY_CLASSES_ROOT\szztClient\shell\open\command]@="\"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\" \"%1\""双击运行写入注册表在网页添加链接<a href="szztClient://">打开客户端</a>本文作者: Shellming本文链接: shellming.com/2019/05/08/browser-link-opens-client-exe/版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!

May 8, 2019 · 1 min · jiezi

✨如何用JS实现划词高亮的在线笔记功能✨????️

1. 什么是“划词高亮”?有些同学可能不太清楚“划词高亮”是指什么,下面就是一个典型的“划词高亮”: 上图的示例网站可以点击这里访问。用户选择一段文本(即划词),即会自动将这段选取的文本添加高亮背景,用户可以很方便地为网页添加在线笔记。 笔者前段时间为线上业务实现了一个与内容结构非耦合的文本高亮在线笔记功能。非耦合是指不需要为高亮功能建立特殊的页面 DOM 结构,而高亮功能对业务近乎透明。该功能核心部分具有较强的通用性与移植性,故拿出来和大家分享交流一下。 本文具体的核心代码已封装成独立库 web-highlighter,阅读中如有疑问可参考其中代码↓↓。2. 实现“划词高亮”需要解决哪些问题?实现一个“划词高亮”的在线笔记功能需要解决的核心问题有两个: 加高亮背景。即如何根据用户在网页上的选取,为相应的文本添加高亮背景;高亮区域的持久化与还原。即如何保存用户高亮信息,并在下次浏览时准确还原,否则下次打开页面用户高亮的信息就丢失了。一般来说,划词高亮的业务需求方主要是针对自己产出的内容,你可以比较容易对内容在网页上的排版、HTML 标签等方面进行控制。这种情况下,处理高亮需求会更方便一些,毕竟自己可以根据高亮需求调整现有内容的 HTML。 而笔者面对的情况是,页面 HTML 排版结构复杂,且无法根据高亮需求来推动业务改动 HTML。这也催生出了对解决方案更通用化的要求,目标就是:针对任意内容均可“划词高亮”并支持后续访问时还原高亮状态,而不用去关心内容的组织结构。 下面就来具体说说,如何解决上面的两个核心问题。 3. 如何“加高亮背景”?根据动图演示我们可以知道,用户选择某一段文本(下文称为“用户选区”)后,我们会给这段文本加一个高亮背景。 例如用户选择了上图中的文本(即蓝色部分)。为其加高亮的基本思路如下: 获取选中的文本节点:通过用户选择的区域信息,获取所有被选中的所有文本节点;为文本节点添加背景色:给这些文本节点包裹一层新的元素,该元素具有指定的背景颜色。3.1. 如何获取选中的文本节点?1)Selection API需要基于浏览器为我们提供的 Selection API 。它的兼容性还不错。如果要支持更低版本的浏览器则需要用 polyfill。 Selection API 可以返回一系列关于用户选区的信息。那么是不是可以通过它直接获取选取中的所有 DOM 元素呢? 很遗憾并不能。但好在它可以返回选区的首尾节点信息: const range = window.getSelection().getRangeAt(0);const start = { node: range.startContainer, offset: range.startOffset};const end = { node: range.endContainer, offset: range.endOffset};Range 对象包含了选区的开始与结束信息,其中包括节点(node)与文本偏移量(offset)。节点信息不用多说,这里解释一下 offset 是指什么:例如,标签<p>这是一段文本的示例</p>,用户选取的部分是“一段文本”这四个字,这时首尾的 node 均为 p 元素内的文本节点(Text Node),而 startOffset 和 endOffset 分别为 2 和 6。 ...

April 25, 2019 · 4 min · jiezi

【14】winter重学前端 笔记 - 浏览器:一个浏览器是如何工作的?(阶段五)渲染

回顾URL->字符流->词流(token)->DOM树->包含样式信息的DOM树-计算了每个元素的位置和大小(排版)渲染定义:把元素变成位图的过程(每一个元素对应的盒变成位图,一个元素可能对应多个盒(比如 inline 元素,可能分成多行)位图:在内存里建立一张二维表格,把一张图片的每个像素对应的颜色保存进去(位图信息也是 DOM 树中占据浏览器内存最多的信息,我们在做内存占用优化时,主要就是考虑这一部分)分类:图形和文字图形类:盒的背景、边框、SVG 元素、阴影等特性盒的特性如何绘制,每一个都有对应的标准规定,不详细探究文字类分类:分为像素字形和矢量字形(会在 6px 8px 等小尺寸提供像素字形,比较大的尺寸则提供矢量字形)最普遍的情况下,渲染过程生成的位图尺寸跟它在上一步排版时占据的尺寸相同,很多属性会影响渲染位图的大小,比如阴影,为了优化,浏览器实际的实现中会把阴影作为一个独立的盒来处理一般的操作系统会提供一个底层库来支持渲染:Android(Skia)Windows(GDI)浏览器会做一个兼容层来处理掉平台差异渲染过程:不会把子元素绘制到渲染的位图上的,当父子元素的相对位置发生变化时,可以保证渲染的结果能够最大程度被缓存,减少重新渲染输入框实现:渲染过程除了位图,最终绘制上去还产生一个"热区",这个“热区”不但跟你说的input相关,还跟用户选择、鼠标事件和scroll等交互相关(太复杂啦,winter没有讲)合成定义:为一些元素创建一个“合成后的位图”(我们把它称为合成层),把一部分子元素渲染到合成的位图上面,性能优化行为,非浏览器必须合成的策略:最大限度减少绘制次数,“猜测”可能变化的元素,把它排除到合成之外主流浏览器一般根据 position、transform 等属性来决定合成策略,来“猜测”这些元素未来可能发生变化新的 CSS 标准中,规定了 will-change 属性,可以由业务代码来提示浏览器的合成策略????合成策略能够把 a、b 两个 div 合成,而不把 c 合成在实际场景中,我们的 b 可能有很多复杂的子元素,所以当合成命中时,性能提升收益非常之高<div id=“a”> <div id=“b”>…</div> <div id=“c” style=“transform:translate(0,0)"></div></div>document.getElementById(“c”).style.transform = “translate(100px, 0)";绘制定义:把“位图最终绘制到屏幕上,变成肉眼可见的图像”的过程,实际上就是按照 z-index 把它们依次绘制到屏幕浏览器不处理,把要显示的位图交给操作系统限制绘制的面积(换一个角度理解重绘repaint):问题:鼠标划过浏览器显示区域。这个过程中,鼠标的每次移动,都造成了重新绘制,如果我们不重新绘制,就会产生大量的鼠标残影解决:“脏矩形”算法–把屏幕均匀地分成若干矩形区域,重新绘制脏矩形区域时,把所有与矩形区域有交集的合成层(位图)的交集部分绘制即可。作业:用 JavaScript 实现一个玩具浏览器(用canvas模拟一个iframe吗)

April 20, 2019 · 1 min · jiezi

【13】winter重学前端 笔记 - 浏览器:一个浏览器是如何工作的?(阶段四)排版layout

基本概念排版:把浏览器确定元素位置的过程分类:正常流中的文字排版、正常流中的盒、绝对定位元素、浮动元素排版、flex 排版、表格相关排版、grid 排版正常流排版:它包含了顺次排布和折行等规则,跟我们平时书写文字的方式一致文字排版:它规定了行模型和文字在行模型中的排布,行模型规定了行顶、行底、文字区域、基线等对齐方式。(英语本:四条线就是一个简单的行模型)盒模型:素被定义为占据长方形的区域,还允许边框、边距和留白(浏览器为支持元素和文字的混排)绝对定位元素:把自身从正常流抽出,直接由 top 和 left 等属性确定自身的位置,不参加排版计算,也不影响其它元素。绝对定位元素由position 属性控制浮动元素:自己在正常流的位置向左或者向右移动到边界,并且占据一块排版空空间。浮动元素由 float 属性控制。这些排版方式由外部元素的 display 属性来控制(注意:display 同时还控制元素在正常流中属于 inline等级还是 block 等级)正常流详细正常流排版的行为查阅24讲正常流是唯一一个文字和盒混排的排版方式正常流文字排版正常书写文字:是从左到右依次书写,每一个字跟上一个字都不重叠,文字之间有一定间距,当写满一行时,我们换到下一行去继续写,书写中文时,文字的上、下、中轴线都对齐,书写英文时,不同字母的高度不同,但是有一条基线对齐(浏览器类似)浏览器特点:还支持改变排版方向文字依次书写的延伸方向称为主轴换行延伸的方向,跟主轴垂直交叉,称为交叉轴开源字体解析库 freetypeadvance:每一个文字排布后在主轴上的前进距离,它跟文字的宽 / 高不相等文字排版还受到一些 CSS 属性影响:line-height(行高)、letter-spacing(字母间距中文字和英文字母)、word-spacing(单词间距:英文单词,中文无作用)横向:纵向:正常流中的盒元素排版display 不为 inline 的元素或者伪元素,会以盒的形式跟文字一起排版display 属性都可以分成两部分:内部的排版和是否 inline,带有 inline- 前缀的盒,被称作行内级盒。盒模型在主轴方向占据的空间是由对应方向的这几个属性之和决定的(margin、border、padding、width/height 等属性)浏览器排版inline行的排版:先行内布局,再确定行的位置,根据行的位置计算出行内盒和文字的排版位置block块级盒: 单独占据一整行,计算出交叉轴方向的高度即可绝对定位元素position 属性为 absolute 的元素其父级的 position 非 static 元素的包含块来确定位置浮动元素排版浏览器对 float 的处理:先排入正常流,再移动到排版宽度的最左 / 最右(这里实际上是主轴的最前和最后)移动之后,float 元素占据了一块排版的空间,因此,在数行之内,主轴方向的排版距离发生了变化,直到交叉轴方向的尺寸超过了浮动元素的交叉轴尺寸范围,主轴排版尺寸才会恢复float 元素排布完成后,float 元素所在的行需要重新确定位置flex排版和其他排版CSS 的每一种排版都有一个很复杂的规定,都有对应的标准flex 排版,支持了 flex 属性,flex 属性将每一行排版后的剩余空间平均分配给主轴方向的 width/height 属性不同排版混用关系复杂,winter不过多赘述遵守可以内外嵌套、但是不混用的规则即可总结这节我可以理解为正常流的排版方式,里面的文字元素的排版,盒元素的排版,以及定义为绝对元素,浮动元素的排版。

April 20, 2019 · 1 min · jiezi

【winter重学前端笔记】10浏览器:一个浏览器是如何工作的?CSS计算

加载css加载是异步,不会影响DOM树的构建,只是说在CSS没处理好之前,构建好的DOM并不会显示出来启发:所以CSS不能太大,页面一打开将会停留较长时间的白屏,所以把图片/字体等转成base64放到CSS里面是一种不太推荐的做法DOM去匹配css rule的时候必须先等页面的css都下载完成启发:head中的css是要下载完的,body中放CSS的话,会重新计算css语法:选择器在9课程中可以了解更多选择器:compound-selector特点(css设计原则):选择器的出现顺序,必定跟构建 DOM 树的顺序一致,即:保证选择器在 DOM 树构建到当前节点时,已经可以准确判断是否匹配,不需要后续节点信息 - 未来不可能会出现“父元素选择器”这种东西流式渲染,每生成一个dom节点,便立刻去匹配相应的css规则空格: 后代,选中它的子节点和所有子节点的后代节点: 子代,选中它的子节点+:直接后继选择器,选中它的下一个相邻节点~:后继,选中它之后所有的相邻节点||:列,选中表格中的一列winter不讲怎么解析css规则啦,词法分析和语法分析不做赘述cssomCSSOM主要是DOM结构上的盒的描述,它基本上是依附于DOM树的,不要和css的语法树混淆cssom是有rule部分和view部分的,rule部分是在dom开始之前就构件完成的,而view部分是跟着dom同步构建的。css选择器匹配流程前进:一个 CSS 选择器按照 compound-selector来拆成数段,每当满足一段条件的时候,就前进一段。后退:选择器的作用范围,匹配到本标签的结束标签时(作用范围边缘)回退。后代选择器 “空格”规则:前进:找到了匹配 a#b 的元素时,我们才会开始检查它所有的子代是否匹配 .cls(前进到.cls)后退:当遇到 时,必须使得规则 a#b .cls 回退一步(回退到a#b)这样第三个 span 才不会被选中 - 后代选择器的作用范围是父节点的所有子节点,因此规则是在匹配到本标签的结束标签时回退。a#b .cls { width: 100px;}<a id=b> <span>1<span> <span class=cls>2<span></a><span class=cls>3<span>后继选择器“ ~ ”规则:给选择器的激活- 带上一个条件:父元素原因:后继选择器只作用于一层.按照 DOM 树的构造顺序,4 在 3 和 5 中间,我们就没有办法像前面讲的后代选择器一样通过激活或者关闭规则来实现匹配过程:当前半段的 .cls 匹配成功时,后续 * 所匹配的所有元素的父元素都已经确定了(后继节点和当前节点父元素相同是充分必要条件.cls~* { border:solid 1px green;}<div><span>1<span><span class=cls>2<span><span> 3 <span>4</span><span><span>5</span></div>子代选择器“ >”规则:拿当前节点的父元素作为父元素当 DOM 树构造到 div 时,匹配了 CSS 规则的第一段激活.cls并且指定父元素必须是当前 divdiv>.cls { border:solid 1px green;}<div><span>1<span><span class=cls>2<span><span> 3 <span>4</span><span><span>5</span></div>直接后继选择器“ +”思路1:只对唯一一个元素生效,把 #id+.cls 都当做检查某一个元素的选择器思路2:给后继选择器加上一个 flag,使它匹配一次后失效列选择器“ || ”专门针对表格的选择器,跟表格的模型建立相关,winter不讲这个啦其他CSS 选择器还支持逗号分隔视为两条规则的一种简易写法a#b, .cls {}a#b {}选择器可能有重合 使用树形结构来进行一些合并 #a .cls {} #a span {} #a>span {} #a < 空格 >.cls < 空格 >span 总结:CSS 计算:把 CSS 规则应用到 DOM 树上,为 DOM 结构添加显示相关属性的过程,得到了一棵带有 CSS 属性的树介绍了选择器的几种复合结构应该如何实现扩展阅读:从Chrome源码看浏览器如何计算CSShttps://zhuanlan.zhihu.com/p/… ...

April 20, 2019 · 1 min · jiezi

flutter在2019年会有怎样的表现?

Flutter的趋势在移动端,受成本和效率的驱使,跨平台一站式开发慢慢成为一个趋势。从Hybird,RN,WEEX,Flutter,到各种小程序或快应用的大量涌现,虽然很多跨平台方案都有各自的优缺点,目前还没有完美无缺的终极方案,但这已是未来移动端开发不可逆转的一大方向。而Google推出并开源的移动应用开发框架Flutter,更是其中的明星。笔者从自身在做Flutter相关的分享中,特别强烈的感受是,有非常非常多的Native技术栈的同学在学习和使用Flutter,有非常多的前端技术栈的同学在时刻关注Flutter的hummingbird和desktop-embedding的进展。尤其自Flutter1.0 发布后,Flutter受到了业界更多的关注和期待。跨平台解决方案比较目前几个主流的跨平台解决方案:基于浏览器技术的Hybird基于桥接Native组件,如RN、WEEX基于底层统一渲染,如Flutter它们有各种的优缺点,但浏览器技术无疑是其中的历史最长、标准最完善、用户最多、生态最丰富的。RN、WEEX也可以归类为javascript生态的一个小分支。而Flutter走的是和前两者截然不同的路线,它是一个新兴的挑战者,通过底层统一渲染,得到高度一致的跨端效果;通过引入dart,得到AOT的接近原生的性能,和JIT的快速开发体验;通过上层完善的组件体系(material design & cupertino),得到高保真的UI体验。但它也并非尽善尽美。同时基于底层统一渲染的跨平台方案有很多,在移动端有实际应用的如QT、cocos2d等。对比Flutter和QT,最大的区别在语言和背后团队。语言:Flutter选择了Dart,QT是C++。Dart相比C++,对开发者来说无疑于相比骑自行车和开飞机的区别,Dart更容易编写,除此以外,Dart还拥有AOT和JIT两种模式、类型安全、快速内存分配等等特点,确实如Flutter团队所述,同时拥有一两条这些优点的语言不少,但是将所有这些优点集于一身的,只有Dart。背后团队:Flutter的背后是Google,QT的背后是TrollTech,从社区影响力和号召力而言不可同日而语。但同时也必须要认识到的是通过底层统一渲染的跨平台方案,也有它天然的劣势。它很难复用系统天然提供的组件。在摆脱对操作系统的依赖和复用操作系统的能力上,要考虑如何达到了一个最佳的平衡。Flutter的生态如果拿Flutter生态同React和Native进行比较的话基于核心UI表达层向上,这一层会更接近前端的体系,以React生态为参照物,主要的几部分路由体系一种面向以Flutter为主的应用,它的路由以Flutter为主,Native的路由部分往往以简易桥接的形态存在。一种面向混合技术栈为主的应用,它的路由以Native为主,Flutter为辅。状态管理体系 | 应用框架基本上在React生态下有的状态管理,Flutter也有,同时有一些是Flutter独有的。开源的代表有:flutter_redux, google的BLoc,scoped_model,及闲鱼的fish-redux,它在真实的复杂场景下得到了非常好的验证。UI库体系目前已有不少UI库,包含常见的组件。基于核心UI表达层向下,这一层会更接近Native的体系,以Native生态为参照物,主要的几部分核心的一些基础中间件,如网络,图片,音视频,存储,埋点,监控等。目前和Native相比还是有非常大的差距。所以也导致了目前大部分这些问题的解决方案,都趋向于桥接的形态。通过复用Native能力来短期补齐Flutter能力不足的。但它不一定是未来的最佳的方案。一些重量级的基础组件,如WebView,MapView等。目前已经能通过PlatformView的形式,得到能力拓展。但是它有使用的局限性和性能上的损失。Flutter今年几个重要的突破点Code-Push在当下国内应用生态环境,热修复或者热部署能力在很多公司和团队做技术选型中,往往是其中非常重要的一个选项。如果有Google官方推出,不管是hotfix,还是dynamic-boundle都将极大的推动Flutter在国内的发展。而基于dart语言的特性判断,在Flutter上做code-push理论上会比目前任何Native的code-push方案有更强的能力。闲鱼团队一直和Flutter团队就这方便保持紧密的联系,在之前的验证中,目前在android端是可以支持的,但还留有一些瑕疵。Humming-Bird在跨平台之外,还有一层更高级别的诉求,多应用投放,打破应用之间的孤岛壁垒,实现更多的商业价值。而要完成多应用的投放,首选的是基于浏览器的方案。Humming-Bird方案为这样的设想,提供了可能。同时Humming-Bird也将大大扩张了Flutter的边界,吸引更多的开发者和厂商的加入,同时让面向终端的全栈解决方案成为可能。Dart语言也有可能成为javascript生态的更好的补充和演进。Flutter面向未来基础架构设计决定了一个软件的发展上限,它带来了更多的想象力。使用Flutter和Dart,既是Google为摆脱和Oracle纠缠多年的“Java 侵权案”提前下的一颗棋,也是Google为下一代操作系统Fusion下的一颗棋,是即Google通过chromium项目渐进的统一浏览器领域,着眼于更多的终端,为了一个更大终端生态的大一统做准备。这让Flutter和Dart充满了更高层次的可能。如果没有这些可能,Flutter的生命无疑是会短暂的,因为它还未能建立被广泛被认可的标准,就像我们终端里走过的那么多的技术一样,都是有限的解决了当下的诉求,但随着终端的更替,操作系统的演进,慢慢变成了明日黄花。而正是这些更多的可能,是Flutter持续演进的源泉,是Flutter相比其他的跨平台方案中最吸引人的部分。本文作者:闲鱼技术-吉丰阅读原文本文为云栖社区原创内容,未经允许不得转载。

April 19, 2019 · 1 min · jiezi

【10】winter重学前端 - 浏览器:一个浏览器是如何工作的?(阶段一)HTTP

10 浏览器 - HTTP简介:HTTP 在 TCP 的基础上,规定了 Request-Response 的模式, 纯粹的文本协议,定了使用 TCP 协议来传输文本格式的一个应用层协议。协议格式HTTP Method(方法)GET POST HEAD PUT DELETE CONNECT OPTIONS TRACE通过地址栏访问页面都是 GET 方法。表单提交产生 POSTHEAD 则是跟 GET 类似,只返回请求头,多数由 JavaScript 发起PUT 和 DELETE 分别表示添加资源和删除资源,语义资源并非强约束CONNECT 现在多用于 HTTPS 和 WebSocketOPTIONS 和 TRACE 一般用于调试,多数线上服务都不支持HTTP Status code(状态码)和 Status text304: 客户端本地已经有缓存的版本,并且在 Request 中告诉了服务端,当服务端通过时间或者 tag,发现没有更新的时候,就返回一个不含 body 的 304 状态。实际上 301 更接近于一种报错,提示客户端下次别来了1xx 的状态被浏览器 http 库直接处理掉了,不会让上层应用知晓HTTP Head (HTTP 头)HTTP Request Body常见body格式form标签:默认会产生 application/x-www-form-urlencoded,当有文件上传时,则会使用 multipart/form-dataHTTPS 作用:一是确定请求的目标服务端身份,二是保证传输的数据不会被网络中间节点窃听或者篡改。区别:使用加密通道来传输 HTTP 的内容,TLS 构建于 TCP 协议之上,它实际上是对传输的内容做一次加密,所以从传输内容上看,HTTPS 跟 HTTP 没有区别HTTP 2.0改进:一是支持服务端推送,二是支持 TCP 连接复用提前把一部分内容推送给客户端,放入缓存当中,这可以避免客户端请求顺序带来的并行度不高,从而导致的性能问题。同一个 TCP 连接来传输多个 HTTP 请求,避免了 TCP 连接建立时的三次握手开销,和初建 TCP 连接时传输窗口小的问题。补充知识DNS查询得到IPtcp/ip的并发限制get和post的区别w3c对于区别做出的定义:(用法上)(1)对参数的数据类型,GET只接受ASCII字符,而POST没有限制,允许二进制。(2)GET在浏览器回退/刷新时是无害的,而POST会再次提交请求。(3)GET请求只能进行url编码(application/x-www-form-urlencoded),而POST支持多种编码方式(application/x-www-form-urlencoded 或 multipart/form-data),可以为二进制使用多重编码 - HTML标准对HTTP协议的用法的约定,是浏览器拦截不让发(4)POST 比 GET 更安全,因为GET参数直接暴露在URL上,POST参数在HTTP消息主体中,而且不会被保存在浏览器历史或 web 服务器日志中。- GET提交数据还可能会造成Cross-site request forgery攻击+对于用户名等敏感信息暴露(5)对参数数据长度的限制,GET方法URL的长度是受限制的,最大是2048个字符,POST参数数据是没有限制的。- 浏览器/服务器行为,http没有做出限制:恶意伪造content-length很大的包头(6)GET请求会被浏览器主动缓存,POST不会,除非手动设置。(7)GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。 (8)GET请求可被收藏为书签,POST不能。常见答案: 1. GET使用URL或Cookie传参,而POST将数据放在BODY中。 2. GET方式提交的数据有长度限制,则POST的数据则可以非常大。 3. POST比GET安全,因为数据在地址栏上不可见。在HTTP上看 HTTP规范定义:GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。 方法都只是规定:使用哪个Method与应用层的数据如何传输是没有相互关系的,get一样可以发送body HTTP协议对GET和POST都没有对长度的限制 GET幂等,POST不幂等幂等是指同一个请求方法执行多次和仅执行一次的效果完全相同。1.按照RFC规范,PUT,DELETE和安全方法都是幂等的。虽说是规范,但服务端实现是否幂等是无法确保的。2.引入幂等主要是为了处理同一个请求重复发送的情况,比如在请求响应前失去连接,如果方法是幂等的,就可以放心地重发一次请求。这也是浏览器在后退/刷新时遇到POST会给用户提示的原因:POST语义不是幂等的,重复请求可能会带来意想不到的后果。3.比如在微博这个场景里,GET的语义会被用在「看看我的Timeline上最新的20条微博」这样的场景,而POST的语义会被用在「发微博、评论、点赞」这样的场景中。总结:GET的语义是请求获取指定的资源。GET方法是安全、幂等、可缓存的(除非有 Cache-Control Header的约束),GET方法的报文主体没有任何语义。 POST的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST不安全,不幂等,(大部分实现)不可缓存。 http的method的定义,只是对行为的定义,幂等(有无副作用)的定义,没有强制定义,对其约束很低,为了服务器/浏览器的压力和安全,才对http的method做出了一些限制,且对于不用浏览器和服务器这些限制是不同,其两者本质都是基于TCP传输,区别只是幂等https://www.jianshu.com/p/8fd…补充知识点五层因特网协议栈-阮一峰的总结长连接与短连接TCP就是数据传输的通道(先建立通道再传输数据,能够保证安全)TCP保活功能:为服务器应用提供,服务器应用希望知道客户主机是否崩溃http2.0与http1.1的显著不同点:强缓存与协商缓存 ...

April 19, 2019 · 1 min · jiezi

【11】winter重学前端 笔记 - 浏览器:一个浏览器是如何工作的?(阶段二)DOM树构建

请支持正版https://time.geekbang.org/col…整体流程字符流 -> 状态机 -> 词token -> 栈 -> dom构建 DOM 的过程是:从父到子,从先到后,一个一个节点构造,并且挂载到DOM树上如何解析请求回来的 HTML 代码把respone拿到的字符流通过状态机解析成一个个的词词(token)是如何被拆分的拆分:最小有意义单元 - token(词)词的种类大约只有标签开始、属性、标签结束、注释、CDATA 节点…eg:<p class=“a”>text text text</p><p“标签开始”的开始;class=“a” 属性“标签开始”的结束;text text text 文本;/p>标签结束状态机为什么使用:我们每读入一个字符,其实都要做一次决策,而且这些决定是跟“当前状态”有关的定义:把每个词的“特征字符”逐个拆开成独立状态,然后再把所有词的特征字符链合并起来,形成一个联通图结构。绝大多数语言的词法部分都是用状态机实现的,HTML 官方文档规定了 80 个状态eg: 词法部分DOM 树又是如何构建的栈来实现,当接收完所有输入,栈顶就是最后的根节点,我们 DOM 树的产出,就是这个 stack 的第一项Node 类,所有的节点都会是这个 Node 类的实例。不一样的 HTML 节点对应了不同的 Node 的子类,此处的实现,我们进行简化,只把 Node 分为 Element 和 Textfunction Element(){ this.childNodes = [];}function Text(value){ this.value = value || “”;}规则:token中tag start和tag end需要成对实现,使用的栈正是用于匹配开始和结束标签的方案(编译原理技巧)Text 节点:把相邻的 Text 节点合并起来,当词(token)入栈时,检查栈顶是否是 Text 节点,果是的话就合并 Text 节点构建过程:(默认:源代码完全遵循 xhtml,HTML 具有很强的容错能力,奥妙在于当 tag end 跟栈顶的 start tag 不匹配的时候如何处理,暂时不考虑)栈顶元素就是当前节点;遇到属性,就添加到当前节点;遇到文本节点,如果当前节点是文本节点,则跟文本节点合并,否则入栈成为当前节点的子节点;遇到注释节点,作为当前节点的子节点;遇到 tag start 就入栈一个节点,当前节点就是这个节点的父节点遇到 tag end 就出栈一个节点(还可以检查是否匹配)完整的语法和词法分析代码词法分析每一个状态是一个函数,通过“if else”来区分下一个字符做状态迁移。这里所谓的状态迁移,就是当前状态函数返回下一个状态函数。const EOF = void 0// 词法分析器接受字符的function HTMLLexicalParser(syntaxer) { let state = data let token = null let attribute = null let characterReference = ’’ this.receiveInput = function (char) { if (state == null) { throw new Error(’there is an error’) } else { // 通过 state 来处理输入的字符流 state = state(char) } } this.reset = function () { state = data } // 状态机 c:每一个字符 function data(c) { switch (c) { case ‘&’: return characterReferenceInData // tagOpenState 是接受了一个“ < ” 字符,来判断标签类型的状态 case ‘<’: return tagOpen // perhaps will not encounter in javascript? // case ‘\0’: // error() // emitToken(c) // return data // can be handle by default case // case EOF: // emitToken(EOF) // return data default: emitToken(c) return data } } // only handle right character reference function characterReferenceInData(c) { if (c === ‘;’) { characterReference += c emitToken(characterReference) characterReference = ’’ return data } else { characterReference += c return characterReferenceInData } } function tagOpen(c) { if (c === ‘/’) { return endTagOpen } if (/[a-zA-Z]/.test(c)) { token = new StartTagToken() token.name = c.toLowerCase() return tagName } // no need to handle this // if (c === ‘?’) { // return bogusComment // } return error(c) } function tagName(c) { if (c === ‘/’) { return selfClosingTag } if (/[\t \f\n]/.test(c)) { return beforeAttributeName } if (c === ‘>’) { emitToken(token) return data } if (/[a-zA-Z]/.test(c)) { token.name += c.toLowerCase() return tagName } } function beforeAttributeName(c) { if (/[\t \f\n]/.test(c)) { return beforeAttributeName } if (c === ‘/’) { return selfClosingTag } if (c === ‘>’) { emitToken(token) return data } if (/["’<]/.test(c)) { return error(c) } attribute = new Attribute() attribute.name = c.toLowerCase() attribute.value = ’’ return attributeName } function attributeName(c) { if (c === ‘/’) { token[attribute.name] = attribute.value return selfClosingTag } if (c === ‘=’) { return beforeAttributeValue } if (/[\t \f\n]/.test(c)) { return beforeAttributeName } attribute.name += c.toLowerCase() return attributeName } function beforeAttributeValue(c) { if (c === ‘"’) { return attributeValueDoubleQuoted } if (c === “’”) { return attributeValueSingleQuoted } if (/\t \f\n/.test(c)) { return beforeAttributeValue } attribute.value += c return attributeValueUnquoted } function attributeValueDoubleQuoted(c) { if (c === ‘"’) { token[attribute.name] = attribute.value return beforeAttributeName } attribute.value += c return attributeValueDoubleQuoted } function attributeValueSingleQuoted(c) { if (c === “’”) { token[attribute.name] = attribute.value return beforeAttributeName } attribute.value += c return attributeValueSingleQuoted } function attributeValueUnquoted(c) { if (/[\t \f\n]/.test(c)) { token[attribute.name] = attribute.value return beforeAttributeName } attribute.value += c return attributeValueUnquoted } function selfClosingTag(c) { if (c === ‘>’) { emitToken(token) endToken = new EndTagToken() endToken.name = token.name emitToken(endToken) return data } } function endTagOpen(c) { if (/[a-zA-Z]/.test(c)) { token = new EndTagToken() token.name = c.toLowerCase() return tagName } if (c === ‘>’) { return error(c) } } // 输出解析好的 token(词) function emitToken(token) { syntaxer.receiveInput(token) } function error(c) { console.log(warn: unexpected char '${c}') }}class StartTagToken {}class EndTagToken {}class Attribute {}module.exports = { HTMLLexicalParser, StartTagToken, EndTagToken}// 使用const { HTMLLexicalParser } = require(’./lexer’)const testHTML = &lt;html maaa=a &gt; &lt;head&gt; &lt;title&gt;cool&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;img src="a" /&gt; &lt;/body&gt;&lt;/html&gt;const dummySyntaxer = { receiveInput: (token) => { if (typeof token === ‘string’) { console.log(String(${token.replace(/\n/, '\\n').replace(/ /, '&lt;whitespace&gt;')})) } else { console.log(token) } }}const lexer = new HTMLLexicalParser(dummySyntaxer)for (let c of testHTML) { lexer.receiveInput(c)}//便于理解:状态迁移代码var state = data;var charwhile(char = getInput()) state = state(char);语法分析//简单实现:伪代码function HTMLSyntaticalParser(){ var stack = [new HTMLDocument]; this.receiveInput = function(token) { //…… } this.getOutput = function(){ return stack[0]; }}const { StartTagToken, EndTagToken } = require(’./lexer’)class HTMLDocument { constructor () { this.isDocument = true this.childNodes = [] }}// 仅仅把 Node 分为 Element 和 Textclass Node {}class Element extends Node { constructor (token) { super(token) for (const key in token) { this[key] = token[key] } this.childNodes = [] } [Symbol.toStringTag] () { return Element&lt;${this.name}&gt; }}class Text extends Node { constructor (value) { super(value) this.value = value || ’’ }}function HTMLSyntaticalParser () { const stack = [new HTMLDocument] // receiveInput 负责接收词法部分产生的词(token),构建dom树的算法 this.receiveInput = function (token) { // 检查栈顶是否是 Text 节点,如果是的话就合并 Text节点 if (typeof token === ‘string’) { if (getTop(stack) instanceof Text) { getTop(stack).value += token } else { let t = new Text(token) getTop(stack).childNodes.push(t) stack.push(t) } } else if (getTop(stack) instanceof Text) { stack.pop() } // 匹配开始和结束标签 if (token instanceof StartTagToken) { let e = new Element(token) getTop(stack).childNodes.push(e) return stack.push(e) } if (token instanceof EndTagToken) { return stack.pop() } } this.getOutput = () => stack[0]}function getTop (stack) { return stack[stack.length - 1]}module.exports = { HTMLSyntaticalParser}// 使用const { HTMLSyntaticalParser } = require(’./syntaxer’)const { HTMLLexicalParser } = require(’./lexer’)const syntaxer = new HTMLSyntaticalParser()const lexer = new HTMLLexicalParser(syntaxer)const testHTML = &lt;html maaa=a &gt; &lt;head&gt; &lt;title&gt;cool&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;img src="a" /&gt; &lt;/body&gt;&lt;/html&gt;for (let c of testHTML) { lexer.receiveInput(c)}console.log(JSON.stringify(syntaxer.getOutput(), null, 2))扩展阅读:从Chrome源码看浏览器如何构建DOM树https://zhuanlan.zhihu.com/p/… ...

April 19, 2019 · 4 min · jiezi

浏览器渲染机制

本文示例源代码请戳github博客,建议大家动手敲敲代码。前言浏览器渲染页面的过程从耗时的角度,浏览器请求、加载、渲染一个页面,时间花在下面五件事情上:DNS 查询TCP 连接HTTP 请求即响应服务器响应客户端渲染本文讨论第五个部分,即浏览器对内容的渲染,这一部分(渲染树构建、布局及绘制),又可以分为下面五个步骤:处理 HTML 标记并构建 DOM 树。处理 CSS 标记并构建 CSSOM 树将 DOM 与 CSSOM 合并成一个渲染树。根据渲染树来布局,以计算每个节点的几何信息。将各个节点绘制到屏幕上。需要明白,这五个步骤并不一定一次性顺序完成。如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。实际页面中,CSS 与 JavaScript 往往会多次修改 DOM 和 CSSOM。1、浏览器的线程在详细说明之前我们来看一下浏览器线程。这将有助于我们理解后续内容。浏览器是多线程的,它们在内核制控下相互配合以保持同步。一个浏览器至少实现三个常驻线程:JavaScript 引擎线程,GUI 渲染线程,浏览器事件触发线程。GUI 渲染线程:负责渲染浏览器界面 HTML 元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在 Javascript 引擎运行脚本期间,GUI 渲染线程都是处于挂起状态的,也就是说被”冻结”了。JavaScript 引擎线程:主要负责处理 Javascript 脚本程序。定时器触发线程:浏览器定时计数器并不是由 JavaScript 引擎计数的, JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此浏览器通过单独线程来计时并触发定时。事件触发线程:当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理。这些事件包括当前执行的代码块如定时任务、浏览器内核的其他线程如鼠标点击、AJAX 异步请求等。由于 JS 的单线程关系所有这些事件都得排队等待 JS 引擎处理。定时块任何和 ajax 请求等这些异步任务,事件触发线程只是在到达定时时间或者是 ajax 请求成功后,把回调函数放到事件队列当中。异步 HTTP 请求线程:在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。在发起了一个异步请求时,http 请求线程则负责去请求服务器,有了响应以后,事件触发线程再把回到函数放到事件队列当中。2、构建DOM树与CSSOM树浏览器从网络或硬盘中获得HTML字节数据后会经过一个流程将字节解析为DOM树:编码: 先将HTML的原始字节数据转换为文件指定编码的字符。令牌化: 然后浏览器会根据HTML规范来将字符串转换成各种令牌(如<html>、<body>这样的标签以及标签中的字符串和属性等都会被转化为令牌,每个令牌具有特殊含义和一组规则)。令牌记录了标签的开始与结束,通过这个特性可以轻松判断一个标签是否为子标签(假设有<html>与<body>两个标签,当<html>标签的令牌还未遇到它的结束令牌</html>就遇见了<body>标签令牌,那么<body>就是<html>的子标签)。生成对象: 接下来每个令牌都会被转换成定义其属性和规则的对象(这个对象就是节点对象)构建完毕: DOM树构建完成,整个对象集合就像是一棵树形结构。可能有人会疑惑为什么DOM是一个树形结构,这是因为标签之间含有复杂的父子关系,树形结构正好可以诠释这个关系(CSSOS同理,层叠样式也含有父子关系。例如: div p {font-size: 18px},会先寻找所有p标签并判断它的父标签是否为div之后才会决定要不要采用这个样式进行渲染)。整个DOM树的构建过程其实就是: 字节 -> 字符 -> 令牌 -> 节点对象 -> 对象模型,下面将通过一个示例HTML代码与配图更形象地解释这个过程。<html> <head> <meta name=“viewport” content=“width=device-width,initial-scale=1”> <link href=“style.css” rel=“stylesheet”> <title>Critical Path</title> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src=“awesome-photo.jpg”></div> </body></html>当上述HTML代码遇见<link>标签时,浏览器会发送请求获得该标签中标记的CSS文件(使用内联CSS可以省略请求的步骤提高速度,但没有必要为了这点速度而丢失了模块化与可维护性),style.css中的内容如下:body { font-size: 16px }p { font-weight: bold }span { color: red }p span { display: none }img { float: right }浏览器获得外部CSS文件的数据后,就会像构建DOM树一样开始构建CSSOM树,这个过程没有什么特别的差别。3、构建渲染树在构建了DOM树和CSSOM树之后,浏览器只是拥有了两个互相独立的对象集合,DOM树描述了文档的结构与内容,CSSOM树则描述了对文档应用的样式规则,想要渲染出页面,就需要将DOM树与CSSOM树结合在一起,这就是渲染树。浏览器会先从DOM树的根节点开始遍历每个可见节点(不可见的节点自然就没必要渲染到页面了,不可见的节点还包括被CSS设置了display: none属性的节点,值得注意的是visibility: hidden属性并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,所以它会被渲染成一个空框)对每个可见节点,找到其适配的CSS样式规则并应用。渲染树构建完成,每个节点都是可见节点并且都含有其内容和对应规则的样式。4、布局与绘制CSS采用了一种叫做盒子模型的思维模型来表示每个节点与其他元素之间的距离,盒子模型包括外边距(Margin),内边距(Padding),边框(Border),内容(Content)。页面中的每个标签其实都是一个个盒子布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小,所有相对的测量值也都会被转换为屏幕内的绝对像素值。<html> <head> <meta name=“viewport” content=“width=device-width,initial-scale=1”> <title>Critial Path: Hello world!</title> </head> <body> <div style=“width: 50%"> <div style=“width: 50%">Hello world!</div> </div> </body></html>当Layout布局事件完成后,浏览器会立即发出Paint Setup与Paint事件,开始将渲染树绘制成像素,绘制所需的时间跟CSS样式的复杂度成正比,绘制完成后,用户就可以看到页面的最终呈现效果了。我们对一个网页发送请求并获得渲染后的页面可能也就经过了1~2秒,但浏览器其实已经做了上述所讲的非常多的工作,总结一下浏览器关键渲染路径的整个过程:处理HTML标记数据并生成DOM树。处理CSS标记数据并生成CSSOM树。将DOM树与CSSOM树合并在一起生成渲染树。遍历渲染树开始布局,计算每个节点的位置信息。将每个节点绘制到屏幕。5、外部资源是如何请求的为了直观的观察浏览器加载和渲染的细节,本地用nodejs搭建一个简单的HTTP Server。index.jsconst http = require(‘http’);const fs = require(‘fs’);const hostname = ‘127.0.0.1’;const port = 8080;http.createServer((req, res) => { if (req.url == ‘/a.js’) { fs.readFile(‘a.js’, ‘utf-8’, function (err, data) { res.writeHead(200, {‘Content-Type’: ’text/plain’}); setTimeout(function () { res.write(data); res.end() }, 5000) }) } else if (req.url == ‘/b.js’) { fs.readFile(‘b.js’, ‘utf-8’, function (err, data) { res.writeHead(200, {‘Content-Type’: ’text/plain’}); res.write(data); res.end() }) } else if (req.url == ‘/style.css’) { fs.readFile(‘style.css’, ‘utf-8’, function (err, data) { res.writeHead(200, {‘Content-Type’: ’text/css’}); res.write(data); res.end() }) } else if (req.url == ‘/index.html’) { fs.readFile(‘index.html’, ‘utf-8’, function (err, data) { res.writeHead(200, {‘Content-Type’: ’text/html’}); res.write(data); res.end() }) }}).listen(port, hostname, () => { console.log(‘Server running at ’ + hostname + ‘:’ + port);});index.html<!DOCTYPE html><html><head> <meta charset=“utf-8”> <meta http-equiv=“cache-control” content=“no-cache,no-store, must-revalidate”/> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <title>浏览器渲染</title> <link rel=“stylesheet” href=“http://127.0.0.1:8080/style.css”> <script src=‘http://127.0.0.1:8080/a.js’></script></head><body><p id=‘header’>1111111</p><script src=‘http://127.0.0.1:8080/b.js’></script><p>222222</p><p>3333333</p></body></html>style.css#header{ color: red;}a.js、b.js暂时为空可以看到,服务端将对a.js的请求延迟5秒返回。Server启动后,在chrome浏览器中打开http://127.0.0.1:8080/index.html我们打开chrome的调试面板第一次解析html的时候,外部资源好像是一起请求的,说资源是预解析加载的,就是说style.css和b.js是a.js造成阻塞的时候才发起的请求,图中也是可以解释得通,因为第一次Parse HTML的时候就遇到阻塞,然后预解析就去发起请求,所以看起来是一起请求的。6、HTML 是否解析一部分就显示一部分我们修改一下html代码<!DOCTYPE html><html><head> <meta charset=“utf-8”> <meta http-equiv=“cache-control” content=“no-cache,no-store, must-revalidate”/> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <title>浏览器渲染</title> <link rel=“stylesheet” href=“http://127.0.0.1:8080/style.css”></head><body><p id=‘header’>1111111</p><script src=‘http://127.0.0.1:8080/a.js’></script><script src=‘http://127.0.0.1:8080/b.js’></script><p>222222</p><p>3333333</p></body></html>因为a.js的延迟,解析到a.js所在的script标签的时候,a.js还没有下载完成,阻塞并停止解析,之前解析的已经绘制显示出来了。当a.js下载完成并执行完之后继续后面的解析。当然,浏览器不是解析一个标签就绘制显示一次,当遇到阻塞或者比较耗时的操作的时候才会先绘制一部分解析好的。7、js文件的位置对HTML解析有什么影响7.1 js文件在头部加载。修改index.html:<!DOCTYPE html><html><head> <meta charset=“utf-8”> <meta http-equiv=“cache-control” content=“no-cache,no-store, must-revalidate”/> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <title>浏览器渲染</title> <link rel=“stylesheet” href=“http://127.0.0.1:8080/style.css”> <script src=‘http://127.0.0.1:8080/a.js’></script> <script src=‘http://127.0.0.1:8080/b.js’></script></head><body><p id=‘header’>1111111</p><p>222222</p><p>3333333</p></body></html>因为a.js的阻塞使得解析停止,a.js下载完成之前,页面无法显示任何东西。7.2、js文件在中间加载。<!DOCTYPE html><html><head> <meta charset=“utf-8”> <meta http-equiv=“cache-control” content=“no-cache,no-store, must-revalidate”/> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <title>浏览器渲染</title> <link rel=“stylesheet” href=“http://127.0.0.1:8080/style.css”></head><body><p id=‘header’>1111111</p><script src=‘http://127.0.0.1:8080/a.js’></script><script src=‘http://127.0.0.1:8080/b.js’></script><p>222222</p><p>3333333</p></body></html>解析到js文件时出现阻塞。阻塞后面的解析,导致后面的不能很快的显示。7.3、js文件在尾部加载。<!DOCTYPE html><html><head> <meta charset=“utf-8”> <meta http-equiv=“cache-control” content=“no-cache,no-store, must-revalidate”/> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <title>浏览器渲染</title> <link rel=“stylesheet” href=“http://127.0.0.1:8080/style.css”></head><body><p id=‘header’>1111111</p><p>222222</p><p>3333333</p><script src=‘http://127.0.0.1:8080/a.js’></script><script src=‘http://127.0.0.1:8080/b.js’></script></body></html>解析到a.js部分的时候,页面要显示的东西已经解析完了,a.js不会影响页面的呈现速度。由上面我们可以总结一下直接引入的 JS 会阻塞页面的渲染(GUI 线程和 JS 线程互斥)JS 不阻塞资源的加载JS 顺序执行,阻塞后续 JS 逻辑的执行下面我们来看下异步js7.4、async和defer的作用是什么?有什么区别?接下来我们对比下 defer 和 async 属性的区别:其中蓝色线代表JavaScript加载;红色线代表JavaScript执行;绿色线代表 HTML 解析。情况1<script src=“script.js”></script>没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。情况2<script async src=“script.js”></script> (异步下载)async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。情况3 <script defer src=“script.js”></script>(延迟执行)defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。8、css文件的影响服务端将style.css的相应也设置延迟。fs.readFile(‘style.css’, ‘utf-8’, function (err, data) { res.writeHead(200, {‘Content-Type’: ’text/css’}); setTimeout(function () { res.write(data); res.end() }, 5000)})<!DOCTYPE html><html><head> <meta charset=“utf-8”> <meta http-equiv=“cache-control” content=“no-cache,no-store, must-revalidate”/> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <title>浏览器渲染</title> <link rel=“stylesheet” href=“http://127.0.0.1:8080/style.css”></head><body><p id=‘header’>1111111</p><p>222222</p><p>3333333</p><script src=‘http://127.0.0.1:8080/a.js’ async></script><script src=‘http://127.0.0.1:8080/b.js’ async></script></body></html>可以看出来,css文件不会阻塞HTML解析,但是会阻塞渲染,导致css文件未下载完成之前已经解析好html也无法先显示出来。我们把css调整到尾部<!DOCTYPE html><html><head> <meta charset=“utf-8”> <meta http-equiv=“cache-control” content=“no-cache,no-store, must-revalidate”/> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <title>浏览器渲染</title></head><body><p id=‘header’>1111111</p><p>222222</p><p>3333333</p><link rel=“stylesheet” href=“http://127.0.0.1:8080/style.css”><script src=‘http://127.0.0.1:8080/a.js’ async></script><script src=‘http://127.0.0.1:8080/b.js’ async></script></body></html>这是页面可以渲染了,但是没有样式。直到css加载完成以上我们可以简单总结。CSS 放在 head 中会阻塞页面的渲染(页面的渲染会等到 css 加载完成)CSS 阻塞 JS 的执行 (因为 GUI 线程和 JS 线程是互斥的,因为有可能 JS 会操作 CSS)CSS 不阻塞外部脚本的加载(不阻塞 JS 的加载,但阻塞 JS 的执行,因为浏览器都会有预先扫描器)参考浏览器渲染过程与性能优化聊聊浏览器的渲染机制你不知道的浏览器页面渲染机制 ...

April 19, 2019 · 3 min · jiezi

Edge 拥抱 Chromium 对前端工程师来说意味着什么?

翻译:疯狂的技术宅原文:https://css-tricks.com/edge-g…本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章在2018年12月,微软宣布 Edge 将采用 Chromium 内核,这是一个为 Google Chrome 提供支持的开源项目。业内许多人对失去浏览器多样性而感到悲伤,然而我却非常高兴。官方正式的发布日期尚未公布,不过可能会在今年的某个时间公布。随着它的发布,一系列 HTML、JavaScript 和 CSS 功能将实现完全的跨浏览器支持。现在 Windows 预览版已经可用 ,即将推出适用于 Mac 的版本。不久前,我写了一篇题为“慢慢死亡的 Internet Explorer ”的文章。 一些人已经很幸运放弃了那个浏览器。但这并不是阻止我们回归的唯一因素。 Internet Explorer 是我们都讨厌的浏览器,Edge 本来就是很好的替代品。不幸的是,Edge 本身就是落后的。 EdgeHTML 是 Trident 的一个分支,这也是 Internet Explorer 的内核。同时微软对 Edge 方面的投资不足,导致了有其父必有其子。 Edge 的用户反馈网站倒是不错,允许开发人员投票选出他们想要实现的功能。但不幸的是,正如 Dave Rupert 吐槽的那样,在网站上投票“就像往许愿池中扔硬币一样。” 最需要的功能多年来一直没有实现。Edge 目前有许多不支持的功能,但是这些功能在其他现代浏览器中是可用的,一旦他们进行了切换,马上就可以用了。而且它有很多不能被修补或解决的问题,所以这个版本是一个大麻烦。值得期待的可用功能那么这些功能究竟是什么呢?让我们在这里兴奋的做一个简述。自定义元素和 Shadow DOM自定义元素和 shadow DOM 允许开发人员能够得到自定义、可重用和可封装的组件。很多人都在要求这个功能。自 2014 年以来,人们一直在投票要求实现它,现在我们终于得到了。HTML 的 details 和 summary 元素<details> 和 <summary> 元素是 HTML5 的一部分,自2011年起就在 Chrome 中得到了支持。这些元素在一起使用,可以生成一个显示和隐藏内容的简单小部件。虽然用 JavaScript 实现类似的东西很简单的,但是 <details> 和 <summary> 元素即使在 JavaScript 被禁用或加载失败时也能工作。https://codepen.io/cssgrid/em…Javascript 的字体加载 API对于某些人来说这有很大的意义。目前所有现代浏览器都支持 CSS font-display 属性。但是你可能仍然希望用 JavaScript 加载字体。 Zach Leatherman 解释了为什么你可能需要用 JavaScript加载字体 ,即使现在 font-display 已经得到了广泛支持。根据 Zach 的说法,这个 API 的抛弃 polyfill 非常重要,因为这个JavaScript是[…]通常在关键路径中内联。浏览器解析和执行 JavaScript 所花费的时间实际上被浪费在了支持本机 CSS 字体加载 API 上了。“在2018年的一篇文章中,Zach 感叹道:[…]浏览器提供的 CSS 字体加载 API 有着相当广泛的支持并且已经存在了很长时间,但是所有人都对 Microsoft Edge 感到很遗憾。“不会再这样了!JavaScript 的 flat 和 flatMap用代码片段来解释是最简单的方式,当一个数组嵌套在另一个数组中时,flat() 非常有用。const things = [’thing1’, ’thing2’, [’thing3’, [’thing4’]]]const flattenedThings = things.flat(2); // Returns [’thing1’, ’thing2’, ’thing3’, ’thing4’]顾名思义,flatMap() 相当于同时使用 map() 和 flat() 方法。Node.js 11也支持这些方法。????JavaScript 的 TextEncoder和TextDecoderTextEncoder 和 TextDecoder 是编码规范的一部分。在使用流时,它们非常有用。JavaScript 对象的 rest 和 spread就像数组的 rest 和 spread 属性一样。const obj1 = { a: 100, b: 2000}const obj2 = { c: 11000, d: 220}const combinedObj = {…obj1, …obj2} // {a: 100, b: 2000, c: 11000, d: 220}JavaScript模块:动态导入使用类似函数的语法,动态导入允许你在用户需要时延迟加载 ES 模块。button.addEventListener(“click”, function() { import("./myModule.js").then(module => module.default());});CSS 的 background-blend-mode 属性background-blend-mode 给 web 带来了 Photoshop 风格的图像处理。CSS prefers-reduced-motion 媒体查询随着网络上的动画变得越来越普遍,我们要意识到到动画可能会导致某些用户出现头晕、恶心和头痛的症状。我不禁觉得不令人感到不适应该是网站的默认设置,因为并非所有用户都会知道这个设置存在。CSS 的 caret-color 属性这是一个相当简单的功能,可以安全、轻松地用作渐进增强功能。它允许你在文本框输入字符时设置闪烁光标的样式。8位十六进制颜色表示法在代码库中保持一致性很重要。这包括固定使用 RGB、十六进制或 HSL 颜色格式中的某一个。如果你的首选格式是十六进制,将会遇到问题,因为当你需要定义透明度时,就要切换到 rgba()。 Hex 现在可以包含 alpha(透明度)值。例如,ffffff80 相当于rgba(255,255,255,.5)。但是它不是最直观的颜色格式,并且也没有比 rgba() 更多的优势。固有尺寸这是我最渴望的一个功能。固有尺寸根据元素的内容确定大小,并在CSS中引入三个新关键字:min-content,max-content 和fit-content()。这些关键字可用于大多数通常使用长度的地方,如 height, width, min-width, max-width, min-height, max-height, grid-template-rows, grid-template-columns 和 flex-basis。CSS 的 text-orientation 属性与 writing-mode属性一起使用,text-orientation 可以指定文本的方向,非常值得期待。https://codepen.io/cssgrid/em…CSS :placeholder-shown 的伪元素placeholder-shown 甚至可以在 Internet Explorer 中使用,但不知何故从未在 Edge 中实现。用户体验研究表明,通常应该避免使用占位符文本。但是如果你用了占位符文本,可以很方便的根据用户是否在 input 中输入文本而有条件地应用样式。CSS 的 place-content 属性place-content 是设置 align-content 和 justify-content 的简写。https://codepen.io/cssgrid/em…CSS 的 will-change 属性will-change 属性可用于性能优化,提前通知浏览器元素 will change。 Pre-Chromium Edge实际上非常擅长处理动画,而不需要这个属性,但现在它将具有完全的跨浏览器支持。CSS 的 all 属性all 是一次设置所有 CSS 属性的简写。例如,设置 button { all: unset; } 相当于:button { background: none; border: none; color: inherit; font: inherit; outline: none; padding: 0;}不幸的是,revert 关键字仍然只在 Safari 中实现了,这在某种程度上限制了以从 all 属性中获得的好处。CSS 形状和剪辑路径传统上的 web 是以矩形为中心的。毕竟它有一个盒子模型。虽然我们不再需要浮动进行布局,但我们可以创造性地用它们来围绕图像和形状对文本 shape-outside 属性进行包装。这可以和 clip-path 属性结合使用,该属性可以在形状内显示图像。Clippy 是一个在线的 clip-path编辑器CSS :focus-within 伪类如果要对表单的某个输入控件在处于焦点时应用特殊样式,那么:focus-within 是你的最佳选择。CSS 内容关键字如果你正在使用 CSS 网格,这几乎是必不可少的。尽管开发者的投出了多达 3,920 张的选票,Edge 还是将其标记为“未计划”。对于 flexbox 和 grid,只有 direct children 分别成为 flex 项或 grid 项。任何嵌套更深的东西都不能用 flex 或 grid-positioning 放置。用规范中的话来说,当 display:contents 应用于父元素时,“该元素必须被看作它已经在元素树中被其内容替换,“允许它们用网格或 flexbox 布局。Chris 文章中更全面的解释值得一读。不幸的是,仍然有某些错误和其他影响可访问性的浏览器实现。对未来更多的承诺我们只研究了 Edge 迁移到 Chromium 时所有现代浏览器都支持的功能。也就是说,传统 Edge 的死亡也让很多其他功能越来越近了。 Edge 是唯一一个迟迟不肯支持 Web 动画 API 的浏览器,并且对 Houdini 规范完全没有兴趣。来源: https://ishoudinireadyyet.com对浏览器测试的影响在 BrowserStack 进行中测试(左)和 iPhone 上的各种浏览器(右)当然,对于 Web 开发人员来说,另一个巨大的优势是测试会减少。在跨浏览器测试期间 Edge 大多都会被忽视,因此 Edge 用户更有可能获得破碎的体验。这是微软决定转向 Chromium 的主要原因。如果你的网站在Chromium 浏览器中没有错误,那么在其它浏览器中可能工作的都很好。 用Edge团队的话来说,Chromium 将为我们的客户提供“更好的Web兼容性,并为所有 Web 开发减少 Web 碎片化”。各种各样的设备和浏览器使浏览器测试成为使前端开发人员的最不愉快的任务之一。 Edge 现在可供 macOS 用户使用,这对于我们在 Mac 上工作的人来说非常有用。对 BrowserStack 的需求将会略微减少。我们会失去什么?据我所知,SVG颜色字体将不再适用于 Edge 浏览器。其他颜色字体格式(COLR,SBIX,CBDT/CBLC)将继续有效。其它浏览器会怎样?不可否认,Edge 并不是最后一个低标准浏览器。 Internet Explorer 始终不支持本文提到的所有功能。如果你在俄罗斯有用户,则需要支持 Yandex。如果你在非洲有用户,则需要支持 Opera Mini。如果你在中国有用户,那么UC 和 QQ 将会是重要的测试对象。如果你不需要考虑这些区域性因素,那么现在就是放弃对 Internet Explorer 的支持并拥抱现代 Web 功能的最佳时机。很多 PC 用户完全不习惯使用 Internet Explorer。希望改进后的 Edge 能够吸引他们。 Microsoft 官方博客中标题为“把 Internet Explorer 作为默认浏览器的危险” 的文章得出结论:“Internet Explorer 是一种兼容性解决方案……大多数开发人员现在都没有在 Internet Explorer 上进行测试。”对于其它用户来说,大多数 web 看起来越来越支离破碎。该让它死掉了。Google 是自大狂?Web 开发人员的工作将变得更加轻松,但对微软公告的回应并非是积极的。例如,Mozilla 有一个极其悲观的回应,指责微软“正式放弃独立的互联网共享平台”。该声明称谷歌“几乎完全控制了我们在线生活的基础设施”,并且“垄断了对独特资产的控制”。它的结论是“将基本的基础在线设施的控制权交给一家公司是非常糟糕的。”许多人已经回想到了 IE6 的时代,这是浏览器最后一次获得如此巨大的市场份额。赢得浏览器大战的 Internet Explorer 让人陷入了停滞状态。相比之下,Chrome 不断推出新功能。 Google 积极参与 W3C 和 WHATWG 的 web 标准化组织。可以说,它在这些机构中具有超大的影响力,并具有决定 web 未来形态的能力。Google 开发人员确实倾向于炒作仅在 Chrome 中发布的功能。来自竞争者的合作Edge 而不是新的 IE 可以帮助 web 创新。虽然它在许多领域是落后的,但它确实引领了 CSS 网格、CSS Exclusions、CSS Regions 和新的HTML导入规范。与以往完全不同,现在微软已成为全球最大的开源项目支持者之一。这意味着所有主流浏览器现在都是开源的。微软已声明他们打算成为Chromium的重要贡献者 —— 事实上,他们已经累计提交了超过300次合并。这将对 Edge 用户有很大帮助,同时也将使 Chrome、Opera、Brave和其他基于 Chromium 的浏览器用户受益。本文首发微信公众号:前端先锋欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目必须要会的 50 个React 面试题世界顶级公司的前端面试都问些什么11 个最好的 JavaScript 动态效果库CSS Flexbox 可视化手册从设计者的角度看 React过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!CSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从14个最好的 JavaScript 数据可视化库8 个给前端的顶级 VS Code 扩展插件Node.js 多线程完全指南把HTML转成PDF的4个方案及实现 ...

April 18, 2019 · 3 min · jiezi

干货:浏览器渲染引擎Webkit和V8引擎工作原理

浏览器的历史W3C再80年代后期90年代初期发明了世界上第一个浏览器WorldWideWeb(后更名为Nexus),支持文本/简单的样式表/电影/声音和图片1993年,网景(netscape)浏览器诞生,没有JavaScript,没有css,只显示简单的html元素1995年,微软推出闻名世界的IE浏览器,自此第一次浏览器大战打响,IE受益于Windows系统获得空前的成功,逐渐取代网景浏览器1998年处于低谷的网景成立了Mozilla基金会,在该基金会推动下,开发了著名的开源项目Firefox并在2004年发布1.0版本,拉开了第二次浏览器大战的序幕,IE发展更新较缓慢,Firefox一推出就深受大家的喜爱,市场份额一直上升。在Firefox浏览器发布1.0版本的前一年,2003年,苹果发布了Safari浏览器,并在2005年释放了浏览器中一种非常重要部件的源代码,发起了一个新的开源项目WebKit2008年,Google以苹果开源项目WebKit作为内核,创建了一个新的项目Chromium,在Chiromium的基础上,Google发布了ChromeWebkit模块和其依赖模块上图是WebKit模块和其依赖模块的关系。在操作系统之上的是WebKit赖以工作的众多第三方库,如何高效使用它们是WebKit和各大浏览器厂商的一个重大课题。WebCore部分都是加载和渲染的基础部分WebKit Ports是WebKit非共享部分,对于不同浏览器移植中由于平台差异/依赖的第三方库和需求不同等方面原因,往往按照自己的方式来设计和实现。在WebCore/js引擎/WebKitPorts之上主要是提供嵌入式编程接口,提供给浏览器调用。页面加载解析渲染过程简介如上图所示,图中虚线是与底层第三方库交互。当访问一个页面的时候,会利用网络去请求获取内容,如果命中缓存了,则会在存储上直接获取;如果内容是个HTML格式,首先会找到html解释器进行解析生成DOM树,解析到style的时候会找到css解释器工作得到CSSOM,解析到script会停止解析并开始解析执行js脚本;DOM树和CSSOM树会构建成一个render树,render树上的节点不和DOM树一一对应,只有显示节点才会存在render树上;render树已经知道怎么绘制了,进入布局和绘图,绘制完成后将调用绘制接口,从而显示在屏幕上。从资源的字节流到DOM树上面我们简单介绍了整个过程,现在我们开始认识一下各个步骤的具体过程。字节流经过解码后是字符流,然后通过词法分析器会被解释成词语(Tokens),之后经过语法分析器构建成节点,最后这些节点组成一棵DOM树 词法分析 在进行词法分析前,解释器首先要检查网页内容实用的编码格式,找到合适的解码器,将字节流转换成特定格式的字符串,然后交给词法分析器进行分析。 每次词法分析器都会根据上次设置的内部状态和上次处理后的字符串来生成一个新的词语。内部使用了超过70种状态。(ps:生成的词语还会进过XssAutitor验证词语是否安全合法,非合法的词语不能通过) 如上图所示,举个例子<div> <img src="/a" /></div>1 接收到"<“进入TagOpen状态 2 接收"d”,根据当前状态是TagOpen判断进入TagName状态,之后接收"i"/“v” 3 接收">“进入TagEnd状态,此时得到了div的开始标签(StartTag) 4 接收”<“进入TagOpen,接收"img"后接收到空格得到了img开始标签 6 进入attribute一系列(笔者自己命名的,不知道叫啥)状态,得到了src属性吗和”/a"属性值 6 同样方式获得div结束标签 词语到节点 得到词语(Tokens)后,就可以开始形成DOM节点了。 注意:这里说的节点不单单指HTMLElement节点,还包括TextNode/Attribute等等一系列节点,它们都继承自Node类。 词语类型只有6种,DOCTYPE/StartTag/EndTag/Comment/Character/EndOfFile组成DOM树 因为节点都可以看成有开始和结束标记,所以用栈的结构来辅助构建DOM树再合适不过了。 当遇到开始标记的时候,推入栈,当遇到结束标记的时候,退栈放再DOM树上,再拿上述的html代码做例子。<div> <img src="/a" /> <span>webkit</span></div>1 遇到div开始标签,将div推入栈; 2 遇到img开始标签,将img推入栈; 3 遇到src属性,将src推入栈; 4 将src从栈中取出,作为DOM树的一部分; 5 遇到img结束标签,说明img包裹着src属性,取出img,作为src的父亲节点; 6 遇到span开始标签,将span推入栈; 7 遇到文本webkit,将文本推入栈; 8 取出webkit文本,待分发; 9 遇到span结束标签,说明span标签包裹着webkit文案,取出span标签,作为文本webkit的父亲节点; 10 遇到div结束标签,取出div标签,说明div标签包裹着img和span,作为它们的公共父亲节点CSS解析WebKit 使用 Flex 和 Bison 解析器生成器,通过 CSS 语法文件自动创建解析器。最后WebKit将创建好的结果直接设置到StyleSheetContents对象中。规则匹配 当WebKit需要为HTML元素创建RenderObject类(后面会讲到)的时候,首先会先去获取样式信息,得到RenderStyle对象——包含了匹配完的结果样式信息。 根据元素的标签名/属性检查规则,如果某个规则匹配上该元素,Webkit把这些规则保存在匹配结果中 最后Webkit对这些规则进行排序,整合,将样式属性值返回脚本设置CSS CSSOM在DOM中的一些节点接口加入了获取和操作css属性或者接口的JavaScript接口,因而JavaScript可以动态操作css样式。 CSSOM定义了样式表的接口CSSStyleSheet,document.styleshheets可以查看当前网页包含的所有css样式表 W3C定义了另外一个规范,CSSOM View,增加一些新的属性到Window.Document,Element.MounseEvent等接口,这些CSS的属性能让JavaScript获取视图信息至此我们已经了解到了文档的解析过程,这里有一些实验可以帮助你更好的了解页面加载过程发生了什么。聊聊浏览器的渲染机制——若邪Y布局只要发生样式的改变,都会触发检查是否需要布局计算当首次加载页面/renderStyle改变/滚动操作的时候,都会触发布局布局是比较耗时的操作,更糟糕的时候布局的下一步就是渲染,我们可以通过硬件加速来跳过布局和渲染,下面我们会讲到。多进程的浏览器一个好的程序常常被划分为几个相互独立又彼此配合的模块,浏览器也是如此,以 Chrome 为例,它由多个进程组成,每个进程都有自己核心的职责,它们相互配合完成浏览器的整体功能,每个进程中又包含多个线程,一个进程内的多个线程也会协同工作,配合完成所在进程的职责。Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。具体说来,Chrome 的主要进程及其职责如下:Browser Process:负责包括地址栏,书签栏,前进后退按钮等部分的工作;负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问;Renderer Process:负责一个 tab 内关于网页呈现的所有事情Plugin Process:负责控制一个网页用到的所有插件,如 flashGPU Process负责处理 GPU 相关的任务通过「页面右上角的三个点点点 — 更多工具 — 任务管理器」即可打开相关面板 加载页面各进程的合作 处理输入UI thread 需要判断用户输入的是 URL 还是 query;开始导航当用户点击回车键,UI thread 通知 network thread 获取网页内容,并控制 tab 上的 spinner 展现,表示正在加载中。读取响应当请求响应返回的时候,network thread 会依据 Content-Type 及 MIME Type sniffing 判断响应内容的格式如果响应内容的格式是 HTML ,下一步将会把这些数据传递给 renderer process,如果是 zip 文件或者其它文件,会把相关数据传输给下载管理器。查找渲染进程当上述所有检查完成,network thread 确信浏览器可以导航到请求网页,network thread 会通知 UI thread 数据已经准备好,UI thread 会查找到一个 renderer process 进行网页的渲染。确认导航进过了上述过程,数据以及渲染进程都可用了, Browser Process 会给 renderer process 发送 IPC 消息来确认导航,一旦 Browser Process 收到 renderer process 的渲染确认消息,导航过程结束,页面加载过程开始。此时,地址栏会更新,展示出新页面的网页信息。history tab 会更新,可通过返回键返回导航来的页面,为了让关闭 tab 或者窗口后便于恢复,这些信息会存放在硬盘中。渲染DOM树构建完成之后,Webkit还要为DOM树构建RenderObject树。什么情况下会为一个DOM节点建立新的RenderObject对象呢1.ducument节点2.可视节点,例如html,body,div等。而WebKit不会为非可视化节点创建RenderObject节点,例如link,head,script3.某些情况下WebKit会建立匿名的RenderObject,该RenderObject不对应DOM树的任何节点,例如匿名的RenderBlocktip:如果一个节点即包含块级节点又包含内联节点,会为内联节点创建一个RenderBlock,即形成RenderObject——RenderObject ——RenderBlock——RenderObject网页是可以分层的,可以让WebKit在渲染处理上获得便利。会产生RenderLayer的情况: document节点和html节点 显示定义position属性的RenderObject 节点有overflow/alpha等效果RenderObject 使用canvas2d或者webgl技术,(注:canvas节点创建的时候不会马上生成RenderLayer对象,在js创建了2d或者3d上下文的时候才创建 Video节点对应的RenderObjectRenderObject对象知道如何绘制自己了,需要调用绘图上下文来进行绘图操作。渲染方式:软件渲染(Cpu完成)和硬件加速渲染(Gpu完成)软件渲染Renderer进程消息循环调用判断是否需要重新计算的布局和更新,如要 Renderer进程创建共享内存 WebKit计算重绘区域中重叠的RenderLayer,RenderLayer重新绘制,绘制结果到共享内存的位图中 绘制完成后,Renderer进程发生消息给Browser进程,Browser进程将更新的区域将共享内存的内容绘制到自己对应存储区域中(绘制过程不会影响该网页结果的显示) Browser进程回复消息给Renderer,回收共享内存 Browser进程绘制到窗口硬件渲染 GPU硬件进行绘图和合成,每个网页的Renderer进程都是将之前介绍的3D绘图和合成操作传递给GPU进程,由它来统一调度 和执行,在安卓中,GPU进程并不存在,WebKit将所有工作放在Browser进程中的一个线程完成。 GPU进程处理一些命令后,会向Renderer进程报告自己当前的状态,Renderer进程通过检查状态信息和自己的期望结果来确定是否满足自己的条件。GPU进程最终绘制的结果不再像软件渲染那样通过共享内存的方式传递给Browser进程,而是直接将页面的内容绘制在浏览器的标签窗口理想情况,每一个层都会有个存储区域,保存绘图结果,最后将这些层的内容合并(compositing)软件渲染机制是没有合成阶段的,软件渲染的结果是一个位图(bitmap),绘制每一层的时候都使用该位图,区别在于绘制的位置可能不一样,每一层按照从后前的顺序。这样软件绘图使用的只是一块内存空间即可。软件渲染只能处理2D方面的操作,并且在高fps的绘图中性能不好,比如视频和canvas2d等,但是cpu使用的缓存机制有效减少了重复绘制的开销硬件绘制和所有的层的合成都使用Gpu完成,硬件加速渲染能支持现在所有的html5定义的2d和3d绘图标准;另外,由于软件渲染没有为每一层提供后端存储,因而需要将和某区域有重叠部分的所有层次相关区域重新绘制一次,而硬件加速渲染只需重新绘制更新发生的层次。实验时间<div id=“box”></div><div id=“bo2”></div> #box { position: relative; width: 100px; height: 100px; background: #ccc; transform: translate3d(0,0,0); transition: transform 2s linear; } #box.move { transform: translate3d(100px,0,0) !important } #box2 { position: relative; width: 100px; height: 100px; background: #ccc; left: 0; transition: left 2s linear; } #box2.move { left: 100px !important }var box2 = document.getElementById(‘box2’)setTimeout(() => { box2.classList.add(‘move’)}, 200);首先我们看下利用开发者工具Layers可以看到,如下图,box1利用了transform3d,从而判断需要为box1独立一层,而其他的内容则依旧附在document层。我们切换到performance进行录制,查看event log如下图。发现在box2在移动的时候,不断重复5各过程:recalculate style——layout——update layer tree——paint——composite layers也就是说document层不断得重新计算布局,重新渲染,再和box2合并layers,这造成了巨大的浪费。我们接下来来看一些box1的移动。var box = document.getElementById(‘box1’)setTimeout(() => { box.classList.add(‘move’)}, 200);如下图,在box1移动的时候,没有了布局和绘制的过程,利用CSS3D加速,只需要在合并层之前改变属性,再次合并层就可以了,不需要重新布局,也没有绘制步骤,这就是为什么我们在写动画的时候要时候3d启用硬件加速的原因,大大减少了布局绘制的资源浪费。V8引擎上面我们已经把渲染过程了解清楚了,接下来来看一下V8引擎这个重头戏吧~!V8引擎和渲染引擎通信当用户在屏幕上触发诸如 touch 等手势时,首先收到手势信息的是 Browser process, 不过 Browser process 只会感知到在哪里发生了手势,对 tab 内内容的处理是还是由渲染进程控制的。事件发生时,浏览器进程会发送事件类型及相应的坐标给渲染进程,渲染进程随后找到事件对象,交给js引擎处理,如果js代码中利用了侨界接口将该节点绑定了事件监听,那么就会触发该事件监听函数。字节码 机器码 JIT编译型语言如c/c++,处理该语言实际上使用编译器直接将它们编译成本地代码,用户知识使用这些变异号的本地代码,被系统的加载起加载执行,这些本地代码由操作系统调度CPU直接执行java做法是明显的两个阶段,首先是编译,不像c/c++编译成本地代码,而是编译生成字节码,字节码是跨平台的中间表示,然后java虚拟机加载字节码,使用解释器执行这些代码。V8之前的版本直接的将抽象语法树通过JIT技术转换成本地代码,放弃了在字节码阶段可以进行的一些性能优化,但保证了执行速度。在V8生成本地代码后,也会通过Profiler采集一些信息,来优化本地代码。虽然,少了生成字节码这一阶段的性能优化,但极大减少了转换时间。但是在2017年4月底,v8 的 5.9 版本发布了,新增了一个 Ignition 字节码解释器,将默认启动(主要动机)减轻机器码占用的内存空间,即牺牲时间换空间 提高代码的启动速度故事得从 Chrome 的一个 bug 说起: http://crbug.com/593477 。Bug 的报告人发现,当在 Chrome 51 (canary) 浏览器下加载、退出、重新加载 facebook 多次,并打开 about:tracing 里的各项监控开关,可以发现第一次加载时 v8.CompileScript 花费了 165 ms,再次加载加入 V8.ParseLazy 居然依然花费了 376 ms。按说如果 Facebook 网站的 js 脚本没有变,Chrome 的缓存功能应该缓存了对 js 脚本的解析结果,不该花费这么久。这是为什么呢?这就是之前 v8 将 JS 代码编译成机器码所带来的问题。因为机器码占空间很大,v8 没有办法把 Facebook 的所有 js 代码编译成机器码缓存下来,因为这样不仅缓存占用的内存、磁盘空间很大,而且再次进入时序列化、反序列化缓存所花费的时间也很长,时间、空间成本都接受不了。在启动速度方面,如今内存占用过大的问题消除了,就可以提前编译所有代码了。因为前端工程为了节省网络流量,其最终 JS 产品往往不会分发无用的代码,所以可以期望全部提前编译 JS 代码不会因为编译了过多代码而浪费资源。v8 对于 Facebook 这样的网站就可以选择全部提前编译 JS 代码到字节码,并把字节码缓存下来,如此 Facebook 第二次打开的时候启动速度就变快了。下图是旧的 v8 的执行时间的统计数据,其中 33% 的解析、编译 JS 脚本的时间在新架构中就可以被缩短。v8 自身的重构方面,有了字节码,v8 可以朝着简化的架构方向发展,消除 Cranshaft 这个旧的编译器,并让新的 Turbofan 直接从字节码来优化代码,并当需要进行反优化的时候直接反优化到字节码,而不需要再考虑 JS 源代码。最终达到如下图所示的架构。其实,Ignition + TurboFan 的组合,就是字节码解释器 + JIT 编译器的黄金组合。这一黄金组合在很多 JS 引擎中都有所使用,例如微软的 Chakra,它首先解释执行字节码,然后观察执行情况,如果发现热点代码,那么后台的 JIT 就把字节码编译成高效代码,之后便只执行高效代码而不再解释执行字节码。隐藏类在V8中建立类有两个主要的理由,即(1)将属性名称相同的对象归类,及(2)识别属性名称不同的对象。同一类中的对象有完全相同的对象描述,而这可以加速属性存取。在V8,符合归类条件的类会配置在各种JavaScript对象上。对象引用所配置的类。然而这些类只存在于V8作为方便之用,所以它们是「隐藏」的。如果对象的描述是相同的,那么隐藏类也会相同。如下图的例子中,对象p和q都属于相同的隐藏类。我们随时可以在JavaScript中新增或删除属性。然而当此事发生时会毁坏归类条件(归纳名称相同的属性)。V8借由建立属性变化所需的新类来解决。属性改变的对象透过一个称为「类型转换(class transition)」的程序纳入新级别中。在类中储存类变换信息当在对象p中加入新属性z时,V8会在Point类内的表格上记录「加入属性z,建立类Point2」。当同一Point类的对象q加入属性z时,V8会先搜寻Point类表。如果它发现了Point2类已加入属性z时,就会将对象q设定在Point2类。内嵌内存正常访问对象属性的过程是:首先获取隐藏类的地址,然后根据属性名查找偏移值,然后计算该属性的地址。虽然相比以往在整个执行环境中查找减小了很大的工作量,但依然比较耗时。能不能将之前查询的结果缓存起来,供再次访问呢?当然是可行的,这就是内嵌缓存。 内嵌缓存的大致思路就是将初次查找的隐藏类和偏移值保存起来,当下次查找的时候,先比较当前对象是否是之前的隐藏类,如果是的话,直接使用之前的缓存结果,减少再次查找表的时间。当然,如果一个对象有多个属性,那么缓存失误的概率就会提高,因为某个属性的类型变化之后,对象的隐藏类也会变化,就与之前的缓存不一致,需要重新使用以前的方式查找哈希表。垃圾回收V8的垃圾回收策略基于分代回收机制,该机制又基于 世代假说。该假说有两个特点:大部分新生对象倾向于早死;不死的对象,会活得更久。在V8中,将内存分为了新生代(new space)和老生代(old space)。它们特点如下:新生代:对象的存活时间较短。新生对象或只经过一次垃圾回收的对象。老生代:对象存活时间较长。经历过一次或多次垃圾回收的对象。新生代内存回收新生代中的对象主要通过 Scavenge 算法进行垃圾回收。Scavenge 的具体实现,主要采用了Cheney算法。 Cheney算法采用复制的方式进行垃圾回收。它将堆内存一分为二,每一部分空间称为 semispace。这两个空间,只有一个 空间处于使用中,另一个则处于闲置。使用中的 semispace 称为 「From 空间」,闲置的 semispace 称为 「To 空间」。 过程如下: 从 From 空间分配对象,若 semispace 被分配满,则执行 Scavenge 算法进行垃圾回收。 检查 From 空间的存活对象,若对象存活,则检查对象是否符合晋升条件,若符合条件则晋升到老生代,否则将对象从 From 空间复制到 To 空间。 若对象不存活,则释放不存活对象的空间。 完成复制后,将 From 空间与 To 空间进行角色翻转(flip)。Scavenge 算法的缺点是,它的算法机制决定了只能利用一半的内存空间。但是新生代中的对象生存周期短、存活对象少,进行对象复制的成本不是很高,因而非常适合这种场景。老生代内存回收 Mark-Sweep,是标记清除的意思。它主要分为标记和清除两个阶段。 标记阶段,它将遍历堆中所有对象,并对存活的对象进行标记; 清除阶段,对未标记对象的空间进行回收。 与 Scavenge 算法不同,Mark-Sweep 不会对内存一分为二,因此不会浪费空间。但是,经历过一次 Mark-Sweep 之后,内存的空间将会变得不连续,这样会对后续内存分配造成问题。比如,当需要分配一个比较大的对象时,没有任何一个碎片内支持分配,这将提前触发一次垃圾回收,尽管这次垃圾回收是没有必要的。Mark-Compact则是将存活的对象移动到一边,然后再清理端边界外的内存。这篇文章我整理了好久,希望转载表明出处~参考:《WebKit技术内幕》——朱永盛图解浏览器的基本工作原理深入理解V8的垃圾回收原理为什么V8引擎这么快?V8引擎详解V8 Ignition:JS 引擎与字节码的不解之缘 ...

April 9, 2019 · 2 min · jiezi

深入剖析浏览器缓存策略

前言在访问一个网页时,客户端会从服务器下载所需的资源。但是有些资源很少发生变动,例如 HTML、JS、CSS、图片、字体文件等。如果每次加载页面都从源服务器下载这些资源,不仅会增加获取资源的时间,也会给服务器带来一定压力。因此,重用已获取的资源十分重要。将请求的资源缓存下来,下次请求同一资源时,直接使用存储的副本,而不会再去源服务器下载。这就是我们常说的缓存技术。缓存的种类很多:浏览器缓存、网关缓存、CDN 缓存、代理服务器缓存等。这些缓存大致可以归为两类:共享缓存和私有缓存。共享缓存能够被多个用户使用,而私有缓存只能用于单个用户。浏览器缓存只存在于每个单独的客户端,因此它是私有缓存。本文主要介绍私有(浏览器)缓存。你将学习:浏览器缓存的分类如何启用和禁止缓存缓存存储的位置如何设置缓存的过期时间缓存过期之后会发生什么如何为自己的应用制定合适的缓存策略调试如何判断网站是否启用了缓存如何禁用浏览器缓存缓存存储启用缓存Cache-Control浏览器会根据 HTTP Response Headers 中的一些字段来决定是否要缓存该资源。通过设置 Response Headers 中的 Cache-Control 和 Expires 可以启用缓存,这样资源就会被缓存到客户端。Cache-Control 可以设置 private 、public 、max-age 、 no-cache 来启用缓存。Cache-Control: private/publicCache-Control: max-age=300Cache-Control: no-cacheprivate :表示该资源只能被浏览器缓存。public :表示该资源既能被浏览器缓存,也能被任何中间人(比如代理服务器、CDN 等)缓存。max-age :表示该资源能够被缓存的最大时间。如果设置 max-age=0 ,该资源仍然会被浏览器缓存,只不过立刻就过期了。no-cache :该资源会被缓存,但是立刻就过期了,因此需要先和服务器确认资源是否发生变化,只有当资源没有变化时,该缓存才会被使用,否则需要从服务器下载。相当于 max-age=0 。ExpiresExpires 标识了缓存的具体过期时间,来控制资源何时过期。通过设置 Expires 可以启用缓存。不过需要注意 Expires 的值是格林威治时间(Greenwich Mean Time, GMT),不是本地时间。Expires: Fri, 08 Mar 2029 08:05:59 GMTExpires: 0 // Expires: 0 仍然会启用缓存,只不过缓存立刻过期。优先级既然 Cache-Control 和 Expires 都能够启用缓存,那么问题来了,如果同时设置 Cache-Control: max-age=600 和 Expires: 0 ,那么浏览器应该如何缓存该资源呢?答案是只有 Cache-Control: max-age=600 生效。因为 Cache-Control 的优先级高于 Expires,如果同时设置了 Cache-Control 和 Expires,以 Cache-Control 为准。浏览器的默认行为设置 Cache-Control 之后,可以看到浏览器确实启用了缓存(from disk cache)。如下所示:Cache-Control:max-age=604800, must-revalidate, public但是我发现,即使 Response Header 中没有设置 Cache-Control 和 Expires,浏览器仍然会缓存某些资源。这是为什么呢?原来当 Response Header 中有 Last-Modified 但是没有 Cache-Control 和 Expires 时,浏览器会用一套自己的算法来决定这个资源会被缓存多长时间。这是浏览器为了提升性能进行的优化,每个浏览器的行为可能不一致,有些浏览器上甚至没有这样的优化。因此,如果要启用缓存,还是应该自己设置合适的 Cache-Control 和 Expires,不要依赖浏览器自身的缓存算法。当然,如果在调试时发现本应该更新的文件没有更新,也别忘了看看是否被浏览器缓存了。禁止缓存给 Cache-Control 设置 no-store 会禁止浏览器和中间人缓存该资源。在处理包含个人隐私数据或银行业务数据的资源时很有用。Cache-Control: no-store缓存目标对象一般来说,浏览器缓存只能存储 GET 响应,例如 HTML、JS、CSS、图片等静态资源。因为这些资源不经常发生变化,所以缓存可以帮助提升获取资源的速度。但是像一些 POST/DELETE 请求,这些请求基本上每一次都不一样,因此也没有什么缓存的价值。缓存位置浏览器可以在内存、硬盘中开辟一个空间用以保存请求资源的副本。我们经常在 Dev Tools 里面看到 Memory Cache(内存缓存)和 Disk Cache(硬盘缓存),指的就是缓存所在的位置。请求一个资源时,会按照优先级(Service Worker -> Memory Cache -> Disk Cache -> Push Cache)依次查找缓存,如果命中则使用缓存,否则发起网络请求。这里只介绍常用的 Memory Cache 和 Disk Cache。200 from Memory Cache表示不访问服务器,直接从内存中读取缓存。因为缓存的资源保存在内存中,所以读取速度较快,但是关闭进程之后,缓存的资源也会随之销毁。一般来说,系统不会给内存分配较大的容量,因此内存缓存一般用于存储小文件。同时,内存缓存在有时效性要求的场景下也很有用(比如浏览器的隐私模式)。200 from Disk Cache表示不访问服务器,直接从硬盘中读取缓存。与内存相比,硬盘的读取速度较慢,但是硬盘缓存持续的时间更长,关闭进程之后,缓存的资源仍然存在。由于硬盘的容量较大,因此一般用于存储大文件。总的来说就是:内存缓存:读取快、持续时间短、容量小硬盘缓存:读取慢、持续时间长、容量大缓存分类浏览器缓存一般分为两类:强缓存(也称本地缓存)和协商缓存(也称弱缓存)。判定过程如下:浏览器发送请求前,会先去缓存里面查看是否命中强缓存,如果命中,则直接从缓存中读取资源,不会发送请求到服务器。否则,进入下一步。当强缓存没有命中时,浏览器一定会向服务器发起请求。服务器会根据 Request Header 中的一些字段来判断是否命中协商缓存。如果命中,服务器会返回响应,但是不会携带任何响应实体,只是告诉浏览器可以直接从缓存中获取这个资源。否则,进入下一步。如果前两步都没有命中,则直接从服务器加载资源。强缓存和协商缓存的共同点在于,如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源。而不同点在于,强缓存不发送请求到服务器,而协商缓存会发送请求到服务器以验证资源是否过期。普通刷新会启用协商缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下,浏览器才会启用强缓存。缓存过期策略当缓存过期之后,浏览器会向服务器发起 HTTP 请求,以确定资源是否发生了变化。如果资源未改变,那么浏览器会继续使用本地的缓存资源;如果该资源已经发生变化了,那么浏览器会删除旧的缓存资源,并将新的资源缓存到本地。过期时间Http Response Header 里面的 Cache-Control: max-age=xxx 和 Expires 都可以设置缓存的过期时间,但是它们有一些区别:Expires :标识该资源过期的时间点,它是一个绝对值,即在这个时间点之后,缓存的资源过期。max-age :标识该资源能够被缓存的最大的时间。它是一个相对值,相对于第一次请求该文档时服务器记录的「请求发起时间」。虽然 Cache-Control 是 HTTP 1.1 提出来的新特性,但并不是说 max-age 优于 Expires。它们都有各自的使用场景,我们应该根据业务需求去决定使用哪一个。比如当某个资源需要在特定的时间点过期时应该使用 Expires 。如果只是为了开启缓存,使用 max-age 可能会更好些,因为 Cache-Control 的优先级高于 Expires。针对应用中几乎不会改变的文件,通常可以设置一个较长的过期时间,以保证缓存的有效。例如图片、CSS、JS 等静态资源。缓存验证上一小节已经提到,当浏览器请求一个资源时,如果发现缓存中有该资源,但是已经过期了,那么浏览器就会向服务器发起 HTTP 请求,以验证缓存的资源是否发生变化。缓存验证时机什么时候会进行缓存验证?刷新页面。一般来说,为了确保用户获取到最新的数据,在刷新页面时大部分浏览器都不会再使用缓存中的数据,而是发起一个请求去服务器验证。Response Header 中设置了 Cache-control: must-revalidate。当缓存的资源过期之后,必须到源服务器去验证,只有确认该资源没有过期,才能继续使用缓存。缓存验证器服务器是怎么判断资源改变与否的呢?服务端在返回响应内容的同时,还会在 Response Header 中设置一些验证标识,当缓存的资源过期之后,浏览器就会携带验证标识向服务器发起请求,服务器通过对比这些标识,就能知道缓存的资源是否发生了改变。Header 中的验证标识字段主要有两组:Etag 和 If-None-Match 、Last-Modified 和 If-Modified-Since 。其中,形如 If-xxx 这样的请求首部字段,可以称之为条件请求。比如只在满足某个条件的情况下返回或上传文件,这样可以节省带宽。 Last-ModifiedLast-Modified 就是一个验证器。服务器在将资源返回给客户端的同时,会将资源的最后修改时间 Last-Modified 加在 Response Header 中一起返回。浏览器会为资源标记上该信息,当缓存过期之后,浏览器会把该信息设置到 Request Header 中的 If-Modified-Since 中向服务器发起请求。如果 If-Modified-Since 中的值和服务器上该资源最终的修改时间一致,就说明该资源没有被修改过,服务器会直接返回 304 状态码,无响应实体,这样就可以节省传输的数据量。如果不一致,服务器会返回 200 状态码,同时和第一次 HTTP 请求一样,返回响应实体和验证器。Last-Modified:Fri, 04 Jan 2019 14:00:21 GMTEtag服务器会通过某种算法,为资源计算出一个唯一标识符,在把响应返回给客户端的时候,会在 Response Header 中加上 Etag: 唯一标识符 一起返回给客户端。Etag:“952d03d8561454120b550f0a5679a172c4822ce8"客户端会将 Etag 保存下来,后续请求时会将 Etag 作为 Request Header 中 If-None-Match 的值发给服务器。通过比对客户端发过来的 Etag 和服务器上保存的 Etag 是否一致,就能够知道资源是否发生了变化。如果资源没有发生变化,返回 304,客户端继续使用缓存。如果资源已经修改,则返回 200。制定缓存策略缓存真的可以说让我们又爱又恨。在开发时,我们经常遇到这样的问题:明明已经修改了这个文件,为什么没有生效?好吧,文件被缓存了。。。但是在上线时,我们又希望文件尽可能地被浏览器缓存,来提高性能。因此为自己的应用制定合适的缓存策略非常重要。为静态资源设置较长缓存时间。有些资源很长时间都不会改变,比如一些三方库,图片,字体文件等。可以为它们设置一个很长的过期时间,例如设定「一年」。通过给文件名的唯一标识来确保文件修改生效。有些时候为了解决 bug,我们可能会修改一些文件,比如应用的 CSS、JS 等。如果这些文件已经被缓存,那么除非用户强制刷新页面,否则用户只有在缓存过期之后才有可能获取新的文件。如何让浏览器不使用缓存,而是重新下载新的文件呢?有一个办法就是给文件名加上唯一标识,比如 Hash 或版本信息。当文件修改之后,这个唯一标识也会随之改变。浏览器发现文件改变之后,就不会使用缓存了。明确是否有资源不能被缓存。比如一些敏感数据,如果不应该被浏览器缓存,需要在 Response Header 中设置 Cache-Control: no-store。调试如何知道请求的资源是否被缓存了?打开 Chrome 的开发者工具,我们可以看到 Size 这一栏下面,如果显示文件真实的大小,则说明该文件未被缓存。如果显示 from xxx cache,则说明该请求使用的是已被缓存的文件。如下:调试时,如果想禁用浏览器缓存,可以在开发者工具上勾选 Disabel cache。最后最后,大家可以通过下面这张图再回顾一下我们刚刚讲过的内容。参考HTTP 缓存奇怪的缓存策略 ...

April 9, 2019 · 2 min · jiezi

cookie已凉,浏览器存储该怎么做

前言随着移动网络的发展与演化,我们手机上现在除了有原生 App,还能跑“WebApp”——它即开即用,用完即走。一个优秀的 WebApp 甚至可以拥有和原生 App 媲美的功能和体验。WebApp 优异的性能表现,有一部分原因要归功于浏览器存储技术的提升。cookie存储数据的功能已经很难满足开发所需,逐渐被WebStorage、IndexedDB所取代,本文将介绍这几种存储方式的差异和优缺点。想阅读更多优质文章请猛戳GitHub博客一、Cookie1.Cookie的来源Cookie 的本职工作并非本地存储,而是“维持状态”。因为HTTP协议是无状态的,HTTP协议自身不对请求和响应之间的通信状态进行保存,通俗来说,服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两瓶饮料。最后结帐时,由于HTTP的无状态性,不通过额外的手段,服务器并不知道用户到底买了什么,于是就诞生了Cookie。它就是用来绕开HTTP的无状态性的“额外手段”之一。服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。我们可以把Cookie 理解为一个存储在浏览器里的一个小小的文本文件,它附着在 HTTP 请求上,在浏览器和服务器之间“飞来飞去”。它可以携带用户信息,当服务器检查 Cookie 的时候,便可以获取到客户端的状态。在刚才的购物场景中,当用户选购了第一项商品,服务器在向用户发送网页的同时,还发送了一段Cookie,记录着那项商品的信息。当用户访问另一个页面,浏览器会把Cookie发送给服务器,于是服务器知道他之前选购了什么。用户继续选购饮料,服务器就在原来那段Cookie里追加新的商品信息。结帐时,服务器读取发送来的Cookie就行了。2.什么是Cookie及应用场景Cookie指某些网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。 cookie是服务端生成,客户端进行维护和存储。通过cookie,可以让服务器知道请求是来源哪个客户端,就可以进行客户端状态的维护,比如登陆后刷新,请求头就会携带登陆时response header中的set-cookie,Web服务器接到请求时也能读出cookie的值,根据cookie值的内容就可以判断和恢复一些用户的信息状态。如上图所示,Cookie 以键值对的形式存在。典型的应用场景有:记住密码,下次自动登录。购物车功能。记录用户浏览数据,进行商品(广告)推荐。3.Cookie的原理及生成方式Cookie的原理第一次访问网站的时候,浏览器发出请求,服务器响应请求后,会在响应头里面添加一个Set-Cookie选项,将cookie放入到响应请求中,在浏览器第二次发请求的时候,会通过Cookie请求头部将Cookie信息发送给服务器,服务端会辨别用户身份,另外,Cookie的过期时间、域、路径、有效期、适用站点都可以根据需要来指定。Cookie的生成方式主要有两种:生成方式一:http response header中的set-cookie我们可以通过响应头里的 Set-Cookie 指定要存储的 Cookie 值。默认情况下,domain 被设置为设置 Cookie 页面的主机名,我们也可以手动设置 domain 的值。Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2018 07:28:00 GMT;//可以指定一个特定的过期时间(Expires)或有效期(Max-Age)当Cookie的过期时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。生成方式二:js中可以通过document.cookie可以读写cookie,以键值对的形式展示例如我们在掘金社区控制台输入以下三句代码,便可以在Chrome 的 Application 面板查看生成的cookie:document.cookie=“userName=hello"document.cookie=“gender=male"document.cookie=‘age=20;domain=.baidu.com’从上图中我们可以得出:Domain 标识指定了哪些域名可以接受Cookie。如果没有设置domain,就会自动绑定到执行语句的当前域。如果设置为”.baidu.com”,则所有以”baidu.com”结尾的域名都可以访问该Cookie,所以在掘金社区上读取不到第三条代码存储Cookie值。4.Cookie的缺陷Cookie 不够大Cookie的大小限制在4KB左右,对于复杂的存储需求来说是不够用的。当 Cookie 超过 4KB 时,它将面临被裁切的命运。这样看来,Cookie 只能用来存取少量的信息。此外很多浏览器对一个站点的cookie个数也是有限制的。这里需注意:各浏览器的cookie每一个name=value的value值大概在4k,所以4k并不是一个域名下所有的cookie共享的,而是一个name的大小。过多的 Cookie 会带来巨大的性能浪费Cookie 是紧跟域名的。同一个域名下的所有请求,都会携带 Cookie。大家试想,如果我们此刻仅仅是请求一张图片或者一个 CSS 文件,我们也要携带一个 Cookie 跑来跑去(关键是 Cookie 里存储的信息并不需要),这是一件多么劳民伤财的事情。Cookie 虽然小,请求却可以有很多,随着请求的叠加,这样的不必要的 Cookie 带来的开销将是无法想象的。cookie是用来维护用户信息的,而域名(domain)下所有请求都会携带cookie,但对于静态文件的请求,携带cookie信息根本没有用,此时可以通过cdn(存储静态文件的)的域名和主站的域名分开来解决。由于在HTTP请求中的Cookie是明文传递的,所以安全性成问题,除非用HTTPS。5.Cookie与安全对于 cookie 来说,我们还需要注意安全性。HttpOnly 不支持读写,浏览器不允许脚本操作document.cookie去更改cookie,所以为避免跨域脚本 (XSS) 攻击,通过JavaScript的 Document.cookie API无法访问带有 HttpOnly 标记的Cookie,它们只应该发送给服务端。如果包含服务端 Session 信息的 Cookie 不想被客户端 JavaScript 脚本调用,那么就应该为其设置 HttpOnly 标记。Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly标记为 Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过Cookie传输,因为Cookie有其固有的不安全性,Secure 标记也无法提供确实的安全保障。为了弥补 Cookie 的局限性,让“专业的人做专业的事情”,Web Storage 出现了。HTML5中新增了本地存储的解决方案—-Web Storage,它分成两类:sessionStorage和localStorage。这样有了WebStorage后,cookie能只做它应该做的事情了——作为客户端与服务器交互的通道,保持客户端状态。二、LocalStorage1.LocalStorage的特点保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取以前保存的数据。大小为5M左右仅在客户端使用,不和服务端进行通信接口封装较好基于上面的特点,LocalStorage可以作为浏览器本地缓存方案,用来提升网页首屏渲染速度(根据第一请求返回时,将一些不变信息直接存储在本地)。2.存入/读取数据localStorage保存的数据,以“键值对”的形式存在。也就是说,每一项数据都有一个键名和对应的值。所有的数据都是以文本格式保存。存入数据使用setItem方法。它接受两个参数,第一个是键名,第二个是保存的数据。localStorage.setItem(“key”,“value”);读取数据使用getItem方法。它只有一个参数,就是键名。var valueLocal = localStorage.getItem(“key”);具体步骤,请看下面的例子:<script>if(window.localStorage){ localStorage.setItem(’name’,‘world’) localStorage.setItem(“gender’,‘famale’)}</script><body><div id=“name”></div><div id=“gender”></div><script>var name=localStorage.getItem(’name’)var gender=localStorage.getItem(‘gender’)document.getElementById(’name’).innerHTML=namedocument.getElementById(‘gender’).innerHTML=gender</script></body>3.使用场景LocalStorage在存储方面没有什么特别的限制,理论上 Cookie 无法胜任的、可以用简单的键值对来存取的数据存储任务,都可以交给 LocalStorage 来做。这里给大家举个例子,考虑到 LocalStorage 的特点之一是持久,有时我们更倾向于用它来存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串:三、sessionStoragesessionStorage保存的数据用于浏览器的一次会话,当会话结束(通常是该窗口关闭),数据被清空;sessionStorage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 sessionStorage 内容便无法共享;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。除了保存期限的长短不同,SessionStorage的属性和方法与LocalStorage完全一样。1.sessionStorage的特点会话级别的浏览器存储大小为5M左右仅在客户端使用,不和服务端进行通信接口封装较好基于上面的特点,sessionStorage 可以有效对表单信息进行维护,比如刷新时,表单信息不丢失。2.使用场景sessionStorage 更适合用来存储生命周期和它同步的会话级别的信息。这些信息只适用于当前会话,当你开启新的会话时,它也需要相应的更新或释放。比如微博的 sessionStorage就主要是存储你本次会话的浏览足迹:lasturl 对应的就是你上一次访问的 URL 地址,这个地址是即时的。当你切换 URL 时,它随之更新,当你关闭页面时,留着它也确实没有什么意义了,干脆释放吧。这样的数据用 sessionStorage 来处理再合适不过。3.sessionStorage 、localStorage 和 cookie 之间的区别共同点:都是保存在浏览器端,且都遵循同源策略。不同点:在于生命周期与作用域的不同作用域:localStorage只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份localStorage数据。sessionStorage比localStorage更严苛一点,除了协议、主机名、端口外,还要求在同一窗口(也就是浏览器的标签页)下生命周期:localStorage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 sessionStorage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。Web Storage 是一个从定义到使用都非常简单的东西。它使用键值对的形式进行存储,这种模式有点类似于对象,却甚至连对象都不是——它只能存储字符串,要想得到对象,我们还需要先对字符串进行一轮解析。说到底,Web Storage 是对 Cookie 的拓展,它只能用于存储少量的简单数据。当遇到大规模的、结构复杂的数据时,Web Storage 也爱莫能助了。这时候我们就要清楚我们的终极大 boss——IndexedDB!四、IndexedDBIndexedDB 是一种低级API,用于客户端存储大量结构化数据(包括文件和blobs)。该API使用索引来实现对该数据的高性能搜索。IndexedDB 是一个运行在浏览器上的非关系型数据库。既然是数据库了,那就不是 5M、10M 这样小打小闹级别了。理论上来说,IndexedDB 是没有存储上限的(一般来说不会小于 250M)。它不仅可以存储字符串,还可以存储二进制数据。1.IndexedDB的特点键值对储存。IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。异步IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。支持事务。IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。同源限制IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。储存空间大IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。支持二进制储存。IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。2.IndexedDB的常见操作在IndexedDB大部分操作并不是我们常用的调用方法,返回结果的模式,而是请求——响应的模式。建立打开IndexedDB —-window.indexedDB.open(“testDB”)这条指令并不会返回一个DB对象的句柄,我们得到的是一个IDBOpenDBRequest对象,而我们希望得到的DB对象在其result属性中除了result,IDBOpenDBRequest接口定义了几个重要属性:onerror: 请求失败的回调函数句柄onsuccess:请求成功的回调函数句柄onupgradeneeded:请求数据库版本变化句柄<script>function openDB(name){var request=window.indexedDB.open(name)//建立打开IndexedDBrequest.onerror=function (e){console.log(‘open indexdb error’)}request.onsuccess=function (e){myDB.db=e.target.result//这是一个 IDBDatabase对象,这就是IndexedDB对象console.log(myDB.db)//此处就可以获取到db实例}}var myDB={name:’testDB’,version:‘1’,db:null}openDB(myDB.name)</script>控制台得到一个 IDBDatabase对象,这就是IndexedDB对象关闭IndexedDB—-indexdb.close()function closeDB(db){ db.close();}删除IndexedDB—-window.indexedDB.deleteDatabase(indexdb)function deleteDB(name) { indexedDB.deleteDatabase(name)}3.WebStorage、cookie 和 IndexedDB之间的区别从上表可以看到,cookie 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 localStorage 和 sessionStorage 。对于不怎么改变的数据尽量使用 localStorage 存储,否则可以用 sessionStorage 存储。总结正是浏览器存储、缓存技术的出现和发展,为我们的前端应用带来了无限的转机。近年来基于存储、缓存技术的第三方库层出不绝,此外还衍生出了 PWA 这样优秀的 Web 应用模型。总结下本文几个核心观点:Cookie 的本职工作并非本地存储,而是“维持状态”Web Storage 是 HTML5 专门为浏览器存储而提供的数据存储机制,不与服务端发生通信IndexedDB 用于客户端存储大量结构化数据给大家推荐一个好用的BUG监控工具Fundebug,欢迎免费试用!欢迎关注公众号:前端工匠,你的成长我们一起见证!优质交流群微信公众号参考文章把cookie聊清楚HTML5本地存储——IndexedDB(一:基本使用)详说 Cookie, LocalStorage 与 SessionStorage前端性能优化原理与实践localstorage 必知必会浏览器数据库 IndexedDB 入门教程 ...

April 3, 2019 · 1 min · jiezi

【3分钟速览】前端广播式通信:Broadcast Channel

Broadcast Channel 是什么?在前端,我们经常会用postMessage来实现页面间的通信,但这种方式更像是点对点的通信。对于一些需要广播(让所有页面知道)的消息,用postMessage不是非常自然。Broadcast Channel 就是用来弥补这个缺陷的。顾名思义,Broadcast Channel 会创建一个所有同源页面都可以共享的(广播)频道,因此其中某一个页面发送的消息可以被其他页面监听到。下面就来速览一下它的使用方法。如何使用?Broadcast Channel 的 API 非常简单易用。创建首先我们会使用构造函数创建一个实例:const bc = new BroadcastChannel(‘alienzhou’);可以接受一个DOMString作为 name,用以标识这个 channel。在其他页面,可以通过传入相同的 name 来使用同一个广播频道。用 MDN 上的话来解释就是:There is one single channel with this name for all browsing contexts with the same origin.该 name 值可以通过实例的.name属性获得console.log(bc.name);// alienzhou监听消息Broadcast Channel 创建完成后,就可以在页面监听广播的消息:bc.onmessage = function(e) { console.log(‘receive:’, e.data);};对于错误也可以绑定监听:bc.onmessageerror = function(e) { console.warn(’error:’, e);};除了为.onmessage赋值这种方式,也可以使用addEventListener来添加’message’监听。关闭可以看到,上述短短几行代码就可以实现多个页面间的广播通信,非常方便。而有时我们希望取消当前页面的广播监听:一种方式是取消或者修改相应的’message’事件监听另一种简单的方式就是使用 Broadcast Channel 实例为我们提供的close方法。bc.close();两者是有区别的:取消’message’监听只是让页面不对广播消息进行响应,Broadcast Channel 仍然存在;而调用close方法这会切断与 Broadcast Channel 的连接,浏览器才能够尝试回收该对象,因为此时浏览器才会知道用户已经不需要使用广播频道了。在关闭后调用postMessage会出现如下报错如果之后又再需要广播,则可以重新创建一个相同 name 的 Broadcast Channel。Demo 效果可以戳这里查看在线 Demo >>下面是 Broadcast Channel Demo 的演示效果:兼容性如何?Broadcast Channel 是一个非常好用的多页面消息同步 API,然而兼容性却不是很乐观。好在我们还有些其他方案可以作为补充(或者作为polyfill),其他的前端跨页面通信可以参考我的另一篇文章《前端跨页面通信的方法》。对文章感兴趣的同学欢迎关注 我的博客 >> https://github.com/alienzhou/blog ...

April 1, 2019 · 1 min · jiezi

面试官:前端跨页面通信,你知道哪些方法?

引言在浏览器中,我们可以同时打开多个Tab页,每个Tab页可以粗略理解为一个“独立”的运行环境,即使是全局对象也不会在多个Tab间共享。然而有些时候,我们希望能在这些“独立”的Tab页面之间同步页面的数据、信息或状态。正如下面这个例子:我在列表页点击“收藏”后,对应的详情页按钮会自动更新为“已收藏”状态;类似的,在详情页点击“收藏”后,列表页中按钮也会更新。这就是我们所说的前端跨页面通信。你知道哪些跨页面通信的方式呢?如果不清楚,下面我就带大家来看看七种跨页面通信的方式。一、同源页面间的跨页面通信以下各种方式的 在线 Demo 可以戳这里 >>浏览器的同源策略在下述的一些跨页面通信方法中依然存在限制。因此,我们先来看看,在满足同源策略的情况下,都有哪些技术可以用来实现跨页面通信。1. BroadCast ChannelBroadCast Channel 可以帮我们创建一个用于广播的通信频道。当所有页面都监听同一频道的消息时,其中某一个页面通过它发送的消息就会被其他所有页面收到。它的API和用法都非常简单。下面的方式就可以创建一个标识为AlienZHOU的频道:const bc = new BroadcastChannel(‘AlienZHOU’);各个页面可以通过onmessage来监听被广播的消息:bc.onmessage = function (e) { const data = e.data; const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from; console.log(’[BroadcastChannel] receive message:’, text);};要发送消息时只需要调用实例上的postMessage方法即可:bc.postMessage(mydata);Broadcast Channel 的具体的使用方式可以看这篇《【3分钟速览】前端广播式通信:Broadcast Channel》。2. Service WorkerService Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。Service Worker 也是 PWA 中的核心技术之一,由于本文重点不在 PWA ,因此如果想进一步了解 Service Worker,可以阅读我之前的文章【PWA学习与实践】(3) 让你的WebApp离线可用。首先,需要在页面注册 Service Worker:/* 页面逻辑 /navigator.serviceWorker.register(’../util.sw.js’).then(function () { console.log(‘Service Worker 注册成功’);});其中../util.sw.js是对应的 Service Worker 脚本。Service Worker 本身并不自动具备“广播通信”的功能,需要我们添加些代码,将其改造成消息中转站:/ ../util.sw.js Service Worker 逻辑 /self.addEventListener(‘message’, function (e) { console.log(‘service worker receive message’, e.data); e.waitUntil( self.clients.matchAll().then(function (clients) { if (!clients || clients.length === 0) { return; } clients.forEach(function (client) { client.postMessage(e.data); }); }) );});我们在 Service Worker 中监听了message事件,获取页面(从 Service Worker 的角度叫 client)发送的信息。然后通过self.clients.matchAll()获取当前注册了该 Service Worker 的所有页面,通过调用每个client(即页面)的postMessage方法,向页面发送消息。这样就把从一处(某个Tab页面)收到的消息通知给了其他页面。处理完 Service Worker,我们需要在页面监听 Service Worker 发送来的消息:/ 页面逻辑 /navigator.serviceWorker.addEventListener(‘message’, function (e) { const data = e.data; const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from; console.log(’[Service Worker] receive message:’, text);});最后,当需要同步消息时,可以调用 Service Worker 的postMessage方法:/ 页面逻辑 /navigator.serviceWorker.controller.postMessage(mydata);3. LocalStorageLocalStorage 作为前端最常用的本地存储,大家应该已经非常熟悉了;但StorageEvent这个与它相关的事件有些同学可能会比较陌生。当 LocalStorage 变化时,会触发storage事件。利用这个特性,我们可以在发送消息时,把消息写入到某个 LocalStorage 中;然后在各个页面内,通过监听storage事件即可收到通知。window.addEventListener(‘storage’, function (e) { if (e.key === ‘ctc-msg’) { const data = JSON.parse(e.newValue); const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from; console.log(’[Storage I] receive message:’, text); }});在各个页面添加如上的代码,即可监听到 LocalStorage 的变化。当某个页面需要发送消息时,只需要使用我们熟悉的setItem方法即可:mydata.st = +(new Date);window.localStorage.setItem(‘ctc-msg’, JSON.stringify(mydata));注意,这里有一个细节:我们在mydata上添加了一个取当前毫秒时间戳的.st属性。这是因为,storage事件只有在值真正改变时才会触发。举个例子:window.localStorage.setItem(’test’, ‘123’);window.localStorage.setItem(’test’, ‘123’);由于第二次的值'123’与第一次的值相同,所以以上的代码只会在第一次setItem时触发storage事件。因此我们通过设置st来保证每次调用时一定会触发storage事件。小憩一下上面我们看到了三种实现跨页面通信的方式,不论是建立广播频道的 Broadcast Channel,还是使用 Service Worker 的消息中转站,抑或是些 tricky 的storage事件,其都是“广播模式”:一个页面将消息通知给一个“中央站”,再由“中央站”通知给各个页面。在上面的例子中,这个“中央站”可以是一个 BroadCast Channel 实例、一个 Service Worker 或是 LocalStorage。下面我们会看到另外两种跨页面通信方式,我把它称为“共享存储+轮询模式”。4. Shared WorkerShared Worker 是 Worker 家族的另一个成员。普通的 Worker 之间是独立运行、数据互不相通;而多个 Tab 注册的 Shared Worker 则可以实现数据共享。Shared Worker 在实现跨页面通信时的问题在于,它无法主动通知所有页面,因此,我们会使用轮询的方式,来拉取最新的数据。思路如下:让 Shared Worker 支持两种消息。一种是 post,Shared Worker 收到后会将该数据保存下来;另一种是 get,Shared Worker 收到该消息后会将保存的数据通过postMessage传给注册它的页面。也就是让页面通过 get 来主动获取(同步)最新消息。具体实现如下:首先,我们会在页面中启动一个 Shared Worker,启动方式非常简单:// 构造函数的第二个参数是 Shared Worker 名称,也可以留空const sharedWorker = new SharedWorker(’../util.shared.js’, ‘ctc’);然后,在该 Shared Worker 中支持 get 与 post 形式的消息:/ ../util.shared.js: Shared Worker 代码 /let data = null;self.addEventListener(‘connect’, function (e) { const port = e.ports[0]; port.addEventListener(‘message’, function (event) { // get 指令则返回存储的消息数据 if (event.data.get) { data && port.postMessage(data); } // 非 get 指令则存储该消息数据 else { data = event.data; } }); port.start();});之后,页面定时发送 get 指令的消息给 Shared Worker,轮询最新的消息数据,并在页面监听返回信息:// 定时轮询,发送 get 指令的消息setInterval(function () { sharedWorker.port.postMessage({get: true});}, 1000);// 监听 get 消息的返回数据sharedWorker.port.addEventListener(‘message’, (e) => { const data = e.data; const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from; console.log(’[Shared Worker] receive message:’, text);}, false);sharedWorker.port.start();最后,当要跨页面通信时,只需给 Shared Worker postMessage即可:sharedWorker.port.postMessage(mydata);注意,如果使用addEventListener来添加 Shared Worker 的消息监听,需要显式调用MessagePort.start方法,即上文中的sharedWorker.port.start();如果使用onmessage绑定监听则不需要。5. IndexedDB除了可以利用 Shared Worker 来共享存储数据,还可以使用其他一些“全局性”(支持跨页面)的存储方案。例如 IndexedDB 或 cookie。鉴于大家对 cookie 已经很熟悉,加之作为“互联网最早期的存储方案之一”,cookie 已经在实际应用中承受了远多于其设计之初的责任,我们下面会使用 IndexedDB 来实现。其思路很简单:与 Shared Worker 方案类似,消息发送方将消息存至 IndexedDB 中;接收方(例如所有页面)则通过轮询去获取最新的信息。在这之前,我们先简单封装几个 IndexedDB 的工具方法。打开数据库连接:function openStore() { const storeName = ‘ctc_aleinzhou’; return new Promise(function (resolve, reject) { if (!(‘indexedDB’ in window)) { return reject(‘don't support indexedDB’); } const request = indexedDB.open(‘CTC_DB’, 1); request.onerror = reject; request.onsuccess = e => resolve(e.target.result); request.onupgradeneeded = function (e) { const db = e.srcElement.result; if (e.oldVersion === 0 && !db.objectStoreNames.contains(storeName)) { const store = db.createObjectStore(storeName, {keyPath: ’tag’}); store.createIndex(storeName + ‘Index’, ’tag’, {unique: false}); } } });}存储数据function saveData(db, data) { return new Promise(function (resolve, reject) { const STORE_NAME = ‘ctc_aleinzhou’; const tx = db.transaction(STORE_NAME, ‘readwrite’); const store = tx.objectStore(STORE_NAME); const request = store.put({tag: ‘ctc_data’, data}); request.onsuccess = () => resolve(db); request.onerror = reject; });}查询/读取数据function query(db) { const STORE_NAME = ‘ctc_aleinzhou’; return new Promise(function (resolve, reject) { try { const tx = db.transaction(STORE_NAME, ‘readonly’); const store = tx.objectStore(STORE_NAME); const dbRequest = store.get(‘ctc_data’); dbRequest.onsuccess = e => resolve(e.target.result); dbRequest.onerror = reject; } catch (err) { reject(err); } });}剩下的工作就非常简单了。首先打开数据连接,并初始化数据:openStore().then(db => saveData(db, null))对于消息读取,可以在连接与初始化后轮询:openStore().then(db => saveData(db, null)).then(function (db) { setInterval(function () { query(db).then(function (res) { if (!res || !res.data) { return; } const data = res.data; const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from; console.log(’[Storage I] receive message:’, text); }); }, 1000);});最后,要发送消息时,只需向 IndexedDB 存储数据即可:openStore().then(db => saveData(db, null)).then(function (db) { // …… 省略上面的轮询代码 // 触发 saveData 的方法可以放在用户操作的事件监听内 saveData(db, mydata);});小憩一下在“广播模式”外,我们又了解了“共享存储+长轮询”这种模式。也许你会认为长轮询没有监听模式优雅,但实际上,有些时候使用“共享存储”的形式时,不一定要搭配长轮询。例如,在多 Tab 场景下,我们可能会离开 Tab A 到另一个 Tab B 中操作;过了一会我们从 Tab B 切换回 Tab A 时,希望将之前在 Tab B 中的操作的信息同步回来。这时候,其实只用在 Tab A 中监听visibilitychange这样的事件,来做一次信息同步即可。下面,我会再介绍一种通信方式,我把它称为“口口相传”模式。6. window.open + window.opener当我们使用window.open打开页面时,方法会返回一个被打开页面window的引用。而在未显示指定noopener时,被打开的页面可以通过window.opener获取到打开它的页面的引用 —— 通过这种方式我们就将这些页面建立起了联系(一种树形结构)。首先,我们把window.open打开的页面的window对象收集起来:let childWins = [];document.getElementById(‘btn’).addEventListener(‘click’, function () { const win = window.open(’./some/sample’); childWins.push(win);});然后,当我们需要发送消息的时候,作为消息的发起方,一个页面需要同时通知它打开的页面与打开它的页面:// 过滤掉已经关闭的窗口childWins = childWins.filter(w => !w.closed);if (childWins.length > 0) { mydata.fromOpenner = false; childWins.forEach(w => w.postMessage(mydata));}if (window.opener && !window.opener.closed) { mydata.fromOpenner = true; window.opener.postMessage(mydata);}注意,我这里先用.closed属性过滤掉已经被关闭的 Tab 窗口。这样,作为消息发送方的任务就完成了。下面看看,作为消息接收方,它需要做什么。此时,一个收到消息的页面就不能那么自私了,除了展示收到的消息,它还需要将消息再传递给它所“知道的人”(打开与被它打开的页面):需要注意的是,我这里通过判断消息来源,避免将消息回传给发送方,防止消息在两者间死循环的传递。(该方案会有些其他小问题,实际中可以进一步优化)window.addEventListener(‘message’, function (e) { const data = e.data; const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from; console.log(’[Cross-document Messaging] receive message:’, text); // 避免消息回传 if (window.opener && !window.opener.closed && data.fromOpenner) { window.opener.postMessage(data); } // 过滤掉已经关闭的窗口 childWins = childWins.filter(w => !w.closed); // 避免消息回传 if (childWins && !data.fromOpenner) { childWins.forEach(w => w.postMessage(data)); }});这样,每个节点(页面)都肩负起了传递消息的责任,也就是我说的“口口相传”,而消息就在这个树状结构中流转了起来。小憩一下显然,“口口相传”的模式存在一个问题:如果页面不是通过在另一个页面内的window.open打开的(例如直接在地址栏输入,或从其他网站链接过来),这个联系就被打破了。除了上面这六个常见方法,其实还有一种(第七种)做法是通过 WebSocket 这类的“服务器推”技术来进行同步。这好比将我们的“中央站”从前端移到了后端。关于 WebSocket 与其他“服务器推”技术,不了解的同学可以阅读这篇《各类“服务器推”技术原理与实例(Polling/COMET/SSE/WebSocket)》此外,我还针对以上各种方式写了一个 在线演示的 Demo >>二、非同源页面之间的通信上面我们介绍了七种前端跨页面通信的方法,但它们大都受到同源策略的限制。然而有时候,我们有两个不同域名的产品线,也希望它们下面的所有页面之间能无障碍地通信。那该怎么办呢?要实现该功能,可以使用一个用户不可见的 iframe 作为“桥”。由于 iframe 与父页面间可以通过指定origin来忽略同源限制,因此可以在每个页面中嵌入一个 iframe (例如:http://sample.com/bridge.html),而这些 iframe 由于使用的是一个 url,因此属于同源页面,其通信方式可以复用上面第一部分提到的各种方式。页面与 iframe 通信非常简单,首先需要在页面中监听 iframe 发来的消息,做相应的业务处理:/ 业务页面代码 /window.addEventListener(‘message’, function (e) { // …… do something});然后,当页面要与其他的同源或非同源页面通信时,会先给 iframe 发送消息:/ 业务页面代码 /window.frames[0].window.postMessage(mydata, ‘’);其中为了简便此处将postMessage的第二个参数设为了’’,你也可以设为 iframe 的 URL。iframe 收到消息后,会使用某种跨页面消息通信技术在所有 iframe 间同步消息,例如下面使用的 Broadcast Channel:/ iframe 内代码 /const bc = new BroadcastChannel(‘AlienZHOU’);// 收到来自页面的消息后,在 iframe 间进行广播window.addEventListener(‘message’, function (e) { bc.postMessage(e.data);}); 其他 iframe 收到通知后,则会将该消息同步给所属的页面:/ iframe 内代码 /// 对于收到的(iframe)广播消息,通知给所属的业务页面bc.onmessage = function (e) { window.parent.postMessage(e.data, ‘’);};下图就是使用 iframe 作为“桥”的非同源页面间通信模式图。其中“同源跨域通信方案”可以使用文章第一部分提到的某种技术。总结今天和大家分享了一下跨页面通信的各种方式。对于同源页面,常见的方式包括:广播模式:Broadcast Channe / Service Worker / LocalStorage + StorageEvent共享存储模式:Shared Worker / IndexedDB / cookie口口相传模式:window.open + window.opener基于服务端:Websocket / Comet / SSE 等而对于非同源页面,则可以通过嵌入同源 iframe 作为“桥”,将非同源页面通信转换为同源页面通信。本文在分享的同时,也是为了抛转引玉。如果你有什么其他想法,欢迎一起讨论,提出你的见解和想法~对文章感兴趣的同学欢迎关注 我的博客 >> https://github.com/alienzhou/blog ...

April 1, 2019 · 4 min · jiezi

《前端面试手记》之JavaScript基础知识梳理(上)

???? 内容速览 ????普通函数和箭头函数的this原始数据类型及其判断和转化方法深浅拷贝及实现JS事件模型常见的高阶函数????查看全部教程 / 阅读原文????普通函数和箭头函数的this还是一道经典题目,下面的这段代码的输出是什么?(为了方便解释,输出放在了注释中)function fn() { console.log(this); // 1. {a: 100} var arr = [1, 2, 3]; (function() { console.log(this); // 2. Window })(); // 普通 JS arr.map(function(item) { console.log(this); // 3. Window return item + 1; }); // 箭头函数 let brr = arr.map(item => { console.log(“es6”, this); // 4. {a: 100} return item + 1; });}fn.call({ a: 100 });其实诀窍很简单,常见的基本是3种情况:es5普通函数、es6的箭头函数以及通过bind改变过上下文返回的新函数。① es5普通函数:函数被直接调用,上下文一定是window函数作为对象属性被调用,例如:obj.foo(),上下文就是对象本身obj通过new调用,this绑定在返回的实例上② es6箭头函数: 它本身没有this,会沿着作用域向上寻找,直到global / window。请看下面的这段代码:function run() { const inner = () => { return () => { console.log(this.a) } } inner()()}run.bind({a: 1})() // Output: 1③ bind绑定上下文返回的新函数:就是被第一个bind绑定的上下文,而且bind对“箭头函数”无效。请看下面的这段代码:function run() { console.log(this.a)}run.bind({a: 1})() // output: 1// 多次bind,上下文由第一个bind的上下文决定run .bind({a: 2}) .bind({a: 1}) () // output: 2最后,再说说这几种方法的优先级:new > bind > 对象调用 > 直接调用至此,这道题目的输出就说可以解释明白了。原始数据类型和判断方法题目:JS中的原始数据类型?ECMAScript 中定义了 7 种原始类型:BooleanStringNumberNullUndefinedSymbol(新定义)BigInt(新定义)注意:原始类型不包含Object和Function题目:常用的判断方法?在进行判断的时候有typeof、instanceof。对于数组的判断,使用Array.isArray():typeof:typeof基本都可以正确判断数据类型typeof null和typeof [1, 2, 3]均返回"object"ES6新增:typeof Symbol()返回"symbol"instanceof:专门用于实例和构造函数对应function Obj(value){ this.value = value; }let obj = new Obj(“test”);console.log(obj instanceof Obj); // output: true判断是否是数组:[1, 2, 3] instanceof Array Array.isArray():ES6新增,用来判断是否是’Array’。Array.isArray({})返回false。原始类型转化当我们对一个“对象”进行数学运算操作时候,会涉及到对象 => 基础数据类型的转化问题。事实上,当一个对象执行例如加法操作的时候,如果它是原始类型,那么就不需要转换。否则,将遵循以下规则:调用实例的valueOf()方法,如果有返回的是基础类型,停止下面的过程;否则继续调用实例的toString()方法,如果有返回的是基础类型,停止下面的过程;否则继续都没返回原始类型,就会报错请看下面的测试代码:let a = { toString: function() { return ‘a’ }}let b = { valueOf: function() { return 100 }, toString: function() { return ‘b’ }}let c = Object.create(null) // 创建一个空对象console.log(a + ‘123’) // output: a123console.log(b + 1) // output: 101console.log(c + ‘123’) // 报错除了valueOf和toString,es6还提供了Symbol.toPrimitive供对象向原始类型转化,并且它的优先级最高!!稍微改造下上面的代码:let b = { valueOf: function() { return 100 }, toString: function() { return ‘b’ }, [Symbol.toPrimitive]: function() { return 10000 }}console.log(b + 1) // output: 10001最后,其实关于instanceof判断是否是某个对象的实例,es6也提供了Symbol.hasInstance接口,代码如下:class Even { static Symbol.hasInstance { return Number(num) % 2 === 0; }}const Odd = { Symbol.hasInstance { return Number(num) % 2 !== 0; }};console.log(1 instanceof Even); // output: falseconsole.log(1 instanceof Odd); // output: true深拷贝和浅拷贝题目:实现对象的深拷贝。在JS中,函数和对象都是浅拷贝(地址引用);其他的,例如布尔值、数字等基础数据类型都是深拷贝(值引用)。值得提醒的是,ES6的Object.assign()和ES7的…解构运算符都是“浅拷贝”。实现深拷贝还是需要自己手动撸“轮子”或者借助第三方库(例如lodash):手动做一个“完美”的深拷贝函数:https://godbmw.com/passages/2019-03-18-interview-js-code/借助第三方库:jq的extend(true, result, src1, src2[ ,src3])、lodash的cloneDeep(src)JSON.parse(JSON.stringify(src)):这种方法有局限性,如果属性值是函数或者一个类的实例的时候,无法正确拷贝借助HTML5的MessageChannel:这种方法有局限性,当属性值是函数的时候,会报错<script> function deepClone(obj) { return new Promise(resolve => { const {port1, port2} = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); } const obj = { a: 1, b: { c: [1, 2], d: ‘() => {}’ } }; deepClone(obj) .then(obj2 => { obj2.b.c[0] = 100; console.log(obj.b.c); // output: [1, 2] console.log(obj2.b.c); // output: [100, 2] })</script>JS事件流事件冒泡和事件捕获事件流分为:冒泡和捕获,顺序是先捕获再冒泡。事件冒泡:子元素的触发事件会一直向父节点传递,一直到根结点停止。此过程中,可以在每个节点捕捉到相关事件。可以通过stopPropagation方法终止冒泡。事件捕获:和“事件冒泡”相反,从根节点开始执行,一直向子节点传递,直到目标节点。addEventListener给出了第三个参数同时支持冒泡与捕获:默认是false,事件冒泡;设置为true时,是事件捕获。<div id=“app” style=“width: 100vw; background: red;"> <span id=“btn”>点我</span></div><script> // 事件捕获:先输出 “外层click事件触发”; 再输出 “内层click事件触发” var useCapture = true; var btn = document.getElementById(“btn”); btn.addEventListener( “click”, function() { console.log(“内层click事件触发”); }, useCapture ); var app = document.getElementById(“app”); app.onclick = function() { console.log(“外层click事件触发”); };</script>DOM0级 和 DOM2级DOM2级:前面说的addEventListener,它定义了DOM事件流,捕获 + 冒泡。DOM0级:直接在html标签内绑定on事件在JS中绑定on系列事件注意:现在通用DOM2级事件,优点如下:可以绑定 / 卸载事件支持事件流冒泡 + 捕获:相当于每个节点同一个事件,至少2次处理机会同一类事件,可以绑定多个函数常见的高阶函数没什么好说的,跑一下下面的代码就可以理解了:// map: 生成一个新数组,遍历原数组,// 将每个元素拿出来做一些变换然后放入到新的数组中let newArr = [1, 2, 3].map(item => item * 2);console.log(New array is ${newArr});// filter: 数组过滤, 根据返回的boolean// 决定是否添加到数组中let newArr2 = [1, 2, 4, 6].filter(item => item !== 6);console.log(New array2 is ${newArr2});// reduce: 结果汇总为单个返回值// acc: 累计值; current: 当前itemlet arr = [1, 2, 3];const sum = arr.reduce((acc, current) => acc + current);const sum2 = arr.reduce((acc, current) => acc + current, 100);console.log(sum); // 6console.log(sum2); // 106更多系列文章⭐在GitHub上收藏/订阅⭐《前端知识体系》JavaScript基础知识梳理(上)JavaScript基础知识梳理(下)谈谈promise/async/await的执行顺序与V8引擎的BUG前端面试中常考的源码实现Flex上手与实战……《设计模式手册》单例模式策略模式代理模式迭代器模式订阅-发布模式桥接模式备忘录模式模板模式……《Webpack4渐进式教程》webpack4 系列教程(二): 编译 ES6webpack4 系列教程(三): 多页面解决方案–提取公共代码webpack4 系列教程(四): 单页面解决方案–代码分割和懒加载webpack4 系列教程(五): 处理 CSSwebpack4 系列教程(八): JS Tree Shakingwebpack4 系列教程(十二):处理第三方 JavaScript 库webpack4 系列教程(十五):开发模式与 webpack-dev-server……⭐在GitHub上收藏/订阅⭐ ...

March 31, 2019 · 3 min · jiezi

前端面试题 -- 综合(一)

前言这篇文章总结一些前端面试过程当中经常遇到的 HTTP、浏览器、SEO 等方面的问题,如果有需要了解其他面试问题的小伙伴, 请点击 这里,查看 HTML+CSS+JavaScript 等方面的问题。总结问题,分享给有需要的人(未完待续)如果文章中有出现纰漏、错误之处,还请看到的小伙伴留言指正,先行谢过以下 ↓HTTP相关1. HTTP有什么特点简单快速:客户向服务器请求服务时,只需传送请求方法和路径灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由 Content-Type 加以标记无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接 (深入-持久连接、管线化)无状态:HTTP协议是无状态协议( Cookie 的出现)2. http和https协议有什么区别http: 是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少https: 是以安全为目标的HTTP通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL ,因此加密的详细内容就需要 SSLhttp 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80 ,后者是 443http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全参考 http与https的区别3. http状态码有那些?分别代表是什么意思常用 http 状态码:200 OK 服务器成功处理了请求301/302 Moved Permanently(重定向)请求的URL已移走404 Not Found (页面丢失)未找到资源403 服务器拒绝请求408 (请求超时) 服务器等候请求时发生超时501 Internal Server Error 服务器遇到一个错误,使其无法对请求提供服务502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求更多 参考 这里4. 什么是HTTP持久化和管线化出现背景: HTTP 最初的版本中,每进行一次 HTTP 通信,就要断开一次 TCP 连接(无连接)为解决上述问题,HTTP/1.1 增加了持久连接(HTTP Persistent Connections )的方法,其特点是,只要一方未明确提出断开连接,则另一方保持 TCP 连接状态管线化是指将多个 HTTP 请求整批发送,在发送过程中不用等待对方响应管线化是在持久连接的基础上实现的,管线化的实现,能够同时并行发送多个请求,而不需要一个接一个的等待响应5. Http报文HTTP 报文是面向文本的,报文中的每一个字段都是一些 ASCII 码串,各个字段的长度是不确定的。HTTP 有两类报文:请求报文和响应报文HTTP的这两种报文都由三部分组成:开始行、首部行、实体主体参考 这里6. 从输入URL到页面加载全过程参考 这里7. 为什么利用多个域名来存储网站资源会更有效CDN 缓存更方便突破浏览器并发限制节约 cookie 带宽节约主域名的连接数,优化页面响应速度防止不必要的安全问题浏览器相关1. 浏览器是由什么组成的从原理构成上分为七个模块,分别是 User Interface(用户界面)、 Browser engine(浏览器引擎) 、 Rendering engine(渲染引擎) 、 Networking(网络) 、 JavaScript Interpreter(js解释器) 、 UI Backend(UI后端) 、Date Persistence(数据持久化存储)其中,最重要的是渲染引擎(内核)和 JavaScript 解释器(JavaScript引擎)浏览器内核主要负责 HTML 、CSS 的解析,页面布局、渲染与复合层合成; JavaScript 引擎负责 JavaScript 代码的解释与执行2. 浏览器缓存机制浏览器的缓存机制也就是我们说的 HTTP 缓存机制,其机制是根据 HTTP 报文的缓存标识进行的参考 这里3. 浏览器渲染机制参考 这里4. 几个很实用的BOM属性对象方法location 对象:主要存储 url 相关信息history 对象:浏览历史信息相关history.go() // 前进或后退指定的页面数 history.go(num);history.back() // 后退一页history.forward() // 前进一页navigator 对象:浏览器信息相关navigator.userAgent //返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串)navigator.cookieEnabled // 返回浏览器是否支持(启用)cookie其他1. 谈谈你对SEO的理解SEO:搜索引擎优化,其目的是为了使网站能够更好的被搜索引擎抓取,提高在搜索引擎内的自然排名,从而带来更多的免费流量,获取收益SEO主要有两种方法,站内优化和站外优化前端SEO优化2. 前端怎么控制管理路由路由就是浏览器地址栏中的 url 与所见网页的对应关系前端路由的实现方式:基于 hash(ocation.hash+hashchange事件)展示层面也就是切换 # 后面的内容,呈现给用户不同的页面。现在越来越多的单页面应用,基本都是基于 hash 实现特性:url 中 hash 值的变化并不会重新加载页面hash 值的改变,都会在浏览器的访问历史中增加一个记录,也就是能通过浏览器的回退、前进按钮控制 hash 的切换我们可以通过 hashchange 事件,监听到 hash 值的变化,从而响应不同路径的逻辑处理基于 istory 新 API( history.pushState()+popState 事件)window.history.pushState(null, null, “http://www.google.com”);这两个 API 的相同之处是都会操作浏览器的历史记录,而不会引起页面的刷新。不同之处在于,pushState 会增加一条新的历史记录,而 replaceState 则会替换当前的历史记录详见History API -MDN3. 防抖和节流的区别防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行节流:指定时间间隔内只会执行一次任务推荐 这里4. 页面重构怎么操作页面重构就是根据原有页面内容和结构的基础上,通过 div+css 写出符合 web 标准的页面结构。具体实现要达到以下三点:功能不全页面的重构:页面功能符合用户体验、用户交互结构完整,可通过标准验证,代码重构:代码质量、SEO 优化、页面性能、更好的语义化、浏览器兼容、CSS 优化充分考虑到页面在站点中的“作用和重要性”,并对其进行有针对性的优化后记这篇文章收集的问题虽然不多,但是每一部分单独拎出来都可以写一篇深入理解,所以还是很需要耐心才可以看完的这段时间收集了很多前端面试的相关问题,发现有些东西还是很需要深入理解的,有时候仅仅停留在 ‘会用’ 的阶段还是远远不够的。所以,后面有时间也会聊一聊自己尝试的东西,发现的问题,有兴趣一起探索的小伙伴可以关注哦期待同行 GitHub完整版面试题以上 ...

March 26, 2019 · 1 min · jiezi

前台性能优化总结

场景某天上完课,走在路上,突然想起来,一个企业中,计算机量可能很大,500到2000左右。分组时,可能会很耗时,前台能不能承受的住。模拟加了1000台计算机,前台直接炸,将近4秒才能出来,并且选择的时候也很卡。学习了很多数据量多时性能优化的方法,目前前台经过一系列优化,能保证在Chrome浏览器环境、1000台测试机的条件下,,2s内完成页面渲染。优化组件介绍前台多选使用的是NZ为我们提供的多选框组件,该组件要求输入信息必须满足一定格式。/** * NZ 多选框 * nz-checkbox-group * 数据格式规范 */export class NzCheckBoxSpec<T> { label: string; value: T; checked: boolean;}所以,计算机多选组件整体逻辑如下:获取外部传入的计算机列表(考虑到默认选中的问题)。从后台查询所有的计算机列表。根据所有计算机构造NZ多选框组件的输入,checked一项根据当前遍历的计算机是否在传入的计算机列表中判断。性能问题看着问题不大:this.hostService.getAllHosts().subscribe((hosts) => { this.hostListValues = []; // 使用主机信息构造多选框绑定数据 hosts.forEach((host) => { this.hostListValues.push({ label: host.name, value: host, checked: HostCheckboxComponent.existIn(host, this._hostList) }); });});查阅相关资料,原来一直都用的有问题,forEach虽然很好使,但是是性能最低的一种循环。性能测试我亲自写测试代码测试三种循环方式的性能(数据量2000,内部执行同样业务操作):实验次数 / 实验方法forEachfor offor1654ms524ms517ms2604ms571ms563ms3550ms506ms508ms4621ms495ms522ms5506ms562ms470ms平均时间587ms531.6ms516ms我这里只是部分少量数据,结果具有随机性,不具有普遍性。反正这里我是总结出几点:当数据量少的时候,我使用forEach,方便。当数据量大的时候,我是用for循环,性能略好。当需要在循环中return时,使用for of,方便。学习过程中还查到了Duff’s Device,这是目前性能最好的循环方式。有人测试,Duff’s Device需要达到30万的数据量才能显示其算法高效的性能。组件的错误使用最开始,组件是这样使用的。<app-host-checkbox [hostList]=“hostGroup.hostList” (hostCheck)=“bindHostList($event)"></app-host-checkbox>看着没啥毛病啊,把计算机组的hostList传进去,然后一个输出事件,绑定hostCheck事件,当选中的计算机有改动的时候就调用bindHostList()方法。bindHostList(hostList: Array<Host>): void { this.hostGroup.hostList = hostList;}注意看这张图:页面把hostGroup的hostList传给组件。组件初始化,当用户选择的时候,再把选中的值回传给hostGroup。hostGroup的hostList变了,又传给组件了。所以,组件初始化执行了两次,本来for循环就已经很耗时了,更何况执行两次。之前测试数据少没发现。新建临时变量,该变量只用于传输入的hostList。总结在校学习过的东西,我们总是很久以后用到。如果不是华软项目,也用不到学过的计算机网络,没有大数据量,学的算法复杂度也用不上。当编码不是问题的时候,我们开始考虑设计与用户体验。

March 8, 2019 · 1 min · jiezi

一文看透浏览器架构

本文由云+社区发表作者:廖彩明在从事前端开发过程中,浏览器作为最重要的开发环境,浏览器基础是是前端开发人员必须掌握的基础知识点,它贯穿着前端的整个网络体系。对浏览器原理的了解,决定着编写前端代码性能的上限。浏览器作为JS的运行环境,学习总结下现代浏览器的相关知识前言经常听说浏览器内核,浏览器内核究竟是什么,以及它做了什么。我们将来了解下浏览器的主要组成部分、现代浏览器的主要架构、浏览器内核、浏览器内部是如何工作的1 浏览器现代浏览器结构如下:The browser’s main componentThe User Interface主要提供用户与Browser Engine交互的方法。其中包括:地址栏(address bar)、向前/退后按钮、书签菜单等等。浏览器除了渲染请求页面的窗口外的所有地方都属于The User InterfaceThe Browser Engine协调(主控)UI和the Rendering Engine,在他们之间传输指令。 提供对The Rendering Engine的高级接口,一方面它提供初始化加载Url和其他高级的浏览器动作(如刷新、向前、退后等)方法。另一方面Browser Engine也为User Interface提供各种与错误、加载进度相关的消息。The Rendering Engine为给定的URL提供可视化的展示。它解析JavaScript、Html、Xml,并且User Interface中展示的layout。其中关键的组件是Html解析器,它可以让Rendering Engine展示差乱的Html页面。 值得注意:不同的浏览器使用不同的Rendering Engine。例如IE使用Trident,Firefox使用Gecko,Safai使用Webkit。Chrome和Opera使用Webkit(以前是Blink)The Networking基于互联网HTTP和FTP协议,处理网络请求。网络模块负责Internet communication and security,character set translations and MIME type resolution。另外网络模块还提供获得到文档的缓存,以减少网络传输。为所有平台提供底层网络实现,其提供的接口与平台无关The JavaScript Interpreter解释和运行网站上的js代码,得到的结果传输到Rendering Engine来展示。The UI Backend用于绘制基本的窗口小部件,比如组合框和窗口。而在底层使用操作系统的用户界面方法,并公开与平台无关的接口。The Data Storage管理用户数据,例如书签、cookie和偏好设置等。2 主流浏览器的架构2.1 FireFoxFireFox的架构可以看到火狐浏览器的渲染引擎(Rendering Engine)使用的是Gecko;XML Parser解析器是Expat;Java Script解释器是Spider-Monkey(c语言实现)2.2 ChromeChrome的架构渲染引擎Rendering Engine使用的是WebKitXML Parser: libXML解析XML,libXSLT处理XSLTJS解释器使用C++实现的V8引擎,2.3 IEIE的架构渲染引擎主要是TridentScripting Engine有JScript和VBScript3 浏览器内核浏览器最重要或者说核心的部分是“Rendering Engine”,可大概译为“渲染引擎”,不过我们一般习惯将之称为“浏览器内核”。主要包括以下线程:3.1 浏览器 GUI 渲染线程,主要包括: HTML Parser 解析HTML文档,将元素转换为树结构DOM节点,称之为Content Tree CSS Parser 解析Style数据,包括外部的CSS文件以及在HTML元素中的样式,用于创建另一棵树,调用“Render Tree” Layout过程 为每个节点计算出在屏幕中展示的准确坐标 Painting 遍历Render Tree,调用UI Backend提供的接口绘制每个节点3.2 JavaScript 引擎线程JS引擎线程负责解析Javascript脚本,运行代码 JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞a) 减少 JavaScript 加载对 DOM 渲染的影响(将 JavaScript 代码的加载逻辑放在 HTML 文件的尾部,减少对渲染引擎呈现工作的影响;b) 避免重排,减少重绘(避免白屏,或者交互过程中的卡顿;c) 减少 DOM 的层级(可以减少渲染引擎工作过程中的计算量;d) 使用 requestAnimationFrame 来实现视觉变化(一般来说我们会使用 setTimeout 或 setInterval 来执行动画之类的视觉变化,但这种做法的问题是,回调将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿)3.3 浏览器定时触发器线程浏览器定时计数器并不是由 JavaScript 引擎计数的, 因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案3.4 浏览器事件触发线程当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JavaScript 引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于 JavaScript 的单线程关系所有这些事件都得排队等待 JavaScript 引擎处理。3.5 浏览器 http 异步请求线程在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。4 以Chrome浏览器为例,演示浏览器内部如何工作上面铺垫了这么多理论,下面结合Chrome讲解当用户在地址栏上输入URL后,浏览器内部都做了写什么4.1 Chrome浏览器中的多进程打开Chrome 任务管理器,可以看到Chrome运行的进程各个进程的功能• Browser进程功能:Controls “chrome” part of the application including address bar, bookmarks, back and forward buttons. Also handles the invisible, privileged parts of a web browser such as network requests and file access.• GPU进程功能:Handles GPU tasks in isolation from other processes. It is separated into different process because GPUs handles requests from multiple apps and draw them in the same surface.• 第三方插件进程功能:Controls any plugins used by the website, for example, flash. 每个插件对应一个进程,当插件运行时创建• 浏览器渲染进程功能:Controls anything inside of the tab where a website is displayed. 默认每个标签页创建一个渲染引擎实例。• V8 Proxy resolver关于V8 Proxy resolver可查看code.google.comgroup.google.com https://groups.google.com/a/c...!topic/net-dev/73f9B5vFphI doc.google.comChrome支持使用代理脚本为给定的网址选择代理服务器,包含使用操作系统提供的代理解析程序的多个平台的回退实现。但默认情况下(iOS除外),它使用内置的解析V8执行代理脚本(V8 pac)。今天(截至2015年1月),V8 pac在浏览器进程中运行。这意味着浏览器进程包含一个V8实例,这是一个潜在的安全漏洞。在浏览器进程中允许V8还需要浏览器进程允许写入 - 执行页面。我们关于将V8 pac迁移到单独进程的建议包括为解析器创建Mojo服务,从实用程序进程导出该服务,以及从浏览器进程创建/连接到该进程。浏览器进程之间主要通过IPC (Inter Process Communication)通信4.2 Per-frame renderer processes - Site IsolationSite Isolation is a recently introduced feature in Chrome that runs a separate renderer process for each cross-site iframe. We’ve been talking about one renderer process per tab model which allowed cross-site iframes to run in a single renderer process with sharing memory space between different sites. Running a.com and b.com in the same renderer process might seem okay. The Same Origin Policy is the core security model of the web; it makes sure one site cannot access data from other sites without consent. Bypassing this policy is a primary goal of security attacks. Process isolation is the most effective way to separate sites. With Meltdown and Spectre, it became even more apparent that we need to separate sites using processes. With Site Isolation enabled on desktop by default since Chrome 67, each cross-site iframe in a tab gets a separate renderer process.每个iframe是单独的渲染进程此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

February 25, 2019 · 2 min · jiezi

全网各大视频网 VIP 视频免费看, VIP 无损音乐免费下载,土豪请忽略

在网上发现个油猴神器,可以免费看爱奇艺、腾讯视频等网站的 VIP 视频,而且还没得广告,比较干净,亲测有效,故分享之~插件名字:聚合 VIP 视频免费在线看 + 聚合 VIP 无损音乐免费下载Greasy Fork 地址: https://greasyfork.org/zh-CN/…功能介绍:聚合 VIP 视频解析在线看(若解析失败可多切换几个线路试试看),支持的网站包括但不限于:[腾讯视频]、[爱奇艺]、[优酷土豆]、[芒果 tv]、[乐视视频]、[PPTV]、[搜狐视频]、[bilibili]、[AcFun]、[暴风影音]等;聚合 VIP 无损音乐免费下载,支持的网站包括但不限于:[网易云音乐]、[QQ 音乐]、[酷狗音乐]、[酷我音乐]、[虾米音乐]、[百度音乐]等等PS:这是油猴脚本,需要安装油猴插件才能使用。

February 25, 2019 · 1 min · jiezi

H5 notification浏览器桌面通知

Notification是HTML5新增的API,用于向用户配置和显示桌面通知。上次在别的网站上看到别人的通知弹窗,好奇之余也想知道如何实现的。实际去查一下发现并不复杂,且可以说比较简单,故写篇博客分享给大家,希望能帮你们了解这个API。npm包:我还发了一个npm包:notification-Koro1,非常轻量简洁,觉得不错的话,点个Star吧chrome下Notification的表现:以谷歌为例,一开始需要用户允许通知:允许通知之后,显示的通知长这样:Notification特性该通知是脱离浏览器的,即使用户没有停留在当前标签页,甚至最小化了浏览器,也会在主屏幕的右上角显示通知,然后在一段时间后消失。我们可以监听通知的显示,点击,关闭等事件,比如点击通知打开一个页面。博客、前端积累文档、公众号、GitHub栗子:去各个网站里面的控制台去运行API的具体细节,等下再说,先试试这个API下面是一个简单的栗子,大家可以先在各个网站的控制台里面运行查看Notification的效果:var options = { dir: “auto”, // 文字方向 body: “通知:OBKoro1评论了你的朋友圈”, // 通知主体 requireInteraction: true, // 不自动关闭通知 // 通知图标 icon: “https://upload-images.jianshu.io/upload_images/5245297-818e624b75271127.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"};notifyMe('这是通知的标题’, options);function notifyMe(title, options) { // 先检查浏览器是否支持 if (!window.Notification) { console.log(‘浏览器不支持通知’); } else { // 检查用户曾经是否同意接受通知 if (Notification.permission === ‘granted’) { var notification = new Notification(title, options); // 显示通知 } else if (Notification.permission === ‘default’) { // 用户还未选择,可以询问用户是否同意发送通知 Notification.requestPermission().then(permission => { if (permission === ‘granted’) { console.log(‘用户同意授权’); var notification = new Notification(title, options); // 显示通知 } else if (permission === ‘default’) { console.warn(‘用户关闭授权 未刷新页面之前 可以再次请求授权’); } else { // denied console.log(‘用户拒绝授权 不能显示通知’); } }); } else { // denied 用户拒绝 console.log(‘用户曾经拒绝显示通知’); } }}浏览器支持:MDN:目前Notification除了IE浏览器不支持外, 其他浏览器都已支持桌面通知,移动端浏览器基本都未支持。因为兼容性问题,所以在使用Notification之前,我们需要查看浏览器是否支持Notification这个API:if(window.Notification){ // 桌面通知的逻辑}通知权限:为了避免网站滥用通知扰民,在向用户显示通知之前,需要经过用户同意。Notification.permission 用于表明当前通知显示的授权状态,它有三个值:default : 默认值,用户还未选择granted : 用户允许该网站发送通知denied : 用户拒绝该网站发送通知检测权限:检测浏览器是否支持Notification之后,需要检测一下用户通知权限。 if (Notification.permission === ‘granted’) { console.log(‘用户曾经同意授权’); // 随时可以显示通知 } else if (Notification.permission === ‘default’) { console.log(‘用户还未选择同意/拒绝’); // 下一步请求用户授权 } else { console.log(‘用户曾经拒绝授权 不能显示通知’); }请求权限当Notification.permission为default的时候,我们需要使用Notification.requestPermission()来请求用户权限。Notification.requestPermission()基于promise语法,then的回调函数参数是用户权限的状态Notification.permission的值。Notification.requestPermission().then(permission => { if (permission === ‘granted’) { console.log(‘用户同意授权’); // 随时可以显示通知 } else if (permission === ‘default’) { console.log(‘用户关闭授权 可以再次请求授权’); } else { console.log(‘用户拒绝授权 不能显示通知’); }});// 老版本使用的是回调函数机制:Notification.requestPermission(callback); 参数一样推送通知当Notification.permission为granted 时,请求到用户权限之后,不必立即发送通知,可以在任意时刻,以任意形式来发送通知。const options = {}; // 传空配置const title = ‘这里是标题’;const notification = new Notification(title, options) // 显示通知上面这段代码就可以显示一个简单的通知了,只要用户允许你弹窗。Notification的参数:title:通知的标题options:通知的设置选项(可选)。body:字符串。通知的body内容。tag:代表通知的一个识别标签,相同tag时只会打开一个通知窗口。icon:字符串。要在通知中显示的图标的URL。data:想要和通知关联的数据,可以在new Notification返回的实例中找到。renotify: 布尔值。相同tag,新通知出现的时候是否替换之前的(开启此项,tag必须设置)。requireInteraction:布尔值。通知不自动关闭,默认为false(自动关闭)。还有一些不太重要的配置可以看张鑫旭老师的博客和MDN的介绍requireInteraction: 保持通知不自动关闭默认值为false,通知会在三四秒之后自动关闭。当设置为true,并且当有超过两个通知(new Notification(title, options))时,会出现如下图的通知叠加状态。这种情况显然,我们只能默认操作最后一个通知,除非你把每个通知返回的实例都保存下来。我发布的npm包:notification-koro1,可以自定义一定的时间间隔自动关闭不自动关闭的通知,也可以一次性关闭所有通知PS:如果没有触发叠加,很可能是因为你两次通知的tag配置项是相同的(相同tag只能出现一个弹窗)。PS: safari下不支持该选项,默认自动关闭renotify:相同默认值为false,chorme下相同tag的通知不替换,还是老的通知设置为true, 两个相同tag的通知,新通知替换之前旧的通知。注意:使用renotify,必须要同时设置tag选项,否则将会报错。PS: safari下不支持该选项,默认两个相同tag的通知,新通知替换之前旧的通知。Notification的实例:生成通知,会返回一个实例,如下:const instanceNotification = new Notification(title, options)instanceNotification就是当前通知的实例,在该实例上,我们可以查询该通知的配置,监听事件,调用实例方法。下文都以instanceNotification指代通知返回的实例。通知的配置:在通知实例上可以读取到设置通知时的所有配置,比如:通知标题:instanceNotification. title、通知内容:instanceNotification. body 、通知图标:instanceNotification. icon等。PS: 这些属性都是只读的,不能删除,不能修改,不能遍历。事件处理:我们可以使用通知的实例来监听通知的事件:click: 用户点击通知时被触发show: 通知显示的时候被触发error: 通知遇到错误时被触发close: 用户关闭通知时被触发instanceNotification.onclick = e => { // do something 可以是:打开网址,发请求,关闭通知等}注意:最好是一发出通知就立即监听事件,否则有些事件可能一开始没被触发或永远不会触发。例如:用定时器5秒后才监听通知的点击和显示事件,则永远不会触发通知显示的回调,点击事件在5秒后才可以正常起作用但会错误五秒之前用户的点击。关闭通知instanceNotification.close()没有设置不自动关闭的话,chrome通知将会在4.5秒左右自动关闭通知,safari则是5秒钟(无法设置不自动关闭)。notification没有定时控制通知多久后消失的功能,当出现多个通知,也无法统一关闭。这两个问题,在我发布的NPM包:notification-koro1中,都解决掉了,并提供更清晰的回调应用场景即时通讯软件(邮件、聊天室)体育赛事结果彩票/抽奖结果新闻网站重大新闻通知网站的重大更新,重大新闻等。notification其他这里是一些API/浏览器细节,以及可能会遇到的问题,可以先不看,等真正遇到了,回头再来看。用户拒绝显示通知:一旦用户禁止网站显示通知,网站就不能再请求用户授权显示通知,需要用户去设置中更改。chrome浏览器的通知设置位置:设置>高级>内容设置>通知saafari浏览器:偏好设置>网站>通知>找到网站>修改权限/恢复默认关闭请求权限:在chorme浏览器中:当用户关闭请求权限的弹窗(右上角的叉叉),页面还没刷新,我们可以再次向用户请求权限。页面刷新过后,浏览器默认用户拒绝。在safari浏览器下,没有关闭请求权限的选项,用户必须选择同意/拒绝。icon不显示问题:可能是网站进行了同源限制(比如github),不是域名下面的图片,会报错,不能调用。tag:tag相同的通知,同时只能出现一个,老通知是否会被覆盖取决于:renotify配置和浏览器。chrome下:当通知关闭之后,上次出现过的tag在一段时间内,不能再出现,比如刷新页面再请求相同tag的通知。(在safari下正常出现)safari下面不能显示icon在safari下面,同一个网站(比如谷歌),同样的代码,chorme可以正常显示icon,safari却没有icon,也没有报错。谷歌之后发现,在stack overflow里面看到safari只支持body和tag选项,并不支持icon选项。连续触发在safari和chrome下短时间内连续触发通知(不设tag,不设requireInteraction),会出现如下表现:这个表现,通知没有icon、标题、内容,就显得没有意义了,浏览器以这种形式,限制开发者不要频繁打扰用户。notification-Koro1:试一下notification-Koro1啦, 持续维护,简单方便~结语本文写的比较细,可以先mark一下,然后以后真正用到这个API了,可以先通过文中的栗子,然后再查找对应的内容。还有就是注意浏览器间的差异,我自己就试了chrome和safari,然后这两个浏览器在实现细节上有很多不一样的地方,开发的时候注意一下。博客、前端积累文档、公众号、GitHub参考资料:notification-Koro1简单了解HTML5中的Web Notification桌面通知Notification MDNHTML5 桌面通知:Notification API ...

February 21, 2019 · 2 min · jiezi

帮谷歌推广Webp图片格式之:Webp的格式转换

参考谷歌官网:Webp: A new image format for the WebWebp是Google强推的新一代网络图片格式,特点就是:高质量压缩。能压缩多少呢?5MB的原图,不降低效果,转换成webp格式后大小是几百KB。100KB的图,转换后是9KB。虽然目前所有主流浏览器都支持这种图片格式,但不幸的是所有主流系统如Mac、Win等都还没有默认支持打开它的程序,更无法显示它的预览、缩略图。如果想查看,最简单的方法是把*.webp文件的打开方式设定为Chrome等浏览器,双击打开在浏览器中查看。还有很多时候我们需要对这种文件进行转换。Google提供了一组工具集合,叫libwebp,其中包括各种webp相关转换的命令:cwebp – 将其它图片转为webp格式图片 (不包括GIF)dwebp – 将webp格式图片转为其它格式图片vwebp – webp图片浏览器webpmux – WebP muxing toolgif2webp – 将GIF转换为webp图片下载安装参考官网:Downloading and Installing WebPUbuntu安装libweb库:$ sudo apt-get install webpMac安装libwebp库:$ brew install webp注意:Homebrew安装的webp并不包括上面所有的工具,而只有cwebp和dwebp。如果我们想要所有的工具,有两种方法:到官网找到自己OS对应版本的二进制包,直接运行使用自己编译最简单就是到官网下载列表里找到自己的OS对应版本的二进制包,下载下来解压缩直接使用。官方下载列表:https://storage.googleapis.co…比如我的系统是Mac 10.12,那么就找到libwebp-0.6.0-mac-10.12.tar.gz这个压缩包下载:cd /tmpwget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-0.6.0-mac-10.12.tar.gztar xvzf libwebp-*.tar.gzcd libweb-*然后在~/.zshrc或~/.bash_profile中的PATH环境变量中加入刚才二进制文件包中的bin目录,或者直接设置alias,即可开始像别的命令开始用了。如果没有自己所用系统的二进制包,那么就只能自己编译了。每种平台的编译方法不一样,需要按照官网方法一步一步安装。编译方法参考官方:Compiling the Utilities将各种图片转换为Webp格式参考:https://developers.google.com…目前输入格式支持:png, jpg$ cwebp INPUT.png -o OUTPUT.webp将Webp图片转换为其它格式图片参考:https://developers.google.com…$ dwebp INPUT.webp -o OUTPUT.png将GIF转换为Webp格式参考:https://developers.google.com…$ gif2webp INPUT.gif -o OUTPUT.webp浏览webp图片这个命令不是在命令行终端里浏览图片,而是在桌面上弹出一个GUI窗口显示图片,所以需要依赖本地电脑的GUI桌面。$ vwebp INPUT.webp

February 17, 2019 · 1 min · jiezi

深入浅出任务队列机制,非常浅

前言众所周知,js是单线程的,就像我们不能一边刷牙一边洗脸(或许有些大佬真的可以),那么单线程如何才能规划调度好要做的任务呢?这个时候就要介绍一下这个任务机制了~任务种类宏任务微任务注意:浏览器环境和node环境是不一样的,本文只讨论浏览器环境规则执行一个宏任务(先执行同步代码)–>执行所有微任务–>UI render–>执行下一个宏任务–>执行所有微任务–>UI render–>根据HTML Standard,一轮事件循环执行结束之后,下轮事件循环执行之前开始进行UI render。即:macro-task任务执行完毕,接着执行完所有的micro-task任务后,此时本轮循环结束,开始执行UI render。UI render完毕之后接着下一轮循环。但是UI render不一定会执行,因为需要考虑ui渲染消耗的性能已经有没有ui变动需要注意的是,微任务是有优先级的,就如同上面的表格从上往下一样,nextTick>Promise>MutationObserver.那么宏任务有没有优先级呢??大部分浏览器会把DOM事件回调优先处理 因为要提升用户体验 给用户反馈,其次是network IO操作的回调,再然后是UIrender,之后的顺序就难以捉摸了,其实不同浏览器的表现也不太一样,这里不做过多讨论。来道经典题目console.log(‘script start’);setTimeout(function() { console.log(‘setTimeout’);}, 0);Promise.resolve().then(function() { console.log(‘promise1’);}).then(function() { console.log(‘promise2’);});console.log(‘script end’);答案是’script start’、‘script end’、‘promise1’、‘promise2’、‘setTimeout’先走完所有同步代码-到promise微任务-宏任务setTimeout

February 15, 2019 · 1 min · jiezi

js关闭当前页面(支付宝,微信,app)

使用js 关闭当前页面 , 一般想到的都是 window.close() , 但是该方法只能关闭通过 window.open() 打开的页面 所以针对这种情况 , 只能分情况去解决 . 在微信 , 支付宝 , app 中打开外部链接 , 都是使用webview打开页面的 , 所以需要app提供映射方法 . 对于微信 , 支付宝 , 我们能通过开放平台找到对应的方法.微信:window.WeixinJSBridge.call(‘closeWindow’)支付宝:window.AlipayJSBridge.call(‘closeWebview’)对应一般的app ,需要开发者封装可以让js调用的方法 . (以下就是js 和 app的交互方法)Javascript调用Java方法以Android的Toast的为例,下面看下如何从Javascript代码中调用系统的Toast。先定义一个AndroidToast的Java类,它有一个show的方法用来显示Toast:public class AndroidToast {@JavascriptInterfacepublic void show(String str) {Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();}}再对WebView进行设置,开启JavaScipt,注册JavascriptInterface的方法:private void initView() {webView = (WebView) findViewById(R.id.webView);WebSettings webSettings = webView.getSettings();webSettings.setJavaScriptEnabled(true);webSettings.setDefaultTextEncodingName(“UTF-8”);webView.addJavascriptInterface(new AndroidToast(), “AndroidToast”);webView.loadUrl(“file:///android_asset/index.html”);}addJavascriptInterface的作用是把AndroidToast类映射为Javascript中的AndroidToast。这样就可以在JavaScript中调用Java中的方法了。在Javascript中调用Java代码:function toastClick(){window.AndroidToast.show(‘from js’);}通过window属性可以找到映射的对象AndroidToast,直接调用它的show方法即可。注意这里传输的数据只能是基本数据类型和string,可以传输string就意味着可以使用json传输结构化数据。这里调用的方法并没有返回值,如果需要在JavaScript中需要得到返回值怎么办呢?JavaScript调用Java有返回值如果想从Javascript调的方法里面获取到返回值,只需要定义一个带返回值的@JavascriptInterface方法即可:public class AndroidMessage {@JavascriptInterfacepublic String getMsg() {return “form java”;}}添加Javascript的映射:webView.addJavascriptInterface(new AndroidMessage(), “AndroidMessage”);在JavaScript直接调用:function showAlert(){var str=window.AndroidMessage.getMsg();console.log(str);}这样就完成了有返回值的方法调用。还有一种场景是,在Java中主动触发JavaScript方法,就需要在Java中调用JavaScript方法了。Java调用JavaScript方法Java在调用JavaScript方法的时候,需要使用WebView.loadUrl()方法,它可以直接在页面里执行JavaScript方法。首先定义一个JavaScript方法给Java调用:function callFromJava(str){console.log(str);}在Java中直接调用该方法:public void javaCallJS(){webView.loadUrl(“javascript:callFromJava(‘call from java’)”);}可以在loadUrl中给Javascript方法直接传参,如果JavaScript方法有返回值,使用WebView.loadUrl()是无法获取到返回值的,需要JavaScript返回值给Java的话,可以定义一个Java方法提供给JavaScript调用,然后Java调用JavaScript之后,JavaScript触发该方法把返回值再传递给Java。注意WebView.loadUrl()必须在Ui线程中运行,不然会会报错。以下是项目中用到的具体代码:var isLppzApp = falsevar ua = navigator.userAgent.toLowerCase()var uaApp = ua ? ua.match(/BeStore/i) : ’’ // match方法返回的是对象var uaAndroid = /android/i.test(ua) // test返回的是true/falsevar uaIos = /iphone|ipad|ipod/i.test(ua)if (uaApp.toString() === ‘bestore’) { // 必须将match返回的对象转成字符串isLppzApp = true} else {isLppzApp = false}if (window.WeixinJSBridge) {window.WeixinJSBridge.call(‘closeWindow’) // 微信} else if (window.AlipayJSBridge) {window.AlipayJSBridge.call(‘closeWebview’) // 支付宝} else if (isLppzApp && uaAndroid) {window.obj.closePageLppzRequest(’’) // 安卓app} else if (isLppzApp && uaIos) {window.webkit.messageHandlers.closePageLppzRequest.postMessage(’’) //ios app} ...

February 13, 2019 · 1 min · jiezi

知识整理至浏览器篇

上一篇整理到HTML部分,发现有些知识点属于浏览器范畴,就单独出一篇来专门归纳。传送门:知识整理之HTML篇介绍一下你对浏览器内核的理解?主要分成两个部分:渲染引擎(Render Engine)和JS引擎。渲染引擎:负责取得网页的内容(html,xml和图像等),整理讯息(例如假如css),以及计算网页的显示方式,然后输出到显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不同。所有网页浏览器、电子邮件客户端以及它需要编辑、显示网络内容的应用程序都需要内核。JS引擎:解析和执行JavaScript来实现网页的动态效果。最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向与只指渲染引擎。关于浏览器工作原理详解,请移步至:浏览器工作原理详解常见的浏览器内核有哪些?IE浏览器内核:Trident内核,也被称为IE内核Chrome浏览器内核:Chromium内核 → Webkit内核 → Blink内核Firefox浏览器内核:Gecko内核,也被称Firefox内核Safari浏览器内核:Webkit内核Opera浏览器内核:最初是自主研发的Presto内核,后跟随谷歌,从Webkit到Blink内核360浏览器、猎豹浏览器内核:IE+Chrome双内核搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+ Webkit(高速模式)百度浏览器、世界之窗内核:IE内核未完待续

January 28, 2019 · 1 min · jiezi

如何使用阿里云ARMS轻松重现用户浏览器问题

客户投诉不断,本地却无法重现?页面加载较慢是用户经常会反馈的问题,也是前端非常关注的问题之一。但定位、排查解决这类问题就通常会花费非常多的时间,主要原因如下:页面是在用户端的浏览器上加载执行,复现困难页面上线前,开发同学都会进行测试,在测试环境下页面加载一般都是正常的才会正式上线。用户在访问页面时,页面的加载是在用户端的浏览器上进行的,由于页面的加载耗时与地域、网络情况、浏览器或者运营商等有关系,想知道用户在访问页面时的具体情况,复现是非常困难的。监控信息缺少,导致无法深入排查大部分前端监控会通过PerformanceTiming对象,获取完整的页面加载耗时信息,但这类监控就缺失了页面静态资源的加载情况,无法直接复现现场,从而无法深入定位性能瓶颈。为了方便用户更快地定位性能瓶颈,阿里云ARMS前端监控推出一新功能: 会话追踪,提供页面静态资源加载的性能瀑布图,根据页面性能数据可深入定位页面资源加载情况。如何通过会话追踪帮助你快速定位问题在阿里云ARMS前端监控SDK上将sendResource配置为true,重新部署应用后,在页面onload时会上报当前页面加载的静态资源信息。从而在阿里云前端监控平台即可以对慢页面加载问题快速进行定位。SDK配置在阿里云ARMS前端监控SDK部分,默认是不上报页面加载的静态资源信息的,如果想获取页面加载的静态资源信息,只需在SDK的config部分将sendResource配置为true,重新部署后,就可以上报相关信息。具体配置如下:<script>!(function(c,b,d,a){c[a]||(c[a]={});c[a].config={pid:“atc889zkcf@8cc3f63543da641”,imgUrl:“https://arms-retcode.aliyuncs.com/r.png?",sendResource:true};with(b)with(body)with(insertBefore(createElement("script"),firstChild))setAttribute("crossorigin","",src=d)})(window,document,"https://retcode.alicdn.com/retcode/bl.js","__bl");</script>注意:静态资源加载信息的上报是在页面onload时会触发,上报信息量较大,如果对于页面性能要求很高的应用,可以不开启该配置。问题排查过程1. 发现问题进入访问速度菜单后,发现页面的性能较差,11点钟的页面完全加载时间达到35s,如下:2. 慢页面会话追踪在慢页面会话追踪模块,提供该页面在指定时间段内加载较慢的TOP20,这样可以快速发现哪些会话加载较慢,如下图所示。在该模块,你可以快速发现在11点钟有一次会话的页面加载时间在36.72s,这次访问应该是直接导致页面加载时间详情中折线图突然暴增的原因了。其中在在模块有7次会话访问的页面加载时间在7s以上,点击对应的页面,可以直接进入到会话详情页面,从而直观查看页面静态资源加载的瀑布图。通过页面资源加载的瀑布图,可以快速定位到资源加载的性能瓶颈,同时可以查看本次访问的客户端IP地址、浏览器、操作系统等UA信息,从而进一步确认是由于网络原因还是其他原因导致的,针对性进行相应的优化。3. 其他发现问题入口会话追踪也可以进入“会话追踪”菜单,可以看到该应用下的会话列表。会话列表中会根据页面完全加载时间排序,展示TOP100,帮助用户可以快速发现耗时较长的会话信息。同时支持按照页面、会话Id、浏览器、浏览器版本号进行过滤,展示相关的会话信息。点击操作后,是该会话的页面资源加载详情。访问明细如果当前会话列表中无法找到你要排查的会话信息,可以通过访问明细查找到相应的日志详细信息,在param中找到对应的sid即会话Id,然后在会话列表中查找相应的会话Id,即可以定位到想排查的会话信息。例如:在已知用户的客户端IP的情况下,想定位相应的会话信息,即可以在访问明细中,通过t=res and 117.136.32.110 进行搜索,找到对应的会话Id。根据查找到的会话Id, 就可以在会话列表中进行过滤,定位到具体的会话内容。使用入口指南进入访问速度菜单,如果发现页面性能较差,可以在"慢页面会话追踪Top20"中查看访问较慢的会话情况点击详情后,可以查看具体的页面资源加载瀑布图如果Top20不满足,可以点击"更多”,从而进入"会话列表"进入会话追踪菜单,展示的是TOP100的会话列表信息,根据页面完全加载时间从高到底排序,排查页面资源加载情况至此,慢页面会话追踪功能及使用方法介绍完成。该功能可以帮助你复现用户在访问页面时的页面资源加载情况,快速定位性能瓶颈问题。附录官网文档介绍阿里云ARMS前端监控官网本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 23, 2019 · 1 min · jiezi

Chrome crx插件扩展离线安装方法 (兼容所有版本)

如果您已经是新版Chrome,会看到如下错误错误提示: 无法从该网站添加应用、扩展程序和用户脚本原因是:自从 Chrome 67 以后就不再支持,拖动安装crx插件的方法了。而目最新版本已经到了 Chrome 71 。 这里简单说一下,兼容所有chrome版本的插件离线安装的方法。一、下载插件使用插件可以极大提高chrome浏览器的用户体验,包括常用的去广告、划词翻译、office文件在线预览、还有一些开发者的调试工具等。插件下载地址 : 极简插件二、安装插件新版chrome的安装方法可以兼容所有版本的chrome浏览器。首先要下载插件伴侣下载地址:官方下载 备用下载打开以后的界面开始之前先退出chrome浏览器选择插件开始安装至此安装完成,打开chrome浏览器,去启用一下插件。点击【右上角3个点】 - 【更多工具】 - 【扩展程序】依次打开 【开发者模式】 和 【插件右下角的开关】 即可。大功告成另外找插件可以参考我之前发的文章:新站上线,分享10个最强chrome浏览器插件!瞬间开发效率加倍

January 18, 2019 · 1 min · jiezi

现代浏览器探秘(part4):事件处理

翻译:疯狂的技术宅原文:https://developers.google.com…当输入到达合成器这是关于Chrome浏览器内部工作原理系列的最后一篇;研究浏览器怎样通过处理代码来显示网站。在上一篇文章中,我们研究了渲染过程并了解了合成器。 在本文中,我们将分析当用户输入时,合成器是怎样实现平滑交互的。从浏览器的角度看输入事件当你听到“输入事件”时,可能只会想到在文本框打字或鼠标单击,但从浏览器的角度来看,输入意味着来自用户的所有动作。 鼠标滚轮滚动是输入事件,触摸或者鼠标移动也是输入事件。当发生类似在屏幕上的触摸的用户动作时,浏览器是最先先接收到动作的进程之一,但是浏览器进程只知道该动作发生的位置。因为选项卡内部的内容由渲染器进程处理,所以浏览器进程会把事件类型(如touchstart)及其坐标发送到渲染器进程。 渲染器进程通过查找事件目标并运行附加的事件侦听器来适当地处理事件。图1:通过浏览器进程路由到渲染器进程的输入事件合成器接收输入事件在上一篇文章中,我们研究了合成器是如何通过合成栅格化图层来平滑地处理滚动的。 如果没有输入事件侦听器附加到页面,那么合成器线程可以创建完全独立于主线程的新复合帧。 但是如果一些事件监听器被附加到页面上会怎样呢? 如果需要处理事件,合成器线程将如何操作呢?图2:将鼠标悬停在页面图层上了解非快速可滚动区域由于JavaScript是运行在主线程上的,所以当合成页面时,合成器线程会标记页面的一个区域,该区域将事件处理程序附加为“非快速可滚动区域”。通过获取此信息,合成器线程可以确保在该区域中发生事件时将输入事件发送到主线程。 如果输入事件来自该区域之外,则合成器线程在不等待主线程的情况下进行合成新帧。图3:输入到非快速可滚动区域的示意图在编写事件处理程序时要注意Web开发中常见的事件处理模式是事件委托。 由于事件冒泡,你可以在最顶层的元素上附加一个事件处理程序,并根据事件目标委派任务。 你可能看到过或写过类似下面的代码。document.body.addEventListener(’touchstart’, event => { if (event.target === area) { event.preventDefault(); }});由于你只需要为所有元素编写一个事件处理程序,因此该事件委托模式在工程上很有吸引力。 但是如果从浏览器的角度来看这段代码,整个页面都被标记成了非快速可滚动区域。那么这意味着什么呢?即使你的应用不关心页面中某些部分的输入,合成器线程也必须与主线程通信,并且在每次输入事件进入时都要等待它。因此合成器的平滑滚动能力被破坏了。图4:在覆盖整个页面的非快速可滚动区域进行输入为了缓解这种情况,你可以在事件侦听器中传递passive:true选项。 这向浏览器提示你仍然希望在主线程中监听事件,同时合成器也可以继续并合成新帧。document.body.addEventListener(’touchstart’, event => { if (event.target === area) { event.preventDefault() } }, {passive: true});检查事件是否可取消想象一下,在页面中有一个框,你希望仅将滚动方向限制为水平滚动。在鼠标事件中使用 passive:true 选项意味着可以平滑滚动页面,但是在你想要用preventDefault 来限制滚动方向时,垂直滚动可能已经开始了。 你可以使用event.cancelable方法对这种情况进行检查。图5:一个部分内容被固定为水平滚动的网页document.body.addEventListener(‘pointermove’, event => { if (event.cancelable) { event.preventDefault(); // block the native scroll /* * do what you want the application to do here */ }}, {passive: true});或者你可以使用CSS规则(例如touch-action)来完全消除事件处理程序。#area { touch-action: pan-x;}查找事件目标当合成器线程向主线程发送输入事件时,首先要做的是命中测试以查找事件目标。 命中测试查找事件发生的坐标之下的内容,它使用在渲染进程中生成的绘制记录数据来完成这一使命。图6:查看绘制记录的主线程询问在x.y坐标点上绘制的内容最小化事件发送到主线程在上一篇文章中,我们讨论了我们的显示器以每秒60次的频率刷新的机制,以及我们怎样跟上节奏来获得流畅的动画效果。 对于输入来说,典型的触摸屏设备每秒发送60-120次触摸事件,而典型的鼠标每秒发送100次事件。 输入事件具有比屏幕刷新更高的保真度。如果类似touchmove的连续事件被发送到主线程120次,那么与屏幕刷新的速度相比,它可能会触发过多的命中测试和JavaScript的执行。图7:充斥在帧时间线上的事件导致页面闪烁为了最大限度地减少对主线程的过度调用,Chrome会合并连续事件(例如wheel, mousewheel, mousemove, pointermove, touchmove),并进行延迟调度,直到下一个 requestAnimationFrame。图8:与上图相同的时间线,但是正在合并和延迟事件任何离散事件,例如 keydown、 keyup、 mouseup、 mousedown、 touchstart、和 touchend 都会被立即发送。使用 getCoalescedEvents 获取帧内事件对于大多数Web应用程序,合并事件应足以提供良好的用户体验。 但是如果要构建一个绘图应用并根据 touchmove 坐标放置路径,则可能会在绘制平滑线时丢失中间坐标。 在这种情况下,你可以在鼠标事件中使用getCoalescedEvents方法来获取有关这些合并事件的信息。图9:左侧是平滑的触摸手势路径,右侧是合并限制路径window.addEventListener(‘pointermove’, event => { const events = event.getCoalescedEvents(); for (let event of events) { const x = event.pageX; const y = event.pageY; // draw a line using x and y coordinates. }});下一步在本系列中,我们介绍了Web浏览器的内部工作原理。 如果你从未想过为什么"开发者工具"建议在你的事件处理中添加{passive: true}或者为什么你可以在脚本标记中编写async属性,我希望本系列能够说明为什么浏览器需要这些信息来提供更快更顺畅的体验。使用Lighthouse如果你想让自己的代码对浏览器友好,但不知道从哪里开始,可以使用Lighthouse这个网站审计工具,它为你提供一份报告,说明正在做什么和需要改进什么。 阅读审核列表还可以让你了解浏览器关注的内容。了解如何衡量性能不同网站的性能调整可能会有所不同,因此,衡量网站的效果并确定最适合你网站的内容至关重要。 Chrome DevTools团队没多少关于如何衡量网站性能的教程。向你的站点添加功能策略功能策略是一个新的Web平台功能,可以在你构建项目时为你提供保护。 启用功能策略可确保应用的某些行为并防止你出错。 例如,如果要确保应用永远不会阻止解析,或者可以在同步脚本策略上运行应用。 启用 sync-script: ’none’ 时,将禁止解析器阻止 JavaScript 执行。 这可以防止你的代码阻止解析器,并且浏览器也不需要担心暂停解析器。总结当开始构建网站时,我几乎只关心如何编写代码以及怎样才能帮助我提高工作效率。 这些很重要,但我们也应该考虑浏览器如何获取我们编写的代码。 现代浏览器将继续致力于为用户提供更好的Web体验。 反过来通过使代码对浏览器友好,也可以改善你的用户体验。 希望我们一起努力追求更好的浏览器!本文首发微信公众号:jingchengyideng点击下面链接查看其它章节文章现代浏览器探秘(part1):架构现代浏览器探秘(part2):导航现代浏览器探秘(part3):渲染现代浏览器探秘(part4):事件处理 ...

January 18, 2019 · 1 min · jiezi

2018年AI和ML(NLP、计算机视觉、强化学习)技术总结和2019年趋势(下)

摘要: 回顾2018,展望2019,计算机科学技术继续前进!4、工具和库工具和库是数据科学家的基础。我参与了大量关于哪种工具最好的辩论,哪个框架会取代另一个,哪个库是经济计算的缩影等等。但有一点共识–我们需要掌握该领域的最新工具,否则就有被淘汰的风险。 Python取代其他所有事物并将自己打造成行业领导者的步伐就是这样的例子。 当然,其中很多都归结为主观选择,但如果你不考虑最先进的技术,我建议你现在开始,否则后果可能将不可预测。那么成为今年头条新闻的是什么?我们来看看吧!PyTorch 1.0什么是PyTorch?我已经多次在本文中提到它了,你可以在Faizan Shaikh的文章中熟悉这个框架。这是我最喜欢的关于深度学习文章之一!当时TensorFlow很缓慢,这为PyTorch打开了大门快速获得深度学习市场。我在GitHub上看到的大部分代码都是PyTorch实现的。这并非因为PyTorch非常灵活,而是最新版本(v1.0)已经大规模应用到许多Facebook产品和服务,包括每天执行60亿次文本翻译。PyTorch的使用率在2019年上升,所以现在是加入的好时机。AutoML—自动机器学习AutoML在过去几年中逐渐取得进展。RapidMiner、KNIME、DataRobot和H2O.ai等公司都发布了非常不错的产品,展示了这项服务的巨大潜力。你能想象在ML项目上工作,只需要使用拖放界面而无需编码吗?这种现象在未来并不太遥远。但除了这些公司之外,ML / DL领域还有一个重要的发布-Auto Keras!它是一个用于执行AutoML任务的开源库。其背后的目的是让没有ML背景的领域专家进行深度学习。请务必在此处查看,它准备在未来几年内大规模运行。TensorFlow.js-浏览器中的深度学习我们一直都喜欢在最喜欢的IDE和编辑器中构建和设计机器学习和深度学习模型。如何迈出一步,尝试不同的东西?我将要介绍如何在你的网络浏览器中进行深度学习!由于TensorFlow.js的发布,已成为现实。TensorFlow.js主要有三个优点/功能:1.使用JavaScript开发和创建机器学习模型;2.在浏览器中运行预先存在的TensorFlow模型;3.重新创建已有的模型;2019年的AutoML趋势我个人特别关注AutoML,为什么?因为我认为未来几年它将成为数据科学领域真正的游戏规则改变者。跟我有同样想法的人是H2O.ai的Marios Michailidis、Kaggle Grandmaster,他们都对AutoML有很高期望:机器学习继续成为未来最重要的趋势之一,鉴于其增长速度,自动化是最大化其价值的关键,是充分利用数据科学资源的关键。它可以应用到的领域是无限的:信用、保险、欺诈、计算机视觉、声学、传感器、推荐、预测、NLP等等,能够在这个领域工作是一种荣幸。AutoML趋势:提供智能可视化和解释,以帮助描述和理解数据;查找/构建/提取给定数据集的更好特征;快速建立更强大/更智能的预测模型;通过机器学习可解释性弥补这些模型的黑匣子建模和生产之间的差距;促进这些模型落地生产;5、强化学习如果我不得不选择一个我看到的渗透更多领域的技术,那就是强化学习。除了不定期看到的头条新闻之外,我还在社区中了解到,它太注重数学,并且没有真正的行业应用程序可供专一展示。虽然这在某种程度上是正确的,但我希望看到的是明年更多来自RL的实际用例。我在每月GitHub和Reddit排序系列中,我倾向于至少保留一个关于RL的存储库或讨论,至少围绕该主题的讨论。OpenAI已经发布了一个非常有用的工具包,可以让初学者从这个领域开始。OpenAI在深度强化学习中的应用如果RL的研究进展缓慢,那么围绕它的教育材料将会很少。但事实上,OpenAI已经开放了一些关于这个主题的精彩材料。他们称这个项目为“Spinning Up in Deep RL”,你可以在这里阅读所有相关内容。它实际上是非常全面RL的资源列表,这里有很多材料包括RL术语、如何成为RL研究者、重要论文列表、一个记录完备的代码存储库、甚至还有一些练习来帮助你入门。如果你打算开始使用RL,那么现在开始!Google Dopamine为了加速研究并让社区更多的参与强化学习,Google AI团队开源了Dopamine,这是一个TensorFlow框架,旨在通过它来使更灵活和可重复性来构建RL模型。你可以在此GitHub存储库中找到整个训练数据以及TensorFlow代码(仅15个Python notebooks!)。这是在受控且灵活的环境中进行简单实验的完美平台,听起来像数据科学家的梦想。2019年强化学习趋势Xander Steenbrugge是DataHack Summit的代表,也是ArxivInsights频道的创始人,他非常擅长强化学习。以下是他对RL当前状态的看法以及2019年的预期:我目前看到RL领域的三个主要问题:样本复杂性(代理需要查看/收集以获得的经验数量);泛化和转移学习(训练任务A,测试相关任务B);分层RL(自动子目标分解);我相信前两个问题可以通过与无监督表示学习相关的类似技术来解决。目前在RL中,我们正在使用稀疏奖励信号训练深度神经网络,从原始输入空间(例如像素)映射到端到端方式的动作(例如,使用反向传播)。我认为能够促进强化学习快速发展的道路是利用无监督的表示学习(自动编码器、VAE、GAN)将凌乱的高维输入空间(例如像素)转换为低维“概念”空间。人工智能:符合伦理才更重要想象一下由算法统治的世界,算法决定了人类采取的每一个行动。这不是一个美好的场景,对吗?AI中的伦理规范是Analytics Vidhya一直热衷于讨论的话题。今年有相当多的组织因为Facebook的剑桥分析公司丑闻和谷歌内部普遍关于设计武器新闻丑闻而遭受危机。没有一个开箱即用的解决方案或一个适合所有解决方案来处理AI的伦理方面。它需要一种细致入微的方法,并结合领导层提出的结构化路径。让我们看看今年出现的重大政策:GDPR。GDPR如何改变游戏规则GDPR或通用数据保护法规肯定会对用于构建AI应用程序的数据收集方式产生影响。GDPR的作用是以确保用户可以更好地控制他们的数据。那么这对AI有何影响?我们可以想象一下,如果数据科学家没有数据(或足够数据),那么构建任何模型都会还没开始就失败。2019年的AI伦理趋势预期这是一个灰色的领域。就像我提到的那样,没有一个解决方案可以解决这个问题。我们必须聚集在一起,将伦理问题整合到AI项目中。那么我们怎样才能实现这一目标呢?正如Analytics Vidhya的创始人兼首席执行官Kunal Jain在2018年DataHack峰会上的演讲中所强调的那样:我们需要确定一个其他人可以遵循的框架。结束语有影响力!这是2018年来描述AI最佳的词汇。今年我成为ULMFiT的狂热用户,我也很期待BERT。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 17, 2019 · 1 min · jiezi

现代浏览器探秘(part3):渲染

翻译:疯狂的技术宅原文:https://developers.google.com…渲染器进程的内部工作原理这是关于浏览器内部工作原理系列的第3部分。 之前,我们介绍了多进程架构和导航流程。 在这篇文章中,我们将看看渲染器进程内部发生了什么。渲染进程涉及Web性能的诸多方面。 由于渲染进程中发生了很多事情,因此本文不能一一赘述。 如果你想深入挖掘,可以在Web基础的性能部分找到更多内容。渲染器进程处理Web内容渲染器进程负责选项卡内发生的所有事情。 在渲染器进程中,主线程处理你为用户编写的大部分代码。 如果你使用了web worker 或 a service worker,有时JavaScript代码的一部分将由工作线程处理。 排版和栅格线程也在渲染器进程内运行,以便高效、流畅地呈现页面。渲染器进程的核心工作是将HTML、CSS和JavaScript转换为用户可以与之交互的网页。图1:渲染器进程内部有主线程、工作线程、排版线程和栅格线程解析构建DOM当渲染器进程收到导航的提交消息并开始接收HTML数据时,主线程开始解析文本字符串(HTML)并将其转换为文档对象模型(DOM—Document Object Model )。DOM是页面在浏览器中的内部表示,同时也是Web开发人员可以通过 JavaScript 与之交互的数据结构和API。HTML标准将HTML文档解析为DOM。 你可能已经注意到,将HTML提供给浏览器从不会引发错误。 例如,缺少结束</ p>标记是有效的HTML。 像 Hi! <b>I’m <i>Chrome</b>!</i> 这样的错误标记(b标签在i标签之前被关闭)被看作是 Hi! <b>I’m <i>Chrome</i></b><i>!</i>。 这是因为HTML规范旨在优雅地处理这些错误。 如果你对如何完成这些工作感到好奇,可以阅读HTML规范中的“解析器中的错误处理和奇怪情况介绍”部分。子资源加载网站通常使用图像、CSS和JavaScript等外部资源。 这些文件需要从网络或缓存中加载。 主线程可以在解析构建DOM时会逐个请求它们,但为了加快速度,“预加载扫描器”也会同时运行。 如果HTML文档中存在<img>或<link>之类的内容,则预加载扫描器会检查由HTML解析器生成的标记,并在浏览器进程中向网络线程发送请求。图2:主线程解析HTML并构建DOM树JavaScript可以阻止解析当HTML解析器找到<script>标记时,它会暂停解析HTML文档,并且必须加载、解析和执行JavaScript代码。 为什么要这样处理? 因为JavaScript可以使用像document.write() 那样改变整个DOM结构的东西来改变文档的形状(HTML规范中的解析模型概述有一个很好的示意图)。 这就是HTML解析器在重新解析HTML文档之前必须等待JavaScript运行的原因。 如果你对JavaScript执行中发生的事情感到好奇,V8团队的博客对此进行了讨论。提示浏览器如何加载资源Web开发人员可以通过多种方式向浏览器发送提示,以便很好地加载资源。 如果你的JavaScript不使用 document.write(),则可以向<script>标记添加async或defer属性。 然后,浏览器异步加载和运行JavaScript代码,不会阻止解析。 如果合适,你也可以使用JavaScript模块。 <link rel =“preload”>是一种通知浏览器当前导航肯定需要这个资源的方法,你希望尽快下载。 你可以在资源优先级找到更多信息。样式表计算拥有DOM不足以知道页面的外观,因为我们可以在CSS中设置页面元素的样式。 主线程解析CSS并确定每个DOM节点的计算样式。 这是有关基于CSS选择器将哪种样式应用于每个元素的信息。 你可以在浏览器中开发者工具中的computed部分中看到此信息。图3:主线程解析CSS以添加计算样式即使你不提供任何CSS,每个DOM节点都具有计算样式。比如 <h1>标签的显示要大于<h2>标签,同时为每个元素定义边距。 这是因为浏览器具有默认样式表。 如果你想知道Chrome的默认CSS是什么样的,你可以在此处查看源代码。布局现在,渲染器进程知道每个节点的文档和样式的结构,但这还不足以呈现页面。 想象一下,你正试图通过手机向朋友描述一幅画: “有一个大的红色圆圈和一个小的蓝色方块” 这并不能完全让你的朋友了解这幅画的外观。图4:一个人站在一幅画,通过电话线与另一个人联系布局是查找元素几何的过程。 主线程遍历DOM并计算样式和创建布局树,其中包含x y坐标和边界框大小等信息。 布局树可以是与DOM树类似的结构,但它仅包含与页面上可见内容相关的信息。 如果display:none,则该元素不是布局树的一部分(但是在布局树中包含visibility:hidden的元素)。 类似地,如果应用具有类似p::before {content:“Hi!}之类的内容的伪类,则它将包含在布局树中,即使它不在DOM中。图5:主线程通过DOM树生成计算样式和布局树确定页面布局是一项具有挑战性的任务。 即使是最简单的页面布局,如从上到下的块流,也必须考虑字体的大小以及在哪里划分它们,因为它们会影响段落的大小和形状; 然后影响下一段所需的位置。图6:由于换行符而移动的段落的框布局CSS可以使元素浮动到一侧,掩盖溢出项,并更改写入方向。 你可以想象,这个布局阶段是一项艰巨的任务。 在Chrome项目中,有一个完整的工程师团队负责布局。 如果你想看到他们工作的细节,看看这些会议记录非常有意思。绘制拥有了DOM、样式和布局仍然不足以呈现页面。 假设你正在尝试重现一幅画。 你不仅需知道元素的大小,形状和位置,还需要判断绘制它们的顺序。图7:一个在画布前拿着画笔的人,正在思考是应该先画圆圈还是矩形例如:可以为某些元素设置z-index,在这种情况下,按HTML中编写的元素顺序绘制将导致不正确的呈现。图8:页面元素按HTML标记的顺序出现,会导致错误的渲染图像,因为没有考虑z-index在此绘制步骤中,主线程遍历布局树以创建绘制记录。 绘制记录是绘制过程的一个注释,如“背景优先,然后是文本,最后是矩形”。 如果你使用JavaScript绘制了<canvas>元素,那么可能对此过程很熟悉。图9:主线程遍历布局树并生成绘制记录更新渲染通道的成本很高在渲染通道中最重要的一件事就是在每个步骤中,前一个操作的结果被用于创建新数据。 例如:如果布局树中的某些内容发生更改,则需要为文档的受影响部分重新生成绘制顺序。图10:DOM + Style,布局和绘制树的生成顺序如果要为元素设置动画,则浏览器必须在每个帧之间运行这些操作。 我们的大多数显示器每秒刷新屏幕60次(60 fps); 当你在每一帧移动屏幕时,动画对人眼来说会很平滑。 但是如果动画错过了其中的帧,则页面将发生闪烁。图11:时间轴上的动画帧即使你的渲染操作能够跟上屏幕刷新,这些计算也是在主线程上运行的,这意味着当你的应用运行 JavaScript 时它可能会被阻止。图12:时间轴上的动画帧,但JavaScript阻止了一帧你可以将JavaScript操作划分为小块,并使用 requestAnimationFrame() 安排在每个帧上运行。 有关此主题的更多信息,请参阅优化JavaScript执行。 你也可以在 Web Workers 中运行 JavaScript 来避免阻塞主线程。图13:在动画帧的时间轴上运行的较小的JavaScript块合成你会如何绘制一个页面?现在浏览器知道文档的结构,每个元素的样式,页面的几何形状和绘制顺序,它是如何绘制页面的? 将此信息转换为屏幕上的像素称为光栅化。图14:简单光栅化过程也许处理这种情况的一种简单的方法是在视口(viewport)内部使用栅格部件。 如果用户滚动页面,则移动光栅帧,并通过更多光栅填充缺少的部分。 这就是Chrome首次发布时处理栅格化的方式。 但是,现代浏览器运行一个称为合成的更复杂的过程。什么是合成合成是一种将页面的各个部分分层,分别栅格化,并在一个被称为合成器线程的独立线程中合成为页面的技术。 如果发生滚动,由于图层已经被栅格化,所以它所要做的就是合成一个新帧。 通过移动图层和合成新帧,可以用相同的方式实现动画。图15:合成过程的示意动画你可以使用浏览器开发者工具的“layout”面板中查看你的网站如何划分为多个图层。分为几层为了找出哪些元素需要放在哪些层中,主线程通过遍历布局树以创建层树(此部分在DevTools性能面板中称为“Update Layer Tree”)。 如果页面某些应该是单独图层(如滑入式侧面菜单)的部分但是没有分配到图层,那么你可以使用CSS中的will-change属性提示浏览器。图16:主线程生通过遍历布局树来成层树也许你想要为每个元素提供图层,但是过多的图层进行合成可能会导致比每帧光栅化页面的小部分更慢的操作,因此测量应用程序的渲染性能至关重要。 有关主题的更多信息,请参阅Stick to Compositor-Only Properties and Manage Layer Count光栅和复合关闭主线程一旦创建了层树并确定了绘制顺序,主线程就会将该信息提交给合成器线程。 合成器线程然后栅格化每个图层。 一个图层可能像页面的整个长度一样大,因此合成器线程会将它们分成图块,并将每个图块发送到光栅线程。 栅格线程栅格化每一个tile并将它们存储在GPU内存中。图17:栅格线程创建tile位图并发送到GPU合成器线程可以优先考虑不同的aster线程,以便视口(或附近)内的事物可以先被光栅化。 图层还具有多个不同分辨率的倾斜度,可以处理放大操作等内容。一旦tile被光栅化,合成器线程会收集称为绘制四边形(draw quads )的tile信息来创建合成器帧(compositor frame)。绘制四边形包含信息,例如图块在内存中的位置以及在考虑页面合成的情况下绘制图块的页面中的位置。合成器帧表示页面帧的绘制四边形的集合。然后通过IPC将合成器帧提交给浏览器进程。这时可以从UI线程添加另一个合成器帧以用于浏览器UI更改,或者从其他渲染器进程添加扩充数据。 这些合成器帧被发送到GPU用来在屏幕上显示。 如果发生滚动事件,合成器线程会创建另一个合成器帧并发送到GPU。图18:合成器线程创建合成帧。 帧先被发送到浏览器进程,然后再发送到GPU合成的好处是它可以在不涉及主线程的情况下完成。 合成线程不需要等待样式计算或 JavaScript 执行。 这就是合成动画是平滑性能的最佳选择的原因。 如果需要再次计算布局或绘图,则必须涉及主线程。总结在本文中,我们研究了从解析到合成的渲染通道。 在本系列的下一篇文章中,我们将更详细地介绍合成器线程,并了解当用户进行鼠标移动和单击等操作时会发生什么。本文首发微信公众号:jingchengyideng点击下面链接查看其它章节文章现代浏览器探秘(part1):架构现代浏览器探秘(part2):导航现代浏览器探秘(part3):渲染现代浏览器探秘(part4):事件处理 ...

January 17, 2019 · 1 min · jiezi

现代浏览器探秘(part2):导航

翻译:疯狂的技术宅原文:https://developers.google.com…导航时都发生了什么这是关于Chrome内部工作原理系列的第2部分。 在上一篇文章中,我们研究了不同的进程与线程是怎样如何处理浏览器不同部分的。 在这一篇中,我们将会深入研究每个进程和线程是如何进行通信以显示网站内容的。让我们看一下Web浏览的简单用例:你在浏览器中键入URL,然后浏览器从Internet获取数据并显示页面。 在这篇文章中,我们将重点关注用户请求网站的部分以及浏览器准备呈现页面的部分 - 也称为导航。从浏览器进程开始正如我们在第1部分(CPU,GPU,内存和多进程架构 )中所描述的,选项卡外部的所有内容都由浏览器进程处理。 浏览器进程具有很多线程,比如于UI线程用于绘制浏览器的按钮和输入框,网络线程负责处理网络堆栈以从互联网接收数据,存储线程控制对文件的访问等。 当在地址栏中键入URL时,你的输入将由浏览器进程的UI线程处理。图1:顶部的浏览器UI,底部有UI,网络和存储线程的浏览器进程图一个简单的导航过程第1步:处理输入当用户开始输入地址栏时,UI线程首先要判断的是“这是搜索查询还是URL?”。 因为在Chrome中,地址栏也是搜索输入框,因此UI线程需要解析并判断是将你的输入发送到搜索引擎还是去请求对应的网站。图1:UI线程询问输入是搜索查询还是URL第2步:开始导航当用户敲回车时,UI线程启动网络调用以获取站点内容。 加载指示图标显示在选项卡的一角,网络线程使用适当的协议,如DNS解析和为请求建立TLS连接。图2:UI线程与网络线程进行通信以导航到mysite.com此时,网络线程可以接收像HTTP 301那样的服务器重定向头。在这种情况下,网络线程会通知UI线程服务器正在请求重定向。之后会启动另一个URL请求。第3步:读取响应一旦响应主体(有效负载)开始进入,网络线程会在必要时查看流的前几个字节。 响应中的Content-Type头应该说明它是什么类型的数据,但由于它可能丢失或发生错误,所以在这里完成MIME类型嗅探。 这在源代码的注释中被称为“棘手的事情”。 你可以阅读这些注释,来了解不同的浏览器是如何处理内容类型与有效载荷的。图3:包含Content-Type和有效负载的响应头,它是实际数据如果响应是HTML文件,那么下一步就是将数据传递给渲染器进程,但如果它是zip文件或其他文件,则表示它是一个下载请求,因此需要将数据传递给 下载管理器。图4:网络线程询问响应数据是否来自安全站点的HTML这也是进行 SafeBrowsing检查的地方。 如果域和响应数据似乎与已知的恶意站点匹配,则网络线程会发出警告以显示警告页面。 此外,发生跨源读取阻止(CORB)检查是为了确保敏感的跨站数据不会进入渲染器进程。第3步:查找渲染器进程完成所有检查并且网络线程确信浏览器应该导航到所请求的站点后,网络线程会告知UI线程数据已准备就绪。 然后UI线程找到渲染器进程以进行网页的渲染。图5:网络线程告诉UI线程找到渲染进程由于网络请求可能需要几百毫秒才能得到响应,所以在这里进行了加速此过程的优化。 当UI线程在第2步向网络线程发送URL请求时,它已经知道他们正在导航到哪个站点。 UI线程尝试与网络请求并行地主动查找或启动渲染器进程。 如果一切按预期进行,当网络线程接收数据时,渲染器进程已处于备用状态。 如果导航重定向跨站点,则可能不会使用此备用进程,在这种情况下可能需要不同的进程。第4步:提交导航现在数据和渲染器进程已准备就绪,IPC将把导航从浏览器进程发送到渲染器进程以进行提交。它同时还传递数据流,因此渲染器进程可以继续接收HTML数据。 一旦浏览器进确认已经提交到了渲染器进程中,导航就完成了,文档加载阶段就开始了。此时,地址栏会更新,安全指示器和站点设置UI会反映新页面的站点信息。 选项卡的会话历史记录将更新,因此后退/前进按钮将可以逐步浏览刚导航到的站点。为了便于在关闭选项卡或窗口时能够对选项卡/会话进行还原,会话的历史记录将被存储在磁盘上。图6:浏览器和渲染器进程之间的IPC,请求呈现页面额外步骤:初始加载完成提交导航后,渲染器进程继续加载资源并呈现页面。 我们将会在下一篇文章中详细介绍这一阶段的详情。 一旦渲染器进程“完成”渲染,它就会将一个IPC发送回浏览器进程(这发生在所有onload事件触发了页面中的所有帧并完成执行之后)。 此时,UI线程会停止选项卡上的加载指示器。尽管已经“完成”,不过客户端 JavaScript 仍然可以加载额外的资源并在此之后呈现新的视图。图7:渲染器进程通过IPC通知浏览器进程页面已“加载完成”导航到其他站点简单的导航完成了! 但是如果用户再次将不同的URL放到地址栏会发生什么? 好吧,浏览器进程会通过相同的步骤导航到不同的站点。 但在它在做到这一点之前,还需要检查当前正在渲染的站点,如果他们关心beforeunload事件的话。当你尝试重新导航或关闭选项卡时,beforeunload可以创建“要离开这个网站吗?” 警告。 由于选项卡内包含JavaScript代码的所有内容都由渲染器进程处理,因此浏览器进程必须在进行新导航请求时检查当前渲染器进程。警告:不要添加无条件的beforeunload处理程序。 因为它会产生更多延迟,甚至在启动导航之前需要执行一些处理。 应该仅在需要时添加此事件处理,例如,如果需要警告用户他们可能会丢失在页面上输入的数据时。图8:浏览器进程通过IPC通知渲染器进程它将要导航到另一个站点如果导航是从渲染器进程启动的(例如用户单击链接或客户端JavaScript执行window.location =“https://newsite.com”),那么渲染器进程会首先检查beforeunload处理。 然后,它经历与浏览器进程启动导航相同的过程。 唯一的区别是导航请求从渲染器进程发送到浏览器进程。当新导航进入的站点与当前渲染的站点不同时,将会调用另一个单独的渲染进程来处理新导航,同时保持当前渲染进程以处理unload等事件。 有关更多信息,请参阅页面生命周期状态概述以及如何使用 页面生命周期 API 挂钩事件。图9:从浏览器进程到新渲染器进程的2个IPC,通知新渲染器渲染页面并通知旧渲染器进程卸载如果是Service Worker最近对该导航过程的一个改变是引入了service worker。 service worker是一种在应用代码中编写网络代理的方法;它允许Web开发人员更好地控制本地缓存内容以及何时从网络获取新数据。 如果将service worker设置为从缓存加载页面,则无需从网络请求数据。要记住的重要一点是Service Worker是在渲染器进程中运行的JavaScript代码。 但是当导航请求到来时,浏览器进程怎么才能知道该站点有Service Worker?图10:浏览器进程中的网络线程查找Service Worker范围注册Service Worker时,将保留Service Worker的范围作为参考(你可以在“Service Worker生命周期”一文中阅读有关范围的更多信息)。 当导航发生时,网络线程根据注册的Service Worker范围检查域,如果为该URL注册了Service Worker,则UI线程找到渲染器进程来执行Service Worker代码。 Service Worker可以从缓存加载数据,无需从网络请求数据,也可以从网络请求新资源。图11:浏览器进程中的UI线程启动渲染器进程以处理Service Worker; 然后,渲染器进程中的工作线程从网络请求数据导航预加载可以看到,如果Service Worker最终决定从网络请求数据,则浏览器进程和渲染器进程之间的往返通信可能会导致延迟。 导航预加载是一种通过与Service Worker并行加载资源来加速此过程的机制。 它用header标记这些请求,允许服务器为这些请求发送不同的内容,例如:只更新部分数据而不是整个文档。图12:浏览器进程中的UI线程启动渲染器进程,在并行启动网络请求的同时处理Service Worker总结在本文中,我们研究了导航过程中发生的事情,以及响应头和客户端JavaScript等Web应用代码是如何与浏览器交互的。 了解浏览器通过网络获取数据的步骤,可以更容易地理解为什么开发导航预加载等API。 在下一篇文章中,我们将深入探讨浏览器如何处理HTML/ CSS/JavaScript来呈现页面。本文首发微信公众号:jingchengyideng点击下面链接查看其它章节文章现代浏览器探秘(part1):架构现代浏览器探秘(part2):导航现代浏览器探秘(part3):渲染 ...

January 16, 2019 · 1 min · jiezi

【译】现代浏览器探秘(part 1):架构

翻译:疯狂的技术宅 原文链接:https://developers.google.com…CPU,GPU,内存和多进程架构在这个由4部分组成的系列文章中,我们将介绍Chrome浏览器从高级架构到渲染管道的具体细节。 如果你想知道浏览器是如何将你的代码转换为功能性网站的,或者你想知道为什么需要使用某些特定技术来提高性能,那么本系列非常适合你。作为本系列的第1部分,我们将介绍核心计算术语和Chrome的多进程架构。注意:如果你熟悉CPU / GPU和进程/线程的概念,则可以跳到本文的浏览器体系结构部分。计算机的核心是CPU和GPU为了理解浏览器运行的环境,我们需要了解一些计算机部件及其功能。CPU首先是中央处理单元(Central Processing Unit)—— CPU。 CPU可以被认为是你计算机的大脑。 CPU核心,在这里作为办公室工作人员,可以在他们进来时逐个处理许多不同的任务。它可以处理从数学到艺术的所有事情,同时知道如何回复客户呼叫。 在过去,大多数CPU都是单芯片。 核心就像生活在同一芯片中的另一个CPU。 在现代硬件中,你通常会获得多个核心,从而为你的手机和笔记本电脑提供更强的计算能力。图1:4个CPU核心作为办公室工作人员坐在每个办公桌处理任务GPU图形处理单元(Graphics Processing Unit )—— GPU是计算机的另一部分。 与CPU不同,GPU擅长处理简单任务,但同时跨多个核心。 顾名思义,它最初是为处理图形而开发的。 这就是为什么在图形环境中“使用GPU”或“GPU支持”与快速渲染和平滑交互相关联。 近年来,随着GPU加速计算,仅在GPU上就可以实现越来越多的计算。图2:许多带有扳手的GPU核心表明它们可以处理有限的任务当你在计算机或手机上启动程序时,CPU和GPU用来支持程序的运转。 通常,程序使用操作系统提供的相关机制在CPU和GPU上运行。图3:三层计算机体系结构。 机器硬件位于底部,操作系统位于中间,应用程序位于顶部。在进程和线程上执行程序在深入浏览器架构之前要掌握的另一个概念是Process和Thread。 进程可以描述为运行状态中的程序。 线程是存在于进程内部并用来执行其程序任务的某一部分。图4:过程划定了边界,线程作为在进程内游动的“抽象鱼”启动程序时,将会创建一个进程。 该程序可能会创建线程来帮助它工作,但这是可选的。 操作系统为进程提供了一“块”内存,并且所有程序状态都保存在该专用内存空间中。 当你关闭程序时,该进程也会消失,操作系统会释放内存。图5:进程使用内存空间和存储数据的示意图(sf不支持svg动图上传,看不到请使用技术手段查看原图)进程可以要求操作系统启动另一个进程来执行不同的任务。 当这种情况发生时,将为新进程分配不同的内存。 如果两个进程需要通信,他们可以通过使用进程间通信(IPC)来实现。 许多程序都是以这种方式工作的,因此如果一个工作进程失去响应,则可以重新启动它,而不会停止运行程序的其他进程。图6:通过IPC进行通信的独立进程示意图(sf不支持svg动图上传,看不到请使用技术手段查看原图)浏览器架构那么如何使用进程和线程构建Web浏览器? 好吧,它可能是一个具有许多不同线程的进程,或是许多具有少量线程的通过IPC进行通信的不同进程。图7:不同浏览器体系结构中的进程/线程示意图在这里有非常重要的一点需要注意,这些不同的架构是实现细节。关于如何构建Web浏览器并没有标准规范。 一种浏览器可能与另一种浏览器的结构完全不同。在本系列文章中,我们将使用下图中描述的Chrome最新架构。最重要的部分是浏览器进程怎样与程序的其他工作进程进行协调。 对于渲染器进程,将创建多个进程并将其分配给每个选项卡。 直到不久前,Chrome才为每个标签提供了一个进程;现在它尝试为每个站点提供自己的进程,其中包括iframe(请参阅:站点隔离部分)。图8:Chrome的多进程架构图。 渲染进程下显示多个图层,表示Chrome为每个选项卡运行多个渲染器进程。每个进程都做些什么?下表介绍了每个Chrome进程及其控制的内容:进程做些什么Browser控制程序的“chrome”部分,包括地址栏,书签,后退和前进按钮。<br/>还处理Web浏览器的不可见的,和特权部分,例如网络请求和文件访问。Renderer负责显示网站的选项卡内的所有内容。Plugin控制网站使用的所有插件,例如flash。GPU独立于其他进程的GPU处理任务。 它被分成多个不同的进程,因为GPU处理来自多个程序的请求并将它们绘制在同一个面中。图9:指向浏览器UI不同部分的不同进程还有更多的进程,如扩展进程和功能进程。 如果你想查看Chrome中正在运行的进程数,请点击右上角的选项菜单图标“more_vert”,选择“更多工具”,然后选择“任务管理器”。 这将打开一个窗口,其中包含当前正在运行的进程列表以及它们使用的CPU/内存量。Chrome中多进程架构的好处前面我曾提到Chrome使用多个渲染器进程。 在最简单的情况下,你可以想象每个选项卡都有自己的渲染器进程。 假设你打开了3个选项卡,每个选项卡都由独立的渲染器进程运行。 如果一个选项卡没有响应,就可以关闭无响应的选项卡并继续运行,同时保持其他选项卡处于活动状态。 如果所有选项卡都在一个进程上运行,那么当一个选项卡无响应时,所有选项卡都不会响应。 那将会很难受。图10:显示多进程运行每个选项卡的示意图(sf不支持svg动图上传,看不到请使用技术手段查看原图)将浏览器的工作分成多个进程的另一个好处是安全性和沙盒。由于操作系统提供了限制进程权限的方法,因此浏览器可以从某些功能中对某些进程进行沙箱处理。 例如,Chrome浏览器限制任意用户输入进程的(如渲染器进程)的任意文件访问。由于进程有自己的私有内存空间,因此它们通常包含公共基础结构的副本(例如V8是Chrome的JavaScript引擎)。 这意味着会消耗更多的内存空间,因为如果它们运行在同一进程内的不同线程上,则无法遵循自己的机制进行共享。 为了节省内存,Chrome限制了它可以启动的进程数量,这种限制因设备的内存和CPU功率而异,但当Chrome达到限制时,它会在一个进程中运行从同个一站点打开的多个选项卡。节省更多内存:Chrome中的服务化同样的方法适用于浏览器进程。 Chrome正在进行体系结构的变更,以便将浏览器程序的每个部分作为一项服务运行,从而可以轻松拆分为不同的流程或汇总为一个流程。一般的想法是,当Chrome在强大的硬件上运行时,它可能会将每个服务拆分为不同的进程,从而提供更高的稳定性,但如果它位于资源有限的设备上,则Chrome会将服务整合到一个进程中,从而节省内存占用。 在进行这种更改之前,在Android平台上已经使用了类似的方法来整合进程以减少内存使用。图11:Chrome的服务化示意图,将不同的服务转移到多个进程或一个浏览器进程中(sf不支持svg动图上传,看不到请使用技术手段查看原图)帧渲染器进程:站点隔离网站隔离是Chrome中最近推出的一项功能,可为每个跨网站的iframe运行单独的渲染进程。 我们一直在讨论每个选项卡一个渲染进程的模型,它允许跨站iframe在单个渲染器进程中运行,并在不同站点之间共享内存空间。 在同一个渲染进程中运行a.com和b.com似乎没问题。 同源策略是Web的核心安全模型,它确保一个站点在未经同意的情况下无法访问其他站点的数据。 绕过此策略是安全攻击的主要目标。进程隔离是分离站点的最有效方法。 由于Meltdown和Spectre漏洞,我们更加需要使用进程来隔离站点。 默认情况下,自从Chrome 67启用桌面隔离功能后,选项卡中的每个跨站点iframe都会得到单独的渲染进程。图12:站点隔离示意,指向站点内iframe的多个渲染器进程启用站点隔离是一项需要很多年的工作。 站点隔离并不像分配不同的渲染进程那么简单;它从根本上改变了iframe彼此的交流方式。 在运行着不同iframe进程的的页面上打开devtools,意味着devtools必须在背后做大量的工作才能使其看起来无缝。即使通过简单的 Ctrl + F 来查找页面中的单词也意味着需要跨越不同的渲染进程进行搜索。 这就是浏览器工程师将站点隔离的发布作为一个重要里程碑的原因!总结在这篇文章中,我们介绍了浏览器体系结构的高级视图,并介绍了多进程体系结构的优点。 我们还介绍了Chrome中与多进程架构密切相关的服务化和站点隔离。 在下一篇文章中,我们将开始深入研究在显示一个网站时,这些进程和线程之间究竟发生了什么事情。本文首发微信公众号:jingchengyideng点击下面链接查看其它章节文章现代浏览器探秘(part1):架构现代浏览器探秘(part2):导航现代浏览器探秘(part3):渲染

January 15, 2019 · 1 min · jiezi

浏览器与Node的事件循环(Event Loop)有何区别?

前言本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的。一、线程与进程1. 概念我们经常说 JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程?官方的说法是:进程是 CPU 资源分配的最小单位;线程是 CPU 调度的最小单位。这两句话并不好理解,我们先来看张图:进程好比图中的工厂,有单独的专属自己的工厂资源。线程好比图中的工人,多个工人在一个工厂中协作工作,工厂与工人是 1:n 的关系。也就是说一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;工厂的空间是工人们共享的,这象征一个进程的内存空间是共享的,每个线程都可用这些共享内存。多个工厂之间独立存在。2. 多进程与多线程多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。以 Chrome 浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程(下文会详细介绍),比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。二、浏览器内核简单来说浏览器内核是通过取得页面内容、整理信息(应用 CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:GUI 渲染线程JavaScript 引擎线程定时触发器线程事件触发线程异步 http 请求线程1. GUI 渲染线程主要负责页面的渲染,解析 HTML、CSS,构建 DOM 树,布局和绘制等。当界面需要重绘或者由于某种操作引发回流时,将执行该线程。该线程与 JS 引擎线程互斥,当执行 JS 引擎线程时,GUI 渲染会被挂起,当任务队列空闲时,JS 引擎才会去执行 GUI 渲染。2. JS 引擎线程该线程当然是主要负责处理 JavaScript 脚本,执行代码。也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS 引擎线程的执行。当然,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执行 JavaScript 脚本时间过长,将导致页面渲染的阻塞。3. 定时器触发线程负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待 JS 引擎线程执行。4. 事件触发线程主要负责将准备好的事件交给 JS 引擎线程执行。比如 setTimeout 定时器计数结束, ajax 等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS 引擎线程的执行。5. 异步 http 请求线程负责执行异步请求一类的函数的线程,如: Promise,axios,ajax 等。主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程执行。三、浏览器中的 Event Loop1. Micro-Task 与 Macro-Task事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。宏任务队列可以有多个,微任务队列只有一个。常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染等。常见的 micro-task 比如: process.nextTick、new Promise().then(回调)、MutationObserver(html5 新特性) 等。2. Event Loop 过程解析一个完整的 Event Loop 过程,可以概括为以下阶段:一开始执行栈空,我们可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)。全局上下文(script 标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。同步代码执行完了,script 脚本会被移出 macro 队列,这个过程本质上是队列的 macro-task 的执行和出队的过程。上一步我们出队的是一个 macro-task,这一步我们处理的是 micro-task。但需要注意的是:当 macro-task 出队时,任务是一个一个执行的;而 micro-task 出队时,任务是一队一队执行的。因此,我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。执行渲染操作,更新界面检查是否存在 Web worker 任务,如果有,则对其进行处理上述过程循环往复,直到两个队列都清空我们总结一下,每一次循环都是一个这样的过程:当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。接下来我们看道例子来介绍上面流程:Promise.resolve().then(()=>{ console.log(‘Promise1’) setTimeout(()=>{ console.log(‘setTimeout2’) },0)})setTimeout(()=>{ console.log(‘setTimeout1’) Promise.resolve().then(()=>{ console.log(‘Promise2’) })},0)最后输出结果是 Promise1,setTimeout1,Promise2,setTimeout2一开始执行栈的同步任务(这属于宏任务)执行完毕,会去查看是否有微任务队列,上题中存在(有且只有一个),然后执行微任务队列中的所有任务输出 Promise1,同时会生成一个宏任务 setTimeout2然后去查看宏任务队列,宏任务 setTimeout1 在 setTimeout2 之前,先执行宏任务 setTimeout1,输出 setTimeout1在执行宏任务 setTimeout1 时会生成微任务 Promise2 ,放入微任务队列中,接着先去清空微任务队列中的所有任务,输出 Promise2清空完微任务队列中的所有任务后,就又会去宏任务队列取一个,这回执行的是 setTimeout2四、Node 中的 Event Loop1. Node 简介Node 中的 Event Loop 和浏览器中的是完全不相同的东西。Node.js 采用 V8 作为 js 的解析引擎,而 I/O 处理方面使用了自己设计的 libuv,libuv 是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的 API,事件循环机制也是它里面的实现(下文会详细介绍)。Node.js 的运行机制如下:V8 引擎解析 JavaScript 脚本。解析后的代码,调用 Node API。libuv 库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个 Event Loop(事件循环),以异步的方式将任务的执行结果返回给 V8 引擎。V8 引擎再将结果返回给用户。2. 六个阶段其中 libuv 引擎中的事件循环分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。从上图中,大致看出 node 中的事件循环的顺序:外部输入数据–>轮询阶段(poll)–>检查阶段(check)–>关闭事件回调阶段(close callback)–>定时器检测阶段(timer)–>I/O 事件回调阶段(I/O callbacks)–>闲置阶段(idle, prepare)–>轮询阶段(按照该顺序反复运行)…timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调idle, prepare 阶段:仅 node 内部使用poll 阶段:获取新的 I/O 事件, 适当的条件下 node 将阻塞在这里check 阶段:执行 setImmediate() 的回调close callbacks 阶段:执行 socket 的 close 事件回调注意:上面六个阶段都不包括 process.nextTick()(下文会介绍)接下去我们详细介绍timers、poll、check这 3 个阶段,因为日常开发中的绝大部分异步任务都是在这 3 个阶段处理的。(1) timertimers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行。(2) pollpoll 是一个至关重要的阶段,这一阶段中,系统会做两件事情回到 timer 阶段执行回调执行 I/O 回调并且在进入该阶段时如果没有设定了 timer 的话,会发生以下两件事情如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制如果 poll 队列为空时,会有两件事发生如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调。(3) check 阶段setImmediate()的回调会被加入 check 队列中,从 event loop 的阶段图可以知道,check 阶段的执行顺序在 poll 阶段之后。我们先来看个例子:console.log(‘start’)setTimeout(() => { console.log(’timer1’) Promise.resolve().then(function() { console.log(‘promise1’) })}, 0)setTimeout(() => { console.log(’timer2’) Promise.resolve().then(function() { console.log(‘promise2’) })}, 0)Promise.resolve().then(function() { console.log(‘promise3’)})console.log(’end’)//start=>end=>promise3=>timer1=>timer2=>promise1=>promise2一开始执行栈的同步任务(这属于宏任务)执行完毕后(依次打印出 start end,并将 2 个 timer 依次放入 timer 队列),会先去执行微任务(这点跟浏览器端的一样),所以打印出 promise3然后进入 timers 阶段,执行 timer1 的回调函数,打印 timer1,并将 promise.then 回调放入 microtask 队列,同样的步骤执行 timer2,打印 timer2;这点跟浏览器端相差比较大,timers 阶段有几个 setTimeout/setInterval 都会依次执行,并不像浏览器端,每执行一个宏任务后就去执行一个微任务(关于 Node 与浏览器的 Event Loop 差异,下文还会详细介绍)。3. 注意点(1) setTimeout 和 setImmediate二者非常相似,区别主要在于调用时机不同。setImmediate 设计在 poll 阶段完成时执行,即 check 阶段;setTimeout 设计在 poll 阶段为空闲时,且设定时间到达后执行,但它在 timer 阶段执行setTimeout(function timeout () { console.log(’timeout’);},0);setImmediate(function immediate () { console.log(‘immediate’);});对于以上代码来说,setTimeout 可能执行在前,也可能执行在后。首先 setTimeout(fn, 0) === setTimeout(fn, 1),这是由源码决定的进入事件循环也是需要成本的,如果在准备时候花费了大于 1ms 的时间,那么在 timer 阶段就会直接执行 setTimeout 回调如果准备时间花费小于 1ms,那么就是 setImmediate 回调先执行了但当二者在异步 i/o callback 内部调用时,总是先执行 setImmediate,再执行 setTimeoutconst fs = require(‘fs’)fs.readFile(__filename, () => { setTimeout(() => { console.log(’timeout’); }, 0) setImmediate(() => { console.log(‘immediate’) })})// immediate// timeout在上述代码中,setImmediate 永远先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行完毕后队列为空,发现存在 setImmediate 回调,所以就直接跳转到 check 阶段去执行回调了。(2) 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=>nextTick=>nextTick=>nextTick=>timer1=>promise1五、Node 与浏览器的 Event Loop 差异浏览器环境下,microtask 的任务队列是每个 macrotask 执行完之后执行。而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。接下我们通过一个例子来说明两者区别:setTimeout(()=>{ console.log(’timer1’) Promise.resolve().then(function() { console.log(‘promise1’) })}, 0)setTimeout(()=>{ console.log(’timer2’) Promise.resolve().then(function() { console.log(‘promise2’) })}, 0)浏览器端运行结果:timer1=>promise1=>timer2=>promise2浏览器端的处理过程如下:Node 端运行结果:timer1=>timer2=>promise1=>promise2全局脚本(main())执行,将 2 个 timer 依次放入 timer 队列,main()执行完毕,调用栈空闲,任务队列开始执行;首先进入 timers 阶段,执行 timer1 的回调函数,打印 timer1,并将 promise1.then 回调放入 microtask 队列,同样的步骤执行 timer2,打印 timer2;至此,timer 阶段执行结束,event loop 进入下一个阶段之前,执行 microtask 队列的所有任务,依次打印 promise1、promise2Node 端的处理过程如下:六、总结浏览器和 Node 环境下,microtask 任务队列的执行时机不同Node 端,microtask 在事件循环的各个阶段之间执行浏览器端,microtask 在事件循环的 macrotask 执行完之后执行参考文章浏览器进程?线程?傻傻分不清楚!事件循环机制的那些事前端性能优化原理与实践前端面试之道深入理解 js 事件循环机制(Node.js 篇)详解 JavaScript 中的 Event Loop(事件循环)机制event-loop-timers-and-nexttick关于FundebugFundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了9亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎大家免费试用!版权声明转载时请注明作者Fundebug以及本文地址:https://blog.fundebug.com/2019/01/15/diffrences-of-browser-and-node-in-event-loop/ ...

January 15, 2019 · 3 min · jiezi

油猴脚本第一家,网页网盘链接实时判断+资源搜索网站导航,资源重度患者的福利

现在网络上找资源,资源都是存在百度网盘的,大家都知道,百度网盘链接失效的非常之多。遇到网盘链接我们都要一个一个点进去查看链接是否失效,这样操作费时又累人。这时这个油猴脚本就可以帮忙了。实时判断网页中百度网盘链接状态。同时,这个油猴脚本还会再网页的适当位置推荐资源网站MAP,找资源什么的方便多了~亲测插件非常给力,安装非常方便,只需点一下即可,插件安装地址:https://greasyfork.org/zh-CN/…插件介绍:功能介绍:1、网盘链接状态判断:实时判断网页中百度网盘链接状态,节约时间,方便又快捷;2、资源搜索网站导航:脚本会在百度、文库、360、搜狗、豆瓣等网站的合适位置推荐各类资源搜索网站,方便对资源的检索。如:豆瓣电影,就会实时的推荐电影相关资源网站,推荐网址长期维护更新。“资源搜索网站导航”做您资源查找的好帮手! 运行截图:脚本运行截图:

January 8, 2019 · 1 min · jiezi

推荐系统之信息茧房问题

什么是信息茧房信息茧房其实是现在社会一个很可怕的现象,从字面意思来看的话其实比喻的是信息被虫茧一般封锁住。这个问题反映了现在随着个性化推荐的普及衍射的一个社会问题。平时在浏览新闻或者淘宝的时候,平台会自动根据用户的浏览记录获取用户的偏好,然后推送感兴趣的文章。久而久之,比如用户A是个体育迷,那么A获取的信息大多是跟体育相关的,很难获取音乐或者军事等其它相关的资讯,因为平台追求点击率,会一直推送A感兴趣的内容以获取高广告浏览量。时间长了,因为信息茧房的作用,因为信息获取单一,A的社交圈可能也会变的狭小。如果整个社会陷入了个性化推荐系统的信息茧房效应,将是病态的。所以,真正的个性化推荐绝对不等于只推荐历史感兴趣的内容,这样其实不是一个长期可持续的推荐系统,如果陷入了信息茧房,一定会有用户觉得审美疲劳。那么如何破解信息茧房,因为从推荐模型角度分析,一旦获取了用户的画像,就很难跳出用户习惯的逻辑,比如昨天买个手机,第二天还推荐手机,这个时候可能比较好的一种方法是跨域推荐(cross-domain recommendation)。跨域推荐的概念跨域推荐做的事情就是跳出推荐的信息茧房。不是一个新概念了,我上研究生的时候学校就有实验室做相关的研究,今天主要讲下思路。具体大家想了解的话可以看下这个Paper: 《Cross-Domain Recommendation: An Embedding and Mapping Approach》有几个关键词我觉得可以充分体现跨域推荐的精髓:“diversity” - “novelty” - “serendipity”如果我们做一个推荐系统,说是“individuality”,其实我会觉得很normal,不够高级,现在几乎所有推荐系统都有个性化推荐,但是如果一个推荐系统标榜自己是“novelty”,那我就觉得很有意思了。下面聊聊怎么实现novelty。第一步:确定什么是target & source这里以新闻推荐为例,如果一用户A,经常浏览同一个类型的新闻,比如体育新闻,如何找到A喜欢的其他类别新闻呢?这其实是一个user overlap的场景,推荐系统的主体user不变,有个source源是体育新闻,要找到target是体育以外user感兴趣的文章。这就建立了跨域推荐中的target和source关系。第二步:确定推荐level跨域推荐有多种level,要确定跨域的种类,大体可以分以下三种:其实跨域推荐确定了source和target后只要确定跨域的幅度即可。Attribute level:挖掘target间的相似属性,推荐同一类别的target。比如一个用户很喜欢买红色、大尺寸的诺基亚手机,attribute level推荐是要在推荐物属性层面做跨域,可以试着给用户推荐黑色、小尺寸的其它手机,这样的跨属性的相同物种推荐会在一定程度上给用户新鲜感Type level:挖掘target间的相似属性,然后推荐相同大品类下不同小品类的物品。比如用户喜欢红色、大尺寸的诺基亚手机,手机和电脑都属于电子产品,可以推荐红色、大尺寸的电脑给用户Item level:挖掘target间的相似属性,推荐不同大品类的物品。比如用户喜欢红色、大尺寸的诺基亚手机,直接推荐红色大尺寸的马桶以上3个跨域level由轻到重,大家可以根据自己的需求选用。其实关键点是如何挖掘物品的属性,因为无论是电脑、手机、马桶,他们都有共通的属性,比如大小、颜色、材质等,下面就介绍如何挖掘这些属性。第三步:挖掘target间的属性既然跨域推荐的关键是能挖掘出target间共有的属性,那么有什么办法可以做到这一点呢。首先要根据业务属性人工挖掘出一些隐性特征,比如电商平台可以挖掘出颜色、材质、价格、使用频率等隐性特征,然后可以通过矩阵分解的方式获取具体每个特征的权重(下图中矩阵A和B之间的矩阵)。总结信息茧房效应是因为个性化推荐系统推荐信息的不平衡性,导致用户长期只能浏览限制领域的信息,可以在推荐系统中加入跨域推荐的逻辑来规避信息茧房的影响,具体流程包含确定推荐逻辑中的source和target,确定跨域的粒度,通过矩阵分解找出隐含的共性属性。参考:https://recsys.acm.org/wp-con…本文作者:傲海阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 27, 2018 · 1 min · jiezi

【性能优化】quicklink:实现原理与给前端的启发

近来,GoogleChromeLabs 推出了 quicklink,用以实现链接资源的预加载(prefetch)。本文在介绍其实现思路的基础上,会进一步探讨在预加载方面前端工程师还可以做什么。1. quicklink 是什么的?quicklink 是一个通过预加载资源来提升后续方案速度的轻量级工具库。旨在提升浏览过程中,用户访问后续页面时的加载速度。当我们提到性能优化,往往都会着眼于对当前用户访问的这个页面,如何通过压缩资源大小、删减不必要资源、加快页面解析渲染等方式提升用户的访问速度;而 quicklink 用了另一种思路:我预先帮你加载(获取)你接下来最可能要用的资源,这样之后的真正使用到该资源(链接)时就会感觉非常顺畅。照着这个思路,我们需要解决的问题就是如何预先帮用户加载资源呢?这里其实涉及到两个问题:如何去预加载一个指定资源?(预加载的方式)如何确定某个资源是否要加载?(预加载的策略)下面就结合 quicklink 源码来看看如何解决这两个问题。注:下文提到的“预加载”/“预获取”均指 prefetch2. quicklink 实现原理2.1. 如何去预加载一个指定资源?首先要解决的是,通过什么方式来实现资源的预加载。即预加载的方式。我们这里的预加载对应的英文是 prefetch。提到 prefetch 自然会想到使用浏览器的 Resource Hints,通过提示浏览器做一些“预操作”(例如 DNS 解析、资源下载等)来加快后续的访问。如果对 prefetch 与 Resource Hints 不熟悉,可以看看这篇《使用Resource Hint提升页面加载性能与体验》。只需要下面这样一行代码就可以实现浏览器的资源预加载。是不是非常美妙?<link rel=“prefetch” href="/my.little.script.js" as=“script”>因此,要预加载一个资源可以通过下面四行代码:const link = document.createElement(link);link.rel = prefetch;link.href = url;document.head.appendChild(link);然而,我们不得不面对兼容性的问题,在低版本 IE 与移动端是重灾区。美梦破灭。既然如此,我们就需要一个类似 prefetch shim 的方式:在不支持 Resource Hints 的浏览器中,使用其他方式来预加载资源。对此,我们可以利用浏览器自身的缓存策略,“实实在在”预先请求这个资源,这也形成了一种资源的“预获取”。而这最方便的就是通过 XHR:const req = new XMLHttpRequest();req.open(GET, url, req.withCredentials=true);req.send();这样 shim 也完成了。最后,如何检测浏览器是否支持 prefetch 呢?我们可以通过 link 元素上 relList 属性的 support 方法来检查对 prefetch 的支持情况:const link = document.createElement(’link’);link.relList || {}).supports && link.relList.supports(‘prefetch’);结合这三个段代码,就形成了一个简易的 prefetcher:判断是否支持 Resource Hints 中的 prefetch,支持则使用它,否则回退使用 XHR 加载。值得一提的是,使用 Resource Hints 与使用 XHR 来预加载资源还是有一些重要差异的。草案中也提到了一些(主要是与性能以及与浏览器其他行为之间的冲突)。其中还有一点就是,Resource Hints 中的 prefetch 是否执行,完全是由浏览器决定的,草案里有句话非常明显 —— the user agent SHOULD fetch。因此,所有 prefetch 的资源并不一定会真正被 prefetch。相较之下,XHR 的方式“成功率”则更高。这点在 Netflix 实施的性能优化案例中也提到了。题外话:quicklink 中使用 fetch API 实现高优先级资源的加载。这是因为浏览器中会为所有的请求都设置一个优先级,高优请求会被优先执行;目前,fetch 在 Chrome 中属于高优先级,在 Safari 中属于中等优先级。2.2. 如何确定某个资源是否要预加载?有了资源预加载的方式,那么接下来就需要一个预加载的策略了。这其实是个见仁见智的问题。例如直接给你一个链接 https://my.test.com/somelink,在没有任何背景信息的情况下,恐怕你完全不知道是否需要预加载它。那对于这个问题,quicklink 是怎么解决的呢?或者说,quicklink 是通过什么策略来进行预加载的呢?quicklink 用了一个比较直观的策略:只对处于视口内的资源进行预加载。这一点也比较好理解,网络上大多的资源加载、页面跳转都伴随着用户点击这类行为,而它要是不在你的视野内,你也就无从点击了。这一定程度上算是个必要条件。这么一来,我们所要解决的问题就是,如果判断一个链接是否处于可视区域内?以前,对于这种问题,我们做的就是监听 scroll 事件,然后判断某元素的位置,从而来“得知”元素是否进入了视区。传统的图片懒加载库 lazysize 等也是用这种策略。document.addEventListener(‘scroll’, function () { // ……判断元素位置});注:目前 lazysize 也有了基于 IntersectionObserver 的实现当然,需要特别注意滚动监听的性能,例如使用截流、避免强制同步布局、 passive: true 等方式缓解性能问题。不过现在我们有了一个新的方式来实现这一功能 —— IntersectionObserver:const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { const link = entry.target; // 预加载链接 } });});// 对所有 a 标签添加观察者Array.from(options.el.querySelectorAll(‘a’), link => { observer.observe(link);});IntersectionObserver 会创建一个观察者,专门用来观察与通知元素进出视口的情况。如上述代码所示,IntersectionObserver 可以观察所有 a 元素的位置情况(主要是进入视野)。对 IntersectionObserver 不了解的同学可以参考 Google 的 IntersectionObserver 介绍文章。但是如下图所示, IntersectionObserver 存在兼容性问题,因此要在不兼容的浏览器中使用 quicklink,会需要一个 polyfill。目前,我们已经把 quicklink 的两大部分(预加载的方式和预加载的策略)的原理和简单实现讲完了。整个 quicklink 非常简洁,这些基本就是 quicklink 的核心。剩下的就是一些参数检查、额外的规则特性等。题外话:为了进一步保证性能,quicklink 使用 requestIdleCallback 在空闲时间查询页面 a 标签并挂载观察者。对 requestIdleCallback 不了解的同学可以看看 Google 的这篇文章。3. 到此为止?不,我们还能做更多到这里,quicklink 的实现就基本讲完了。仔细回想一下,quicklink 其实提供了我们一种通过“预加载”来实现性能优化的思路(粗略来说像是用流量换体验)。这种方式我在前面也提到了,其实可以分为两个部分:如何去预加载一个指定资源?(预加载的方式)如何确定某个资源是否要加载?(预加载的策略)其实两部分似乎都有可以作为的地方。例如如何保证 prefetcher(资源预加载器)的成功率能更高,以及目前使用的回退方案 XHR 其实在预加载无法缓存的资源时所受的限制等。此外,我们在这里还可以来聊一聊策略这块。由于 quicklink 是一个业务无关的轻量级功能库,所以它采用了一个简单但一定程度上有效的策略:预加载视野内的链接资源。然而在实际生产中,我们面对的是更复杂的环境,更复杂的业务,反而会需要更精准的预加载判断。因此,我们完全可以从 quicklink 中剥离出 prefetcher 来作为一个预加载器;而在策略部分使用自己的实现,例如:结合访问日志、打点记录的更精准的预加载。例如,我们可以通过访问日志、打点记录,根据 refer 来判断,从 A 页面来的 B、C、D 页面的比例,从而设置一个阈值,超过该阈值则认为访问 A 页面的用户接下来更容易访问它,从而对其预加载。结合用户行为数据来进行个性化的预加载。例如我们有一个阅读类或商品展示类站点,从用户行为发现,当该链接暴露在该用户视野内 XX 秒(用户阅读内容 XX 秒)后点击率达到 XX%。而不是简单的一刀切或进入视野就预加载。后置非必要资源,精简某类落地页。落地页就是要让新用户尽快“落地”,为此我们可以像 Netflix 介绍的那样,在宣贯页/登录页精简加载内容,而预加载后续主站的主包(主资源)。例如有些站点的首页大多偏静态,可以用原生 JavaScript 加 内联关键 CSS 的方式,加快加载,用户访问后再预加载 React、Vue 等一系列主站资源。等等。上面这些场景只是抛砖引玉,相信大家还会有更多更好的场景可以来助力我们的前端应用“起飞”。此外,我们完全可以借助一些构建工具、数据采集与分析平台来实现策略的自动提取与注入,优化整个预加载的流程。写在最后预加载、Resource Hints等由来已久。quicklink 通过提出了一种可行的方案让它又进入了大家的视野,给我们展现了性能优化的另一面。希望大家通过了解 quicklink 的实现,也能有自己的想法与启发。相信随着浏览器的不断进化,标准的不断前行,前端工程师对极致体验与性能要求的不断提高,我们的产品将会越来越好。 ...

December 25, 2018 · 2 min · jiezi

掌握浏览器重绘(reflow)重排(repaint)-前端进阶

很多人都知道要减少浏览器的重排和重绘,但对其中的具体原理以及如何具体操作并不是很了解,当突然提起这个话题的时候,还是会一脸懵逼。希望大家可以耐着性子阅读本文,仔细琢磨,彻底掌握这个知识点!博客、前端积累文档、公众号、GitHub网页生成过程:HTML被HTML解析器解析成DOM 树css则被css解析器解析成CSSOM 树结合DOM树和CSSOM树,生成一棵渲染树(Render Tree)生成布局(flow),即将所有渲染树的所有节点进行平面合成将布局绘制(paint)在屏幕上第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染。网上找了一张图片,我加了注释会更直观一些:渲染:网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断重新渲染重新渲染需要重复之前的第四步(重新生成布局)+第五步(重新绘制)或者只有第五个步(重新绘制)。重排比重绘大:大,在这个语境里的意思是:谁能影响谁?重绘:某些元素的外观被改变,例如:元素的填充颜色重排:重新生成布局,重新排列元素。就如上面的概念一样,单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。比如改变元素高度,这个元素乃至周边dom都需要重新绘制。也就是说:“重绘"不一定会出现"重排”,“重排"必然会出现"重绘"重排(reflow):概念:当DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。重排也叫回流,重排的过程以下面这种理解方式更清晰一些:回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流常见引起重排属性和方法任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发重排,下面列一些栗子:添加或者删除可见的DOM元素;元素尺寸改变——边距、填充、边框、宽度和高度内容变化,比如用户在input框中输入文字浏览器窗口尺寸改变——resize事件发生时计算 offsetWidth 和 offsetHeight 属性设置 style 属性的值常见引起重排属性和方法 widthheightmarginpaddingdisplayborderpositionoverflowclientWidthclientHeightclientTopclientLeftoffsetWudthoffsetHeightoffsetTopoffsetLeftscrollWidthscrollHeightscrollTopscrollLeftscrollIntoView()scrollTo()getComputedStyle()getBoundingClientRect()scrollIntoViewIfNeeded()重排影响的范围:由于浏览器渲染界面是基于流失布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种:全局范围:从根节点html开始对整个渲染树进行重新布局。局部范围:对渲染树的某部分或某一个渲染对象进行重新布局全局范围重排:<body> <div class=“hello”> <h4>hello</h4> <p><strong>Name:</strong>BDing</p> <h5>male</h5> <ol> <li>coding</li> <li>loving</li> </ol> </div></body>当p节点上发生reflow时,hello和body也会重新渲染,甚至h5和ol都会收到影响。局部范围重排:用局部布局来解释这种现象:把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界。尽可能的减少重排的次数、重排范围:重排需要更新渲染树,性能花销非常大:它们的代价是高昂的,会破坏用户体验,并且让UI展示非常迟缓,我们需要尽可能的减少触发重排的次数。重排的性能花销跟渲染树有多少节点需要重新构建有关系:所以我们应该尽量以局部布局的形式组织html结构,尽可能小的影响重排的范围。而不是像全局范围的示例代码一样一溜的堆砌标签,随便一个元素触发重排都会导致全局范围的重排。重绘(Repaints):概念:当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。常见的引起重绘的属性: colorborder-stylevisibilitybackgroundtext-decorationbackground-imagebackground-positionbackground-repeat outline-coloroutlineoutline-styleborder-radiusoutline-widthbox-shadowbackground-size浏览器的渲染队列:思考以下代码将会触发几次渲染?div.style.left = ‘10px’;div.style.top = ‘10px’;div.style.width = ‘20px’;div.style.height = ‘20px’;根据我们上文的定义,这段代码理论上会触发4次重排+重绘,因为每一次都改变了元素的几何属性,实际上最后只触发了一次重排,这都得益于浏览器的渲染队列机制:当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。强制刷新队列:div.style.left = ‘10px’;console.log(div.offsetLeft);div.style.top = ‘10px’;console.log(div.offsetTop);div.style.width = ‘20px’;console.log(div.offsetWidth);div.style.height = ‘20px’;console.log(div.offsetHeight);这段代码会触发4次重排+重绘,因为在console中你请求的这几个样式信息,无论何时浏览器都会立即执行渲染队列的任务,即使该值与你操作中修改的值没关联。因为队列中,可能会有影响到这些值的操作,为了给我们最精确的值,浏览器会立即重排+重绘。强制刷新队列的style样式请求:offsetTop, offsetLeft, offsetWidth, offsetHeightscrollTop, scrollLeft, scrollWidth, scrollHeightclientTop, clientLeft, clientWidth, clientHeightgetComputedStyle(), 或者 IE的 currentStyle我们在开发中,应该谨慎的使用这些style请求,注意上下文关系,避免一行代码一个重排,这对性能是个巨大的消耗重排优化建议就像上文提到的我们要尽可能的减少重排次数、重排范围,这样说很泛,下面是一些行之有效的建议,大家可以参考一下。1. 分离读写操作div.style.left = ‘10px’;div.style.top = ‘10px’;div.style.width = ‘20px’;div.style.height = ‘20px’;console.log(div.offsetLeft);console.log(div.offsetTop);console.log(div.offsetWidth);console.log(div.offsetHeight);还是上面触发4次重排+重绘的代码,这次只触发了一次重排:在第一个console的时候,浏览器把之前上面四个写操作的渲染队列都给清空了。剩下的console,因为渲染队列本来就是空的,所以并没有触发重排,仅仅拿值而已。2. 样式集中改变div.style.left = ‘10px’;div.style.top = ‘10px’;div.style.width = ‘20px’;div.style.height = ‘20px’;虽然现在大部分浏览器有渲染队列优化,不排除有些浏览器以及老版本的浏览器效率仍然低下:建议通过改变class或者csstext属性集中改变样式// badvar left = 10;var top = 10;el.style.left = left + “px”;el.style.top = top + “px”;// good el.className += " theclassname”;// goodel.style.cssText += “; left: " + left + “px; top: " + top + “px;";3. 缓存布局信息// bad 强制刷新 触发两次重排div.style.left = div.offsetLeft + 1 + ‘px’;div.style.top = div.offsetTop + 1 + ‘px’;// good 缓存布局信息 相当于读写分离var curLeft = div.offsetLeft;var curTop = div.offsetTop;div.style.left = curLeft + 1 + ‘px’;div.style.top = curTop + 1 + ‘px’;4. 离线改变dom隐藏要操作的dom在要操作dom之前,通过display隐藏dom,当操作完成之后,才将元素的display属性为可见,因为不可见的元素不会触发重排和重绘。dom.display = ’none’// 修改dom样式dom.display = ‘block’通过使用DocumentFragment创建一个dom碎片,在它上面批量操作dom,操作完成之后,再添加到文档中,这样只会触发一次重排。复制节点,在副本上工作,然后替换它!5. position属性为absolute或fixedposition属性为absolute或fixed的元素,重排开销比较小,不用考虑它对其他元素的影响6. 优化动画可以把动画效果应用到position属性为absolute或fixed的元素上,这样对其他元素影响较小动画效果还应牺牲一些平滑,来换取速度,这中间的度自己衡量:比如实现一个动画,以1个像素为单位移动这样最平滑,但是reflow就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多。启用GPPU加速此部分来自优化CSS重排重绘与浏览器性能GPU(图像加速器):GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。GPU 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。/* * 根据上面的结论 * 将 2d transform 换成 3d * 就可以强制开启 GPU 加速 * 提高动画性能 */div { transform: translate3d(10px, 10px, 0);}结语重排也是导致DOM脚本执行效率低的关键因素之一,重排与重绘作为大厂经常出现的面试题,并且涉及的性能优化,这是前端必须掌握的基本概念/技能之一(敲黑板!)。重排会不断触发这是不可避免的,但我们在开发时,应尽量按照文中的建议来组织代码,这种优化,需要平时有意识的去做,一点一滴的去做,希望大家重视一下。希望看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励。博客、前端积累文档、公众号、GitHub以上2018.12.17参考资料:网页性能管理详解优化CSS重排重绘与浏览器性能 ...

December 24, 2018 · 1 min · jiezi

Web运行环境总结

页面加载过程1.1 加载一个资源的过程在浏览器地址栏输入URL浏览器查看缓存(强缓存)浏览器解析URL获取协议,主机,端口,path浏览器组装一个HTTP(GET)请求报文浏览器根据DNS服务器得要域名的IP地址打开一个socket与目标IP地址,端口建立TCP链接向这个IP的机器发送http/https请求服务器收到处理并返回http请求判断协商缓存服务器将响应报文通过TCP连接发送回浏览器关闭TCP连接浏览器检查响应状态吗做出不同处理如果资源可缓存,进行缓存浏览器得到返回的内容1.2 浏览器渲染页面的过程根据HTML结构生成DOM Tree根据CSS生成CSSOM将DOM和CSSOM整合成RenderTree(渲染树,比DOM树多了样式)根据RenderTree开始渲染和展示HTML解析器遇到没有async和defer的script时,会执行并阻塞渲染 (js可能会改变dom结构)浏览器在Document对象上触发DOMContentLoaded事件显示页面图片加载完毕调用onload1.3 为什么把css放在head中在html渲染之前渲染css如果css放在html元素后面,会先按没有css的情况渲染html,然后加载到css后再重新渲染html元素,这样会损耗性能。1.4 为什么要把js放在body的最下面1.不会阻塞body中元素的渲染,能让页面更快出来2.script能拿到所有的标签1.5 图片图片是异步请求,不会影响dom树的渲染1.6 load,DOMContentLoaded window.addEventListener(’load’,function(){ //页面资源全部加载完成,包括图片视频 }); document.addEventListener(‘DOMContentLoaded’,function(){ //DOM 渲染完即可执行,图片视频还没有加载完 });DOMContentLoaded : dom结构化完成以后load:dom 结构化以及静态资源全部加载完毕以后$(document).ready 底层是DOMContentLoaded函数$(window).load 底层是load函数$(function () {}) 是$(document).ready的缩写var show = console.log.bind(console); show(‘观察脚本加载的顺序’) document.addEventListener(“DOMContentLoaded”, function () { show(‘DOMContentLoaded回调’) }, false); window.addEventListener(“load”, function () { show(’load事件回调’) }, false); show(‘脚本解析一’); $(document).ready(function () { show(’$(document).ready’) }) // 测试加载 $(function () { show(‘脚本解析二’); }) $(window).load(function () { show(’$(document).load’); }); show(‘脚本解析三’);1.7 重排ReflowDOM结构中的元素都有自己的盒子,这些都需要浏览器根据各种样式来计算并根据计算结果来将元素放到他们该出现的位置,称为Reflow。触发reflow的条件:1.增删改dom节点2.移动dom位置或者有动画3.修改css样式4.resize窗口或者滚动(移动端没有该问题)1.8 重绘Repaint页面要呈现的内容绘制在屏幕上。DOM改动,CSS改动(判断页面呈现的内容有没有发生变化)1.9 避免relow和repaint1.尽量用class来修改样式如果要修改多个样式,每个样式修改时都会出发reflow,可以将这些样式保存在一个class中,每次修改只reflow一次。将元素的position设置为absolute和fixed可以使元素从DOM树结构中脱离出来独立的存在,而浏览器在需要渲染时只需要渲染该元素以及位于该元素下方的元素,从而在某种程度上缩短浏览器渲染时间,这在当今越来越多的Javascript动画方面尤其值得考虑。不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

December 22, 2018 · 1 min · jiezi

浏览器渲染过程

内核浏览器组成过程基本流程 解析HTML构建DOM Tree–>构造Render Tree–>布局Render Tree–>绘制Render Tree概念1) HTML—-> DOM Tree2) Style Rules —> CSS Tree3) JS操作DOM API/CSSDOM API—>操作DOM / CSS4)DOM+CSS DOM=Render Tree5) 通过Native GUI API 绘制–>painting6) 页面某部分改变影响了布局需要回流–>reflow7)改变某个元素但不影响布局,不改变几何尺寸–>repaint主要过程

December 14, 2018 · 1 min · jiezi

被忽略的console.log

除了console.log之外,还有更多方式调试JavaScript来输出值。 看起来很明显我们没有。人们告诉我,做JavaScript应该使用浏览器的调试器,但这肯定是要看运行环境的。 但是很多时候你只想知道代码的某一部分是执行还是变量是什么,而不会看着断点消失庞大的代码类库中。尽管如此,虽然我们使用console.log,但是很多人都没有意识到控制台本身除了基本日志之外还有很多其他选项。 适当使用这些功能可以使调试更容易,更快速,更直观。console.log()在旧的console.log中有超出人期望令人惊讶的功能。 虽然大多数人将它用作console.log(obj),但您也可以执行console.log(object,otherObject,string),它会将它们全部记录下来。 有时候方便。除此之外,还有另一种格式:console.log(msg,values)。 这很像像C或PHP中的sprintf。console.log(‘I like %s but I do not like %s.’, ‘Skittles’, ‘pus’);将完全按照您的预期输出。> I like Skittles but I do not like pus.常见的占位符是%o(这是一个字母o,而不是零),它接受一个对象,%s接受一个字符串,%d是一个十进制或整数。另一个有趣的是%c, 它实际上是CSS值的占位符。console.log(‘I am a %cbutton’, ‘color: white; background-color: orange; padding: 2px 5px; border-radius: 2px’);这些值会运行到后面的任何内容上,没有“结束标记”,这有点奇怪。 但你可以将它变得像这样。它不优雅,也不是特别有用。 当然,这不是一个真正的按钮。它有用吗?Ehhhhh。console.dir()在大多数情况下,console.dir()函数非常类似于log(),尽管它看起来有点不同。向下的小箭头将显示与上面相同的确切对象详细信息,这也可以从console.log版本中看到。 当你看到元素时,事物的分歧更加剧烈,更有趣。let element = document.getElementById(‘2x-container’);这是记录输入的输出:我打开了一些元素。 这清楚地显示了DOM,我们可以浏览它。 但是console.dir(element)为我们提供了惊人的不同输出。这是一种更加客观的方式来查看元素。 有时候这就是你真正想要的东西,更像是检查元素。console.warn()可能是最明显的直接替换log(),你可以用完全相同的方式使用console.warn()。 唯一真正的区别是输出有点黄。 具体来说,输出处于警告级别而不是信息级别,因此浏览器将稍微区别对待它。 这具有使其在杂乱输出中更明显的效果。但是,有一个更大的优势。 因为输出是警告而不是信息,所以您可以过滤掉所有console.log并仅保留console.warn。 这对于偶尔会在浏览器中输出大量无用废话的偶尔繁琐的应用程序尤其有用。 清除噪音可以让您更轻松地看到输出。console.table()令人惊讶的是,这并不是更为人所知,但是console.table()函数旨在以比抛出原始对象数组更简洁的方式显示表格数据。例如,这是一个数据列表。const transactions = [{ id: “7cb1-e041b126-f3b8”, seller: “WAL0412”, buyer: “WAL3023”, price: 203450, time: 1539688433},{ id: “1d4c-31f8f14b-1571”, seller: “WAL0452”, buyer: “WAL3023”, price: 348299, time: 1539688433},{ id: “b12c-b3adf58f-809f”, seller: “WAL0012”, buyer: “WAL2025”, price: 59240, time: 1539688433}];如果我们使用console.log来转储上面的内容,我们会得到一些非常无用的输出:▶ (3) [{…}, {…}, {…}]小箭头让你点击并打开阵列,当然,但这并不是我们想要的“一目了然”。虽然console.tabl(data)的输出更有帮助。可选的第二个参数是您想要的列的列表。 显然默认为所有列,但我们也可以这样做。> console.table(data, [“id”, “price”]);我们得到这个输出,只显示id和价格。 适用于过大的物体,细节基本无关。 索引列是自动创建的,并且据我所知不可以去掉。这里要注意的是这是乱序的 - 最右边的列标题上的箭头显示了原因。 我点击该列进行排序。 找到列的最大或最小,或者只是对数据进行不同的查看非常方便。 顺便说一句,该功能与显示部分列无关。 它始终可用。console.table()只能处理最多1000行,因此可能不适合所有数据集。console.assert()断言有用的函数assert() 与log() 相同,但仅在第一个参数为false的情况下。 如果第一个参数为真,它什么都不做。这对于有循环(或几个不同的函数调用)并且只有一个显示特定行为的情况特别有用。 基本上它和这样做是一样的。if (object.whatever === ‘value’) { console.log(object);}澄清的是,当我说“相同”时,做起来却是相反的。 所以你需要反转条件。因此,让我们假设上面的一个值是在时间戳中使用null或0,这会搞砸我们的代码格式化日期。console.assert(tx.timestamp, tx);当与任何有效的事务对象一起使用时,它只是跳过去。 但是false会触发我们的日志记录,因为时间戳是0或null。有时我们想要更复杂的条件。 例如,我们已经看到用户WAL0412的数据存在问题,并且只想显示来自它们的事务。 这是直观的解决方案。console.assert(tx.buyer === ‘WAL0412’, tx);这看起来正确,但不起作用。 请记住,条件必须是false…我们要断言,而不是过滤。console.assert(tx.buyer !== ‘WAL0412’, tx);这将做我们想要的。 买方不是WAL0412的任何交易在该条件下都是正确的,只留下那些。 或者……不是。像其中的一些,console.assert()并不总是特别有用。 但在特定情况下它可以是一个优雅的解决方案。console.count()另一个使用有用的功能,count只是作为一个计数器,可选择作为一个命名计数器。for(let i = 0; i < 10000; i++) { if(i % 2) { console.count(‘odds’); } if(!(i % 5)) { console.count(‘multiplesOfFive’); } if(isPrime(i)) { console.count(‘prime’); }}这不是有用的代码,有点抽象。 此外,我不打算演示isPrime函数,这是个伪代码。我们得到的应该基本上是一个列表odds: 1odds: 2prime: 1odds: 3multiplesOfFive: 1prime: 2odds: 4prime: 3odds: 5multiplesOfFive: 2…等等。 这对于您可能只是转储索引的情况很有用,或者您希望保留一个(或多个)运行计数。您也可以像这样使用console.count(),不带参数。 这样做会将其称为默认值。如果您愿意,还可以使用相关的console.countReset()来重置计数器。console.trace()这很难用简单的数据进行演示。 它擅长的地方在于你试图弄清楚实际调用者导致问题的类或库。例如,可能有12个不同的组件调用服务,但其中一个组件没有正确设置依赖关系。export default class CupcakeService { constructor(dataLib) { this.dataLib = dataLib; if(typeof dataLib !== ‘object’) { console.log(dataLib); console.trace(); } } …}在这里单独使用console.log()会告诉我们传入的dataLib是什么,而不是在哪里。 但是,堆栈跟踪将非常清楚地告诉我们问题是Dashboard.js,我们可以看到它是新的CupcakeService(false)并导致错误。console.time()用于跟踪操作所用时间的专用函数console.time()是跟踪JavaScript执行所用微时间的更好方法。function slowFunction(number) { var functionTimerStart = new Date().getTime(); // something slow or complex with the numbers. // Factorials, or whatever. var functionTime = new Date().getTime() - functionTimerStart; console.log(Function time: ${ functionTime });}var start = new Date().getTime();for (i = 0; i < 100000; ++i) { slowFunction(i);}var time = new Date().getTime() - start;console.log(Execution time: ${ time });这是一种老式的方法。 我还应该指向上面的console.log。 很多人都没有意识到你可以在那里使用模板字符串和插值,但你可以。 很有帮助。所以让我们使用新方法试试。const slowFunction = number => { console.time(‘slowFunction’); // something slow or complex with the numbers. // Factorials, or whatever. console.timeEnd(‘slowFunction’);}console.time();for (i = 0; i < 100000; ++i) { slowFunction(i);}console.timeEnd();我们现在不再需要做任何数学运算或设置临时变量。console.group()现在可能是控制台输出中最复杂和最先进的区域。 group让你……好吧,分组。 特别是它可以让你嵌套东西。 它擅长的地方在于显示代码中存在的结构。// this is the global scopelet number = 1;console.group(‘OutsideLoop’);console.log(number);console.group(‘Loop’);for (let i = 0; i < 5; i++) { number = i + number; console.log(number);}console.groupEnd();console.log(number);console.groupEnd();console.log(‘All done now’);这又是一种循环。 你可以在这里看到输出。虽然不是很有用,但你可能会看到其中一些是如何组合在一起的。class MyClass { constructor(dataAccess) { console.group(‘Constructor’); console.log(‘Constructor executed’); console.assert(typeof dataAccess === ‘object’, ‘Potentially incorrect dataAccess object’); this.initializeEvents(); console.groupEnd(); } initializeEvents() { console.group(’events’); console.log(‘Initialising events’); console.groupEnd(); }}let myClass = new MyClass(false);这是很多工作和很多用于调试信息的代码,可能不是那么有用。 但它仍然是一个有趣的想法,您可以看到它可以使您的日志记录更加清晰。最后要指出的是console.groupCollapsed。 在功能上,这与console.group相同,但块开始关闭。 它没有得到很好的支持,但如果你有一大堆废话,你可能想要默认隐藏它是一个选项。结论这里没有太多结论。 所有这些工具都可能有用,如果你可能只需要一点点console.log(pet),但实际上并不需要调试器。可能最有用的是console.table,但所有其他api也都有自己的作用。 我是console.assert的粉丝,因为我们想调试一些东西,但只能在特定情况下调试。 ...

December 11, 2018 · 2 min · jiezi

关于回流与重绘优化的探索

前言杭州下雪了,冷到不行,在家躺在床上玩手机,打开微信进入前端交流群里日常吹水,看到大佬在群里发了一篇文章你应该要知道的重绘与重排,文章里有一段骚操作,就是为了减少重绘与重排,合并样式操作,这个骚操作成功的引起了我的注意,然后开启了我的探索。正文前言中描述的合并样式的骚操作是如下:var el = document.querySelector(‘div’);el.style.borderLeft = ‘1px’;el.style.borderRight = ‘2px’;el.style.padding = ‘5px’;原文描述的大概意思是这段代码多次对 DOM 的修改和对样式的修改,页面会进行多次回流或者重绘,应该进行如下优化:var el = document.querySelector(‘div’);el.style.cssText = ‘border-left: 1px; border-right: 1px; padding: 5px;‘这样的优化在以前我刚开始学习前端的时候,经常也在一些相关的性能优化的文章里看到,因为一直没有探究过,概念里一直觉得自己应该把多次 DOM 的样式的修改合并在一起,这样效率会更高,直到后来,自己对浏览器的进程与线程慢慢有了了解,曾经也写过一篇博客,浅谈浏览器多进程与JS线程,其中有一个概念是,JS线程与GUI渲染线程是互斥关系,大概的意思就是当js引擎在执行js代码的时候,浏览器的渲染引擎是被冻结了的,无法渲染页面的,必须等待js引擎空闲了才能渲染页面。这个概念,JS线程与GUI渲染线程是互斥关系与上面描述的骚操作似乎有点冲突,也就是当我们对el.style进行一系列赋值的时候,渲染引擎是被冻结的状态,怎么会进行多次重绘或者回流?带着这样的疑问,写了一个小demo,代码如下。<!DOCTYPE html><html><head> <title>测试页</title> <style> #box { width: 109px; height: 100px; background-color: lightsteelblue; border-style: solid; } </style></head><body> <div id=“box”></div></body><script>var box = document.getElementById(‘box’);var toggle = 0;var time = 500;function toggleFun() { var borderWidth = toggle ? 20 : 0; var borderColor = toggle ? ‘coral’ : ’transparent’; if (toggle) { box.style.borderWidth = ‘50px’; box.style.borderWidth = borderWidth + ‘px’; box.style.borderColor = borderColor; } else { box.style.cssText = ‘border: ’ + borderWidth + ‘px solid’ + borderColor; } toggle = toggle ? 0 : 1;}setInterval(toggleFun, time)</script></html>代码大概的意思就是定时以两种操作设置样式,收集浏览器的回流或者重绘次数。打开chrome的开发者工具,切换到Performance选项卡,点击左上角的圆 ○,开始record,等几秒后stop,点击Frames查看Event log选项卡,内容如下:大概可以看到,Recalculate Style -> Layout -> Update Layer Tree -> Paint -> Composite Layers 这个过程在循环进行,触发的目标代码是第25行代码合29行代码,也就是box.style.borderWidth = ‘50px’;和box.style.cssText = ‘border: ’ + borderWidth + ‘px solid’ + borderColor;。首先回顾一下浏览器渲染页面的流程:请求拿到html报文。同时解析生成CSS规则树和DOM树。合并CSS规则树和DOM树,生成render树。渲染进程根据render树进行Layout。绘制paint页面。然后在看看上面的过程,可以容易看出,首先,Recalculate Style,重新计算css规则树。进行Layout,这里的Layout可以理解成回流,重新计算每个元素的位置。Update Layer Tree,字面意思理解,更新层级树。Paint,绘制页面,在这里可以理解成重绘。Composite Layers,字面意思理解,合并层级。由上面过程得到结果,当在同一执行任务里面对DOM的样式进行多次操作的时候,只会进行一次回流或者重绘,也就是说,只要我们的js引擎时候忙碌的,渲染引擎是冻结的时候,无论对DOM样式进行多少次操作,都只会进行一次回流或者重绘,也就是说前面说的合并样式优化是无效的。这个时候,我对上面过程又产生了新的疑问,为什么要Paint之后在Composite Layers呢?为什么不把所有层合并完了在绘制页面呢?…………………….(看搜索相关资料去了)翻看资料结束后,我得到以下理解。首先理解layer概念,可以理解成PS里面的图层,我们知道PS文件最后保存层PSD文件,当图层越多的时候,PSD文件就越大,在我们的浏览器里面也是一样的,我们的layer越多,所占的内存就越大。然后理解Paint真正做的事情,paint的任务大概就是把所有的layer绘制到页面中,这个绘制与canvas的绘制不一样,canvas的绘制相当于在画布里把像素直接绘制成指定颜色,然后我们直接看到的东西就直接是像素颜色,而我们这里说的Paint只是把图层丢到页面中,最后的绘制,需要交给Composite线程处理。最后是Composite Layers,由composite线程进行,这个线程在浏览器的Renderer进程中,任务是把Paint时候丢上页面的图层转化成位图,最终生成我们肉眼可以看到的图像,所以,真正的绘制,应该是Composite Layers过程进行的。由于paint与composite解耦,浏览器对每一个layer都有一个标识,这个标识用来标识该layer是否需要重绘,在有CSS规则树变化的时候,浏览器只会对这些被标识的layer进行重绘,用这样的方式提高浏览器的渲染性能。最后前端大法博大精深,越往下学越觉得自己不适合前端!!!仿佛看到自己在从入门到跑路这条路上快走到了终点。。。参考Chrome 性能调优简介浏览器解析过程GPU:合成加速 ...

December 9, 2018 · 1 min · jiezi

基于 Nginx 的 HTTPS 性能优化实践

摘要: 随着相关浏览器对HTTP协议的“不安全”、红色页面警告等严格措施的出台,以及向 iOS 应用的 ATS 要求和微信、支付宝小程序强制 HTTPS 需求,以及在合规方面如等级保护对传输安全性的要求都在推动 HTTPS 的发展。前言分享一个卓见云的较多客户遇到HTTPS优化案例。随着相关浏览器对HTTP协议的“不安全”、红色页面警告等严格措施的出台,以及向 iOS 应用的 ATS 要求和微信、支付宝小程序强制 HTTPS 需求,以及在合规方面如等级保护对传输安全性的要求都在推动 HTTPS 的发展。虽然 HTTPS 优化了网站访问体验(防劫持)以及让传输更加安全,但是很多网站主赶鸭子上架式的使用了 HTTPS 后往往都会遇到诸如:页面加载速度变慢、服务器负载过高以及证书过期不及时更新等问题。所以本文就来探讨一下 HTTPS 的优化实践。选型其实像 Apache Httpd、LigHttpd、Canddy 等 Web 服务软件都可以设置 HTTPS,但是在相应的扩展生态和更新率上都不如 Nginx。 Nginx 作为大型互联网网站的 Web 入口软件有着广泛的支持率,例如阿里系的 Tengine、CloudFlare 的 cloudflare-nginx、又拍云用的 OpenResty 都是基于 Nginx 而来的,Nginx 是接受过大规模访问验证的。同时大家也将自己开发的组件回馈给 Nginx 社区,让 Nginx 有着非常良好的扩展生态。 所以说 Nginx 是一款很好的 Web 服务软件,选择 Nginx 在提升性能的同时能极大的降低我们的扩展成本。新功能围绕 Web 服务已经有非常多的新功能需要我们关注并应用了,这里先罗列相关新功能。HTTP/2相比廉颇老矣的 HTTP/1.x,HTTP/2 在底层传输做了很大的改动和优化包括有:每个服务器只用一个连接,节省多次建立连接的时间,在TLS上效果尤为明显加速 TLS 交付,HTTP/2 只耗时一次 TLS 握手,通过一个连接上的多路利用实现最佳性能更安全,通过减少 TLS 的性能损失,让更多应用使用 TLS,从而让用户信息更安全在 Akamai 的 HTTP/2 DEMO中,加载300张图片,HTTP/2 的优越性极大的显现了出来,在 HTTP/1.X 需要 14.8s 的操作中,HTTP/2 仅需不到1s。HTTP/2 现在已经获得了绝大多数的现代浏览器的支持。只要我们保证 Nginx 版本大于 1.9.5 即可。当然建议保持最新的 Nginx 稳定版本以便更新相关补丁。同时 HTTP/2 在现代浏览器的支持上还需要 OpenSSL 版本大于 1.0.2。TLS 1.3和 HTTP/1.x 一样,目前受到主流支持的 TLS 协议版本是 1.1 和 1.2,分别发布于 2006年和2008年,也都已经落后于时代的需求了。在2018年8月份,IETF终于宣布TLS 1.3规范正式发布了,标准规范(Standards Track)定义在 rfc8446。TLS 1.3 相较之前版本的优化内容有:握手时间:同等情况下,TLSv1.3 比 TLSv1.2 少一个 RTT应用数据:在会话复用场景下,支持 0-RTT 发送应用数据握手消息:从 ServerHello 之后都是密文。会话复用机制:弃用了 Session ID 方式的会话复用,采用 PSK 机制的会话复用。密钥算法:TLSv1.3 只支持 PFS (即完全前向安全)的密钥交换算法,禁用 RSA 这种密钥交换算法。对称密钥算法只采用 AEAD 类型的加密算法,禁用CBC 模式的 AES、RC4 算法。密钥导出算法:TLSv1.3 使用新设计的叫做 HKDF 的算法,而 TLSv1.2 是使用PRF算法,稍后我们再来看看这两种算法的差别。总结一下就是在更安全的基础上还做到了更快,目前 TLS 1.3 的重要实现是 OpenSSL 1.1.1 开始支持了,并且 1.1.1 还是一个 LTS 版本,未来的 RHEL8、Debian10 都将其作为主要支持版本。在 Nginx 上的实现需要 Nginx 1.13+。BrotliBrotli 是由 Google 于 2015 年 9 月推出的无损压缩算法,它通过用变种的 LZ77 算法,Huffman 编码和二阶文本建模进行数据压缩,是一种压缩比很高的压缩方法。根据Google 发布的研究报告,Brotli 具有如下特点:针对常见的 Web 资源内容,Brotli 的性能要比 Gzip 好 17-25%;Brotli 压缩级别为 1 时,压缩速度是最快的,而且此时压缩率比 gzip 压缩等级为 9(最高)时还要高;在处理不同 HTML 文档时,brotli 依然提供了非常高的压缩率;在兼容 GZIP 的同时,相较 GZIP:JavaScript 上缩小 14%HTML上缩小 21%CSS上缩小 17%Brotli 的支持必须依赖 HTTPS,不过换句话说就是只有在 HTTPS 下才能实现 Brotli。ECC 证书椭圆曲线密码学(Elliptic curve cryptography,缩写为ECC),一种建立公开金钥加密的算法,基于椭圆曲线数学。椭圆曲线在密码学中的使用是在1985年由Neal Koblitz和Victor Miller分别独立提出的。内置 ECDSA 公钥的证书一般被称之为 ECC 证书,内置 RSA 公钥的证书就是 RSA 证书。由于 256 位 ECC Key 在安全性上等同于 3072 位 RSA Key,加上 ECC 运算速度更快,ECDHE 密钥交换 + ECDSA 数字签名无疑是最好的选择。由于同等安全条件下,ECC 算法所需的 Key 更短,所以 ECC 证书文件体积比 RSA 证书要小一些。ECC 证书不仅仅可以用于 HTTPS 场景当中,理论上可以代替所有 RSA 证书的应用场景,如 SSH 密钥登陆、SMTP 的 TLS 发件等。不过使用 ECC 证书有两个点需要注意:一、 并不是每一个证书类型都支持的,一般商业证书中带增强型字眼的才支持ECC证书的签发。二、 ECC证书在一些场景中可能还不被支持,因为一些产品或者软件可能还不支持 ECC。 这时候就要虚线解决问题了,例如针对部分旧操作系统和浏览器不支持ECC,可以通过ECC+RSA双证书模式来解决问题。安装下载源码综合上述我们要用到的新特性,我们整合一下需求:HTTP/2 要求 Nginx 1.9.5+,,OpenSSL 1.0.2+TLS 1.3 要求 Nginx 1.13+,OpenSSL 1.1.1+Brotli 要求 HTTPS,并在 Nginx 中添加扩展支持ECC 双证书 要求 Nginx 1.11+这里 Nginx,我个人推荐 1.15+,因为 1.14 虽然已经能支持TLS1.3了,但是一些 TLS1.3 的进阶特性还只在 1.15+ 中提供。然后我们定义一下版本号:# VersionOpenSSLVersion=‘openssl-1.1.1a’;nginxVersion=‘nginx-1.14.1’;建议去官网随时关注最新版:http://nginx.org/en/download.htmlhttps://www.openssl.org/source/https://github.com/eustas/ngx_brotli/releasesNginxcd /optwget http://nginx.org/download/$nginxVersion.tar.gztar xzf $nginxVersion.tar.gzOpenSSLcd /optwget https://www.openssl.org/source/$OpenSSLVersion.tar.gztar xzf $OpenSSLVersion.tar.gzBrotlicd /optgit clone https://github.com/eustas/ngx_brotli.gitcd ngx_brotligit submodule update –init –recursive编译cd /opt/$nginxVersion/./configure --prefix=/usr/local/nginx \ ## 编译后安装的目录位置–with-openssl=/opt/$OpenSSLVersion \ ## 指定单独编译入 OpenSSL 的源码位置–with-openssl-opt=enable-tls1_3 \ ## 开启 TLS 1.3 支持–with-http_v2_module \ ## 开启 HTTP/2 –with-http_ssl_module \ ## 开启 HTTPS 支持–with-http_gzip_static_module \ ## 开启 GZip 压缩–add-module=/opt/ngx_brotli ## 编译入 ngx_BroTli 扩展make && make install ## 编译并安装后续还有相关变量设置和设置服务、开启启动等步骤,篇幅限制就省略了,这篇文章有介绍在 Ubuntu 下的 Nginx 编译:https://www.mf8.biz/ubuntu-nginx/ 。配置接下来我们需要修改配置文件。HTTP2listen 443 ssl http2;只要在 server{} 下的lisen 443 ssl 后添加 http2 即可。而且从 1.15 开始,只要写了这一句话就不需要再写 ssl on 了,很多小伙伴可能用了 1.15+ 以后衍用原配置文件会报错,就是因为这一点。TLS 1.3ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;如果不打算继续支持 IE8,或者一些合规的要求,可以去掉TLSv1。然后我们再修改对应的加密算法,加入TLS1.3引入的新算法:ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;如果不打算继续支持 IE8,可以去掉包含 3DES 的 Cipher Suite。默认情况下 Nginx 因为安全原因,没有开启 TLS 1.3 0-RTT,可以通过添加 ssl_early_data on; 指令开启 0-RTT的支持。————实验性尝试众所周知,TLS1.3 由于更新了很久,很多浏览器的旧版本依旧只支持 Draft 版本,如 23 26 28 分别在 Chrome、FirFox 上有支持,反而正式版由于草案出来很久,导致TLS1.3在浏览器上兼容性不少太好。可以使用 https://github.com/hakasenyang/openssl-patch/ 提供的 OpenSSL Patch 让 OpenSSL 1.1.1 同时支持草案23,26,28和正式版输出。 不过由于不是官方脚本,稳定性和安全性有待考量。ECC双证书双证书配置的很简单了,保证域名的证书有RSA和ECC各一份即可。 ##证书部分 ssl_certificate /usr/local/nginx/conf/ssl/www.mf8.biz-ecc.crt; #ECC证书 ssl_certificate_key /usr/local/nginx/conf/ssl/www.mf8.biz-ecc.key; #ECC密钥 ssl_certificate /usr/local/nginx/conf/ssl/www.mf8.biz.crt; #RSA证书 ssl_certificate_key /usr/local/nginx/conf/ssl/www.mf8.biz.key; #RSA密钥Brotli需要在对应配置文件中,添加下面代码即可: brotli on; brotli_comp_level 6; brotli_min_length 1k; brotli_types text/plain text/css text/xml text/javascript text/x-component application/json application/javascript application/x-javascript application/xml application/xhtml+xml application/rss+xml application/atom+xml application/x-font-ttf application/vnd.ms-fontobject image/svg+xml image/x-icon font/opentype;为了防止大家看糊涂了,放一个完整的 server{}供大家参考: server { listen 443 ssl http2; # 开启 http/2 server_name mf8.biz www.mf8.biz; #证书部分 ssl_certificate /usr/local/nginx/conf/ssl/www.mf8.biz-ecc.crt; #ECC证书 ssl_certificate_key /usr/local/nginx/conf/ssl/www.mf8.biz-ecc.key; #ECC密钥 ssl_certificate /usr/local/nginx/conf/ssl/www.mf8.biz.crt; #RSA证书 sl_certificate_key /usr/local/nginx/conf/ssl/www.mf8.biz.key; #RSA密钥 #TLS 握手优化 ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; keepalive_timeout 75s; keepalive_requests 100; #TLS 版本控制 ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers ‘TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5’; # 开启 1.3 o-RTT ssl_early_data on; # GZip 和 Brotli gzip on; gzip_comp_level 6; gzip_min_length 1k; gzip_types text/plain text/css text/xml text/javascript text/x-component application/json application/javascript application/x-javascript application/xml application/xhtml+xml application/rss+xml application/atom+xml application/x-font-ttf application/vnd.ms-fontobject image/svg+xml image/x-icon font/opentype; brotli on; brotli_comp_level 6; brotli_min_length 1k; brotli_types text/plain text/css text/xml text/javascript text/x-component application/json application/javascript application/x-javascript application/xml application/xhtml+xml application/rss+xml application/atom+xml application/x-font-ttf application/vnd.ms-fontobject image/svg+xml image/x-icon font/opentype; location / { root html; index index.html index.htm; } }先验证一下配置文件是否有误:nginx -t如果反馈的是:nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is oknginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful就可以重启 Nginx ,然后到对应网站中去查看效果了。验证HTTP/2通过浏览器的开发者工具,我们可以在 Network 栏目中看到 Protocol 中显示 h2 有无来判断。TLS 1.3老地方,我们可以通过浏览器的开发者工具 中的 Security 栏目看到 Connection 栏目下是否有显示 TLS 1.3ECC 双证书ECC 双证书配置了以后无非就是在旧浏览器设别上的验证了。这里用足够老的上古XP虚拟机来给大家证明一波。XP系统上:现代操作系统上的:Brotli通过浏览器的开发者工具,我们可以在 Network 栏目中,打开具体页面的头信息,看到 accept-encoding 中有 br 字眼就行。总结通过上述手段应该可以让 HTTPS 访问的体验优化不少,而且会比没做 HTTPS 的网站访问可能更快。这样的模式比较适合云服务器单机或者简单集群上搭建,如果有应用 SLB 七层代理、WAF、CDN 这样的产品可能会让我们的这些操作都白费。 我们的这几项操作都是自建的 Web 七层服务,如果有设置 SLB 七层代理、WAF、CDN 这样设置在云服务器之前就会被覆盖掉。由于 SLB 七层和CDN这样的产品会更加追求广泛的兼容性和稳定性并不会第一时间就用上上述的这些新特性(HTTP/2 是普遍有的),但是他们都配备了阿里云的 Tengine 的外部专用算法加速硬件如 Intel® QuickAssist Technology(QAT) 加速器可以显著提高SSL/TLS握手阶段性能。 所有 HTTPS 的加密解密都在 SLB 或 CDN 上完成,而不会落到ECS上,可以显著降低 ECS 的负载压力,并且提升访问体验。目前云上的网络产品中能支持四层的都是可以继续兼容我们这套设计的,例如:SLB 的四层转发(TCP UDP)、DDOS高防的四层转发。本文作者:妙正灰阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 5, 2018 · 3 min · jiezi

聊聊Flexbox布局中的flex的演算法

到目前为止,Flexbox布局应该是目前最流行的布局方式之一了。而Flexbox布局的最大特性就是让Flex项目可伸缩,也就是让Flex项目的宽度和高度可以自动填充Flex容器剩余的空间或者缩小Flex项目适配Flex容器不足的宽度。而这一切都是依赖于Flexbox属性中的flex属性来完成。一个Flex容器会等比的按照各Flex项目的扩展比率分配Flex容器剩余空间,也会按照收缩比率来缩小各Flex项目,以免Flex项目溢出Flex容器。但其中Flex项目又是如何计算呢?他和扩展比率或收缩比率之间又存在什么关系呢?在这篇文章中我们将一起来探来。在Flexbox布局中,容器中显示式使用display设置为flex或inline-flex,那么该容器就是Flex容器,而该容器的所有子元素就是Flex项目。简介在这篇文章中,我们将要聊的是有关于flex属性的事情,特别是如何使用该属性来计算Flex项目?在开始之前,先来简单的了解一下flex属性。在Flexbox中,flex属性是flex-grow(扩展比率)、flex-shrink(收缩比率)和flex-basis(伸缩基准)三个属性的简称。这三个属性可以控制一个Flex项目(也有人称为Flex元素),主要表现在以下几个方面:flex-grow:Flex项目的扩展比率,让Flex项目得到(伸张)多少Flex容器多余的空间(Positive free space)flex-shrink:Flex项目收缩比率,让Flex项目减去Flex容器不足的空间(Negative free space)flex-basis:Flex项目未扩展或收缩之前,它的大小是多少在Flexbox布局中,只有充分理解了这三个属性才能彻底的掌握Flex项目是如何扩展和收缩的,也才能更彻底的掌握Flexbox布局。因此掌握这三个属性,以及他们之间的计算关系才是掌握Flexbox布局的关键所在。相关概念在具体介绍flex相关的技术之前,先对几个概念进行描述,因为理解了这几个概念更有易于大家对后面知识的理解。主轴长度和主轴长度属性Flex项目在主轴方向的宽度或高度就是Flex项目的主轴长度,Flex项目的主轴长度属性是width或height属性,具体是哪一个属性,将会由主轴方向决定。剩余空间和不足空间在Flexbox布局中,Flex容器中包含一个或多个Flex项目(该容器的子元素或子节点)。Flex容器和Flex项目都有其自身的尺寸大小,那么就会有:Flex项目尺寸大小之和大于或小于Flex容器 情景:当所有Flex项目尺寸大小之和小于Flex容器时,Flex容器就会有多余的空间没有被填充,那么这个空间就被称为Flex容器的剩余空间(Positive Free Space)当所有Flex项目尺寸大小之和大于Flex容器时,Flex容器就没有足够的空间容纳所有Flex项目,那么多出来的这个空间就被称为负空间(Negative Free Space)举个例子向大家阐述这两个情形:“假设我们有一个容器(Flex容器),显式的给其设置了width为800px,padding为10px,并且box-sizing设置为border-box”。根据CSS的盒模型原理,我们可以知道Flex容器的内宽度(Content盒子的宽度)为800px - 10px * 2 = 780px:假设Flex容器中包含了四个Flex项目,而且每个Flex项目的width都为100px,那么所有Flex项目的宽度总和则是100px * 4 = 400px(Flex项目没有设置其他任何有关于盒模型的尺寸),那么Flex容器将会有剩余的空间出来,即780px - 400px = 380px。这个380px就是我们所说的Flex容器的剩余空间:假设把Flex项目的width从100px调到300px,那么所有Flex项目的宽度总和就变成了300px * 4 = 1200px。这个时候Flex项目就溢出了Flex容器,这个溢出的宽度,即1200px - 780px = 420px。这个420px就是我们所说的Flex容器的不足空间:上面演示的是主轴在x轴方向,如果主轴变成y轴的方向,同样存在上述两种情形,只不过把width变成了height。接下来的内容中,如果没有特殊说明,那么所看到的示例都仅演示主轴在x轴的方向,即flex-direction为row!min-content 和 max-contentmin-content和max-content是CSS中的一个新概念,隶属于CSS Intrinsic and Extrinsic Sizing Specification模块。简单的可以这么理解。CSS可以给任何一个元素显式的通过width属性指定元素内容区域的宽度,内容区域在元素padding、border和margin里面。该属性也是CSS盒模型众多属性之一。记住,CSS的box-sizing可以决定width的计算方式。如果我们显式设置width为关键词auto时,元素的width将会根据元素自身的内容来决定宽度。而其中的min-content和max-content也会根据元素的内容来决定宽度,只不过和auto有较大的差异min-content: 元素固有的最小宽度max-content: 元素固有的首选宽度比如下面这个示例:如果内容是英文的话,min-content的宽度将取决于内容中最长的单词宽度,中文就有点怪异(其中之因目前并未深究),而max-content则会计算内容排整行的宽度,有点类似于加上了white-space:nowrap一样。上例仅展示了min-content和max-content最基本的渲染效果(Chrome浏览器渲染行为)。这里不做深入的探讨论,毕竟不是本文的重点,如果感兴趣,欢迎关注后续的相关更新,或者先阅读@张鑫旭 老师写的一篇文章《理解CSS3 max/min-content及fit-content等width值》回到我们自己的主题上来。前面在介绍Flex剩余空间和不足空间的时候,我们可以得知,出现这两种现象取决于Flex容器和Flex项目的尺寸大小。而flex属性可以根据Flex容器的剩余空间(或不足空间)对Flex项目进行扩展(或收缩)。那么为了计算出有多少Flex容器的剩余空间能用于Flex项目上,客户端(浏览器)就必须知道Flex项目的尺寸大小。要是没有显式的设置元素的width属性,那么问题就来了,浏览器它是如何解决没有应用于绝对单位的宽度(或高度)的Flex项目,即如何计算?这里所说的min-content和max-content两个属性值对于我们深入的探讨flex属性中的flex-grow和 flex-grow属性有一定的影响。所以提前向大家简单的阐述一正是这两个属性值在浏览器中的渲染行为。简单的总结一下:min-content的大小,从本质上讲,是由字符串中最长的单词决定了大小;max-content则和min-content想反. 它会变得尽可能大, 没有自动换行的机会。如果Flex容器太窄, 它就会溢出其自身的盒子!Flex项目的计算在Flexbox布局当中,其中 flex-grow、flex-shrink和flex-basis都将会影响Flex项目的计算。接下来我们通过一些简单的示例来阐述这方面的知识。flex-basisflex-basis属性在任何空间分配发生之前初始化Flex项目的尺寸。其默认值为auto。如果flex-basis的值设置为auto,浏览器将先检查Flex项目的主尺寸是否设置了绝对值再计算出Flex项目的初始值。比如说,你给Flex项目设置的width为200px,那么200px就是Flex项目的flex-basis值。如果你的Flex项目可以自动调整大小,则auto会解析为其内容的大小,这个时候,min-content和max-content变会起作用。此时将会把Flex项目的max-content作为 flex-basise的值。比如,下面这样的一个简单示例:flex-grow和flex-shrink的值都为0,第一个Flex项目的width为150px,相当于flex-basis的值为150px,而另外两个Flex项目在没有设置宽度的情况之下,其宽度由内容的宽度来设置。如果flex-basis的值设置为关键词content,会导致Flex项目根据其内容大小来设置Flex项目,叧怕是Flex项目显式的设置了width的值。到目前为止,content还未得到浏览器很好的支持。flex-basis除了可以设置auto、content、fill、max-content、min-content和fit-content关键词之外,还可以设置<length>值。如果<length>值是一个百分比值,那么Flex项目的大小将会根据Flex容器的width进行计算。比如下面这个示例:Flex容器显式设置了width(和box-sizing取值有关系,上图为border-box的示例结果),那么flex-basis会根据Flex容器的width计算出来,如果Flex容器未显示设置width值,则计算出来的结果将是未定义的(会自动根据Flex容器的宽度进行计算)。在Flexbox布局中,如果你想完全忽略Flex项目的尺寸,则可以将flex-basis设置为0。这样的设置,基本上是告诉了浏览器,Flex容器所有空间都可以按照相关的比例进行分配。来看一个简单的示例,Flex项目未显式设置width情况之下,flex-basis不同取值的渲染效果。到写这篇文章为止,使用Firefox浏览器查看效果更佳。当Flex项目显式的设置了min-width或max-width的值时,就算Flex项目显式的设置了flex-basis的值,也会按min-width和max-width设置Flex项目宽度。当计算的值大于max-width时,则按max-width设置Flex项目宽度;当计算的值小于min-width时,则按min-width设置Flex项目宽度:有关于flex-basis属性相关的运用简单的小结一下:flex-basis默认值为auto如果Flex项目显式的设置了width值,同时flex-basis为auto时,则Flex项目的宽度为按width来计算,如果未显式设置width,则按Flex项目的内容宽度来计算如果Flex项目显式的设置了width值,同时显式设置了flex-basis的具体值,则Flex项目会忽略width值,会按flex-basis来计算Flex项目当Flex容器剩余空间不足时,Flex项目的实际宽度并不会按flex-basis来计算,会根据flex-grow和flex-shrink设置的值给Flex项目分配相应的空间对于Flexbox布局中,不建议显式的设置Flex项目的width值,而是通过flex-basis来控制Flex项目的宽度,这样更具弹性如果Flex项目显式的设置了min-width或max-width值时,当flex-basis计算出来的值小于min-width则按min-width值设置Flex项目宽度,反之,计算出来的值大于max-width值时,则按max-width的值设置Flex项目宽度flex-grow前面提到过,flex-grow是一个扩展因子(扩展比例)。其意思是,当Flex容器有一定的剩余空间时,flex-grow可以让Flex项目分配Flex容器剩余的空间,每个Flex项目将根据flex-grow因子扩展,从而让Flex项目布满整个Flex容器(有效利用Flex容器的剩余空间)。flex-grow的默认值是0,其接受的值是一个数值,也可以是一个小数值,但不支持负值。一旦flex-grow的值是一个大于0的值时,Flex项目就会占用Flex容器的剩余空间。在使用flex-grow时可以按下面的方式使用:所有Flex项目设置相同的flex-grow值每个Flex项目设置不同的flex-grow值不同的设置得到的效果将会不一样,但flex-grow的值始终总量为1,即Flex项目占有的量之和(分子)和分母相同。我们来具体看看flex-grow对Flex项目的影响。当所有的Flex项目具有一个相同的flex-grow值时,那么Flex项目将会平均分配Flex容器剩余的空间。在这种情况之下将flex-grow的值设置为1。比如下面这个示例,Flex容器(width: 800px,padding: 10px)中有四个子元素(Flex项目),显式的设置了flex-basis为150px,根据前面介绍的内容,我们可以知道每个Flex项目的宽度是150px,这样一来,所有Flex项目宽度总和为150px * 4 = 600px。容器的剩余空间为780px - 600px = 180px。当显式的给所有Flex项目设置了flex-grow为1(具有相同的值)。这样一来,其告诉浏览器,把Flex容器剩余的宽度(180px)平均分成了四份,即:180px / 4 = 45px。而flex-grow的特性就是按比例把Flex容器剩余空间分配给Flex项目(当然要设置了该值的Flex项目),就该例而言,就是给每个Flex项目添加了45px,也就是说,此时Flex项目的宽度从150px扩展到了195px(150px + 45px = 195px)。如下图所示:特别声明,如果Flex项目均分Flex容器剩余的空间,只要给Flex项目设置相同的flex-grow值,大于1即可。比如把flex-grow设置为10,就上例而言,把剩余空间分成了40份,每个Flex项目占10份。其最终的效果和设置为1是等效的。上面我们看到的均分Flex容器剩余空间,事实上我们也可以给不同的Flex项目设置不同的flex-grow值,这样一来就会让每个Flex项目根据自己所占的比例来占用Flex容器剩余的空间。比如上面的示例,把Flex项目的flex-grow分别设置为1:2:3:4。也就是说把Flex容器的剩余空间分成了10份(1 + 2 + 3 + 4 = 10),而每个Flex项目分别占用Flex容器剩余空间的1/10、2/10、3/10和4/10。就上例而言,Flex容器剩余空间是180px,按这样的计算可以得知,每一份的长度是180px / 10 = 18px,如此一来,每个Flex项目的宽度则变成:Flex1: 150px + 18px * 1 = 168pxFlex2: 150px + 18px * 2 = 186pxFlex3: 150px + 18px * 3 = 204pxFlex4: 150px + 18px * 4 = 222px最终效果如下图所示:前面两个示例向大家演示了,Flex项目均分和非均分Flex容器剩余的空间。从示例中可以看出来,flex-grow的值都是大于或等于1的数值。事实上,flex-grow还可以设置小数。比如,给所有Flex项目设置flex-grow的值为0.2。由于Flex项目的flex-grow的值都相等,所以扩展的值也是一样的,唯一不同的是,所有的Flex项目并没有把Flex容器剩余空间全部分完。就我们这个示例而言,四个Flex项目的flex-grow加起来的值是0.8,小于1。换句话说,四个Flex项目只分配了Flex容器剩余空度的80%,按上例的数据来计算,即是180px * .8 = 144px(只分去了144px),而且每个Flex项目分得都是36px(144px / 4 = 36px 或者 144px * 0.2 / 0.8 = 36px)。最终效果如下图所示:上面的示例中,flex-basis都显式的设置了值。事实上,flex-grow和flex-basis会相互影响的。这也令我们的Flex项目计算变得复杂化了。比如说,flex-basis的值为auto,而且没有给Flex项目显式的设置width。根据前面的内容我们可以得知,此时Flex项目的大小都取决于其内容的max-content大小。此时Flex容器的剩余的空间将由浏览器根据Flex项目的内容宽度来计算。比如接下来的这个示例,四个Flex项目都是由其内容max-content大小决定。同时将flex-grow都设置为1(均匀分配Flex容器剩余空间)。具体的数据由下图所示(Chrome浏览器计算得出的值):特别注意,不同浏览器对小数位的计算略有差异,上图是在Chrome浏览器下得出的值。所以最终加起来的值略大于Flex容器的宽度708px。针对这样的使用场景,如果你想让所有Flex项目具有相同的尺寸,那么可以显式的设置Flex项目的flex-basis值为0(flex: 1 1 0)。从flex-basis一节中可以得知,当flex-basis值为0时,表示所有空间都可以用来分配,而且flex-grow具有相同的值,因此Flex项目可以获取均匀的空间。如此一来Flex项目宽度将会相同。flex-basis还可以由其他值为设置Flex项目的宽度,这里不再一一演示。感兴趣的同学可以自己根据flex-basis的取值写测试用例。换句话说,如果你理解了前面介绍的flex-basis内容,就能更好的理解flex-grow和flex-basis相结合对Flex项目分配Flex容器剩余空间的计算。也将不会再感到困惑。flex-shrinkflex-shrink和flex-grow类似,只不过flex-shrink是用来控制Flex项目缩放因子。当所有Flex项目宽度之和大于Flex容器时,将会溢出容器(flex-wrap为nowrap时),flex-shrink就可以根据Flex项目设置的数值比例来分配Flex容器的不足空间,也就是按比例因子缩小自身的宽度,以免溢出Flex容器。flex-shrink接收一个<number>值,其默认值为1。也就是说,只要容器宽度不足够容纳所有Flex项目时,所有Flex项目默认都会收缩。如果你不想让Flex项目进行收缩时,可以设置其值为0,此时Flex项目始终会保持原始的fit-content宽度。同样的,flex-shrink也不接受一个负值做为属性值。基于上面的示例,简单的调整一下参数,所有Flex项目都设置了flex: 0 0 300px,可以看到Flex项目溢出了Flex容器:在这个示例中,由于flex-shrink显式的设置了值为0,Flex项目不会进行收缩。如果你想让Flex项目进行收缩,那么可以把flex-shrink设置为1。从上图的结果我们可以看出,当所有Flex项目的flex-shrink都设置为相同的值,比如1,将会均分Flex容器不足空间。比如此例,所有Flex项目的宽度总和是1200px(flex-basis: 300px),而Flex容器宽度是780px(width: 800px,padding: 10px,盒模型是border-box),可以算出Flex容器不足空间为420px(1200 - 780 = 420px),因为所有Flex项目的flex-shrink为1,其告诉浏览器,将Flex容器不足空间均分成四份,那么每份则是105px(420 / 4 = 105px),这个时候Flex项目就会自动缩放105px,其宽度就由当初的300px变成了195px(300 - 105 = 195px)。这个示例演示的是Flex项目设置的值都是相同的值,其最终结果是将会均分Flex容器不足空间。其实flex-shrink也可以像flex-grow一样,为不同的Flex项目设置不同的比例因子。比如1:2:3:4,这个时候Flex项目就不会均分了,而是按自己的比例进行收缩,比例因子越大,收缩的将越多。如下图所示:就上图而言,所有Flex项目的flex-shrink之和为10(1 + 2 + 3 + 4 = 10),此时把Flex容器不足空间420px分成了十份,每一份42px(420 / 10 = 42px),每个Flex项目按照自己的收缩因子相应的去收缩对应的宽度,此时每个Flex项目的宽度就变成:Flex1: 300 - 42 * 1 = 258pxFlex2: 300 - 42 * 2 = 216pxFlex3: 300 - 42 * 3 = 174pxFlex4: 300 - 42 * 4 = 132px按照该原理来计算的话,当某个Flex项目的收缩因子设置较大时,就有可能会出现小于0的现象。基于上例,如果把第四个Flex项目的flex-shrink设置为15。这样一来,四个Flex项目的收缩因子就变成:1:2:3:15。也就是说把Flex容器不足空间分成了21份,每份占据的宽度是20px(420 / 21 = 20px)。那么Flex项目的宽度就会出现0的现象(300 - 15 * 20 = 0)。这个时候会不会出现无空间容纳Flex项目的内容呢?事实上并不会这样:在Flexbox布局当中,会阻止Flex项目元素宽度缩小至0。此时Flex项目会以min-content的大小进行计算,这个大小是它们利用任何可以利用的自动断行机会后所变成的如果某个Flex项目按照收缩因子计算得出宽度趋近于0时,Flex项目将会按照该元素的min-content的大小来设置宽度,同时这个宽度将会转嫁到其他的Flex项目,再按相应的收缩因子进行收缩。比如上例,Flex项目四,其flex-shrink为15,但其宽度最终是以min-content来计算(在该例中,Chrome浏览器渲染的宽度大约是22.09px)。而这个22.09px最终按照1:2:3的比例分配给了Flex项目一至三(Flex1,Flex2和Flex3)。对应的Flex项目宽度就变成:Flex1: 300 - 20 * 1 - 22.09 / 6 * 1 = 276.334pxFlex2: 300 - 20 * 2 - 22.09 / 6 * 2 = 252.636pxFlex3: 300 - 20 * 3 - 22.09 / 6 * 3 = 228.955pxFlex4: min-content,在该例中大约是22.09px对于该情形,计算相对而言就更为复杂一些了。但浏览器会很聪明的帮你处理这些场景,会倾向于给你合理的结果。只不过大家需要知道这样的一个细节,碰到类似的场景才不会一脸蒙逼(^_^)。flex-grow可以设置一个小于0的值,同样的,flex-shrink也可以设置一个小于0的值,比如我们给所有的Flex项目设置flex-shrink的值为0.2,你将看到的结果如下:从结果的示例图中我们可以看出来,当所有Flex项目的收缩因子(flex-shrink)总和小于1时,Flex容器不足空间不会完全分配完,依旧会溢出Flex容器。好比该例,flex-shrink的总和是.8,分配了Flex容器剩余空间420px的80%,即336px(还有84px剩余空间未完全分配完),由于每个Flex项目的收缩因子是相同的,好比前面的示例,都设置了1类似,把分配的空间336px均分为四份,也就是84px,因此每个Flex项目的宽度由当初的300px变成了216px(300 - 84 = 216px)。这个其实和flex-grow类似,只不过flex-shrink只是收缩而以。Flex项目计算公式Flex项目伸缩计算是一个较为复杂的过程,但它们之间还是有据可查。@Chris和@Otree对该方面就有深入的研究。他们给Flex项目的计算总结出了一套计算公式,具体公式如下:@Chris还依据这套公式写了一个JavaScript的案例,来模拟Flex项目计算。flex常见的值大部分情形之下,我们都是使用flex属性来设置Flex项目的伸缩的值。其常见值的效果有:flex: 0 auto和flex:initial,这两个值与flex: 0 1 auto相同,也是初始值。会根据width属性决定Flex项目的尺寸。当Flex容器有剩余空间时,Flex项目无法扩展;当Flex容器有不足空间时,Flex项目收缩到其最小值min-content。flex: auto与flex: 1 1 auto相同。Flex项目会根据width来决定大小,但是完全可以扩展Flex容器剩余的空间。如果所有Flex项目均为flex: auto、flex:initial或flex: none,则Flex项目尺寸决定后,Flex容器剩余空间会被平均分给是flex:a uto的Flex项目。flex: none与flex: 0 0 auto相同。Flex项目根据width决定大小,但是完全不可伸缩,其效果和initial类似,这种情况下,即使在Flex容器空间不够而溢出的情况之下,Flex项目也不会收缩。flex: <positive-number>(正数)与flex: 1 0px相同。该值使Flex项目可伸缩,并将flex-basis值设置为0,导致Flex项目会根据设置的比例因子来计算Flex容器的剩余空间。如果所有Flex项目都使用该模式,则它们的尺寸会正比于指定的伸缩比。默认状态下,伸缩项目不会收缩至比其最小内容尺寸(最长的英文词或是固定尺寸元素的长度)更小。可以靠设置min-width属性来改变这个默认状态。如何掌握Flex项目的大小通过前面的内容介绍,应该可以了解到Flex项目的大小计算是非常的复杂。如果要真正的理解Flex项目是如何工作的话,最为关键的是理解有多少东西参与影响Flex项目。我们可以按下面这样的方式来进行思考。怎么设置Flex项目的基本大小在CSS中设置一个元素的基本大小可以通过width来设置,或者通过min-width或max-width来设置元素的最小或最大宽度,在未来我们还可以通过content、min-content、max-content或fit-content等关键词来设置元素的大小。对于Flex项目,我们还可以通过flex-basis设置Flex项目大小。对于如何设置Flex项目的基本大小,我们可以围绕以下几点来进行思考:flex-basis的值是auto?Flex项目显式的设置了宽度吗?如果设置了,Flex项目的大小将会基于设置的宽度flex-basis的值是auto还是content?如果是auto,Flex项目的大小为原始大小flex-basis的值是0的长度单位吗?如果是这样那这就是Flex项目的大小flex-basis的值是0呢? 如果是这样,则Flex项目的大小不在Flex容器空间分配计算的考虑之内更为具体的可以参阅flex-basis相关的介绍。我们有可用空间吗?如果Flex容器没有剩余空间,Flex项目就不会扩展;如果Flex容器没有不足空间,Flex项目就不会收缩:所有的Flex项目的宽度总和是否小于Flex容器的总宽度? 如果是这样,那么Flex容器有剩余空间,flex-grow会发挥作用, 具体如何发挥作用,可以参阅flex-grow相关的介绍所有的Flex项目的宽度总和是否大于Flex容器的总宽度? 如果是这样,那么Flex容器有不足空间,flex-shrink会发挥作用,具体如何发挥作用,可以参阅flex-shrink相关的介绍分配空间的其他方式如果我们不想把Flex容器的剩余空间扩展到Flex项目中,我们可以使用Flexbox中其他属性,比如justify-content属性来分配剩余空间。当然也可以给Flex项目设置margin值为处理Flex容器剩余空间。不过这一部分没有在这里阐述,如果感兴趣的话,不仿阅读一下Flexbox相关的介绍。总结很久以为,一直以为Flexbox布局中,Flex项目都会根据Flex容器自动计算。而事实上呢?正如文章中介绍的一样,Flex项目的计算是相当的复杂。设置Flex项目大小的值以及flex-basis、flex-grow和flex-shrink的设置都会对其有较大的影响,而且它们的组合场景也是非常的多,并且不同的场景会造成不一样的结果。当然,文章中所介绍的内容或许没有覆盖到所有的场景,但这些基本的演示或许能帮助大家更好的理解Flex项目是如何计算的。最后希望该文对大家有所帮助,如果你有更深的了解欢迎在下面的评论中与我一起分享。如果文章中有不对之处,还望各路大婶拍正。本文作者:大漠_w3cplus阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 23, 2018 · 2 min · jiezi

浏览器缓存是什么?它的机制又是什么?

对于浏览器缓存,相信很多开发者对它真的是又爱又恨。一方面极大地提升了用户体验,而另一方面有时会因为读取了缓存而展示了“错误”的东西,而在开发过程中千方百计地想把缓存禁掉。那么浏览器缓存究竟是个什么样的神奇玩意呢?什么是浏览器缓存: 简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个URL地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。 比如说,在页面请求之后,web资源都被缓存了,在后面的重复请求中,许多资源都是直接从缓存中读取的(from cache),而不是重新去向服务器请求。为什么使用缓存:(1)减少网络带宽消耗 无论对于网站运营者或者用户,带宽都代表着金钱,过多的带宽消耗,只会便宜了网络运营商。当Web缓存副本被使用时,只会产生极小的网络流量,可以有效的降低运营成本。(2)降低服务器压力 给网络资源设定有效期之后,用户可以重复使用本地的缓存,减少对源服务器的请求,间接降低服务器的压力。同时,搜索引擎的爬虫机器人也能根据过期机制降低爬取的频率,也能有效降低服务器的压力。(3)减少网络延迟,加快页面打开速度 带宽对于个人网站运营者来说是十分重要,而对于大型的互联网公司来说,可能有时因为钱多而真的不在乎。那Web缓存还有作用吗?答案是肯定的,对于最终用户,缓存的使用能够明显加快页面打开速度,达到更好的体验。浏览器端的缓存规则: 对于浏览器端的缓存来讲,这些规则是在HTTP协议头和HTML页面的Meta标签中定义的。他们分别从新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。 新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下条件,浏览器会认为它是有效的,足够新的: 1. 含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内; 2. 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度; 满足以上两个情况的一种,浏览器会直接从缓存中获取副本并渲染。 校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。浏览器缓存的控制: (1)使用HTML Meta 标签 Web开发者可以在HTML页面的<head>节点中加入<meta>标签,代码如下<meta http-equiv=“Pragma” content=“no-cache”> <!- Pragma是http1.0版本中给客户端设定缓存方式之一,具体作用会在后面详细介绍 –> 上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。但是!这里有个坑… 事实上这种禁用缓存的形式用处很有限: a. 仅有IE才能识别这段meta标签含义,其它主流浏览器仅识别“Cache-Control: no-store”的meta标签。 b. 在IE中识别到该meta标签含义,并不一定会在请求字段加上Pragma,但的确会让当前页面每次都发新请求(仅限页面,页面上的资源则不受影响)。 (2)使用缓存有关的HTTP消息报头 在这里就需要先跟大家介绍一下HTTP的相关知识。一个URI的完整HTTP协议交互过程是由HTTP请求和HTTP响应组成的。有关HTTP详细内容可参考《Hypertext Transfer Protocol — HTTP/1.1》、《HTTP协议详解》等。 在HTTP请求和响应的消息报头中,常见的与缓存有关的消息报头有: 在我们对HTTP请求头和响应头的部分字段有了一定的认识之后,我们接下来就来讨论不同字段之间的关系和区别: · Cache-Control与Expires Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。 · Last-Modified/ETag与Cache-Control/Expires 配置Last-Modified/ETag的情况下,浏览器再次访问统一URI的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器; Cache-Control/Expires则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用时,Cache-Control/Expires的优先级要高于Last-Modified/ETag。即当本地副本根据Cache-Control/Expires发现还在有效期内时,则不会再次发送请求去服务器询问修改时间(Last-Modified)或实体标识(Etag)了。 一般情况下,使用Cache-Control/Expires会配合Last-Modified/ETag一起使用,因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时Last-Modified/ETag将能够很好利用304,从而减少响应开销。 · Last-Modified与ETag你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。Etag的服务器生成规则和强弱Etag的相关内容可以参考,《互动百科-Etag》和《HTTP Header definition》,这里不再深入。 注意: 1. Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存,但是需要注意的是分布式系统里多台机器间文件的last-modified必须保持一致,以免负载均衡到不同机器导致比对失败,Yahoo建议分布式系统尽量关闭掉Etag(每台机器生成的etag都会不一样,因为除了 last-modified、inode 也很难保持一致)。 2. Last-Modified/If-Modified-Since要配合Cache-Control使用,Etag/If-None-Match也要配合Cache-Control使用。浏览器HTTP请求流程: 第一次请求: 再次请求: 用户行为与缓存: 浏览器缓存行为还有用户的行为有关,具体情况如下:如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加Java进阶交流群:895244712 ,有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。不能缓存的请求: 当然并不是所有请求都能被缓存,无法被浏览器缓存的请求如下: 1. HTTP信息头中包含Cache-Control:no-cache,pragma:no-cache(HTTP1.0),或Cache-Control:max-age=0等告诉浏览器不用缓存的请求 2. 需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的 3. 经过HTTPS安全加密的请求(有人也经过测试发现,ie其实在头部加入Cache-Control:max-age信息,firefox在头部加入Cache-Control:Public之后,能够对HTTPS的资源进行缓存,参考《HTTPS的七个误解》) 4. POST请求无法被缓存 5. HTTP响应头中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的请求无法被缓存参考资料:1. http://www.cnblogs.com/520yang/articles/4807408.html 浏览器 HTTP 协议缓存机制详解[](http://www.cnblogs.com/520yan...2. https://my.oschina.net/leejun2005/blog/369148  浏览器 HTTP 协议缓存机制详解3. http://web.jobbole.com/82997/ 浏览器缓存机制浅析4. http://www.alloyteam.com/2012/03/web-cache-2-browser-cache/ Web浏览器的缓存机制 5. http://www.cnblogs.com/vajoy/p/5341664.html 浅谈浏览器http的缓存机制6. http://mp.weixin.qq.com/s/yf0pWRFM7v9Ru3D9_JhGPQ 浏览器缓存机制剖析 ...

November 17, 2018 · 1 min · jiezi