Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

The basics

Slide 4

Slide 4 text

Values: primitives vs. objects

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Prototypes and methods

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Categorizing values

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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'

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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'

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Dr. Axel Rauschmayer, Ecmanauten.de Crossing frames Each frame has own global environment 㱺 problems with instanceof. 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 } 25

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Warning: WTFs ahead

Slide 28

Slide 28 text

Built-in instance prototypes

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Instances with [[PrimitiveValue]]

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Recommendations

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Thank you! Slides: speakerdeck.com/rauschma

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Bonus slides

Slide 49

Slide 49 text

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'] …

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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