js的运行机制

浏览器线程

js运作在浏览器中,是单线程的,即js代码始终在一个线程上执行,这个线程称为js引擎线程。
但浏览器是多线程的,除了js引擎线程,它还有:

  • UI渲染线程

  • 浏览器事件触发线程

  • http请求线程

  • EventLoop轮询的处理线程

    ……..

这些线程的作用

  1. js线程用于执行js任务
  2. UI线程用于渲染页面
  3. 浏览器事件触发线程用于控制交互,响应用户
  4. http线程用于处理请求,ajax是委托给浏览器新开一个http线程
  5. EventLoop处理线程用于轮询消息队列

浏览器中的js任务

  1. 执行JavaScript代码
  2. 对用户的输入(包含鼠标点击、键盘输入等等)做出反应
  3. 处理异步的网络请求

理解js单线程

  1. 单线程的含义是js只能在一个线程上运行,也就说,js同时只能执行一个js任务,其它的任务则会排队等待执行。
  2. js是单线程的,并不代表js引擎线程只有一个。js引擎有多个线程,一个主线程,其它的后台配合主线程。
  3. 多线程之间会共享运行资源,浏览器端的js会操作dom,多个线程必然会带来同步的问题,所有js核心选择了单线程来避免处理这个麻烦。js可以操作dom,影响渲染,所以js引擎线程和UI线程是互斥的。这也就解释了js执行时会阻塞页面的渲染。

js的运行机制

(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还存在一个”任务队列”。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

简单说,浏览器的两个线程:一个负责程序本身的运行,称为”主线程”;另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为”Event Loop线程”(可以译为”消息线程”)。

宏任务和微任务

事件轮询其实就是轮流询问宏任务队列和微任务队列

执行顺序:

  1. 先执行同步代码再执行异步代码
  2. 先执行微任务再执行宏任务
常见宏任务: setTimeout 、setInterva、I/O

常见微任务:process.nextTick(nodejs独有) 、 Promise.then 、catch、finally

例子

由于js是单线程的,代码从上往下依次执行。

  1. setTimeout是宏任务,放入宏任务队列
  2. promise构造函数中的代码是同步执行的,但setTimeout是宏任务,又放入宏任务队列,打印promise
  3. 执行同步代码,打印输出33333,接着执行循环代码,输出1到19999
  4. 根据执行优先级,即使在打印1到19999时,定时器已经到了时间,也不会去执行setTimeout。若打印完19999,根据宏任务队列先进先出原则,打印11111
  5. 执行剩下的宏任务,在promise中执行resolve()后才会把回调函数then中的代码放入微任务队列中。此时虽然微任务比宏任务的优先级别高,但此刻正在处理宏任务,最节省资源和快捷的方式是先把当前宏任务执行完毕,于是打印22222
  6. 最后执行then微任务,打印“成功”
setTimeout(() => {
        console.log("11111");
        }, 0);

    let promise = new Promise(resolve => {
      setTimeout(() => {
        resolve();
        console.log("22222");
      }, 0);
      console.log("promise");
    }).then(value => console.log("成功"));
    console.log("33333");
    for(let i=0;i<20000;i++) {
        console.log(i);
    }

//promise
//33333
//0
//...
//19999
//11111
//22222
//成功

web worker

Web Worker是h5新增的,它的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。
在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

使用注意点

  1. 同源限制

分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。(所谓同源是指,域名,协议,端口相同。)

  1. DOM 限制

    Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。

  2. 通信联系

    Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

  3. 脚本限制

    Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

  4. 文件限制

    Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

用法

主线程

  1. 用new命令,Worker()构造函数,创建一个Worker线程

    // main.js
    
    var myworker = new Worker('work.js');
    
  2. 主线程是通过myworker.postMessage()方法向子线程传递消息

    myworker.postMessage('hello world')
    myworker.postMessage({name: 'charming'})
    

    worker.postMessage()方法的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。

  3. 主线程是通过myworker.onmessage()方法监听子线程发回来的消息

    myworker.onmessage  = function (event) {
    	console.log('接收到子线程的消息:' + event.data)
    }
    
  4. 终止子线程

    myworker.terminate()
    

子线程
子线程代码都写在work.js里面

  1. 子线程内部通过监听message接收主线程传递过来的信息。

    self.addEventListener('message', function (e) {
      self.postMessage('从主线程接收到: ' + e.data);
    }, false);
    

    在上面代码中,self表示子线程自身。即子线程的全局对象。等同于下面两种写法

    this.addEventListener('message', function (e) {
      this.postMessage('从主线程接收到: ' + e.data);
    }, false);
    
    addEventListener('message', function (e) {
      postMessage('从主线程接收到: ' + e.data);
    }, false);
    
    

    当然直接用onmessage监听message事件也是可以的

    onmessage = function(e) {
    	postMessage('从主线程接收到: ' + e.data);
    }
    
posted @ 2021-03-03 15:57  Hhhighway  阅读(324)  评论(0编辑  收藏  举报