浏览器进程/线程

进程与线程

概括
  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
进程之间的通信

五种通讯方式总结

  • 管道:速度慢,容量有限,只有父子进程能通讯

  • FIFO:任何进程间都能通讯,但速度慢

  • 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

  • 信号量:不能传递复杂消息,只能用来同步

  • 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存。

详细介绍

由于浏览器每一个tab页面即为进程,页面间的通信即为进程的通信
1. window.open
window.open(URL,name,features,replace)

   URL:(可选)为空则打开空白新窗口。

   Name:(可选)子窗口的句柄,声明新窗口的名称。若名字已存在则在指定窗口打开。仅当同源或该脚本打开了这个窗口才可以通过名字进行指定窗口。
   也可以设置成 _blank 空白窗口,_parent直接父级窗口,_top顶层窗口,

   Features (可选) 声明新窗口要显示的标准浏览器的特征(必须是打开空白窗口)。

扫描二维码关注公众号,回复: 2683895 查看本文章

   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运行机制最全面的一次梳理

猜你喜欢

转载自blog.csdn.net/lay136362687/article/details/81198076
今日推荐