概述
进程与线程
概括
- 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
- 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
进程之间的通信
五种通讯方式总结
管道:速度慢,容量有限,只有父子进程能通讯
FIFO:任何进程间都能通讯,但速度慢
消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
信号量:不能传递复杂消息,只能用来同步
共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存。
详细介绍
由于浏览器每一个tab页面即为进程,页面间的通信即为进程的通信
1. window.open
window.open(URL,name,features,replace)
URL:(可选)为空则打开空白新窗口。
Name:(可选)子窗口的句柄,声明新窗口的名称。若名字已存在则在指定窗口打开。仅当同源或该脚本打开了这个窗口才可以通过名字进行指定窗口。
也可以设置成 _blank 空白窗口,_parent直接父级窗口,_top顶层窗口,
Features (可选) 声明新窗口要显示的标准浏览器的特征(必须是打开空白窗口)。
Replace(可选) 为true的话则替换浏览历史中的当前条目(返回回不去),默认为false,创建新条目。
实现进程之间通信
window.open方法返回打开URL对应窗口的Window对象的引用,当URL为空或不存在的时候返回Window about blank(FF中测试), 当被拦截的时候返回undefined(opera中测试)。借这个返回值来实现页面间的通信。
注意顺序 test.html -> parent.html -> child.html
在test.html中用newWindow取得parent.html中Window的引用
var newWindow = window.open('parent.html', 'parent');
通过newWindow在test.html中操作parent.html。(前提是parent.html是由test.html打开的)
A.关闭parent.html
newWindow.close();
B.操作parent.html中的DOM
newWindow.document.body.innerHTML = 'your code here';
C.控制parent.html跳转(和上面的效果差不多)
newWindow.location.href='child.html';
//绝对URL也行 newWindow.location.href = 'http://www.baidu.com';
D.parent.html通过window.opener反向操作test.html (在parent.html中)
window.opener.document.body.innerHTML = '哈哈,我也能操作你!';
由于parent.html由test.html打开,因此parent.html的window.opener指向test.html的Window对象。为了维护这种联系,并不能用window.opener.close()来关闭test.html,并且当test.html被手动关闭的时候,parent.html的window.opener变成了null。值得注意的是parent.html依旧可以控制test.html的跳转,并且始终指向test.html的标签页(只要没关)。
有些浏览器(如IE8和Chrome)会在独立的进程中运行每一个标签页。当一个标签页打开另一个标签页时,如果两个Window对象之间需要彼此通信,那么新标签页就不能运行在独立的进程中。在Chrome中,将新创建标签页的opener设置为null(解除引用关系),即表示在单独的进程中运行新标签页。
2.利用iframe
父控制子页面
<!-- in test.html -->
<!DOCTYPE html>
<html>
<body onload="load()">
<iframe src="parent.html"></iframe>
<script type="text/javascript">
function load() {
top[0].document.body.innerHTML = 'test.html控制parent.html';
}
</script>
</body>
</html>
<!-- in parent.html -->
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function load() {
parent.document.getElementById('text').innerHTML = '相互作用';//子控制父界面
}
</script>
</head>
<body onload='load()'>
aaa
</body>
</html>
3.postMessage
postMessage()允许在有限通信,在不同源的脚本之间。跨文档消息异步传递
接受两个参数,第一个参数是要传递的消息,第二个参数是指定目标窗口的源(url)。
若指定的源匹配的话,当调用postMessage()方法的时候,在目标窗口的window对象会触发一个message事件的处理函数并且,该事件的处理对象拥有以下属性
- data:消息内容副本
- source:消息源自的window对象
- origin: 一个字符串,指定消息的来源
弹窗拦截
弹窗被浏览器拦截
在广告泛滥的时代,浏览器安全十分重要,因此一般情况下弹窗都会被浏览器的安全策略拦截。
(1) 拦截标准:
对于浏览器判断是用户操作打开的窗口,可以弹出;浏览器判断不是用户操作打开的窗口,会拦截。
(2) 不被拦截的措施:
利用html原生的方法,少用js方法,具体如下
A.绑定事件在a标签上,通过href打开新窗口,设置target=_blank弹出新窗口。如新浪SAE打开代码编辑页(有些浏览器还是会拦截…)。
B.通过form表单的submit()方法(或内部input type=”submit”的click方法),设置target=_blank实现打开新页面。在我遇到的问题当中就是用这个解决第三方支付的跳转(支付完了还要在原页面确认支付成功)。
以上内容转载至博客——js页面间的通信;
浏览器
浏览器是多进程的,有一个主控进程,以及每一个tab页面都会新开一个进程(某些情况下多个tab会合并进程)
进程可能包括主控进程,插件进程,GPU,tab页(浏览器内核)等等
- Browser进程:浏览器的主进程(负责协调、主控),只有一个
- 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
- GPU进程:最多一个,用于3D绘制
- 浏览器渲染进程(内核):默认每个Tab页面一个进程,互不影响,控制页面渲染,脚本执行,事件处理等(有时候会优化,如多个空白tab会合并成一个进程)
多进程浏览器的优点
相比于单进程浏览器,多进程有如下优点:
- 避免单个page crash影响整个浏览器
- 避免第三方插件crash影响整个浏览器
- 多进程充分利用多核优势
- 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性
浏览器渲染进程(内核)
浏览器渲染进程是多线程的。
- js引擎线程
- JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
- GPU渲染线程
- 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
- 定时器线程
- 传说中的setInterval与setTimeout所在线程
- 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
- 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
- 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
- 事件触发线程
- 异步http请求线程
- 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
- 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。
Browser进程和浏览器内核(Renderer进程)的通信过程
- Browser进程收到用户请求,首先需要获取页面内容(譬如通过网络下载资源),随后将该任务通过RendererHost接口传递给Render进程
Renderer进程的Renderer接口收到消息,简单解释后,交给渲染线程,然后开始渲染
- 渲染线程接收请求,加载网页并渲染网页,这其中可能需要Browser进程获取资源和需要GPU进程来帮助渲染
- 当然可能会有JS线程操作DOM(这样可能会造成回流并重绘)
- 最后Render进程将结果传递给Browser进程
- Browser进程接收到结果并将结果绘制出来
Event Loop
* 主线程运行时会产生执行栈,
* 栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕)
* 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调
如此循环
注意,总是要等待栈中的代码执行完毕后才会去读取事件队列中的事件
定时器
JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时。
* W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
* 就算不等待4ms,就算假设0毫秒就推入事件队列,也会先执行begin(因为只有可执行栈内空了后才会主动读取事件队列)。
setTimeout 和 setInterval的区别
因为每次setTimeout计时到后就会去执行,然后执行一段时间后才会继续setTimeout,中间就多了误差
(误差多少与代码执行时间有关)而setInterval则是每次都精确的隔一段时间推入一个事件
(但是,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了)
setTimeInterval的问题
而且setInterval有一些比较致命的问题就是:
- 累计效应(上面提到的),如果setInterval代码在(setInterval)再次添加到队列之前还没有完成执行,
就会导致定时器代码连续运行好几次,而之间没有间隔。 - 就算正常间隔执行,多个setInterval的代码执行时间可能会比预期小(因为代码执行需要一定时间。
解决: 用setTimeout模拟setInterval,或者特殊场合直接用requestAnimationFrame
macrotask与microtask
macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
- 每一个task会从头到尾将这个任务执行完毕,不会执行其它
- 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染
例如::主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask)
microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务
- 也就是说,在当前task任务后,下一个task之前,在渲染之前
- 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
- 也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都 执行完毕(在渲染前)
例如:microtask:Promise,process.nextTick
参考资料及转载 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
最后
以上就是飞快镜子为你收集整理的浏览器进程/线程的全部内容,希望文章能够帮你解决浏览器进程/线程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复