Javascript 中的子类化

Subclassing in Javascript

我正在尝试制作 image library on github called Jimp 的子class。据我从文档中得知,您不会以通常的方式实例化 class 。而不是说 new Jimp(),class 似乎有一个称为 read 的静态方法,它充当构造函数。来自文档...

Jimp.read("./path/to/image.jpg").then(function (image) {
    // do stuff with the image
}).catch(function (err) {
    // handle an exception
});

从文档看来,由 read() 编辑的 image return 是一个允许调用者执行 image.resize( w, h[, mode] ); 等操作的实例。

我想让我的 subclass 调用者从一个不同的静态方法开始,该方法读取图像并做一些事情,总结如下...

class MyJimpSubclass extends Jimp {

    static makeAnImageAndDoSomeStuff(params) {
        let image = null;
        // read in a blank image and change it
        return Jimp.read("./lib/base.png").then(_image => {
            console.log(`image is ${_image}`);
            image = _image;
            let foo = image.bar();  // PROBLEM!
            // ...
            // ...
        .then(() => image);
    }

    bar() {
        // an instance method I wish to add to the subclass
    }

// caller
MyJimpSubclass.makeAnImageAndDoSomeStuff(params).then(image => {
    //...
});

你可能猜到nodejs在let foo = image.bar();线上生气了,说

TypeError image.bar is not a function

.

我认为这是可以理解的,因为我使用 Jimp.read() 获得了该图像。当然,这不会 return 我的子实例 class。

  1. 第一个想法:将其更改为 MyJimpSubclass.read()。同样的问题。
  2. 思路二:实现自己的静态读取方法。同样的问题。

    static read(params) {
        return super.read(params);
    }
    
  3. 第三个想法:问SO

implementation of Jimp.read 具体指的是 Jimp,因此您必须在子类中复制并更改它(恶心,但不会破坏任何东西,因为构造函数也是 API) 或提出拉取请求以将其更改为此并明确支持子类化:

static read(src) {
    return new Promise((resolve, reject) => {
        void new this(src, (err, image) => {
            if (err) reject(err);
            else resolve(image);
        });
    });
}

或者,您可以将所有功能作为模块上的一组函数来实现。在发出拉取请求后,这将是我列表中的下一个。不推荐代理。

const makeAnImageAndDoSomeStuff = (params) =>
    Jimp.read("./lib/base.png").then(image => {
        console.log(`image is ${image}`);
        let foo = bar(image);
        // …
        return image;
    });

function bar(image) {
    // …
}

module.exports = {
    makeAnImageAndDoSomeStuff,
    bar,
};

即使更改原型也比代理更好(但这只是第一个选项的更差版本,重新实现 read):

static read(src) {
    return super.read(src)
        .then(image => {
            Object.setPrototypeOf(image, this.prototype);
            return image;
        });
}

你有几个选择。最干净的方法可能是像您开始的那样创建一个 subclass,然后在其上实现 Jimp 静态方法,以及您自己的方法。在这种情况下,它不是真正的继承,所以不要使用 extends.

class MyJimp {
  static read(...args) {
    return Jimp.read.apply(Jimp, args);
  }

  static makeAnImage(params) {
    return this.read(params)
      .then(image => {
        // do stuff
        return image
      });
  }
}

从那里,我将创建一个对象,其中包含您要应用的所有新功能 image:

const JimpImageExtension = {
  bar: () => { /* do something */ }
};

最后,在您的静态方法中,获取图像并使用 Object.assign() 将您的新函数应用于它:

class MyJimp {
  static read(...args) {
    return Jimp.read.apply(Jimp, args)
      .then(image => Object.assign(image, JimpImageExtension));
  }

  static makeAnImage(params) {
    return this.read(params)
      .then(image => {
        // do stuff
        image.bar();
        return image;
      });
  }
}

这应该通过将您的额外功能应用于图像来达到目的。您只需要确保在可以生成图像的每个点都应用它(如果不仅仅是 read)。由于在其他函数中,它使用的是您的 read() 版本,您只需添加其中一个函数即可。

另一种方法是,如果 Jimp 使他们的图像 class 可访问,您也可以将它们添加到原型中(尽管通常在这样的库中, class 是经常无法访问或实际上根本不是 class)。

这可能是一种方法。从您自己的 read 方法开始,让它更改返回对象的原型。

static read(...params) {
    return super.read(...params).then(image) {
        image.prototype = MyJimpSubclass;
        resolve(image);
    }
}