前端基础知识
DNS 解析
DNS(Domain Name System,域名系统)是指由域名到ip地址的映射,DNS协议运行在udp协议之上,使用端口号53。该域名->ip地址的映射分两种:静态映射(本地维护一个映射表),动态映射(需要一套域名解析系统)
主机名 : 三级域名.二级域名.顶级域名
一个完整的域名由二个或二个以上部分组成,各部分之间用英文的句号".“来分隔,倒数第一个”.“的右边部分称为顶级域名(TLD,也称为一级域名,包含一个合法字符串,和一个域名后缀),顶级域名的左边部分字符串到下个”."为止称为二级域名(SLD),二级域名的左边部分称为三级域名,以此类推,每一级的域名控制它下一级域名的分配。
DNS原理
本地主机、域名服务器中都有缓存,保存了域名->ip地址的映射关系,能加快查询速度。本地主机仅仅向本地DNS进行查询,而本地DNS则分别需要向根、顶级、二级、三级等域名服务器查询,最终得到查询结果,返回给客户端。下面举个实例进行分析
当主机A需要和 www.abc.com
主机进行通信时,它就需要知道www.abc.com
域名所对应的ip地址。下面是它需要做的几个查询步骤:
(1)主机A先查找本地缓存,看有没有该主机所对应的ip地址,若没有,再向本地域名服务器(DNS)发出查询请求;
(2)同样,本地DNS先查找本地缓存,看有没有该主机所对应的ip地址,若没有,则直接向根DNS发出请求;
(3)根DNS回复本地DNS,告诉本地DNS “.com”顶级域名服务器的ip地址;
(4)本地DNS收到.com域名服务器的ip地址之后,向该.com DNS 发出查询请求,查询.abc.com的二级域名服务器的ip地址;
(5)同理,.com告诉本地DNS .abc.com的地址;
(6)本地DNS向.abc.com发出查询请求,询问www.abc.com
的地址;
(7).abc.com二级域名服务器告诉本地DNS www.abc.com
主机的ip地址;
(8)本地DNS得到主机www.abc.com
的ip地址后,在本地缓存下来,并发送给主机A;
(9)主机A就得到www.abc.com
主机的ip地址。
DNS劫持
DNS劫持是通过劫持DNS服务器,从而修改某域名的解析结果,将错误ip地址反馈给客户,导致用户不能正常上网或者访问的是虚假网址,即:DNS劫持的目的就是为了告诉用户一个错误的ip地址,这样就能达到钓鱼、窃取用户信息的目的。它发生在本主机与本地DNS服务器之间的通信。
DNS劫持的表现为,你访问www.abc.com
网址是,结果却被解析成百度的首页。
浏览器加载网页的顺序
浏览器加载和渲染html的顺序
-
用户输入网址(假设是个 HTML 页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回 HTML 文件;
-
浏览器开始载入 HTML 代码,发现 标签内有一个 标签引用外部 CSS 文件;
-
浏览器又发出 CSS 文件的请求,服务器返回这个 CSS 文件;
-
浏览器继续载入 HTML 中 部分的代码,并且 CSS 文件已经拿到手了,可以开始渲染页面了;
-
浏览器在代码中发现一个 标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;
-
服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
-
浏览器发现了一个包含一行 JavaScript 代码的
各个步骤的加载渲染时间,可以通过各个浏览器的插件跟踪到,例如IE的 httpwatch、火狐的firebug等等。
如何加快HTML页面加载速度
1. 页面减肥
a. 页面的肥瘦是影响加载速度最重要的因素。
b. 删除不必要的空格、注释。
c. 将inline的script和css移到外部文件。
d. 可以使用HTML Tidy来给HTML减肥,还可以使用一些压缩工具来给JavaScript减肥。
2. 减少文件数量
a. 减少页面上引用的文件数量可以减少HTTP连接数。
b. 许多JavaScript、CSS文件可以合并最好合并,人家财帮子都把自己的JavaScript. functions和Prototype.js合并到一个base.js文件里去了。
3. 减少域名查询
a. DNS查询和解析域名也是消耗时间的,所以要减少对外部JavaScript、CSS、图片等资源的引用,不同域名的使用越少越好。
4. 缓存重用数据
a. 对重复使用的数据进行缓存。
5. 优化页面元素加载顺序
a. 首先加载页面最初显示的内容和与之相关的JavaScript和CSS,然后加载HTML相关的东西,像什么不是最初显示相关的图片、flash、视频等很肥的资源就最后加载。
6. 减少inline JavaScript的数量
a. 浏览器parser会假设inline JavaScript会改变页面结构,所以使用inline JavaScript开销较大。
b. 不要使用document.write()这种输出内容的方法,使用现代W3C DOM方法来为现代浏览器处理页面内容。
7. 使用现代CSS和合法的标签
a. 使用现代CSS来减少标签和图像,例如使用现代CSS+文字完全可以替代一些只有文字的图片。
b. 使用合法的标签避免浏览器解析HTML时做“error correction”等操作,还可以被HTML Tidy来给HTML减肥。
8. Chunk your content
a. 不要使用嵌套table,而使用非嵌套table或者div。将基于大块嵌套的table的layout分解成多个小table,这样就不需要等到整个页面(或大table)内容全部加载完才显示。
9. 指定图像和table的大小
a. 如果浏览器可以立即决定图像或table的大小,那么它就可以马上显示页面而不要重新做一些布局安排的工作。
b. 这不仅加快了页面的显示,也预防了页面完成加载后布局的一些不当的改变。
c. image使用height和width。
JS线程
参考文章:https://segmentfault.com/a/1190000013119813
浏览器包括4个进程:
- 主进程(Browser进程),浏览器只有一个主进程,负责资源下载,界面展示等主要基础功能
- GPU进程,负责3D图示绘制
- 第三方插件进程,负责第三方插件处理
- 渲染进程(Renderer进程),负责js执行,页面渲染等功能,也是本章重点内容
渲染进程主要包括GUI渲染线程、Js引擎线程、事件循环线程、定时器线程、http异步线程。
GUI渲染线程
先看看浏览器得到一个网站资源后干了哪些事:
- 首先浏览器会解析html代码(实际上html代码本质是字符串)转化为浏览器认识的节点,生成DOM树,也就是DOM Tree
- 然后解析css,生成CSSOM(CSS规则树)
- 把DOM Tree 和CSSOM结合,生成Rendering Tree(渲染树)
GUI就是来干这个事情的,如果修改了一些元素的颜色或者背景色,页面就会重绘(Repaint),如果修改元素的尺寸,页面就会回流(Reflow),当页面需要Repaing和Reflow时GUI多会执行,进行页面绘制。
这里提示一点:Reflow比Repaint的成本更高,在js性能优化中会将如何避免Reflow和Repaint
JS引擎线程
js引擎线程就是js内核,负责解析与执行js代码,也称为主线程。浏览器同时只能有一个JS引擎线程在运行JS程序,所以js是单线程运行的。
需要注意的是,js引擎线程和GUI渲染线程同时只能有一个工作,js引擎线程会阻塞GUI渲染线程
<html>
<body>
<div id="div1"> a </div>
<script>
document.getElementById('div1').innerHTML = 'b'
</script>
<div id='div2'> div2 </div>
</body>
</html>
在浏览器渲染的时候遇到
事件循环线程
事件循环线程用来管理控制事件循环,并且管理着一个事件队列(task queue),当js执行碰到事件绑定和一些异步操作时,会把对应的事件添加到对应的线程中(比如定时器操作,便把定时器事件添加到定时器线程),等异步事件有了结果,便把他们的回调操作添加到事件队列,等待js引擎线程空闲时来处理。
定时器线程
由于js是单线程运行,所以不能抽出时间来计时,只能另开辟一个线程来处理定时器任务,等计时完成,把定时器要执行的操作添加到事件任务队列尾,等待js引擎线程来处理。这个线程就是定时器线程。
异步请求线程
当执行到一个http异步请求时,便把异步请求事件添加到异步请求线程,等收到响应(准确来说应该是http状态变化),把回调函数添加到事件队列,等待js引擎线程来执行。
Event Loop
上面介绍了渲染进程中的5个主要的线程,可能看完上面对各个线程简单的介绍,还有点不明白他们之间到底怎么协作工作的,下面就从Event Loop的角度来聊一聊他们之间是怎样那么愉快合作的。
已经知道了js是单线程运行的,也知道js中有同步操作和异步操作。同步和异步大家应该很熟了,不多介绍。
同步操作运行在js引擎线程(主线程)上,会形成一个执行栈,而异步操作则在他们对应的异步线程上处理(比如:定时操作在定时器线程上;http请求则在异步请求线程上处理)。
而事件循环线程则监视着这些异步线程们,等异步线程们里面的操作有了结果(比如:定时器计时完成,或者http请求获取到响应),便把他们的毁掉函数添加到事件队列尾部,整个过程中执行栈、事件队列就构成Event Loop。
有关定时器(setTimeout、setInterval)的更多趣事
定时器会按照规定时间执行吗?
定时器是规定在一段时间之后执行一段代码,但是在js执行中不会准确无误的按照预期的时间去执行定时器里面的代码。
一个原因是W3C标准规定setTimeout中最小的时间周期是4毫秒,凡是低于4ms的时间间隔都按照4ms来处理。
其实还有一个重要的原因,如果仔细看上面的文章,大家应该会想到在js执行的时候,主线程碰到定时器的时候,是不会直接处理的,应该是先把定时器事件交给定时器线程去处理,这时主线程继续执行下面的代码,同时定时器线程开始计时处理,等到计时完毕,事件循环线程会把定时器要执行的操作放在事件队列末尾,等主线程空闲的时候再来执行事件队列里面的操作。
这样js碰到定时器,会交给定时器线程处理,然后等计时完毕,定时器里面的操作添加到事件队列,等主线程空闲去执行,主线程执行的时候又会发遇到定时器,这是又开始执行上面的一系列操作。
你会发现,这样做会在每一次定时器执行完毕才开始下一个定时器,其中的误差只是等待主线程空闲所需要等待的时间。
而setInterval是规定每隔固定的时间就往定时器线程中推入一个事件,这样做有一个问题,就是累积效应。
- 累积效应:就是如果定时器里面的代码执行所需的时间大于定时器的执行周期,就会出现累计效应,简单来说就是上一次定时器里面的操作还没执行完毕,下一次定时器事件又来了
累积效应会导致有些事件丢失
macrotask与microtask
microtask是Promise里一个新的概念。
macrotask
- macrotask中的事件都是放在一个事件队列中的,而这个队列由事件触发线程维护
- macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
- 每一个task会从头到尾将这个任务执行完毕,不会执行其它
- 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染
microtask
- microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务
- microtask中的所有微任务都是添加到微任务队列(Job Queues)中,等待当前macrotask执行完毕后执行,而这个队列由JS引擎线程维护
- 在当前task任务后,下一个task之前,在渲染之前执行
- 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
- 也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)
所以js运行过程:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)