likes
comments
collection
share

🥳🥳🥳Worker中还可以创建多个Worker,打开多线程编程的大门

作者站长头像
站长
· 阅读数 24

本文正在参加「金石计划 . 瓜分6万现金大奖」

上篇讲到了如何使用Worker,介绍了Worker的基本用法,如何创建Worker,如何向Worker发送消息,如何接收Worker的消息,以及如何关闭Worker

本篇探索Worker的更多用法,主要是如何创建多个Worker,和在Worker中如何发送请求,以及如何使用Worker进行多线程编程。

历史回顾:

创建多个 Worker

在上篇文章中,我们已经介绍了如何创建一个Worker,但是在实际开发中,我们可能需要创建多个Worker,这还不简单嘛,多new几个不就好了。

// main.js
const worker1 = new Worker('worker.js')
const worker2 = new Worker('worker.js')
const worker3 = new Worker('worker.js')

worker1.postMessage('worker1')
worker2.postMessage('worker2')
worker3.postMessage('worker3')

// worker.js
self.addEventListener('message', (e) => {
  console.log(e.data)
})

这里的worker.js是可以复用的,但是数据不会共享,每个Worker都是独立的,我们可以创建多个Worker,但是不要创建太多,因为每个Worker都会占用一定的内存。

🥳🥳🥳Worker中还可以创建多个Worker,打开多线程编程的大门

Worker 中创建 Worker

上面说到可以创建多个Worker,但是这里的Worker是指主线程中的Worker,那么我们可以在Worker中创建Worker吗?答案是肯定的。

Worker中,我们可以创建多个Worker,创建方法也很简单,就和我们上面讲到的创建Worker一样,使用new Worker()即可。

这里就将创建的Worker当作主线程来理解就好了,主线程中可以创建WorkerWorker中也可以创建Worker

// worker.js
var worker = new Worker('worker1.js');

worker.postMessage('这里是Worker的主线程发送的消息');

worker.onmessage = (e) => {
    console.log(e.data);
}

// worker1.js
self.addEventListener('message', (event) => {
    console.log(event.data);
});

self.postMessage('这里是Worker1的子线程发送的消息');

🥳🥳🥳Worker中还可以创建多个Worker,打开多线程编程的大门

到这里我们已经摸到了Worker的奥妙,也打开了前端多线程的编程大门,接下来了就开始进阶吧。

Worker 中引用外部文件

Worker中,我们可以引用外部文件,这样有利于我们将代码进行拆分,方便管理,代码如下:

self.importScripts('worker1.js', 'worker2.js', ...);

这里使用了importScripts方法,它的作用是引入外部文件,可以引入多个文件,文件之间用逗号隔开。

它的实际作用是将外部文件的内容拷贝到Worker中,这样就可以在Worker中使用外部文件中的内容了。

我们先来看一下实际效果:

// worker.js
importScripts('worker1.js', 'worker2.js');
worker1();
worker2();

// worker1.js
var worker1 = () => {
    console.log('worker1');
}

// worker2.js
var worker2 = () => {
    console.log('worker2');
}

🥳🥳🥳Worker中还可以创建多个Worker,打开多线程编程的大门

可以看到,Worker中引用了外部文件,且可以使用外部文件中的内容。

这样我们的Worker也可以分模块了,不用把所有的代码都写在一个文件中。

importScripts() 方法

importScriptsWorker中的一个全局方法,它的作用是引入外部文件,上面已经体验过了,现在来详解。

importScripts()self.importScripts()是相等的,他们就是一个东西,在Worker的全局作用域中,都是可以省去self的。

除了可以引入外部文件,还可以引入外部的URL,代码如下:

importScripts('https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js');

但是这样会直接报错,因为这个URL不是同源的,所以使用importScripts()引入外部URL时,必须是同源的。

Worker 中使用 XMLHttpRequest

上面我们已经知道了Worker中可以引入外部文件,现在我们还需要使用外部数据,就是请求后台数据,这时候就需要使用ajax了。

但是上面可以看到我们引用Jquery的时候报错了,那么引用axios啥的肯定也是不行的,但是我们可以使用XMLHttpRequest

XMLHttpRequestajax的基础,它是ajax的核心,我们可以使用它来请求后台数据。

// worker.js
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js', true);
xhr.send();
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(xhr.responseText);
    }
}

可以看到,我们使用XMLHttpRequest请求了JqueryCDN,并且成功拿到了数据。

