Why does the `instanceof` operator return false on instance passed to library? (No inheritance involved)

huangapple go评论67阅读模式
英文:

Why does the `instanceof` operator return false on instance passed to library? (No inheritance involved)

问题

In your code, the issue may be related to how the Client class from your library's ssh2 dependency is compared with the Client instance from the consumer code. It's possible that the TypeScript compiler is considering them as two different classes, even though they should be the same.

One way to ensure that the instanceof check works as expected is to import the Client class directly from your library's ssh2 dependency within your runCommand function:

import { Client, ConnectConfig } from 'ssh2';

export function runCommand(params: Client | ConnectConfig) {
  import { Client } from 'ssh2'; // Import 'Client' here as well
  if (params instanceof Client) {
    // do something
  } else {
    // create our own client
    // do something
  }
}

This should make sure that the Client class used within the runCommand function is the same as the one used in the consumer code, which should resolve the issue.

英文:

I am building a library with TS. This library uses the ssh2 library as a dependency.

I'm trying to create a function that can either accept an ssh2 configuration object or an already existing Client instance to execute a command (this is a simplified case):

import { Client, ConnectConfig } from 'ssh2';

export function runCommand(params: Client | ConnectConfig) {
  if(params instanceof Client) {
    // do something
  } else {
    // create our own client
    // do something
  }
}

When I build this library and call the function like so:

const { readFileSync } = require("fs");
const { Client } = require("ssh2");
const { runCommand } = require("myLib");
const conn = new Client();

conn
  .on("ready", () => {
    console.log("Client :: ready");
    runCommand(conn);
  })
  .connect({
    host: "example.com",
    port: 22,
    username: "me",
    privateKey: readFileSync(process.env.HOME + "/.ssh/id_ed25519"),
  });

the instanceof check in my function will return false for some reason. What exactly is happening here? Is it a TS compilation issue or does Node.js see the Client from my lib's dependency and my consumer code's dependency as two different classes?

The compiled code looks something like:

const ssh2_1 = require("ssh2");
//...
function runCommand(params) {
    if (params instanceof ssh2_1.Client) {
        // do something
    }
    else {
       // create our own client
    // do something
    }
}

答案1

得分: 2

不显然的是,instanceof 运算符的行为中,类必须在 JavaScript 对象引用的父链中完全相同

因此,如果您导入 Client 类两次,它们是不同的实际对象引用,而 instanceof 可能会产生"false"否定。

这很可能是这里发生的情况:ssh2 与您的库捆绑在一起,因此导入其自己的依赖项副本。而您的应用程序单独导入 ssh2,导致单独的副本。

您有几个选项:

  • 不要将依赖项与您的库捆绑在一起(例如使用 webpack 的 externals),让应用程序安装单个版本,供您的库和应用程序都导入;这是指定 peerDependencies 的典型情况
  • 不要使用 instanceof 运算符;相反,使用一些启发式方法来确定对象是否具有您需要的结构;通常检查某些属性及其类型
  • 从您的库中重新导出依赖项(例如,在您的情况下是 Client 类),并在您的应用程序中使用该依赖项(const { Client, runCommand } = require("myLib");),这样它就是完全相同的副本(因为它已经捆绑了,为什么不重复使用它而不是重新捆绑它?)
英文:

What may not be obvious from the behaviour of instanceof operator, is that the class must be exactly in the parent chain, in the sense of JavaScript object reference.

Hence, if you import Client class twice, they are different actual object references, and instanceof may give a "false" negative.

This is very probably what happens here: ssh2 is bundled with your library, hence it imports its own copy of the dependency. Whereas your app imports ssh2 on its own, leading to a separate copy.

You have a few options:

  • Do not bundle the dependency with your library (e.g. with webpack externals), let the app install a single version that both your library and app will import; typical case of specifying peerDependencies
  • Do not use the instanceof operator; instead, use some heuristic to determine whether the object has the shape you need; typically check for some properties and their type
  • Re-export the dependency (e.g. Client class in your case) from your library, and use that one in your app (const { Client, runCommand } = require("myLib");), so that it is the exact same copy (since it is already bundled, why not re-using it instead of re-bundling it?)

huangapple
  • 本文由 发表于 2023年4月10日 19:19:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/75976618.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定