toxic-johann / toxic-johann.github.io

my blog
6 stars 0 forks source link

【2016-04-10】尝试用webworker优化体验 #29

Open toxic-johann opened 7 years ago

toxic-johann commented 7 years ago

之前编写的ascii化网站会存着太大运算量导致进程卡死页面假死的问题。其实这个问题在业界已经有很好的解决方法。调用webworker分派到其他进程进行运算即可。于是我就在这个项目上使用了一下web worker。

因为web worker的脚本是在页面生成后再进行拉取的。而我在页面中使用的是webpack页面打包机制。两者给我的感觉理念使用相悖的。

其实如果没什么特别需求,可以直接在页面上使用正常的webworker方式进行处理。

var myWorker = new Worker("my_task.js");

myWorker.onmessage = function (oEvent) {
  console.log("Called back by the worker!\n");
};

个人认为也是没什么问题。不过我比较懒,想在webpack打包的时候把所有问题解决掉。

而且我并不想破坏我的一些插件的内部结构。所以我希望将插件打包到我的worker中,然后再进行处理。

于是我尝试了使用worker-loader插件。

配置的方法不算难。

module.exports = {
    entry: {
        "/page/home/index/ascii": dir + '/page/home/index/ascii.js',
        "/page/home/index/photoshop": dir + '/page/home/index/photoshop.js',

    },
    output: {
        path: dir + "/../www/static/js/",
        filename: "[name].js",
        publicPath:"/static/js/"
    },
    module: {
        loaders: [
            {
              test: /\.js$/,
              exclude: /(node_modules|bower_components)/,
              loader: 'babel',
              query: {
                    presets: ['es2015']
                }
            },
            //在此处加上worker-loader即可
            { 
                test: /\.worker$/,
                loader: "worker-loader",
            },
        ]
    },
    externals: {
        // require("jquery") 是引用自外部模块的
        // 对应全局变量 jQuery
        "jquery": "jQuery",
        "wx":"wx",
    }
}

使用的时候,加上一个前缀。

let MyWorker = require("worker!./ascii-worker.js");

let myWorker = new MyWorker();

就会进行自动编译。不过这个插件,貌似没有办法进行命名。而且会很自觉地放在公共路径下面。这样子会导致派生出很多个文件。需要进行清理。

不过这个问题不大,并不会影响什么。但是以后看看有没有办法解决掉吧。这是后话。

我的做法是将webworker作为一个中转站。根据传入的事件,进行相关事件的处理,返回需要的数据。

我们先看一下我写的代码。

let Photoshop = require("../../../utils/photoshop.js");
let photoshop = new Photoshop;

let AsciiPic = require("../../../utils/asciiPic.js");
let asciiPic = new AsciiPic;

首先,在webworker引入我需要的处理用的插件。

onmessage = function (oEvent) {
    let ret = {
        behavior:"出错了~~~",
        data:null
    };
    if(oEvent.data.behavior == "sketch"){
        let pic = photoshop.sketch.apply(photoshop,oEvent.data.args);
        ret = {
            behavior:oEvent.data.behavior,
            data:[pic]
        }
    } else if (oEvent.data.behavior == "adjustContrast"){
        let pic = photoshop.adjustContrast.apply(photoshop,oEvent.data.args);
        ret = {
            behavior:oEvent.data.behavior,
            data:[pic]
        }
    } else if(oEvent.data.behavior == "asciiFromImagedata"){
        let pic = asciiPic.asciiFromImagedata.apply(asciiPic,oEvent.data.args);
        ret = {
            behavior:oEvent.data.behavior,
            data:[pic]
        }
    }
    postMessage(ret);
};

// webpack打包所需
module.exports = {};

然后就是一个事件处理。onmessage是浏览器给我们提供的一个事件机制,我们可以监听到主程序发出的指令。然后我在事件中定义一个标记,根据标记不同,进行不同的处理。

我将参数作为数据传入,然后利用apply将参数传给执行者。

最后利用postMessage返回。

postMessage也是浏览器提供的与主进程之间处理的一个方法。

就这样,我完成了一个中转站的设置。

那么在主进程中,我们也是同样的建立一个事件机制即可。

let MyWorker = require("worker!./ascii-worker.js");
let ToxicEvents = require("../../../utils/events.js");

let myWorker = new MyWorker();
let events = new ToxicEvents();

// 设定沟通之间的事件监听
myWorker.onmessage = function (oEvent) {
    events.trigger(oEvent.data.behavior,oEvent.data.data);
};

为此,我写了一个小的事件模型进行处理。

当我收到worker的信息后,将信息作为一个事件转发。并且带上参数。

那么我在主进程中就可以进行监听和处理了。举个简单的栗子。

tips("正在素描化中")
let radius = parseInt($("#radius").val());
myWorker.postMessage({
    behavior:"sketch",
    args:[imageData,radius]
})

events.on("sketch",function(evt,data){
    imageData.data.set(data[0]);
    drawImageData(imageData);
    events.off(evt.type,evt.id);
    tips("完成",null,"hide");
})

用myworker.postMessage通知web worker进行处理。然后他处理完后会触发我的事件机制,将结果传入。然后我作出页面响应。

在v8的strong mode里面arguments被禁用掉了。因此在webpack的打包中,你会发现使用arguments会拿不到结果。

这个对于我们一些参数转移并不是很友好。辣么怎么办呢。使用...args吧。而且args会是一个真正的数组。也很方便。

this.trigger = (...args)=>{
    let self = this;
    let type = args[0];
    if(!type){
        throw new Error("事件触发需申明事件类型");
    }
    if(!_events[type]){
        console.warn("该类型没有绑定事件");
        return;
    }
    _events[type].forEach(each=>{
        let tmpArgs = args;
        tmpArgs.shift();
        tmpArgs.unshift({
            type:each.type,
            id:each.id,
        })
        each.listener.apply(self,tmpArgs);
    });
}

在我的事件机制里面trigger就是用了这个方法。这样子我可以成功将用户绑定的多个参数传回去给用户。

最后由于大家在交大的节日已经很excited了。所以这次就来一个乔帮主吧。

我和华莱士当年也是谈笑风生的