浏览器基本概念
浏览器是用户访问互联网的接口。
本质上,浏览器是方便一般互联网用户通过洁面解析和发送HTTP请求的软件。
浏览器历史
1.1991年, 世界第一个浏览器WorldWideWeb (后改为Nexus) ,功能简单,不支持图片
2.1993年,Mosaic浏览器出现,可以显示图片,为了区分浏览器是否能显示图片,出现了UserAgent
3.1994年,Mozilla浏览器出现,也就是后来大名鼎鼎的网景浏览器Netscape,它的UserAgent为Mozilla/1.0
4.1995年,IE浏览器出现,为了抢夺市场,UserAgent为Mozilla/1.22
5.1998年,网景浏览器失利,成立Mozilla组织
6.2003年,网景公司解散,Mozilla基金会成立,这个组织推进了后来的Firefox
7. Mozilla开发了Geoko,变成了Firefox,它的UserAgentMozilla/5.0
8.群雄并起,众多公司的浏览器的UserAgent.上都带有Mozilla
9. chrome和safari出现,占有了很大份额
浏览器的组成
用户界面
2. 浏览器引擎
3. 渲染引擎
4. 网络
5. UI后端
6. Js引擎
7. 数据存储用户界面
指用户所看到的部分,界面。 shell部分。
2. 浏览器引擎
浏览器运行中所需要的程序。
3. 渲染引擎
把html和css渲染出来,展现给用户。
4. 网络
网络请求,发送数据。Js引擎
解释和运行js
7. 数据存储
浏览器的缓存
浏览器的主要进程
Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
- 负责浏览器界面显示,与用户交互。如前进,后退等
- 负责各个页面的管理,创建和销毁其他进程
- 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
- 网络资源的管理,下载等
第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
3. GPU进程:最多一个,用于3D绘制等
4. 浏览器内核(浏览器渲染进程)(Renderer进程,内部是多线程的)主要作用为页面渲染,脚本执行,事件处理等。
在浏览器中打开一个网页相当于新起了一个进程(进程内有自己的多线程)
浏览器常驻线程(浏览器内核,也叫渲染进程)
- js引擎线程 (也叫js内核。解释执行js代码、用户输入、网络请求)
- GUI线程 (绘制用户界面、与js主线程是互斥的)
- http网络请求线程 (处理用户的get、post等请求,等返回结果后将回调函数推入任务队列)
- 定时触发器线程 (setTimeout、setInterval等待时间结束后把执行函数推入任务队列中)
- 浏览器事件触发线程 (将click、mouse等交互事件发生后将这些事件放入事件队列中)
JS可以操作DOM元素,进而会影响到GUI的渲染结果,因此JS引擎线程与GUI渲染线程是互斥的。也就是说当JS引擎线程处于运行状态时,GUI渲染线程将处于冻结状态。
JS的执行机制是单线程的。也就是说同一时间只能做一件事。js设计出来就是为了与用户交互,处理DOM,假如js是多线程,同一时间一个线程想要修改DOM,另一个线程想要删除DOM,问题就变得复杂许多,浏览器不知道听谁的。
JavaScript是基于单线程运行的,同时又是可以异步执行的,一般来说这种既是单线程又是异步的语言都是基于事件来驱动的,恰好浏览器就给JavaScript提供了这么一个环境。
stack 栈:用于存放基本类型
基本类型 : Number、String 、Boolean、Null 和 Undefined , Symbol(es6 新增); 基本数据类型是按值访问 由高向低分配,栈内存最大是 8MB,(超出报栈溢出), String:是特殊的栈内存 (向高分配大小不定),由程序员分配。
Heap 堆: 对象是分配在堆里面
Object 、Array 、Function 、Data。
引用类型,值大小不固定。栈内存中存放地址,地址指向堆内存中的对象。是按引用访问的。如下图所示:栈内存中存放的只是该对象的访问地址,在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址大小的固定的,因此可以将内存地址保存在栈内存中。 这样,当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。
Quene队列: JS运行时候包含一个消息队列, 它是要处理的消息列表(事件)
Quene是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈(Call Stack)一清空,Quene上第一位的事件就自动进入主线程。但是,由于存在”定时器”功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能进入到事件队列,读取事件,执行事件相应的回调。
基本上来说,这些事件是响应外部异步事件而排队的(例如鼠标被点击或接收到对HTTP请求的响应,当然如果一个事件没有回调, 那么事件是不会进入Quene排队的。
浏览器端异步事件: DOM事件,http请求,setTimeout等异步事件。
宏任务和微任务
macro-task(宏任务): setTimeout, setInterval, setImmediate, I/O
micro-task(微任务):process.nextTick, 原生Promise(有些实现的promise将then方法放到了宏任务中),Object.observe(已废弃), MutationObserver。
微任务比宏任务优先执行
例:
setTimeout(() => console.log(‘timeout’), 0)
let promise = new Promise((resolve, reject) => {
console.log(‘Promise’)
resolve()
})
promise.then(() => {
console.log(‘resolved’)
})
console.log(‘hi’)
输出顺序:Promise,hi,resolved,timeout
JS执行机制 ( Event loop )
- JS引擎线程
- 事件触发线程
- 定时触发器线程
JS分为同步任务和异步任务
同步任务都在主线程上执行,形成一个执行栈
主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
过程:
- 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数。
2. 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
3. 主线程内的任务执行完毕为空,会去Event Queue读取第一个可运行的Event推入栈中,进入主线程执行。
4. 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
例:
console.log(1);
setTimeout(function() { // A
console.log(‘A’);
setTimeout(function() { // C
console.log(‘C’);
setTimeout(function() { // E
console.log(‘E’);
},0)
},0)
}, 0)
setTimeout(function() { // B
console.log(‘B’);
setTimeout(function() { // D
console.log(‘D’);
},0)
}, 0)
console.log(‘ok’);
以上输出: 1 ok A B C D E
浏览器执行顺序: 主线程( 执行栈 ) 中的代码 => 微任务 => 宏任务
这串代码中,栈的代码执行的时候,当触发 setTimeout 函数时,会将 setTimeout 函数放到队列中。
所以,先输出1和ok。栈里的代码执行完之后,会先读取第一个setTimeout,输出A ,这时发现里面还有一个setTimeout( setTimeout C ),这个setTimeout又会放到队列中去。然后执行setTimeout B ,输出B ,这时还有个setTimeout D ,这个setTimeout又会放到队列中。当栈里代码执行完之后,又会在队列中读取代码,这时读取的是setTimeout C ,放到栈执行,输出C ,紧接着又发现setTimeout E ,这个setTimeout E 又放到队列中排队。栈的代码执行完了,又在队列中读取setTimeout D ,输出 D 。执行完之后,又在队列里读取setTimeout E ,输出 E 。
同步任务
例:
function outer(ot) {
function inner(it) {
console.log(it);
}
inner(20);
console.log(ot);
}
outer(10); // 20 10
- 代码没有执行的时候,执行栈为空栈
1. foo函数执行时,创建了一帧,这帧中包含了形参、局部变量(预编译过程),然后把这一帧压入栈中
2. 然后执行foo函数内代码,执行bar函数
3. 创建新帧,同样有形参、局部变量,压入栈中
4. bar函数执行完毕,弹出栈
5. foo函数执行完毕,弹出栈
6. 执行栈为空
执行栈其实相当于js主线程(所有同步任务都在主线程上执行,形成一个执行栈)
异步任务
$.ajax({
url: ‘localhost: /js/demo.json’,
data: {},
success: function(data) {
console.log(data);
}
});
console.log(‘run’);
- Ajax 进入Event Table,注册回调函数success
2. 执行console.log(‘run’)
3. ajax事件完成http网络请求线程把任务放入Event Queue中
4. 主线程(调用栈)读取任务下执行success函数
重新理解定时器
setTimeout的等待时间结束后并不是直接执行的而是先推入浏览器的一个任务队列,在同步队列结束后在依次调用任务队列中的任务。
setTimeout(function(){}, 0)Js主线程中的执行栈为空时,0毫秒实际上也达不到的,根据HTML标准,最低4毫秒。
setInterval是每隔一段时间把任务放到Event Queue之中。
例1:
var fTime = new Date().getTime()
setTimeout(function(){
console.log(new Date().getTime() - fTime)
},100)
浏览器输出的数值始终比100大。
例2:
var fTime = new Date().getTime()
setTimeout(function(){
console.log(new Date().getTime() - fTime)
},100)
sleep()
function sleep(){
for(var i = 0;i < 10000; i++){
console.log(‘sleep’)
}
}
定时器输出的时间更长了。
渲染 渲染引擎 渲染过程 重排重绘 DOMTree CSSTree RenderTree
渲染:在电脑绘图中指软件从模型生成图像的过程。
渲染引擎: 其职责就是渲染,即在浏览器窗口中将html和css解析和绘制。
过程:解析html和css从而构建DOM树 -> CSS树 -> 构建Render树 -> 布局Render数(Layout/reflow) -> 绘制Render树(paint)。(构建树深度优先)
PS:遇到不显示在页面中的标签不会出现在Render树中(html head标签),css为display:none的dom元素也不会出现在Render树中。
reflow (重排):
触发条件
1.添加或删除DOM元素 ,display:none/block
2.元素位置改变
3.元素尺寸改变(内外边距、边框厚宽高等)
4.内容改变 (内容导致尺寸变化的时候)
5.浏览器窗口尺寸变化
6. offsetWidth,offsetLeft。
repanit(重绘):
触发条件:改变颜色,背景颜色,背景图片,字体颜色。
PS:重绘比重排浪费的效率少一些。
经典问题:为什么css放头部,js放底部
一般来说css资源不会阻碍渲染过程,但JavaScript在旧版本的浏览器中会阻碍渲染进程,JavaScript是单线程,所以JavaScript的加载和执行是从上至下加载执行完一个再继续加载执行下一个文件,会阻塞页面资源的加载,所以一般情况下JavaScript文件放在body标签内底部。如果放在头部渲染进程会暂停,造成“白屏”,但现在浏览器改进了。当渲染被阻塞时,会重新开一个进程继续渲染。
渲染模式
标准模式/混杂模式(怪异模式)
背景:
在多年以前(IE6诞生以前),各浏览器都处于各自比较封闭的发展中(基本没有兼容性可谈)。随着WEB的发展,兼容性问题的解决越来越显得迫切,随即,各浏览器厂商发布了按照标准模式(遵循各厂商制定的统一标准)工作的浏览器,比如IE6就是其中之一。但是考虑到以前建设的网站并不支持标准模式,所以各浏览器在加入标准模式的同时也保留了混杂模式(即以前那种未按照统一标准工作的模式,也叫怪异模式)。
不过现在很少有公司去兼容IE6和IE6以前的版本了,而且现在很多主流浏览器已经不支持怪异模式了,统一用标准模式来解析页面。
三种标准模式的写法
1.<!DOCTYPE html>
2.<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01//EN” “http://www.w3.org/TR/html4/strict.dtd">
3.<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
如果写错或者不写的浏览器就会用怪异模式的方法来渲染页面。
如何判断:
document.compatMode;
CSS1Compat == 标准模式;
BackCompat == 怪异模式;
css3中有一个可以选择盒模型的属性 box-sizing : border-box content-box inherit;
- content-box为W3C标准盒子;
- border-box为IE6混杂模式的盒子;
- inherit从父元素继承;
标准模式下box的区域
怪异模式下怪异模式下可以给行级元素设置宽高;
渲染过程
- 创建Document对象,开始解析web页面。解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段document.readyState = ‘loading’。(document.onreadystatechage监听document.readyState)
2. 遇到link外部css,创建线程异步加载,并继续解析文档。(不会阻塞DOM树解析)
3. 遇到script外部js,并且没有设置async、defer,浏览器加载,并阻塞,等待js加载完成并执行该脚本,然后继续解析文档。
4. 遇到script外部js,并且设置有async、defer,浏览器创建线程加载,并继续解析文档。
对于async属性的脚本,脚本加载完成后立即执行。 ( 异步禁止使用document.write() )
5. 遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。
6. 当文档解析完成,document.readyState = ‘interactive’。
7 .文档解析完成后,所有设置有defer的脚本会按照顺序执行。(注意与async的不同,但同样禁止使用document.write());
8 .document对象触发 DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。
( jQuery的.ready()原理。只能用document.addEventListener监听)
9. 当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = ‘complete’,window对象触发load事件。
10. 从此,以异步响应方式处理用户输入、网络事件等。
经典例题:window.onload 和 $(document).ready 的区别
- 执行时间
.ready 等dom结构加载完了之后开始执行。
.onload 等dom和所有外联引入的资源加载完开始执行
2. 执行次数
.ready 会执行多次
.onload 只会执行一次, 后面写的会覆盖之前的
异步加载JS/按需加载JS
function loadScript(url, callback) {
var oScript = document.createElement(‘script’);
if (oScript.readyState) {
oScript.onreadystatechange = function() {
if (oScript.readyState == “complete” || oScript.readyState == ‘loaded’) {
oScript.onreadystatechange = null;
callback();
}
}
} else {
oScript.onload = function() {
oScript.onload = null;
callback()
}
}
oScript.src = url;
document.body.appendChild(oScript)
}
loadScript(‘1.js’, function() {
demo();
})
从浏览器输入一个网址敲一下回车到整个页面展现在用户面前,发生了什么事情
- 从浏览器接收url到开启网络请求线程( 这一部分可以展开浏览器的机制以及进程与线程之间的关系)
解析URL:
输入URL后,会进行解析(URL的本质就是统一资源定位符)
URL一般包括几大部分:
protocol,协议头,譬如有http,ftp等
host,主机域名或IP地址
port,端口号
path,目录路径
query,即查询参数
fragment,即#后的hash值,一般用来定位到某个位置
2. 开启网络线程到发出一个完整的http请求( 这一部分涉及到dns查询, tcp / ip请求, 五层因特网协议栈等知识)3. 从服务器接收到请求到对应后台接收到请求( 这一部分可能涉及到负载均衡, 安全拦截以及后台内部的处理等等)4. 后台和前台的http交互( 这一部分包括http头部、 响应码、 报文结构、 cookie等知识, 可以提下静态资源的cookie优化, 以及编码解码, 如gzip压缩等)5. 单独拎出来的缓存问题, http的缓存( 这部分包括http缓存头部, etag,catch - control等)6. 浏览器接收到http数据包后的解析流程( 解析html - 词法分析然后解析成dom树、 解析css生成css规则树、 合并成render树, 然后layout、 painting渲染、 复合图层的合成、 GPU绘制、 外链资源的处理、 loaded和domcontentloaded等)7. CSS的可视化格式模型( 元素的渲染规则, 如包含块, 控制框, BFC, IFC等概念)8. JS引擎解析过程( JS的解释阶段, 预处理阶段, 执行阶段生成执行上下文, VO, 作用域链、 回收机制等等)9. 其它( 可以拓展不同的知识模块, 如跨域, web安全, hybrid模式等等内容)
