Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

The basics

Slide 5

Slide 5 text

Values

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Categorizing values

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Categorizing values 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 } Dr. Axel Rauschmayer (2ality.com) Categorizing values 2013-07-06 20 / 46

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Warning: WTFs

Slide 23

Slide 23 text

Built-in instance prototypes

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Instances with [[PrimitiveValue]]

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Recommendations

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Bonus slides

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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