Your Search Results

    Sameness in JavaScript

    この記事はまだ日本語に翻訳されていません。MDN の翻訳はボランティアによって行われています。是非 MDN に登録し、私たちの力になって下さい。

    ES6 has three built-in facilities for determining whether some x and some y are "the same". They are: equality or "double equals" (==), strict equality or "triple equals" (===), and Object.is. (Note that Object.is was added in ES6. Both double equals and triple equals existed prior to ES6, and their behavior remains unchanged.)

    Overview

    For demonstration, here are the three sameness comparisons in use:

    x == y
    x === y
    Object.is(x, y)

    Briefly, double equals will perform a type conversion when comparing two things; triple equals will do the same comparison without type conversion (by simply always returning false if the types differ); and Object.is will behave the same way as triple equals, but with special handling for NaN and -0 and +0 so that the last two are not said to be the same, while Object.is(NaN, NaN) will be true. (Comparing NaN with NaN ordinarily—i.e., using either double equals or triple equals—evaluates to false, because IEEE 754 says so.)

    Do note that the distinction between these all have to do with their handling of primitives; none of them compares whether the parameters are conceptually similar in structure. For any non-primitive objects x and y which have the same structure but are distinct objects themselves, all of the above forms will evaluate to false.

    For example:

    let x = { value: 17 };
    let y = { value: 17 };
    console.log(Object.is(x, y)); // false;
    console.log(x === y);         // false
    console.log(x == y);          // false

    Abstract equality, strict equality, and same value

    In ES5, the comparison performed by == is described in Section 11.9.3, The Abstract Equality Algorithm. The === comparison is 11.9.6, The Strict Equality Algorithm. (Go look at these. They're brief and readable. Hint: read the strict equality algorithm first.) ES5 also describes, in Section 9.12, The SameValue Algorithm for use internally by the JS engine. It's largely the same as the Strict Equality Algorithm, except that 11.9.6.4 and 9.12.4 differ in handling Numbers. ES6 simply proposes to expose this algorithm through Object.is.

    We can see that with double and triple equals, with the exception of doing a type check upfront in 11.9.6.1, the Strict Equality Algorithm is a subset of the Abstract Equality Algorithm, because 11.9.6.2–7 correspond to 11.9.3.1.a–f.

    A model for understanding equality comparisons?

    Prior to ES6, you might have said of double equals and triple equals that one is an "enhanced" version of the other. For example, someone might say that double equals is an extended version of triple equals, because the former does everything that the latter does, but with type conversion on its operands. E.g., 6 == "6". (Alternatively, someone might say that double equals is the baseline, and triple equals is an enhanced version, because it requires the two operands to be the same type, so it adds an extra constraint. Which one is the better model for understanding depends on how you choose to view things.)

    However, this way of thinking about the built-in sameness operators is not a model that can be stretched to allow a place for ES6's Object.is on this "spectrum". Object.is isn't simply "looser" than double equals or "stricter" than triple equals, nor does it fit somewhere in between (i.e., being both stricter than double equals, but looser than triple equals). We can see from the sameness comparisons table below that this is due to the way that Object.is handles NaN. Notice that if Object.is(NaN, NaN) evaluated to false, we could say that it fits on the loose/strict spectrum as an even stricter form of triple equals, one that distinguishes between -0 and +0. The NaN handling means this is untrue, however. Unfortunately, Object.is simply has to be thought of in terms of its specific characteristics, rather than its looseness or strictness with regard to the equality operators.

    Sameness Comparisons
    x y == === Object.is
    undefined undefined true true true
    null null true true true
    true true true true true
    false false true true true
    "foo" "foo" true true true
    { foo: "bar" } x true true true
    0 0 true true true
    +0 -0 true true false
    0 false true false false
    "" false true false false
    "" 0 true false false
    "0" 0 true false false
    "17" 17 true false false
    [1,2] "1,2" true false false
    new String("foo") "foo" true false false
    null undefined true false false
    null false false false false
    undefined false false false false
    { foo: "bar" } { foo: "bar" } false false false
    new String("foo") new String("foo") false false false
    0 null false false false
    0 NaN false false false
    "foo" NaN false false false
    NaN NaN false false true

    When to use Object.is versus triple equals

    Aside from the way it treats NaN, generally, the only time Object.is's special behavior towards zeroes is likely to be of interest is in the pursuit of certain metaprogramming schemes, especially regarding property descriptors when it is desirable for your work to mirror some of the characteristics of Object.defineProperty. If your use case does not require this, it is suggested to avoid Object.is and use === instead. Even if your requirements involve having comparisons between two NaN values evaluate to true, generally it is easier to special-case the NaN checks (using the isNaN method available from previous versions of ECMAScript) than it is to work out how surrounding computations might affect the sign of any zeroes you encounter in your comparison.

    Here's an inexhaustive list of built-in methods and operators that might cause a distinction between -0 and +0 to manifest itself in your code:

    - (unary negation)

    It's obvious that negating 0 produces -0. But the abstraction of an expression can cause -0 to creep in when you don't realize it. For example, consider:

    let stoppingForce = obj.mass * -obj.velocity

    If obj.velocity is 0 (or computes to 0), a -0 is introduced at that place and propogates out into stoppingForce.

    Math.atan2
    Math.ceil
    Math.pow
    Math.round
    It's possible for a -0 to be introduced into an expression as a return value of these methods in some cases, even when no -0 exists as one of the parameters. E.g., using Math.pow to raise -Infinity to the power of any negative, odd exponent evaluates to -0. Refer to the documentation for the individual methods.
    Math.floor
    Math.max
    Math.min
    Math.sin
    Math.sqrt
    Math.tan
    It's possible to get a -0 return value out of these methods in some cases where a -0 exists as one of the parameters. E.g., Math.min(-0, +0) evalutes to -0. Refer to the documentation for the individual methods.
    ~
    <<
    >>
    Each of these operators uses the ToInt32 algorithm internally. Since there is only one representation for 0 in the internal 32-bit integer type, -0 will not survive a round trip after an inverse operation. E.g., both Object.is(~~(-0), -0) and Object.is(-0 << 2 >> 2, -0) evaluate to false.

    Relying on Object.is when the signedness of zeroes is not taken into account can be hazardous. Of course, when the intent is to distinguish between -0 and +0, it does exactly what's desired.

    Document Tags and Contributors

    Contributors to this page: sdepold, rwaldron, Sevenspade, SphinxKnight
    最終更新者: Sevenspade,