zoukankan      html  css  js  c++  java
  • .11-浅析webpack源码之Storage模块

      至此已完成NodeJsInputFileSysten模块的讲解,下一步就是实际实用的模块:

    compiler.inputFileSystem = new CachedInputFileSystem(new NodeJsInputFileSystem(), 60000);

      挂载到compiler对象上的输入模块其实是带有缓存的输入模块,源码整理如下(用ES6的class重写)

    class CachedInputFileSystem {
        constructor() {
            // fileSystem => NodeJsInputFileSystem => graceful-fs => fs
            this.fileSystem = fileSystem;
            // 生成缓存容器
            this._statStorage = new Storage(duration);
            this._readdirStorage = new Storage(duration);
            this._readFileStorage = new Storage(duration);
            this._readJsonStorage = new Storage(duration);
            this._readlinkStorage = new Storage(duration);
            this._stat = this.fileSystem.stat ? this.fileSystem.stat.bind(this.fileSystem) : null;
            if (!this._stat) this.stat = null;
            // ...more
            // 自定义JSON读取
            if (this.fileSystem.readJson) {
                this._readJson = this.fileSystem.readJson.bind(this.fileSystem);
            } else if (this.readFile) {
                this._readJson = function(path, callback) { /*...*/ }.bind(this);
            } else {
                this.readJson = null;
            }
            // sync...
        }
        stat(path, callback) {
            this._statStorage.provide(path, this._stat, callback);
        };
        // readdir,readFile,readJson,readlink
        // sync...
        purge(what) {
            this._statStorage.purge(what);
            this._readdirStorage.purge(what);
            this._readFileStorage.purge(what);
            this._readlinkStorage.purge(what);
            this._readJsonStorage.purge(what);
        };
    }
    
    module.exports = CachedInputFileSystem;

      这里的核心是利用Storage来生成一个缓存容器,缓存对应的读操作。

      有两个需要注意的地方。

      一个是purge方法,这个是Storage的原型方法,所以暂时先放着(形参名有点意思,叫what)。

      第二个是这个模块自定义了一个方法专门用来读取JSON文件,源码如下:

    this._readJson = function(path, callback) {
        // fs.readFile读取文件
        this.readFile(path, function(err, buffer) {
            if (err) return callback(err);
            try {
                // 先将字节流字符转换成utf-8格式的字符串
                // 再调用JSON.parse进行解析
                var data = JSON.parse(buffer.toString("utf-8"));
            } catch (e) {
                return callback(e);
            }
            // 使用回调处理数据
            callback(null, data);
        });
    }.bind(this);

      只是调用JSON.parse解析字符,这个方法只能专门处理JSON格式的数据,不然会报错。

    Storage

      该模块核心在于Storage对象,下面就看一看Storage内部实现,源码如下:

    class Storage {
        constructor() {
            // duration => 60000
            this.duration = duration;
            this.running = new Map();
            this.data = new Map();
            this.levels = [];
            if (duration > 0) {
                this.levels.push(new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set());
                // (duration - 8000) / 500 => 52000 / 500 => 104
                for (var i = 8000; i < duration; i += 500)
                    this.levels.push(new Set());
            }
            this.count = 0;
            this.interval = null;
            this.needTickCheck = false;
            this.nextTick = null;
            this.passive = true;
            this.tick = this.tick.bind(this);
        }
        ensureTick() { /*...*/ };
        finished(name, err, result) { /*...*/ };
        finishedSync(name, err, result) { /*...*/ };
        provide(name, provider, callback) { /*...*/ };
        provideSync(name, provider) { /*...*/ };
        tick() { /*...*/ };
        checkTicks() { /*...*/ };
        purge(what) { /*...*/ };
    }

      构造函数中的Set与Map均为ES6新添加的数据结构,详情自行查阅。

      其中levels数组除去本身的9个Set,根据duration的值,再次加了104个Set对象,之后看具体含义。  

      接下来依次讲解原型函数。

    ensureTick

    Storage.prototype.ensureTick = function() {
        // 第一调用进行初始化
        // this.tick为定期执行的函数
        // 执行间隔为 (60000 / 113)|0 = 530
        if (!this.interval && this.duration > 0 && !this.nextTick)
            this.interval = setInterval(this.tick, Math.floor(this.duration / this.levels.length));
    };

      可以看出这是一个初始化的方法,初始化一个定时器,间隔取决于传进来的duration。

      做了一个测试,检测从8000开始到60000定时间隔的变化:

    let startNum = 8000,
        startLen = 9,
        result = [];
    for (; startNum < 60000; startNum += 500, startLen++) {
        result.push((startNum / startLen | 0));
    }

      输出如图:

      由于levels的长度最低为9,当传入8000时会达到最大值,所以间隔一定小于0.888秒,且随着duration的值增加而减少,将duration设为100万可以发现这个间隔在500会趋于平缓,大部分暂且可以认为间隔是稳定在0.5秒~0.6秒。

    checkTicks

    Storage.prototype.checkTicks = function() {
        this.passive = false;
        if (this.nextTick) {
            // 无限执行tick直到返回true
            while (!this.tick());
        }
    };

    finished

    Storage.prototype.finished = function(name, err, result) {
        // 获取指定名字的回调事件流
        var callbacks = this.running.get(name);
        this.running.delete(name);
        if (this.duration > 0) {
            // 设置进data
            this.data.set(name, [err, result]);
            // 获取levels的第一个Set对象
            var levelData = this.levels[0];
            // 新增count才会+1
            this.count -= levelData.size;
            levelData.add(name);
            this.count += levelData.size;
            this.ensureTick();
        }
        // 遍历执行回调
        for (var i = 0; i < callbacks.length; i++) {
            callbacks[i](err, result);
        }
    };

      不应用的话不知道是干嘛用的。

    finishedSync

    Storage.prototype.finishedSync = function(name, err, result) {
        if (this.duration > 0) {
            // ...一模一样
        }
    };

    provide

    Storage.prototype.provide = function(name, provider, callback) {
        if (typeof name !== "string") {
            callback(new TypeError("path must be a string"));
            return;
        }
        var running = this.running.get(name);
        // 将回调函数加进runnning直接返回
        if (running) {
            running.push(callback);
            return;
        }
        if (this.duration > 0) {
            this.checkTicks();
            // 获取data中对应的事件 异步执行
            var data = this.data.get(name);
            if (data) {
                return process.nextTick(function() {
                    callback.apply(null, data);
                });
            }
        }
        // 无法获取running与data时
        this.running.set(name, running = [callback]);
        var _this = this;
        provider(name, function(err, result) {
            _this.finished(name, err, result);
        });
    };

      该方法会先后尝试从running与data中获取对应的事件,无法获取将设置到running中,并调用提供的provider方法。

    tick

    Storage.prototype.tick = function() {
        var decay = this.levels.pop();
        for (var item of decay) {
            this.data.delete(item);
        }
        this.count -= decay.size;
        decay.clear();
        // 清空后头部插入
        this.levels.unshift(decay);
        // 当没有事件时初始化条件
        if (this.count === 0) {
            clearInterval(this.interval);
            this.interval = null;
            this.nextTick = null;
            return true;
        } else if (this.nextTick) {
            this.nextTick += Math.floor(this.duration / this.levels.length);
            var time = new Date().getTime();
            if (this.nextTick > time) {
                this.nextTick = null;
                // 初始化定时器
                this.interval = setInterval(this.tick, Math.floor(this.duration / this.levels.length));
                return true;
            }
        } else if (this.passive) {
            clearInterval(this.interval);
            this.interval = null;
            this.nextTick = new Date().getTime() + Math.floor(this.duration / this.levels.length);
        } else {
            this.passive = true;
        }
    };

      这个方法在使用中再解释吧。

    purge

    Storage.prototype.purge = function(what) {
        // 不传参数 
        // 清空所有数据
        if (!what) {
            this.count = 0;
            clearInterval(this.interval);
            this.nextTick = null;
            this.data.clear();
            this.levels.forEach(function(level) {
                level.clear();
            });
        }
        // 传字符串
        // 移除data中所有以参数开头的key 
        else if (typeof what === "string") {
            for (var key of this.data.keys()) {
                if (key.startsWith(what))
                    this.data.delete(key);
            }
        }
        // 传数组
        // 递归批量移除
        else {
            for (var i = what.length - 1; i >= 0; i--) {
                this.purge(what[i]);
            }
        }
    };

      用于清空数据的方法。

      总体来说,模块内容如下:

    1、levels数组 => 总数据源

    2、running对象 => 存储待运行回调事件流

    3、data对象 => 存储已完成事件流

    4、count => 记录levels数据数量

    5、interval => 当前定时器的id

    6、needTick,nextTick,passive均为标记

      由于没有应用,所以讲起来十分僵硬,后面的源码中会重新回来看这些方法。

  • 相关阅读:
    LamBda学习(一)
    如何返回一个只读泛型集合
    Socket编程笔记同步
    如何快速读取大文件(看csdn一网友要求写的)没有测试具体的速度。
    如何实现项目脚本的批量生成
    如何实现WORD查找完成后不提示的代码
    W32/Pate.a 病毒处理小记
    在WORD中用VBA实现光标移动与内容选择
    2. WCF 消息操作
    3. WCF 异常处理
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8045361.html
Copyright © 2011-2022 走看看