Share
Embed
Categorizing Values: typeof, instanceof and Beyond (December 2014)
Axel Rauschmayer
December 16, 2014
Transcript
53 slides
-
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
Dr. Axel Rauschmayer, Ecmanauten.de Four ways of categorizing values 1.
Internal property [[Class]] 2. typeof 3. instanceof 4. Array.isArray() 16 -
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' -
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 -
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 -
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 -
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' -
Dr. Axel Rauschmayer, Ecmanauten.de Is a value an object? function
isObject(v) { return (typeof v === 'object' && v !== null) || typeof v === 'function'; } 22 -
Dr. Axel Rauschmayer, Ecmanauten.de 3. instanceof 23 Object Foo Object.prototype
Foo.prototype null foo [[Prototype]] [[Prototype]] [[Prototype]] prototype prototype -
Dr. Axel Rauschmayer, Ecmanauten.de instanceof Categorize objects. Syntax: v instanceof
C Semantics: function myInstanceof(v, C) { return C.prototype.isPrototypeOf(v); } 24 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
Dr. Axel Rauschmayer, Ecmanauten.de String.prototype > getClass(String.prototype) 'String' > String.prototype.valueOf()
'' > getClass(new String('')) 'String' > String.prototype == '' true 38 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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'] … -
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 -
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 -
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 -
Dr. Axel Rauschmayer, Ecmanauten.de constructor use case: clone current instance
SuperConstr.prototype.createCopy = function () { return new this.constructor(...); }; 53