Worker 中使用 fetch

上面使用XMLHttpRequest请求数据,这个太底层了,不利于我们的开发,这里庆幸的是,Worker全局中提供了fetch方法,我们可以使用它来请求数据。

// worker.js
fetch('https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js')
    .then(res => res.text())
    .then(data => {
        console.log(data);
    })

可以看到,我们使用fetch请求了JqueryCDN,并且成功拿到了数据。

fetchajax的新标准,它的优点是:简单、易用、强大,它的缺点是:兼容性不好,不支持IE。 抛开IE其实fetch已经被很广泛的使用了,各大浏览器都已经支持了,所以我们可以放心的使用它。 fetchXMLHttpRequest都是用于发送HTTP请求的,但是fetch的语法更加简洁,而且fetch支持Promise,所以我们可以使用async/await来发送请求。 由于本系列文章是为了学习Web Worker,所以这里就不详细介绍fetchXMLHttpRequest了,有兴趣的可以自行查阅相关资料。

实践

现在我们来实践一下,还是拿上次的10万数据的例子来,不过这次我们弄到100万数据,然后使用Worker来处理。

// main.js
const worker = new Worker('worker.js');

worker.onmessage = (event) => {
    renderData(event.data);
};

const renderData = (data) => {
    console.log(data);
    // 渲染数据,这里就是网上说的虚拟滚动的实现
};

// 延时进行查询请求,这是为了防止 Worker 把数据发送过来就操作,导致不生效
setTimeout(() => {
    const params = {
        name: '1',
        age: ''
    }
    worker.postMessage({search: params});
}, 1000);

// worker.js
const data = [];
const loadData = () => {
    // 加载数据
    // 这里可以使用fetch请求数据,现在这里还是空着,留着下次完成
    for (var i = 0; i < 1000000; i++) {
        data.push({
            name: 'name' + i,
            age: i
        });
    }

    self.postMessage(data);
};
loadData();

self.addEventListener('message', (event) => {
    const {search} = event.data;
    shardQuery(search).then((data) => {
        self.postMessage(data);
    });
});

// 这里分片处理数据,交给多个线程处理
const shardQuery = (search) => {
    return new Promise((resolve, reject) => {
        // 这里定义一个计数器,确保所有线程都执行完毕
        let counter = 0;
        const shardData = [];
        // 这里每个线程处理10万条数据,最终会有10个线程
        const shardSize = 100000;
        for (let i = 0; i < data.length; i += shardSize) {
            counter += 1;
            const filterWorker = new Worker('filterData.js');
            filterWorker.postMessage({data: data.slice(i, i + shardSize), search});
            filterWorker.onmessage = (event) => {
                shardData.push(...event.data);
                counter -= 1;

                // 所有线程都执行完毕,返回结果
                if (counter === 0) {
                    resolve(shardData);
                }
            };
        }
    });
};


// filterData.js
const filterData = (data, search) => {
    if (!data || data.length === 0) {
        return [];
    }

    const {name, age} = search;
    const result = [];
    for (var i = 0; i < data.length; i++) {
        const item = data[i];

        let flag = true;
        if (search.name && item.name.indexOf(name) === -1) {
            flag = false;
        }

        if (search.age && item.age !== age) {
            flag = false;
        }

        if (flag) {
            result.push(item);
        }
    }
    return result;
};

self.addEventListener('message', (event) => {
    const {data, search} = event.data;
    const result = filterData(data, search);
    self.postMessage(result);
});

可以看到,我们使用Worker来处理数据,这里我们使用了分片处理数据,交给多个线程处理,这样可以提高处理效率。

注意:Worker的创建是需要消耗资源的,所以不要创建太多的Worker,这样会导致性能下降; 我这里创建了10个Worker,理论上是有点多的; 同时我这里的Worker也没有销毁,这里其实可以使用线程池的思想来处理。

总结与预告

到这里,我们已经学习完了Worker的相关知识;

第一篇我们知道怎么使用Worker,知道主线程和子线程的通讯,知道了子线程和主线程的通讯;

这一篇我们学习了创建多个Worker,知道了Worker中如何引入外部资源,同时也进一步的了解了Worker中的一些限制。

接下来我们会认识新的WorkerSeviceWorker,这个Worker可以让我们在离线的情况下也能访问网站,可以实现缓存,实现消息推送等等,让我们一起来期待吧。