web-dev-qa-db-ja.com

lodashの.isObject、.isPlainObjectが "typeof x === 'object'"とは異なる動作をするのはなぜですか?

次のことを考慮してください。

var o1 = {}
var O = function () {
  return this
}
var o2 = new O()
var o3 = function() {}
var o4 = [o1, o1]

var output = [
    [_.isObject(o1), _.isObject(o2), _.isObject(o3), _.isObject(o4)], 
    [_.isPlainObject(o1), _.isPlainObject(o2), _.isPlainObject(o3), _.isPlainObject(o4)],
    [typeof o1 === 'object', typeof o2 === 'object', typeof o3 === 'object', typeof o4 === 'object'],
    [o1 instanceof Array, o2 instanceof Array, o3 instanceof Array, o4 instanceof Array]
]

/* outputs:

[
    [true,true,true,true],
    [true,false,false,false],
    [true,true,false,true],
    [false,false,false,true]
]

*/

明らかに、.isObject()の間に切断があることがわかります。

これ 値がオブジェクトの言語タイプであるかどうかを確認します(例:配列、関数、オブジェクト、正規表現、新しい数値(0)、新しい文字列( '')

.isPlainObject()

どの 値がプレーンオブジェクト、つまり、オブジェクトコンストラクターによって作成されたオブジェクトまたはプロトタイプがnullのオブジェクトの場合

そして、私たちの古き良き信頼できる友人typeof x === 'object'

私は3つの質問があります:

  1. .isObject.isPlainObjectの動作をネイティブの.js型チェックとは異なるものにするという意識的な設計上の決定はありましたか?
  2. 私の最初の質問が本当なら、設計上の決定は何でしたか、そしてそれをこのようにすることの利点は何ですか?
  3. is*とまったく同じように動作するネイティブ lodash (または nderscore.jstypeof x === 'object'関数はありますか?

もちろん、typeofを使い続けることはできますが、構文的には、場所によってはどちらか一方を使用するのは少し奇妙です。たとえば、.isObjectを使用すると、typeof x === 'object' && typeof x !== 'function'をチェックするときに誤検知が返されます。 .isObjectがすでに存在する場合、関数に対して.isFunctionがtrueを返すことの利点は実際にはわかりません。

13
brandonscript

typeofは、何かがオブジェクトであるかどうかとは何の関係もありません。関数、文字列、および{}には異なるtypeofがあり、それらはすべてオブジェクトです。文字列がファーストクラスのオブジェクトであるのと同じように、関数はもちろんファーストクラスのオブジェクトです。したがって、isObjectは文字列とオブジェクトに対してtrueを返す必要があります。

記録のために、ドキュメントはこれをカバーしています:

値がオブジェクトの言語タイプであるかどうかを確認します。 (例:配列、関数、オブジェクト、正規表現、新しい数値(0)、新しい文字列( ''))

うわー!これは、便利なisObjectメソッドがなくてもテストすることが本当にたくさんあります。公平を期すために、ほとんどの場合typeofobjectとして返しますが、特にlodashのようなライブラリでの高レベルのメソッドのポイントは、プログラマーがそのナンセンスを忘れることができるようにすることです。

typeof引数が気になる場合は、typeofを使用してください。関数ではないオブジェクトを気にする場合は、いくつかのオプションがあります。typeofandチェック文字列を特別に使用するか、isObject && !isFunctionを使用できます。私は後者を好むでしょう。後者はたまたまあなたが伝えようとしていることを正確に言っているので、それは本当に正しいコーディングです。 「オブジェクト」と言うときに暗黙のうちに関数を意味しないと思う場合は、関数がファーストクラスのオブジェクトであるとは思わないか、コードをそうでない言語にもっと近づけたいと考えています。しかし、関数がファーストクラスのオブジェクトであるという事実を広範に使用して、関数がファーストクラスのオブジェクトである言語をより表現力豊かにするライブラリであることをlodashのせいにすることはできません。

それがあなたの質問の大部分だったと思います。 isPlainObjectの使用例は、「これは単なるデータですか?」という質問に答えることだと思います。または「このコードですか?」したがって、疑似クラス(何かのnew)として作成されたオブジェクトはカウントされません。

8
djechlin

コードが言葉よりも雄弁になることがあります。 lodashからのソースは次のとおりです。

function isObject(value) {
  var type = typeof value;
  return !!value && (type == 'object' || type == 'function');
}
function isPlainObject(value) {
  var Ctor;
  if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) ||
      (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
    return false;
  }
  var result;
  if (lodash.support.ownLast) {
    baseForIn(value, function(subValue, key, object) {
      result = hasOwnProperty.call(object, key);
      return false;
    });
    return result !== false;
  }
  baseForIn(value, function(subValue, key) {
    result = key;
  });
  return result === undefined || hasOwnProperty.call(value, result);
}

Lodashのドキュメントによると:

isObject

値がオブジェクトの 言語タイプ であるかどうかを確認します。 (例:配列、関数、オブジェクト、正規表現、新しい数値(0)、新しい文字列( ''))

isPlainObject

値がプレーンオブジェクト、つまりObjectコンストラクターによって作成されたオブジェクトであるか[[Prototype]]がnullのオブジェクトであるかを確認します。

オブジェクトとは何ですか?

おそらく、JavaScriptで非常に多くのものがオブジェクトのように機能する場合、isObject関数の存在は本質的に少し混乱します。重要なアイデアは、JavaScriptの一部の値はプリミティブと見なされ、他の値は本格的なオブジェクトと見なされるということです。文字列、数値、ブール値は、オブジェクトリテラルまたはコンストラクター関数で作成されたオブジェクトとは内部レベルで異なる方法で処理されます(プリミティブは必要に応じて自動的にオブジェクトにキャストされるため、これは実用的な意味を持ちません)。

事実 typeof null === 'object'おそらく、オブジェクトは参照型であり、オブジェクトの代わりに関数からnullが返されることが多いという事実に起因します。 (CおよびC++でのポインターとNULLPTRの概念に耳を傾ける可能性のある癖。タイプvoid *には多くの値がありますが、NULLPTRは依然としてポインター型の有効な値と見なされます。)

8
Ethan Lynn
  1. Lodash docs は、Lodashを「一貫性、モジュール性、パフォーマンス、および追加機能を提供するJavaScriptユーティリティライブラリ」と説明しています。 isObjectisPlainObjectは余分です。それらは効用関数です。彼らは彼らがタイプとは異なることを知っていたと確信しています。それが彼らが何人かの人々に役立つかもしれない理由です。彼らはおそらく、ネイティブのtypeofのパフォーマンス、一貫性、および構文は、同じことを行う.typeOfの作成を保証しないと考えていました。
  2. 上記のように、利点は、一部の人にとって便利なtypeofとは機能が少し異なることです。また、lodashはパフォーマンスの向上にも焦点を当てていますが、このような単純な関数のパフォーマンスが大幅に向上するかどうかはわかりません。 ループのパフォーマンスを向上させる と言われていますが。 JSPerf を使用してパフォーマンスの違いをテストし、お知らせください。関数に対して.isObjectがtrueを返すことにした理由は、JSの関数がオブジェクトであるためです。ネイティブのtypeofがオブジェクトではなく関数を返す場合、これは一種の欺瞞です。 typeof []は配列を返さず、代わりにオブジェクトを返すという事実によってさらに複雑になります。なぜ、関数のオブジェクトではなく関数を返す必要があるのでしょうか。
  3. Lodashもアンダースコアもtypeofと同じ関数を持っているようには見えません。アンダースコアには_.isObjectがあり、これはlodash .isObjectと同じです。 lodashの.isFunction.isObjectを使用して、ネイティブのtypeofと同じものを返す独自のtypeof関数を作成できます。
3
Daniel Kobe