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. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. Dr. Axel Rauschmayer, Ecmanauten.de Four ways of categorizing values 1.

    Internal property [[Class]] 2. typeof 3. instanceof 4. Array.isArray() 16
  12. 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'
  13. 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
  14. 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
  15. 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
  16. 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'
  17. Dr. Axel Rauschmayer, Ecmanauten.de Is a value an object? function

    isObject(v) { return (typeof v === 'object' && v !== null) || typeof v === 'function'; } 22
  18. Dr. Axel Rauschmayer, Ecmanauten.de 3. instanceof 23 Object Foo Object.prototype

    Foo.prototype null foo [[Prototype]] [[Prototype]] [[Prototype]] prototype prototype
  19. Dr. Axel Rauschmayer, Ecmanauten.de instanceof Categorize objects. Syntax: v instanceof

    C Semantics: function myInstanceof(v, C) { return C.prototype.isPrototypeOf(v); } 24
  20. Dr. Axel Rauschmayer, Ecmanauten.de 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> 25
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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'] …
  37. 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
  38. 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
  39. 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
  40. Dr. Axel Rauschmayer, Ecmanauten.de constructor use case: clone current instance

    SuperConstr.prototype.createCopy = function () { return new this.constructor(...); }; 53