zoukankan      html  css  js  c++  java
  • 可能是你见过最详细的WebWorker实用指南

    困在笼子里的Web Worker

    在使用Web Worker前我们要了解它的能力边界,让我们避免无谓的撞壁:

    同源限制

    1.1. 以http(s)://协议加载给WebWorker线程运行的脚本时,其URL必须和UI线程所属页面的URL同源;
    1.2. 不能加载客户端本地脚本给WebWorker线程运行(即采用file://协议),即使UI线程所属页面也是本地页面;

    DOM和BOM限制

    1.1. 无法访问UI线程所属页面的任何DOM元素;
    1.2. 可访问如下BOM元素
    1.2.1. XMLHttpRequest/fetch
    1.2.2. setTimeout/clearTimeout
    1.2.3. setInterval/clearInterval
    1.2.4. location,注意该location指向的是WebWorker创建时以UI线程所属页面的当前Location为基础创建的WorkerLocation对象,即使此后页面被多次重定向,该location的信息依然保持不变。
    1.2.5. navigator,注意该navigator指向的是WebWorker创建时以UI线程所属页面的当前Navigator为基础创建的WorkerNavigator对象,即使此后页面被多次重定向,该navigator的信息依然保持不变。

    通信限制,UI线程和WebWorker线程间必须通过消息机制进行通信。

     

    Dedicated Web Worker详解

    Web Worker分为Dedicated Web Worker和Shared Web Worker两类,它们的特性如下:

    Dedicated Web Worker仅为创建它的JSVM进程服务,当其所属的JSVM进程结束该Dedicated Web Worker线程也将结束;

    Shared Web Worker为创建它的JSVM进程所属页面的域名服务,当该域名下的所有JSVM进程均结束时该Shared Web Worker线程才会结束。

     

    基本使用

    UI线程

    const worker = new Worker('work.js') // 若下载失败如404,则会默默地失败不会抛异常,即无法通过try/catch捕获。                                                                                                                      
    const workerWithName = new Worker('work.js', {name: 'worker2'}) // 为Worker线程命名,那么在Worker线程内的代码可通过 self.name 获取该名称。                                                                                        
                                                                                                                                                                                                                                                  
    worker.postMessage('Send message to worker.') // 发送文本消息                                                                                                                                                                     
    worker.postMessage({type: 'message', payload: ['hi']}) // 发送JavaScript对象,会先执行序列化为JSON文本消息再发送,然后在接收端自动反序列化为JavaScript对象。                                                                      
    const uInt8Array = new Uint8Array(new ArrayBuffer(10))                                                                                                                                                                            
    for (let i = 0; i < uint8array.length; ++i) {                                                                                                                                                                                     
      uInt8Array[i] = i * 2                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                 
    worker.postMessage(uInt8Array) // 以先序列化后反序列化的方式发送二进制数据,发送后主线程仍然能访问uInt8Array变量的数据,但会造成性能问题。                                                                                        
    worker.postMessage(uInt8Array, [uInt8Array]) // 以Transferable Objets的方式发送二进制数据,发送后主线程无法访问uInt8Array变量的数据,但不会造成性能问题,适合用于影像、声音和3D等大文件运算。                                     
                                                                                                                                                                                                                                                  
    // 接收worker线程向主线程发送的消息                                                                                                                                                                                               
    worker.onmessage = event => {                                                                                                                                                                                                     
      console.log(event.data)                                                                                                                                                                                                       
    }                                                                                                                                                                                                                                 
    worker.addEventListener('message', event => {                                                                                                                                                                                     
      console.log(event.data)                                                                                                                                                                                                       
    })                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                  
    // 当发送的消息序列化失败时触发该事件。                                                                                                                                                                                           
    worker.onmessageerror = error => console.error(error)                                                                                                                                                                             
                                                                                                                                                                                                                                                  
    // 捕获Worker线程发生的异常                                                                                                                                                                                                       
    worker.onerror = error => {                                                                                                                                                                                                       
      console.error(error)                                                                                                                                                                                                          
    }                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                  
    // 关闭worker线程                                                                                                                                                                                                                 
    worker.terminate()                                                                                                                                                                                                                

    Worker线程

    // Worker线程的全局对象为WorkerGlobalScrip,通过self或this引用。调用全局对象的属性和方法时可以省略全局对象。                                                                                                                      
                                                                                                                                                                                                                                                  
    // 接收主线程向worker线程发送的消息                                                                                                                                                                                               
    self.addEventListener('message', event => {                                                                                                                                                                                       
      console.log(event.data)                                                                                                                                                                                                       
    })                                                                                                                                                                                                                                
    addEventListener('message', event => {                                                                                                                                                                                            
      console.log(event.data)                                                                                                                                                                                                       
    })                                                                                                                                                                                                                                
    this.onmessage = event => {                                                                                                                                                                                                       
      console.log(event.data)                                                                                                                                                                                                       
    }                                                                                                                                                                                                                                 
    // 当发送的消息序列化失败时触发该事件。                                                                                                                                                                                           
    self.onmessageerror = error => console.error(error)                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          // 向主线程发送消息                                                                                                                                                                                                               
    self.postMessage('send text to main worker')                                                                                                                                                                                      
                                                                                                                                                                                                                                                  
    // 结束自身所在的Worker线程                                                                                                                                                                                                       
    self.close()                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                  
    // 导入其他脚本到当前的Worker线程,不要求所引用的脚本必须同域。                                                                                                                                                                   
    self.importScripts('script1.js', 'script2.js')                                                                                                                                                                                    
     

    通过WebWorker运行本页脚本

    方式1——Blob和URL.createObjectURL

    限制:UI线程所属页面不是本地页面,即必须为http(s)://协议。

    const script = `addEventListener('message', event => {                                                                                                                                                                                    
      console.log(event.data)                                                                                                                                                                                                               
      postMessage('echo')                                                                                                                                                                                                                   
    }`                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
    const blob = new Blob([script])                                                                                                                                                                                                           
    const url = URL.createObjectURL(blob)                                                                                                                                                                                                     
    const worker = new Worker(url)                                                                                                                                                                                                            
    worker.onmessage = event => console.log(event.data)                                                                                                                                                                                       
    worker.postMessage('main thread')                                                                                                                                                                                                         
    setTimeout(()=>{                                                                                                                                                                                                                          
      worker.terminate()                                                                                                                                                                                                                    
      URL.revokeObjectURL(url) // 必须手动释放资源,否则需要刷新Browser Context时才会被释放。                                                                                                                                               
    }, 1000)  

    方式2——Data URL

    限制:无法利用JavaScript的ASI机制少写分号。
    优点:即使UI线程所属页面是本地页面也可以执行。

    // 由于Data URL的内容为必须压缩为一行,因此JavaScript无法利用换行符达到分号的效果。                                                                                                                                                       
    const script = `addEventListener('message', event => {                                                                                                                                                                                    
      console.log(event.data);                                                                                                                                                                                                              
      postMessage('echo');                                                                                                                                                                                                                  
    }`                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    const worker = new Worker(`data:,${script}`)                                                                                                                                                                                              
    // 或 const worker = new Worker(`data:application/javascript,${script}`)                                                                                                                                                                  
    worker.onmessage = event => console.log(event.data)                                                                                                                                                                                       
    worker.postMessage('main thread')  
     

    Shared Web Worker详解

    共享线程可以和多个同域页面间通信,当所有相关页面都关闭时共享线程才会被释放。
    这里的多个同域页面包括:

    iframe之间

    浏览器标签页之间

    简单示例

    UI主线程

    const worker = new SharedWorker('./worker.js')                                                                                                                                                                                             
    worker.port.addEventListener('message', e => {                                                                                                                                                                                             
      console.log(e.data)                                                                                                                                                                                                                      
    }, false)                                                                                                                                                                                                                                  
    worker.port.start()  // 连接worker线程                                                                                                                                                                                                     
    worker.port.postMessage('hi')                                                                                                                                                                                                              
                                                                                                                                                                                                                                                  
    setTimeout(()=>{                                                                                                                                                                                                                           
      worker.port.close() // 关闭连接                                                                                                                                                                                                          
    }, 10000)                                                                                                                                                                                                                                  

    Shared Web Worker线程

    let conns = 0                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                  
    // 当UI线程执行worker.port.start()时触发建立连接                                                                                                                                                                                           
    self.addEventListener('connect', e => {                                                                                                                                                                                                    
      const port = e.ports[0]                                                                                                                                                                                                                  
      conns+=1                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                  
      port.addEventListener('message', e => {                                                                                                                                                                                                  
        console.log(e.data)  // 注意console对象指向第一个创建Worker线程的UI线程的console对象。即如果A先创建Worker线程,那么后续B、C等UI线程执行worker.port.postMessage时回显信心依然会发送给A页面。                                            
      })                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
      // 建立双向连接,可相互通信                                                                                                                                                                                                              
      port.start()                                                                                                                                                                                                                             
      port.postMessage('hey')                                                                                                                                                                                                                  
    })                                                                                                                                                                                                                                         

    示例——广播

    UI主线程

       const worker = new SharedWorker('./worker.js')                                                                                                                                                                                             
       worker.port.addEventListener('message', e => {                                                                                                                                                                                             
         console.log('SUM:', e.data)                                                                                                                                                                                                              
       }, false)                                                                                                                                                                                                                                  
       worker.port.start()  // 连接worker线程                                                                                                                                                                                                     
                                                                                                                                                                                                                                                  
       const button = document.createElement('button')                                                                                                                                                                                            
       button.textContent = 'Increment'                                                                                                                                                                                                           
       button.onclick = () => worker.port.postMessage(1)                                                                                                                                                                                          
       document.body.appendChild(button)                                                                                                                                                                                                          

    Shared Web Worker线程

       let sum = 0                                                                                                                                                                                                                                
       const conns = []                                                                                                                                                                                                                           
       self.addEventListener('connect', e => {                                                                                                                                                                                                    
         const port = e.ports[0]                                                                                                                                                                                                                  
         conns.push(port)                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                  
         port.addEventListener('message', e => {                                                                                                                                                                                                  
           sum += e.data                                                                                                                                                                                                                          
           conns.forEach(conn => conn.postMessage(sum))                                                                                                                                                                                           
         })                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
         port.start()                                                                                                                                                                                                                             
       })                                                                                                                                                                                                                                         
     

    即使是Web Worker也阻止不了你卡死浏览器的决心

    通过WebWorker执行计算密集型任务是否就可以肆无忌惮地编写代码,并保证用户界面操作流畅呢?当然不是啦,工具永远只能让你更好地完成工作,但无法禁止你用错。
    只要在频繁持续执行的代码中加入console对象方法的调用,加上一不小心打开Devtools工具,卡死浏览器简直不能再就简单了。这是为什么呢?
    因为UI线程在创建WebWorker线程时会将自身的console对象绑定给WebWorker线程的console属性上,那么WebWorker线程是以同步阻塞方式调用console将参数传递给UI线程的console对象,自然会占用UI线程的处理时间。

    工程化——通过webpack的worker-loader打包代码

    上面说了这么多那实际项目中应该怎么使用呢?或者说如何更好的集成到工程自动化工具——webpack呢?
    worker-loader和shared-worker-loader就是我们想要的。
    通过worker-loader将代码转换为Blob类型,并通过URL.createObjectURL创建url分配给WebWorker线程执行。

    安装loader

    npm install worker-loader -D

    配置Webpack.config.js

    // 处理worker代码的loader必须位于js和ts之前                                                                                                                                                                                              
    {                                                                                                                                                                                                                                        
     test: /.worker.ts$/,                                                                                                                                                                                                                 
     use: {                                                                                                                                                                                                                                 
     loader: 'worker-loader',                                                                                                                                                                                                             
     options: {                                                                                                                                                                                                                           
       name: '[name]:[hash:8].js', // 打包后的chunk的名称                                                                                                                                                                                 
       inline: true // 开启内联模式,将chunk的内容转换为Blob对象内嵌到代码中。                                                                                                                                                            
       }                                                                                                                                                                                                                                    
     }                                                                                                                                                                                                                                      
    },                                                                                                                                                                                                                                      
    {                                                                                                                                                                                                                                        
     test: /.js$/,                                                                                                                                                                                                                         
     use: {                                                                                                                                                                                                                                 
      loader: 'babel-loader'                                                                                                                                                                                                               
     },                                                                                                                                                                                                                                     
     exclude: [path.resolve(__dirname, 'node_modules')]                                                                                                                                                                                     
    },                                                                                                                                                                                                                                       
    {                                                                                                                                                                                                                                        
     test: /.ts(x?)$/,                                                                                                                                                                                                                     
     use: [                                                                                                                                                                                                                                 
       { loader: 'babel-loader' },                                                                                                                                                                                                          
       { loader: 'ts-loader' } // loader顺序从后往前执行                                                                                                                                                                                    
     ],                                                                                                                                                                                                                                     
     exclude: [path.resolve(__dirname, 'node_modules')]                                                                                                                                                                                     
    }                                                                                                                                                                                                                                                

    UI线程代码

    import MyWorker from './my.worker'                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
    const worker = new MyWorker('');                                                                                                                                                                                                         
    worker.postMessage('hi')                                                                                                                                                                                                                 
    worker.addEventListener('message', event => console.log(event.data))   

    Worker线程代码

    cosnt worker: Worker = self as any                                                                                                                                                                                                       
    worker.addEventListener('message', event => console.log(event.data))                                                                                                                                                                     
                                                                                                                                                                                                                                                  
    export default null as any // 标识当前为TS模块,避免报xxx.ts is not a module的异常   
     

    工程化——RPC类库Comlink

    一般场景下我们会这样使用WebWorker,

    UI线程传递参数并调用运算函数;

    在不影响用户界面响应的前提下等待函数返回值;

    获取函数返回值继续后续代码。

    翻译为代码就是

    let arg1 = getArg1()
    let arg2 = getArg2()
    const result = await performCalcuation(arg1, arg2)
    doSomething(result)

    而UI线程和WebWorker线程的消息机制通信机制显然会加大代码复杂度,而Comlink类库恰好能抚平这道伤疤。

    UI线程

    import * as Comlink from 'comlink'                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
    async function init() {                                                                                                                                                                                                                  
     const cl = Comlink.wrap(new Worker('worker.js'))                                                                                                                                                                                     
     console.log(`Counter: ${await cl.counter}`)                                                                                                                                                                                          
     await cl.inc()                                                                                                                                                                                                                       
     console.log(`Counter: ${await cl.counter}`)                                                                                                                                                                                          
    }                                                                                                                                                                                                                                        

    Worker线程

    import * as Comlink from 'comlink'                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
    const obj = {                                                                                                                                                                                                                            
      counter: 0,                                                                                                                                                                                                                            
      inc() {                                                                                                                                                                                                                                
        this.counter+=1                                                                                                                                                                                                                      
      }                                                                                                                                                                                                                                      
    }                                                                                                                                                                                                                                        
    Comlink.expose(obj)

    困在笼子里的Web Worker

    在使用Web Worker前我们要了解它的能力边界,让我们避免无谓的撞壁:

    同源限制

    1.1. 以http(s)://协议加载给WebWorker线程运行的脚本时,其URL必须和UI线程所属页面的URL同源;
    1.2. 不能加载客户端本地脚本给WebWorker线程运行(即采用file://协议),即使UI线程所属页面也是本地页面;

    DOM和BOM限制

    1.1. 无法访问UI线程所属页面的任何DOM元素;
    1.2. 可访问如下BOM元素
    1.2.1. XMLHttpRequest/fetch
    1.2.2. setTimeout/clearTimeout
    1.2.3. setInterval/clearInterval
    1.2.4. location,注意该location指向的是WebWorker创建时以UI线程所属页面的当前Location为基础创建的WorkerLocation对象,即使此后页面被多次重定向,该location的信息依然保持不变。
    1.2.5. navigator,注意该navigator指向的是WebWorker创建时以UI线程所属页面的当前Navigator为基础创建的WorkerNavigator对象,即使此后页面被多次重定向,该navigator的信息依然保持不变。

    通信限制,UI线程和WebWorker线程间必须通过消息机制进行通信。

     

    Dedicated Web Worker详解

    Web Worker分为Dedicated Web Worker和Shared Web Worker两类,它们的特性如下:

    Dedicated Web Worker仅为创建它的JSVM进程服务,当其所属的JSVM进程结束该Dedicated Web Worker线程也将结束;

    Shared Web Worker为创建它的JSVM进程所属页面的域名服务,当该域名下的所有JSVM进程均结束时该Shared Web Worker线程才会结束。

     

    基本使用

    UI线程

    const worker = new Worker('work.js') // 若下载失败如404,则会默默地失败不会抛异常,即无法通过try/catch捕获。                                                                                                                      
    const workerWithName = new Worker('work.js', {name: 'worker2'}) // 为Worker线程命名,那么在Worker线程内的代码可通过 self.name 获取该名称。                                                                                        
                                                                                                                                                                                                                                                  
    worker.postMessage('Send message to worker.') // 发送文本消息                                                                                                                                                                     
    worker.postMessage({type: 'message', payload: ['hi']}) // 发送JavaScript对象,会先执行序列化为JSON文本消息再发送,然后在接收端自动反序列化为JavaScript对象。                                                                      
    const uInt8Array = new Uint8Array(new ArrayBuffer(10))                                                                                                                                                                            
    for (let i = 0; i < uint8array.length; ++i) {                                                                                                                                                                                     
      uInt8Array[i] = i * 2                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                 
    worker.postMessage(uInt8Array) // 以先序列化后反序列化的方式发送二进制数据,发送后主线程仍然能访问uInt8Array变量的数据,但会造成性能问题。                                                                                        
    worker.postMessage(uInt8Array, [uInt8Array]) // 以Transferable Objets的方式发送二进制数据,发送后主线程无法访问uInt8Array变量的数据,但不会造成性能问题,适合用于影像、声音和3D等大文件运算。                                     
                                                                                                                                                                                                                                                  
    // 接收worker线程向主线程发送的消息                                                                                                                                                                                               
    worker.onmessage = event => {                                                                                                                                                                                                     
      console.log(event.data)                                                                                                                                                                                                       
    }                                                                                                                                                                                                                                 
    worker.addEventListener('message', event => {                                                                                                                                                                                     
      console.log(event.data)                                                                                                                                                                                                       
    })                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                  
    // 当发送的消息序列化失败时触发该事件。                                                                                                                                                                                           
    worker.onmessageerror = error => console.error(error)                                                                                                                                                                             
                                                                                                                                                                                                                                                  
    // 捕获Worker线程发生的异常                                                                                                                                                                                                       
    worker.onerror = error => {                                                                                                                                                                                                       
      console.error(error)                                                                                                                                                                                                          
    }                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                  
    // 关闭worker线程                                                                                                                                                                                                                 
    worker.terminate()                                                                                                                                                                                                                

    Worker线程

    // Worker线程的全局对象为WorkerGlobalScrip,通过self或this引用。调用全局对象的属性和方法时可以省略全局对象。                                                                                                                      
                                                                                                                                                                                                                                                  
    // 接收主线程向worker线程发送的消息                                                                                                                                                                                               
    self.addEventListener('message', event => {                                                                                                                                                                                       
      console.log(event.data)                                                                                                                                                                                                       
    })                                                                                                                                                                                                                                
    addEventListener('message', event => {                                                                                                                                                                                            
      console.log(event.data)                                                                                                                                                                                                       
    })                                                                                                                                                                                                                                
    this.onmessage = event => {                                                                                                                                                                                                       
      console.log(event.data)                                                                                                                                                                                                       
    }                                                                                                                                                                                                                                 
    // 当发送的消息序列化失败时触发该事件。                                                                                                                                                                                           
    self.onmessageerror = error => console.error(error)                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          // 向主线程发送消息                                                                                                                                                                                                               
    self.postMessage('send text to main worker')                                                                                                                                                                                      
                                                                                                                                                                                                                                                  
    // 结束自身所在的Worker线程                                                                                                                                                                                                       
    self.close()                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                  
    // 导入其他脚本到当前的Worker线程,不要求所引用的脚本必须同域。                                                                                                                                                                   
    self.importScripts('script1.js', 'script2.js')                                                                                                                                                                                    
     

    通过WebWorker运行本页脚本

    方式1——Blob和URL.createObjectURL

    限制:UI线程所属页面不是本地页面,即必须为http(s)://协议。

    const script = `addEventListener('message', event => {                                                                                                                                                                                    
      console.log(event.data)                                                                                                                                                                                                               
      postMessage('echo')                                                                                                                                                                                                                   
    }`                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
    const blob = new Blob([script])                                                                                                                                                                                                           
    const url = URL.createObjectURL(blob)                                                                                                                                                                                                     
    const worker = new Worker(url)                                                                                                                                                                                                            
    worker.onmessage = event => console.log(event.data)                                                                                                                                                                                       
    worker.postMessage('main thread')                                                                                                                                                                                                         
    setTimeout(()=>{                                                                                                                                                                                                                          
      worker.terminate()                                                                                                                                                                                                                    
      URL.revokeObjectURL(url) // 必须手动释放资源,否则需要刷新Browser Context时才会被释放。                                                                                                                                               
    }, 1000)  

    方式2——Data URL

    限制:无法利用JavaScript的ASI机制少写分号。
    优点:即使UI线程所属页面是本地页面也可以执行。

    // 由于Data URL的内容为必须压缩为一行,因此JavaScript无法利用换行符达到分号的效果。                                                                                                                                                       
    const script = `addEventListener('message', event => {                                                                                                                                                                                    
      console.log(event.data);                                                                                                                                                                                                              
      postMessage('echo');                                                                                                                                                                                                                  
    }`                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    const worker = new Worker(`data:,${script}`)                                                                                                                                                                                              
    // 或 const worker = new Worker(`data:application/javascript,${script}`)                                                                                                                                                                  
    worker.onmessage = event => console.log(event.data)                                                                                                                                                                                       
    worker.postMessage('main thread')  
     

    Shared Web Worker详解

    共享线程可以和多个同域页面间通信,当所有相关页面都关闭时共享线程才会被释放。
    这里的多个同域页面包括:

    iframe之间

    浏览器标签页之间

    简单示例

    UI主线程

    const worker = new SharedWorker('./worker.js')                                                                                                                                                                                             
    worker.port.addEventListener('message', e => {                                                                                                                                                                                             
      console.log(e.data)                                                                                                                                                                                                                      
    }, false)                                                                                                                                                                                                                                  
    worker.port.start()  // 连接worker线程                                                                                                                                                                                                     
    worker.port.postMessage('hi')                                                                                                                                                                                                              
                                                                                                                                                                                                                                                  
    setTimeout(()=>{                                                                                                                                                                                                                           
      worker.port.close() // 关闭连接                                                                                                                                                                                                          
    }, 10000)                                                                                                                                                                                                                                  

    Shared Web Worker线程

    let conns = 0                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                  
    // 当UI线程执行worker.port.start()时触发建立连接                                                                                                                                                                                           
    self.addEventListener('connect', e => {                                                                                                                                                                                                    
      const port = e.ports[0]                                                                                                                                                                                                                  
      conns+=1                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                  
      port.addEventListener('message', e => {                                                                                                                                                                                                  
        console.log(e.data)  // 注意console对象指向第一个创建Worker线程的UI线程的console对象。即如果A先创建Worker线程,那么后续B、C等UI线程执行worker.port.postMessage时回显信心依然会发送给A页面。                                            
      })                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
      // 建立双向连接,可相互通信                                                                                                                                                                                                              
      port.start()                                                                                                                                                                                                                             
      port.postMessage('hey')                                                                                                                                                                                                                  
    })                                                                                                                                                                                                                                         

    示例——广播

    UI主线程

       const worker = new SharedWorker('./worker.js')                                                                                                                                                                                             
       worker.port.addEventListener('message', e => {                                                                                                                                                                                             
         console.log('SUM:', e.data)                                                                                                                                                                                                              
       }, false)                                                                                                                                                                                                                                  
       worker.port.start()  // 连接worker线程                                                                                                                                                                                                     
                                                                                                                                                                                                                                                  
       const button = document.createElement('button')                                                                                                                                                                                            
       button.textContent = 'Increment'                                                                                                                                                                                                           
       button.onclick = () => worker.port.postMessage(1)                                                                                                                                                                                          
       document.body.appendChild(button)                                                                                                                                                                                                          

    Shared Web Worker线程

       let sum = 0                                                                                                                                                                                                                                
       const conns = []                                                                                                                                                                                                                           
       self.addEventListener('connect', e => {                                                                                                                                                                                                    
         const port = e.ports[0]                                                                                                                                                                                                                  
         conns.push(port)                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                  
         port.addEventListener('message', e => {                                                                                                                                                                                                  
           sum += e.data                                                                                                                                                                                                                          
           conns.forEach(conn => conn.postMessage(sum))                                                                                                                                                                                           
         })                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
         port.start()                                                                                                                                                                                                                             
       })                                                                                                                                                                                                                                         
     

    即使是Web Worker也阻止不了你卡死浏览器的决心

    通过WebWorker执行计算密集型任务是否就可以肆无忌惮地编写代码,并保证用户界面操作流畅呢?当然不是啦,工具永远只能让你更好地完成工作,但无法禁止你用错。
    只要在频繁持续执行的代码中加入console对象方法的调用,加上一不小心打开Devtools工具,卡死浏览器简直不能再就简单了。这是为什么呢?
    因为UI线程在创建WebWorker线程时会将自身的console对象绑定给WebWorker线程的console属性上,那么WebWorker线程是以同步阻塞方式调用console将参数传递给UI线程的console对象,自然会占用UI线程的处理时间。

    工程化——通过webpack的worker-loader打包代码

    上面说了这么多那实际项目中应该怎么使用呢?或者说如何更好的集成到工程自动化工具——webpack呢?
    worker-loader和shared-worker-loader就是我们想要的。
    通过worker-loader将代码转换为Blob类型,并通过URL.createObjectURL创建url分配给WebWorker线程执行。

    安装loader

    npm install worker-loader -D

    配置Webpack.config.js

    // 处理worker代码的loader必须位于js和ts之前                                                                                                                                                                                              
    {                                                                                                                                                                                                                                        
     test: /.worker.ts$/,                                                                                                                                                                                                                 
     use: {                                                                                                                                                                                                                                 
     loader: 'worker-loader',                                                                                                                                                                                                             
     options: {                                                                                                                                                                                                                           
       name: '[name]:[hash:8].js', // 打包后的chunk的名称                                                                                                                                                                                 
       inline: true // 开启内联模式,将chunk的内容转换为Blob对象内嵌到代码中。                                                                                                                                                            
       }                                                                                                                                                                                                                                    
     }                                                                                                                                                                                                                                      
    },                                                                                                                                                                                                                                      
    {                                                                                                                                                                                                                                        
     test: /.js$/,                                                                                                                                                                                                                         
     use: {                                                                                                                                                                                                                                 
      loader: 'babel-loader'                                                                                                                                                                                                               
     },                                                                                                                                                                                                                                     
     exclude: [path.resolve(__dirname, 'node_modules')]                                                                                                                                                                                     
    },                                                                                                                                                                                                                                       
    {                                                                                                                                                                                                                                        
     test: /.ts(x?)$/,                                                                                                                                                                                                                     
     use: [                                                                                                                                                                                                                                 
       { loader: 'babel-loader' },                                                                                                                                                                                                          
       { loader: 'ts-loader' } // loader顺序从后往前执行                                                                                                                                                                                    
     ],                                                                                                                                                                                                                                     
     exclude: [path.resolve(__dirname, 'node_modules')]                                                                                                                                                                                     
    }                                                                                                                                                                                                                                                

    UI线程代码

    import MyWorker from './my.worker'                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
    const worker = new MyWorker('');                                                                                                                                                                                                         
    worker.postMessage('hi')                                                                                                                                                                                                                 
    worker.addEventListener('message', event => console.log(event.data))   

    Worker线程代码

    cosnt worker: Worker = self as any                                                                                                                                                                                                       
    worker.addEventListener('message', event => console.log(event.data))                                                                                                                                                                     
                                                                                                                                                                                                                                                  
    export default null as any // 标识当前为TS模块,避免报xxx.ts is not a module的异常   
     

    工程化——RPC类库Comlink

    一般场景下我们会这样使用WebWorker,

    UI线程传递参数并调用运算函数;

    在不影响用户界面响应的前提下等待函数返回值;

    获取函数返回值继续后续代码。

    翻译为代码就是

    let arg1 = getArg1()
    let arg2 = getArg2()
    const result = await performCalcuation(arg1, arg2)
    doSomething(result)

    而UI线程和WebWorker线程的消息机制通信机制显然会加大代码复杂度,而Comlink类库恰好能抚平这道伤疤。

    UI线程

    import * as Comlink from 'comlink'                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
    async function init() {                                                                                                                                                                                                                  
     const cl = Comlink.wrap(new Worker('worker.js'))                                                                                                                                                                                     
     console.log(`Counter: ${await cl.counter}`)                                                                                                                                                                                          
     await cl.inc()                                                                                                                                                                                                                       
     console.log(`Counter: ${await cl.counter}`)                                                                                                                                                                                          
    }                                                                                                                                                                                                                                        

    Worker线程

    import * as Comlink from 'comlink'                                                                                                                                                                                                       
                                                                                                                                                                                                                                                  
    const obj = {                                                                                                                                                                                                                            
      counter: 0,                                                                                                                                                                                                                            
      inc() {                                                                                                                                                                                                                                
        this.counter+=1                                                                                                                                                                                                                      
      }                                                                                                                                                                                                                                      
    }                                                                                                                                                                                                                                        
    Comlink.expose(obj)                                                                                                                                                                                                                      
     

    Electron中使用WebWorker

    Electron中使用Web Worker的同源限制中开了个口——UI线程所属页面URL为本地文件时,所分配给Web Worker的脚本可为本地脚本。
    其实Electron打包后读取的HTML页面、脚本等都是本地文件,如果不能分配本地脚本给Web Worker执行,那就进入死胡同了。

    const path = window.require('path')                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                              
    const worker = new Worker(path.resolve(__dirname, 'worker.js'))                                                                                                                                                                                                         

    上述代码仅表示Electron可以分配本地脚本给WebWorker线程执行,但实际开发阶段一般是通过 ~http(s)://~ 协议加载页面资源,而发布时才会打包为本地资源。
    所以这里还要分为开发阶段用和发布用代码,还涉及资源的路径问题,所以还不如直接转换为Blob数据内嵌到UI线程的代码中更便捷。

    https://www.houdianzi.com/ logo设计公司

    总结

    随着边缘计算的兴起,客户端承担部分计算任务提高运算时效性和降低服务端压力必将成为趋势。WebWorker这一秘技你Get到了吗?:)

     

    Electron中使用WebWorker

    Electron中使用Web Worker的同源限制中开了个口——UI线程所属页面URL为本地文件时,所分配给Web Worker的脚本可为本地脚本。
    其实Electron打包后读取的HTML页面、脚本等都是本地文件,如果不能分配本地脚本给Web Worker执行,那就进入死胡同了。

    const path = window.require('path')                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                              
    const worker = new Worker(path.resolve(__dirname, 'worker.js'))                                                                                                                                                                                                         

    上述代码仅表示Electron可以分配本地脚本给WebWorker线程执行,但实际开发阶段一般是通过 ~http(s)://~ 协议加载页面资源,而发布时才会打包为本地资源。
    所以这里还要分为开发阶段用和发布用代码,还涉及资源的路径问题,所以还不如直接转换为Blob数据内嵌到UI线程的代码中更便捷。

    总结

    随着边缘计算的兴起,客户端承担部分计算任务提高运算时效性和降低服务端压力必将成为趋势。WebWorker这一秘技你Get到了吗?:)

  • 相关阅读:
    DNS服务器配置
    动态网站技术CGI
    SED单行脚本快速参考(Unix 流编辑器)
    xen 安静的角落
    IP命令
    oracle 监听文件 说明
    LRU ,LRUW,CKPT-Q
    重建控制文件ORA-12720
    历史备份控制文件恢复数据库
    增量检查点和完全检查点
  • 原文地址:https://www.cnblogs.com/xiaonian8/p/15149666.html
Copyright © 2011-2022 走看看