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

Categorizing Values: typeof, instanceof and Beyond (December 2014)

Categorizing Values: typeof, instanceof and Beyond (December 2014)

Axel Rauschmayer

December 16, 2014
Tweet

More Decks by Axel Rauschmayer

Other Decks in Programming

Transcript

  1. Categorizing Values:
    typeof, instanceof
    and Beyond
    NomadJS, 2014-12-16

    View Slide

  2. Dr. Axel Rauschmayer, Ecmanauten.de
    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
    2

    View Slide

  3. The basics

    View Slide

  4. Values: primitives vs.
    objects

    View Slide

  5. Dr. Axel Rauschmayer, Ecmanauten.de
    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
    5

    View Slide

  6. Dr. Axel Rauschmayer, Ecmanauten.de
    Main difference: comparison
    Object – compared by reference: unique identity, only equal to itself.
    > var obj1 = {};
    > var obj2 = {};
    > obj1 === obj2
    false
    > obj1 === obj1
    true
    Primitive value – compared by value: equal to all values with same
    content.
    > var prim1 = 123;
    > var prim2 = 123;
    > prim1 === prim2
    true
    6

    View Slide

  7. Dr. Axel Rauschmayer, Ecmanauten.de
    Primitive values (short:
    primitives)
    • Compared by value (comparing “the content”)
    • Always immutable
    • Fixed set of types (can’t define your own)
    • Categorization: typeof
    7

    View Slide

  8. Dr. Axel Rauschmayer, Ecmanauten.de
    Objects
    • Compared by reference (comparing object
    identities)
    • Mutable by default
    • User-extensible (constructors as pseudo-types)
    • Categorization: instanceof (most of the time)
    8

    View Slide

  9. Prototypes and
    methods

    View Slide

  10. Dr. Axel Rauschmayer, Ecmanauten.de
    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)
    10

    View Slide

  11. Dr. Axel Rauschmayer, Ecmanauten.de
    Terminology: two prototypes
    1. Prototype: relationship between
    objects.
    • Object inherits properties of prototype.
    • 㱺 prototype chain of objects
    2. Instance prototype: Value of
    Point.prototype.
    • Shared [[Prototype]] of all instances of
    Point
    11
    dist
    ···
    function() {···}
    Point.prototype
    prototype
    Point
    x 0
    [[Prototype]]
    y 7
    x 8
    [[Prototype]]
    y 5
    p1 p2
    2
    1 1

    View Slide

  12. Dr. Axel Rauschmayer, Ecmanauten.de
    Object
    Foo
    Object.prototype
    Foo.prototype
    null
    foo
    [[Prototype]]
    [[Prototype]]
    [[Prototype]]
    prototype
    prototype
    Objects: prototype chain
    Where do methods come
    from?
    12

    View Slide

  13. Dr. Axel Rauschmayer, Ecmanauten.de
    The prototype chain
    > var p = Object.getPrototypeOf;
    > var arr = [];
    > p(arr) === Array.prototype
    true
    > p(p(arr)) === Object.prototype
    true
    > p(p(p(arr)))
    null
    > [].push === Array.prototype.push
    true
    13

    View Slide

  14. Dr. Axel Rauschmayer, Ecmanauten.de
    Where do methods come
    from?
    Primitive values:
    • Properties are inherited from wrapper constructors:
    Boolean, Number, String
    • Ignore: wrapper constructors have their own
    instances (objects!)
    > Object(true) instanceof Boolean
    true
    > true.toString === Boolean.prototype.toString
    true
    14

    View Slide

  15. Categorizing values

    View Slide

  16. Dr. Axel Rauschmayer, Ecmanauten.de
    Four ways of categorizing
    values
    1. Internal property [[Class]]
    2. typeof
    3. instanceof
    4. Array.isArray()
    16

    View Slide

  17. Dr. Axel Rauschmayer, Ecmanauten.de
    1. Internal property [[Class]]
    Internal own (non-inherited) property of all objects.
    17
    Value [[Class]]
    arguments 'Arguments'
    Instances of built-in
    constructors
    'Boolean', 'Number', 'String',

    'Array', 'Function', 'RegExp',
    'Date', 'Error'
    Global values 'JSON', 'Math'
    All other objects 'Object'

    View Slide

  18. Dr. Axel Rauschmayer, Ecmanauten.de
    Reading [[Class]]
    Indirect access via
    Object.prototype.toString(), which returns:
    someObj '[object '+someObj.[[Class]]
    +']'
    undefined '[object Undefined]'
    null '[object Null]'
    Compare:
    > /abc/.toString()
    '/abc/'
    > Object.prototype.toString.call(/abc/)
    '[object RegExp]'
    18

    View Slide

  19. Dr. Axel Rauschmayer, Ecmanauten.de
    Tool function getClass()
    function getClass(x) {
    var str = Object.prototype.toString.call(x);
    var match = /^\[object (.*)\]$/.exec(str);
    if (match) {
    return match[1];
    } else {
    return str;
    }
    }
    Use case: whenever instanceof doesn’t work (explained later).
    19

    View Slide

  20. Dr. Axel Rauschmayer, Ecmanauten.de
    Tool function getClass()
    > getClass(null)
    'Null'
    > getClass([])
    'Array'
    > getClass(JSON)
    'JSON'
    > function Foo() {}
    > getClass(Foo)
    'Function'
    > getClass(new Foo())
    'Object'
    20

    View Slide

  21. Dr. Axel Rauschmayer, Ecmanauten.de
    2. typeof
    21
    Operand Result
    undefined 'undefined'
    null 'object' (bug)
    boolean value 'boolean'
    number value 'number'
    string value 'string'
    function 'function' (has property [[Call]])
    all other values 'object'

    View Slide

  22. Dr. Axel Rauschmayer, Ecmanauten.de
    Is a value an object?
    function isObject(v) {
    return (typeof v === 'object' && v !== null)
    || typeof v === 'function';
    }
    22

    View Slide

  23. Dr. Axel Rauschmayer, Ecmanauten.de
    3. instanceof
    23
    Object
    Foo
    Object.prototype
    Foo.prototype
    null
    foo
    [[Prototype]]
    [[Prototype]]
    [[Prototype]]
    prototype
    prototype

    View Slide

  24. Dr. Axel Rauschmayer, Ecmanauten.de
    instanceof
    Categorize objects.
    Syntax:
    v instanceof C
    Semantics:
    function myInstanceof(v, C) {
    return C.prototype.isPrototypeOf(v);
    }
    24

    View Slide

  25. Dr. Axel Rauschmayer, Ecmanauten.de
    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/>





    25

    View Slide

  26. Dr. Axel Rauschmayer, Ecmanauten.de
    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]';
    }
    26

    View Slide

  27. Warning: WTFs ahead

    View Slide

  28. Built-in instance
    prototypes

    View Slide

  29. Dr. Axel Rauschmayer, Ecmanauten.de
    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
    29

    View Slide

  30. Dr. Axel Rauschmayer, Ecmanauten.de
    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
    30

    View Slide

  31. Dr. Axel Rauschmayer, Ecmanauten.de
    Array.prototype
    An array (except for instanceof):
    > Array.prototype
    []
    > Array.prototype.length
    0
    > getClass(Array.prototype)
    'Array'
    > Array.isArray(Array.prototype) // uses [[Class]]
    true
    31

    View Slide

  32. Dr. Axel Rauschmayer, Ecmanauten.de
    RegExp.prototype
    It’s the empty regular expression (equivalent to non-
    capturing empty group):
    > RegExp.prototype
    /(?:)/
    > new RegExp('')
    /(?:)/
    The empty regular expression matches everything:
    > RegExp.prototype.test('abc')
    true
    > RegExp.prototype.test('')
    true
    32

    View Slide

  33. Dr. Axel Rauschmayer, Ecmanauten.de
    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'
    33

    View Slide

  34. Instances with
    [[PrimitiveValue]]

    View Slide

  35. Dr. Axel Rauschmayer, Ecmanauten.de
    [[PrimitiveValue]]
    Constructors whose instances have the property [[PrimitiveValue]]:
    • Boolean
    • Number
    • String
    • Date
    Things to note:
    • Instance prototypes also have [[PrimitiveValue]].
    • Method valueOf() returns value of [[PrimitiveValue]].
    35

    View Slide

  36. Dr. Axel Rauschmayer, Ecmanauten.de
    Boolean.prototype
    > getClass(Boolean.prototype)
    'Boolean'
    > Boolean.prototype.valueOf() // [[PrimitiveValue]]
    false
    > getClass(new Boolean(false))
    'Boolean'
    > Boolean.prototype == false
    true
    > Boolean(Boolean.prototype)
    true
    36

    View Slide

  37. Dr. Axel Rauschmayer, Ecmanauten.de
    Number.prototype
    > getClass(Number.prototype)
    'Number'
    > Number.prototype.valueOf()
    0
    > getClass(new Number(0))
    'Number'
    > Number.prototype == 0
    true
    37

    View Slide

  38. Dr. Axel Rauschmayer, Ecmanauten.de
    String.prototype
    > getClass(String.prototype)
    'String'
    > String.prototype.valueOf()
    ''
    > getClass(new String(''))
    'String'
    > String.prototype == ''
    true
    38

    View Slide

  39. Dr. Axel Rauschmayer, Ecmanauten.de
    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.
    39

    View Slide

  40. Dr. Axel Rauschmayer, Ecmanauten.de
    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
    40

    View Slide

  41. Recommendations

    View Slide

  42. Dr. Axel Rauschmayer, Ecmanauten.de
    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 () { ··· };
    ···
    42

    View Slide

  43. Dr. Axel Rauschmayer, Ecmanauten.de
    Recommendations: normal
    code
    If you can’t avoid categorization:
    • Use typeof and instanceof
    • Normally ignore [[Class]] and Array.isArray()
    Careful with typeof:
    • 'function' versus 'object'
    • typeof null
    43

    View Slide

  44. Dr. Axel Rauschmayer, Ecmanauten.de
    Recommendations: values
    crossing frames
    If values cross frames:
    • instanceof is not reliable, anymore.
    • Consider [[Class]] and Array.isArray()
    Avoid if possible, e.g. via window.postMessage()
    44

    View Slide

  45. Dr. Axel Rauschmayer, Ecmanauten.de
    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.
    45

    View Slide

  46. Thank you!
    Slides: speakerdeck.com/rauschma

    View Slide

  47. Dr. Axel Rauschmayer, Ecmanauten.de
    Resources
    • In-depth article on categorization: “Categorizing
    values in JavaScript”
    • Using objects as constructors, not functions:
    “Prototypes as classes – an introduction to
    JavaScript inheritance”
    • “Values” – chapter in “Speaking JavaScript”
    47

    View Slide

  48. Bonus slides

    View Slide

  49. Dr. Axel Rauschmayer, Ecmanauten.de
    The property constructor
    49
    … …
    push function() {...}
    [[Prototype]]
    constructor
    Array.prototype
    prototype
    Array
    length 2
    1 'b'
    0 'a'
    [[Prototype]]
    ['a', 'b']

    View Slide

  50. Dr. Axel Rauschmayer, Ecmanauten.de
    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]
    50

    View Slide

  51. Dr. Axel Rauschmayer, Ecmanauten.de
    constructor use case:
    switch
    try {
    ...
    } catch (e) {
    switch (e.constructor) {
    case SyntaxError:
    ...
    break;
    case CustomError:
    ...
    break;
    ...
    }
    }
    // Warning: only detects direct instances
    51

    View Slide

  52. Dr. Axel Rauschmayer, Ecmanauten.de
    constructor use case:
    name of constructor
    function Foo() {}
    var f = new Foo();
    console.log(f.constructor.name); // 'Foo'
    // Warning: not supported in all engines
    52

    View Slide

  53. Dr. Axel Rauschmayer, Ecmanauten.de
    constructor use case:
    clone current instance
    SuperConstr.prototype.createCopy = function () {
    return new this.constructor(...);
    };
    53

    View Slide