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. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. Categorizing values Crossing frames Each frame has own global environment

    ⇒ problems with instanceof. <head> <script> function test(arr) { var iframe = frames[0]; console.log(arr instanceof Array); // false console.log(arr instanceof iframe.Array); // true console.log(Array.isArray(arr)); // true } </script> </head> <body> <iframe srcdoc="<script>window.parent.test([])</script>"> </iframe> </body> Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 20 / 46
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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