Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Categorizing values: typeof, instanceof and beyond

Categorizing values: typeof, instanceof and beyond

You have probably heard of two common ways of categorizing values in JavaScript: typeof and instanceof. In this talk, we first review the basics of this kind of categorization and then move on to quirks and advanced topics: primitive values versus objects; typeof and instanceof (and their quirks); the internal property [[Class]]; the odd nature of the prototypes of built-in constructors; and more.

Axel Rauschmayer

July 06, 2013
Tweet

More Decks by Axel Rauschmayer

Other Decks in Programming

Transcript

  1. Categorizing values
    typeof, instanceof and beyond
    Dr. Axel Rauschmayer
    2ality.com
    2013-07-06
    Spain.js 2013, Madrid

    View Slide

  2. About me
    Axel Rauschmayer:
    Editor of JavaScript Weekly
    Blogger at 2ality.com
    Co-organizer of MunichJS (JavaScript user group)
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 2 / 46

    View Slide

  3. About this talk
    A lesson in JavaScript:
    What kinds of values exist?
    How are values categorized?
    Structure:
    1 Basics: primitives vs. objects, typeof, instanceof.
    2 Advanced: typeof and instanceof are not always enough.
    3 WTFs: weird aspects of categorization.
    4 Best practices
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 3 / 46

    View Slide

  4. The basics

    View Slide

  5. Values

    View Slide

  6. Values
    Values: primitive values versus objects
    Not everything is an object. Quoting the ECMAScript 5.1 specification:
    A primitive value is a member of one of the following built-in types:
    Undefined, Null, Boolean, Number, and String;
    an object is a member of the remaining built-in type Object
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 6 / 46

    View Slide

  7. Values
    Main difference: comparison
    Object: unique identity, only equal to itself.
    > var obj1 = {};
    > var obj2 = {};
    > obj1 === obj2
    false
    > obj1 === obj1
    true
    Primitive value: equal to all values with the same content.
    > var prim1 = 123;
    > var prim2 = 123;
    > prim1 === prim2
    true
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 7 / 46

    View Slide

  8. Values
    Primitive values (short: primitives)
    Compared by value (comparing “the content”)
    Always immutable
    Fixed set of values
    typeof
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 8 / 46

    View Slide

  9. Values
    Objects
    Compared by reference (comparing object identities)
    Mutable by default
    User-extensible
    instanceof (most of the time)
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 9 / 46

    View Slide

  10. Values
    Internal properties
    A special kind of object property:
    Not accessible from the language,
    but influence how it works.
    Names are written in [[double square brackets]].
    Example: [[Extensible]] – can properties be added?
    Check: Object.isExtensible(obj)
    Set to false: Object.preventExtensions(obj)
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 10 / 46

    View Slide

  11. Values
    JavaScript terminology: the two prototypes
    Prototype: Prototype-of relationship between objects.
    Internal property [[Prototype]]
    Value: the prototype object or null
    Read: Object.getPrototypeOf()
    Set, while creating an object: Object.create()
    Instance prototype: Value of Constr.prototype (of a constructor
    Constr).
    The common [[Prototype]] of all instances of Constr.
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 11 / 46

    View Slide

  12. Categorizing values

    View Slide

  13. Categorizing values
    Four ways of categorizing values
    1 Internal property [[Class]]
    2 typeof
    3 instanceof
    4 Array.isArray()
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 13 / 46

    View Slide

  14. Categorizing values
    1. Internal property [[Class]]
    Internal own (non-inherited) property of all objects. Values:
    arguments:
    "Arguments"
    Instances of built-in constructors:
    "Boolean", "Number", "String",
    "Array", "Function", "RegExp", "Date", "Error"
    Global values:
    "JSON", "Math"
    All other objects:
    "Object"
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 14 / 46

    View Slide

  15. Categorizing values
    Reading [[Class]]
    Indirect access via Object.prototype.toString(), which returns:
    undefined [object Undefined]
    null [object Null]
    Object obj [object + obj.[[Class]] + ]
    Compare:
    > /abc/.toString()
    /abc/
    > Object.prototype.toString.call(/abc/)
    [object RegExp]
    Tool function:
    function getClass(x) {
    var str = Object.prototype.toString.call(x);
    return /^\[object (.*)\]$/.exec(str)[1];
    }
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 15 / 46

    View Slide

  16. Categorizing values
    getClass() in action
    > getClass(null)
    Null
    > getClass([])
    Array
    > getClass(JSON)
    JSON
    > function Foo() {}
    > getClass(Foo)
    Function
    > getClass(new Foo())
    Object
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 16 / 46

    View Slide

  17. Categorizing values
    2. typeof
    Mainly: categorize primitives.
    Operand Result
    undefined undefined
    null object bug!
    Boolean value boolean
    Number value number
    String value string
    Function function callable object (has property [[Call]])
    All other values object
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 17 / 46

    View Slide

  18. Categorizing values
    Determine: is a value an object?
    function isObject(v) {
    return (typeof v === object && v !== null)
    || typeof v === function ;
    }
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 18 / 46

    View Slide

  19. Categorizing values
    3. instanceof
    Categorize objects.
    Syntax:
    value instanceof Constr
    Semantics:
    function myInstanceof(value, Constr) {
    return Constr.prototype.isPrototypeOf(value);
    }
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 19 / 46

    View Slide

  20. Categorizing values
    Crossing frames
    Each frame has own global environment ⇒ problems with instanceof.

    <br/>function test(arr) {<br/>var iframe = frames[0];<br/>console.log(arr instanceof Array); // false<br/>console.log(arr instanceof iframe.Array); // true<br/>console.log(Array.isArray(arr)); // true<br/>}<br/>





    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 20 / 46

    View Slide

  21. Categorizing values
    4. Array.isArray()
    Uses [[Class]] to determine whether a value is an array.
    Original goal: allow JSON.stringify() to safely detect arrays.
    But: generally useful when crossing frames.
    function isArray(value) {
    var str = Object.prototype.toString.call(value);
    return str === [object Array] ;
    }
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 21 / 46

    View Slide

  22. Warning: WTFs

    View Slide

  23. Built-in instance prototypes

    View Slide

  24. Built-in instance prototypes
    Object.prototype
    It’s an object:
    > Object.prototype
    {}
    > typeof Object.prototype
    object
    > getClass(Object.prototype)
    Object
    But it’s not an instance of Object:
    > Object.prototype instanceof Object
    false
    Why? Can’t be its own prototype:
    > Object.getPrototypeOf(Object.prototype)
    null
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 24 / 46

    View Slide

  25. Built-in instance prototypes
    Function.prototype
    It’s a function:
    > Function.prototype(1,2,3)
    undefined
    > typeof Function.prototype // has [[Call]]
    function
    > getClass(Function.prototype)
    Function
    It’s not an instance of Function (can’t be its own prototype):
    > Function.prototype instanceof Function
    false
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 25 / 46

    View Slide

  26. Built-in instance prototypes
    Array.prototype
    An array (except for instanceof):
    > Array.prototype
    []
    > Array.prototype.length
    0
    > getClass(Array.prototype)
    Array
    > Array.isArray(Array.prototype) // uses [[Class]]
    true
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 26 / 46

    View Slide

  27. Built-in instance prototypes
    RegExp.prototype
    It’s the empty regular expression (equivalent to a non-capturing empty
    group):
    > RegExp.prototype
    /(?:)/
    > new RegExp( )
    /(?:)/
    The empty regular expression matches everything:
    > RegExp.prototype.test( abc )
    true
    > RegExp.prototype.test( )
    true
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 27 / 46

    View Slide

  28. Built-in instance prototypes
    String.prototype.match
    Accepts RegExp.prototype:
    > xyz .match(RegExp.prototype)
    [ , index: 0, input: xyz ]
    Reason: checks its parameter via [[Class]]:
    > getClass(RegExp.prototype)
    RegExp
    > getClass(/abc/)
    RegExp
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 28 / 46

    View Slide

  29. Instances with [[PrimitiveValue]]

    View Slide

  30. Instances with [[PrimitiveValue]]
    [[PrimitiveValue]]
    Constructors whose instances have property [[PrimitiveValue]]:
    Boolean
    Number
    String
    Date
    Things to note:
    Instance prototypes also have [[PrimitiveValue]].
    Method valueOf() returns value of [[PrimitiveValue]].
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 30 / 46

    View Slide

  31. Instances with [[PrimitiveValue]]
    Boolean.prototype
    > getClass(Boolean.prototype)
    Boolean
    > Boolean.prototype.valueOf() // returns [[PrimitiveValue]]
    false
    > getClass(new Boolean(false))
    Boolean
    > Boolean.prototype == false
    true
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 31 / 46

    View Slide

  32. Instances with [[PrimitiveValue]]
    Number.prototype
    > getClass(Number.prototype)
    Number
    > Number.prototype.valueOf()
    0
    > getClass(new Number(0))
    Number
    > Number.prototype == 0
    true
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 32 / 46

    View Slide

  33. Instances with [[PrimitiveValue]]
    String.prototype
    > getClass(String.prototype)
    String
    > String.prototype.valueOf()
    > getClass(new String( ))
    String
    > String.prototype ==
    true
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 33 / 46

    View Slide

  34. Instances with [[PrimitiveValue]]
    Dates
    Dates wrap numbers. Quoting the ECMAScript 5.1 specification:
    A Date object contains a Number indicating a particular instant
    in time to within a millisecond. Such a Number is called a time
    value. A time value may also be NaN, indicating that the Date
    object does not represent a specific instant of time.
    Time is measured in ECMAScript in milliseconds since 01
    January, 1970 UTC.
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 34 / 46

    View Slide

  35. Instances with [[PrimitiveValue]]
    Date.prototype
    It’s a date:
    > getClass(Date.prototype)
    Date
    It’s an invalid date, wrapping NaN:
    > Date.prototype
    Invalid Date
    > Date.prototype.valueOf()
    NaN
    Compare:
    > new Date(NaN)
    Invalid Date
    > new Date(NaN).valueOf()
    NaN
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 35 / 46

    View Slide

  36. Recommendations

    View Slide

  37. Recommendations
    Recommendations: normal code
    Avoid categorization, if you can. For example, via polymorphism.
    Don’t do this:
    function bla(x) {
    if (x instanceof Foo) {
    ...
    } else if (x instanceof Bar) {
    ...
    } else ...
    }
    Do this:
    Foo.prototype.bla = function () { ... };
    Bar.prototype.bla = function () { ... };
    ...
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 37 / 46

    View Slide

  38. Recommendations
    Recommendations: normal code
    If you can’t avoid categorization:
    Use typeof and instanceof
    Ignore [[Class]] and Array.isArray()
    Careful with typeof:
    function versus object
    typeof null
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 38 / 46

    View Slide

  39. Recommendations
    Recommendations: values crossing frames
    If values cross frames:
    instanceof is not reliable, any more.
    Consider [[Class]] and Array.isArray()
    Avoid if possible, e.g. via window.postMessage()
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 39 / 46

    View Slide

  40. Recommendations
    WTFs
    Recommendation: use constructors in the canonical way.
    Reason: optimizations (hidden classes etc.).
    Reason: ECMAScript 6 classes.
    Recommendation: ignore that built-in instance prototypes are “primal
    instances”.
    Clashes with canonical use.
    Only confuses beginners.
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 40 / 46

    View Slide

  41. Recommendations
    Thank you!
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 41 / 46

    View Slide

  42. Recommendations
    Resources
    In-depth article on categorization: “Categorizing values in JavaScript”
    Using objects as constructors, not functions: “Prototypes as classes –
    an introduction to JavaScript inheritance”
    Quickly learn a safe subset of JavaScript: “Basic JavaScript: an
    introduction to the language ”
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 42 / 46

    View Slide

  43. Bonus slides

    View Slide

  44. Bonus slides
    Where do values get their methods from?
    Instances (objects) of a constructor C: C.prototype
    > [].push === Array.prototype.push
    true
    Primitive values (except undefined and null): from wrapper types.
    Booleans: Boolean
    > true.toString === Boolean.prototype.toString
    true
    Numbers: Number
    Strings: String
    Note: the actual instances of wrapper types are objects.
    Recommendation: ignore.
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 44 / 46

    View Slide

  45. Bonus slides
    The property constructor
    Prototypes point back to their constructor:
    > function Foo() { }
    > Foo.prototype.constructor === Foo
    true
    > RegExp.prototype.constructor === RegExp
    true
    Inherited by instance: find out who created it.
    > new Foo().constructor
    [Function: Foo]
    > /abc/.constructor
    [Function: RegExp]
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 45 / 46

    View Slide

  46. Bonus slides
    Prototype chain
    null

    Object.prototype

    Function.prototype

    function () { }
    Most objects have Object.prototype in their prototype chain. Avoid it
    via:
    Object.create(null)
    Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 46 / 46

    View Slide