Classes (which are explained in the next chapter) are the major new OOP feature in ECMAScript 6. However, it also includes new features for object literals and new utility methods in Object
. This chapter describes them.
Object
Object
Object.assign(target, source_1, source_2, ···)
Object.getOwnPropertySymbols(obj)
Object.setPrototypeOf(obj, proto)
__proto__
in ECMAScript 6
__proto__
prior to ECMAScript 6__proto__
in ECMAScript 6__proto__
__proto__
__proto__
is pronounced “dunder proto”__proto__
Symbol.hasInstance
(method)Symbol.toPrimitive
(method)Symbol.toStringTag
(string)Symbol.unscopables
(Object)super
in object literals?Method definitions:
Property value shorthands:
Computed property keys:
This new syntax can also be used for method definitions:
The main use case for computed property keys is to make it easy to use symbols as property keys.
Object
The most important new method of Object
is assign()
. Traditionally, this functionality was called extend()
in the JavaScript world. In contrast to how this classic operation works, Object.assign()
only considers own (non-inherited) properties.
In ECMAScript 5, methods are properties whose values are functions:
In ECMAScript 6, methods are still function-valued properties, but there is now a more compact way of defining them:
Getters and setters continue to work as they did in ECMAScript 5 (note how syntactically similar they are to method definitions):
Let’s use obj
:
There is also a way to concisely define properties whose values are generator functions:
This code is equivalent to:
Property value shorthands let you abbreviate the definition of a property in an object literal: If the name of the variable that specifies the property value is also the property key then you can omit the key. This looks as follows.
The last line is equivalent to:
Property value shorthands work well together with destructuring:
One use case for property value shorthands are multiple return values (which are explained in the chapter on destructuring).
Remember that there are two ways of specifying a key when you set a property.
obj.foo = true;
obj['b'+'ar'] = 123;
In object literals, you only have option #1 in ECMAScript 5. ECMAScript 6 additionally provides option #2 (line A):
This new syntax can also be used for method definitions:
The main use case for computed property keys are symbols: you can define a public symbol and use it as a special property key that is always unique. One prominent example is the symbol stored in Symbol.iterator
. If an object has a method with that key, it becomes iterable: The method must return an iterator, which is used by constructs such as the for-of
loop to iterate over the object. The following code demonstrates how that works.
obj
is iterable, due to the generator method definition starting in line A.
Object
Object.assign(target, source_1, source_2, ···)
This method merges the sources into the target: It modifies target
, by first copying all enumerable own (non-inherited) properties of source_1
into it, then all own properties of source_2
, etc. At the end, it returns the target.
Let’s look more closely at how Object.assign()
works:
Object.assign()
is aware of both strings and symbols as property keys.Object.assign()
ignores inherited properties and properties that are not enumerable.That means that if the source has a getter whose key is propKey
then it will be invoked. Consequently, properties created by Object.assign()
are data properties, it won’t transfer getters to the target.
Consequently, if the target has a setter whose key is propKey
then it will be invoked with value
.
This is how you would copy all own properties (not just enumerable ones), while correctly transferring getters and setters and without invoking setters on the target:
Consult Sect. “Property Attributes and Property Descriptors” in ”Speaking JavaScript” for more information on property descriptors (as used by Object.getOwnPropertyDescriptor()
and Object.defineProperty()
).
Object.assign()
doesn’t work well for moving methods On one hand, you can’t move a method that uses super
: Such a method has the internal slot [[HomeObject]]
that ties it to the object it was created in. If you move it via Object.assign()
, it will continue to refer to the super-properties of the original object. Details are explained in a section in the chapter on classes.
On the other hand, enumerability is wrong if you move methods created by an object literal into the prototype of a class. The former methods are all enumerable (otherwise Object.assign()
wouldn’t see them, anyway), but the prototype normally only has non-enumerable methods.
Object.assign()
Let’s look at a few use cases.
this
You can use Object.assign()
to add properties to this
in a constructor:
Object.assign()
is also useful for filling in defaults for missing properties. In the following example, we have an object DEFAULTS
with default values for properties and an object options
with data.
In line A, we created a fresh object, copied the defaults into it and then copied options
into it, overriding the defaults. Object.assign()
returns the result of these operations, which we assign to options
.
Another use case is adding methods to objects:
You can also manually assign functions, but then you don’t have the nice method definition syntax and need to mention SomeClass.prototype
each time:
One last use case for Object.assign()
is a quick way of cloning objects:
This way of cloning is also somewhat dirty, because it doesn’t preserve the property attributes of orig
. If that is what you need, you have to use property descriptors, like we did to implement copyAllOwnProperties()
.
If you want the clone to have the same prototype as the original, you can use Object.getPrototypeOf()
and Object.create()
:
Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols(obj)
retrieves all own (non-inherited) symbol-valued property keys of obj
. It complements Object.getOwnPropertyNames()
, which retrieves all string-valued own property keys. Consult a later section for more details on traversing properties.
The strict equals operator (===
) treats two values differently than one might expect.
First, NaN
is not equal to itself.
That is unfortunate, because it often prevents us from detecting NaN
:
Second, JavaScript has two zeros, but strict equals treats them as if they were the same value:
Doing this is normally a good thing.
Object.is()
provides a way of comparing values that is a bit more precise than ===
. It works as follows:
Everything else is compared as with ===
.
Object.is()
to find Array elements In the following function myIndexOf()
, we combine Object.is()
with the new ES6 Array method findIndex()
to find NaN
in Arrays.
As you can see in the last line, indexOf()
does not find NaN
.
Object.setPrototypeOf(obj, proto)
This method sets the prototype of obj
to proto
. The non-standard way of doing so in ECMAScript 5, that is supported by many engines, is via assigning to the special property __proto__
. The recommended way of setting the prototype remains the same as in ECMAScript 5: during the creation of an object, via Object.create()
. That will always be faster than first creating an object and then setting its prototype. Obviously, it doesn’t work if you want to change the prototype of an existing object.
In ECMAScript 6, the key of a property can be either a string or a symbol. The following are five operations that traverse the property keys of an object obj
:
Object.keys(obj) : Array<string>
Object.getOwnPropertyNames(obj) : Array<string>
Object.getOwnPropertySymbols(obj) : Array<symbol>
Reflect.ownKeys(obj) : Array<string|symbol>
for (const key in obj)
ES6 defines two traversal orders for properties.
Own Property Keys:
Object.assign()
, Object.defineProperties()
, Object.getOwnPropertyNames()
, Object.getOwnPropertySymbols()
, Reflect.ownKeys()
Enumerable Own Names:
for-in
traverses properties.JSON.parse()
, JSON.stringify()
, Object.keys()
The order in which for-in
traverses properties is not defined. Quoting Allen Wirfs-Brock:
Historically, the
for-in
order was not defined and there has been variation among browser implementations in the order they produce (and other specifics). ES5 addedObject.keys
and the requirement that it should order the keys identically tofor-in
. During development of both ES5 and ES6, the possibility of defining a specificfor-in
order was considered but not adopted because of web legacy compatibility concerns and uncertainty about the willingness of browsers to make changes in the ordering they currently produce.
Even though you access Array elements via integer indices, the spec treats them as normal string property keys:
Integer indices are special in only two ways: they affect the length
of an Array and they come first when listing property keys.
Roughly, an integer index is a string that, if converted to a 53-bit non-negative integer and back, is the same value. Therefore:
'10'
and '2'
are integer indices.'02'
is not an integer index. Converting it to an integer and back results in the different string '2'
.'3.141'
is not an integer index, because 3.141 is not an integer.Further reading:
The following code demonstrates the traversal order “Own Property Keys”:
Explanation:
'2'
and '10'
are integer indices, come first and are sorted numerically (not lexicographically).'02'
and '01'
are normal string keys, come next and appear in the order in which they were added to obj
.Symbol('first')
and Symbol('second')
are symbols and come last, in the order in which they were added to obj
.Answer by Tab Atkins Jr.:
Because, for objects at least, all implementations used approximately the same order (matching the current spec), and lots of code was inadvertently written that depended on that ordering, and would break if you enumerated it in a different order. Since browsers have to implement this particular ordering to be web-compatible, it was specced as a requirement.
There was some discussion about breaking from this in Maps/Sets, but doing so would require us to specify an order that is impossible for code to depend on; in other words, we’d have to mandate that the ordering be random, not just unspecified. This was deemed too much effort, and creation-order is reasonably valuable (see OrderedDict in Python, for example), so it was decided to have Maps and Sets match Objects.
The following parts of the spec are relevant for this section:
[[OwnPropertyKeys]]
is used by Reflect.ownKeys()
and others.EnumerableOwnNames
is used by Object.keys()
and others.[[Enumerate]]
is used by for-in
.There are two similar ways of adding a property prop
to an object obj
:
obj.prop = 123
Object.defineProperty(obj, 'prop', { value: 123 })
There are three cases in which assigning does not create an own property prop
– even if it doesn’t exist, yet:
prop
exists in the prototype chain. Then the assignment causes a TypeError
in strict mode.prop
exists in the prototype chain. Then that setter is called.prop
without a setter exists in the prototype chain. Then a TypeError
is thrown in strict mode. This case is similar to the first one.None of these cases prevent Object.defineProperty()
from creating an own property. The next section looks at case #3 in more detail.
If an object obj
inherits a property prop
that is read-only then you can’t assign to that property:
This is similar to how an inherited property works that has a getter, but no setter. It is in line with viewing assignment as changing the value of an inherited property. It does so non-destructively: the original is not modified, but overridden by a newly created own property. Therefore, an inherited read-only property and an inherited setter-less property both prevent changes via assignment. You can, however, force the creation of an own property by defining a property:
__proto__
in ECMAScript 6 The property __proto__
(pronounced “dunder proto”) has existed for a while in most JavaScript engines. This section explains how it worked prior to ECMAScript 6 and what changes with ECMAScript 6.
For this section, it helps if you know what prototype chains are. Consult Sect. “Layer 2: The Prototype Relationship Between Objects” in “Speaking JavaScript”, if necessary.
__proto__
prior to ECMAScript 6 Each object in JavaScript starts a chain of one or more objects, a so-called prototype chain. Each object points to its successor, its prototype via the internal slot [[Prototype]]
(which is null
if there is no successor). That slot is called internal, because it only exists in the language specification and cannot be directly accessed from JavaScript. In ECMAScript 5, the standard way of getting the prototype p
of an object obj
is:
There is no standard way to change the prototype of an existing object, but you can create a new object obj
that has the given prototype p
:
__proto__
A long time ago, Firefox got the non-standard property __proto__
. Other browsers eventually copied that feature, due to its popularity.
Prior to ECMAScript 6, __proto__
worked in obscure ways:
Array
via __proto__
The main reason why __proto__
became popular was because it enabled the only way to create a subclass MyArray
of Array
in ES5: Array instances were exotic objects that couldn’t be created by ordinary constructors. Therefore, the following trick was used:
Subclassing in ES6 works differently than in ES5 and supports subclassing builtins out of the box.
__proto__
is problematic in ES5 The main problem is that __proto__
mixes two levels: the object level (normal properties, holding data) and the meta level.
If you accidentally use __proto__
as a normal property (object level!), to store data, you get into trouble, because the two levels clash. The situation is compounded by the fact that you have to abuse objects as maps in ES5, because it has no built-in data structure for that purpose. Maps should be able to hold arbitrary keys, but you can’t use the key '__proto__'
with objects-as-maps.
In theory, one could fix the problem by using a symbol instead of the special name __proto__
, but keeping meta-mechanisms completely separate (as done via Object.getPrototypeOf()
) is the best approach.
__proto__
in ECMAScript 6 Because __proto__
was so widely supported, it was decided that its behavior should be standardized for ECMAScript 6. However, due to its problematic nature, it was added as a deprecated feature. These features reside in Annex B in the ECMAScript specification, which is described as follows:
The ECMAScript language syntax and semantics defined in this annex are required when the ECMAScript host is a web browser. The content of this annex is normative but optional if the ECMAScript host is not a web browser.
JavaScript has several undesirable features that are required by a significant amount of code on the web. Therefore, web browsers must implement them, but other JavaScript engines don’t have to.
In order to explain the magic behind __proto__
, two mechanisms were introduced in ES6:
Object.prototype.__proto__
.'__proto__'
a special operator for specifying the prototype of the created objects.Object.prototype.__proto__
ECMAScript 6 enables getting and setting the property __proto__
via a getter and a setter stored in Object.prototype
. If you were to implement them manually, this is roughly what it would look like:
__proto__
as an operator in an object literal If __proto__
appears as an unquoted or quoted property key in an object literal, the prototype of the object created by that literal is set to the property value:
Using the string value '__proto__'
as a computed property key does not change the prototype, it creates an own property:
__proto__
__proto__
In ECMAScript 6, if you define the own property __proto__
, no special functionality is triggered and the getter/setter Object.prototype.__proto__
is overridden:
Object.prototype
as a prototype The __proto__
getter/setter is provided via Object.prototype
. Therefore, an object without Object.prototype
in its prototype chain doesn’t have the getter/setter, either. In the following code, dict
is an example of such an object – it does not have a prototype. As a result, __proto__
now works like any other property:
__proto__
and dict objects If you want to use an object as a dictionary then it is best if it doesn’t have a prototype. That’s why prototype-less objects are also called dict objects. In ES6, you don’t even have to escape the property key '__proto__'
for dict objects, because it doesn’t trigger any special functionality.
__proto__
as an operator in an object literal lets you create dict objects more concisely:
Note that in ES6, you should normally prefer the built-in data structure Map
to dict objects, especially if keys are not fixed.
__proto__
and JSON Prior to ES6, the following could happen in a JavaScript engine:
With __proto__
being a getter/setter in ES6, JSON.parse()
works fine, because it defines properties, it doesn’t assign them (if implemented properly, an older version of V8 did assign).
JSON.stringify()
isn’t affected by __proto__
, either, because it only considers own properties. Objects that have an own property whose name is __proto__
work fine:
__proto__
Support for ES6-style __proto__
varies from engine to engine. Consult kangax’ ECMAScript 6 compatibility table for information on the status quo:
The following two sections describe how you can programmatically detect whether an engine supports either of the two kinds of __proto__
.
__proto__
as getter/setter A simple check for the getter/setter:
A more sophisticated check:
__proto__
as an operator in an object literal You can use the following check:
__proto__
is pronounced “dunder proto” Bracketing names with double underscores is a common practice in Python to avoid name clashes between meta-data (such as __proto__
) and data (user-defined properties). That practice will never become common in JavaScript, because it now has symbols for this purpose. However, we can look to the Python community for ideas on how to pronounce double underscores.
The following pronunciation has been suggested by Ned Batchelder:
An awkward thing about programming in Python: there are lots of double underscores. For example, the standard method names beneath the syntactic sugar have names like
__getattr__
, constructors are__init__
, built-in operators can be overloaded with__add__
, and so on. […]My problem with the double underscore is that it’s hard to say. How do you pronounce
__init__
? “underscore underscore init underscore underscore”? “under under init under under”? Just plain “init” seems to leave out something important.I have a solution: double underscore should be pronounced “dunder”. So
__init__
is “dunder init dunder”, or just “dunder init”.
Thus, __proto__
is pronounced “dunder proto”. The chances for this pronunciation catching on are good, JavaScript creator Brendan Eich uses it.
__proto__
It is nice how well ES6 turns __proto__
from something obscure into something that is easy to understand.
However, I still recommend not to use it. It is effectively a deprecated feature and not part of the core standard. You can’t rely on it being there for code that must run on all engines.
More recommendations:
Object.getPrototypeOf()
to get the prototype of an object.Object.create()
to create a new object with a given prototype. Avoid Object.setPrototypeOf()
, which hampers performance on many engines.__proto__
as an operator in an object literal. It is useful for demonstrating prototypal inheritance and for creating dict objects. However, the previously mentioned caveats do apply.Enumerability is an attribute of object properties. This section explains how it works in ECMAScript 6. Let’s first explore what attributes are.
Each object has zero or more properties. Each property has a key and three or more attributes, named slots that store the data of the property (in other words, a property is itself much like a JavaScript object or like a record with fields in a database).
ECMAScript 6 supports the following attributes (as does ES5):
enumerable
: Setting this attribute to false
hides the property from some operations.configurable
: Setting this attribute to false
prevents several changes to a property (attributes except value
can’t be change, property can’t be deleted, etc.).value
: holds the value of the property.writable
: controls whether the property’s value can be changed.get
: holds the getter (a function).set
: holds the setter (a function).You can retrieve the attributes of a property via Object.getOwnPropertyDescriptor()
, which returns the attributes as a JavaScript object:
This section explains how the attribute enumerable
works in ES6. All other attributes and how to change attributes is explained in Sect. “Property Attributes and Property Descriptors” in “Speaking JavaScript”.
ECMAScript 5:
for-in
loop: traverses the string keys of own and inherited enumerable properties.Object.keys()
: returns the string keys of enumerable own properties.JSON.stringify()
: only stringifies enumerable own properties with string keys.ECMAScript 6:
Object.assign()
: only copies enumerable own properties (both string keys and symbol keys are considered).for-in
is the only built-in operations where enumerability matters for inherited properties. All other operations only work with own properties.
Unfortunately, enumerability is quite an idiosyncratic feature. This section presents several use cases for it and argues that, apart from protecting legacy code from breaking, its usefulness is limited.
for-in
loop The for-in
loop traverses all enumerable properties of an object, own and inherited ones. Therefore, the attribute enumerable
is used to hide properties that should not be traversed. That was the reason for introducing enumerability in ECMAScript 1.
Non-enumerable properties occur in the following locations in the language:
prototype
properties of built-in classes are non-enumerable:
prototype
properties of classes are non-enumerable:
length
is not enumerable, which means that for-in
only traverses indices. (However, that can easily change if you add a property via assignment, which is makes it enumerable.)
The main reason for making all of these properties non-enumerable is to hide them (especially the inherited ones) from legacy code that uses the for-in
loop or $.extend()
(and similar operations that copy both inherited and own properties; see next section). Both operations should be avoided in ES6. Hiding them ensures that the legacy code doesn’t break.
When it comes to copying properties, there are two important historical precedents that take enumerability into consideration:
Object.extend(destination, source)
$.extend(target, source1, source2, ···)
copies all enumerable own and inherited properties of source1
etc. into own properties of target
.
Problems with this way of copying properties:
The only instance property that is non-enumerable in the standard library is property length
of Arrays. However, that property only needs to be hidden due to it magically updating itself via other properties. You can’t create that kind of magic property for your own objects (short of using a Proxy).
Object.assign()
In ES6, Object.assign(target, source_1, source_2, ···)
can be used to merge the sources into the target. All own enumerable properties of the sources are considered (that is, keys can be either strings or symbols). Object.assign()
uses a “get” operation to read a value from a source and a “set” operation to write a value to the target.
With regard to enumerability, Object.assign()
continues the tradition of Object.extend()
and $.extend()
. Quoting Yehuda Katz:
Object.assign would pave the cowpath of all of the extend() APIs already in circulation. We thought the precedent of not copying enumerable methods in those cases was enough reason for Object.assign to have this behavior.
In other words: Object.assign()
was created with an upgrade path from $.extend()
(and similar) in mind. Its approach is cleaner than $.extend
’s, because it ignores inherited properties.
If you make a property non-enumerable, it can’t by seen by Object.keys()
and the for-in
loop, anymore. With regard to those mechanisms, the property is private.
However, there are several problems with this approach:
JSON.stringify()
JSON.stringify()
does not include properties in its output that are non-enumerable. You can therefore use enumerability to determine which own properties should be exported to JSON. This use case is similar to marking properties as private, the previous use case. But it is also different, because this is more about exporting and slightly different considerations apply. For example: Can an object be completely reconstructed from JSON?
An alternative for specifying how an object should be converted to JSON is to use toJSON()
:
I find toJSON()
cleaner than enumerability for the current use case. It also gives you more control, because you can export properties that don’t exist on the object.
In general, a shorter name means that only enumerable properties are considered:
Object.keys()
ignores non-enumerable propertiesObject.getOwnPropertyNames()
lists all property namesHowever, Reflect.ownKeys()
deviates from that rule, it ignores enumerability and returns the keys of all properties. Additionally, starting with ES6, the following distinction is made:
Therefore, a better name for Object.keys()
would now be Object.names()
.
It seems to me that enumerability is only suited for hiding properties from the for-in
loop and $.extend()
(and similar operations). Both are legacy features, you should avoid them in new code. As for the other use cases:
toJSON()
method is more powerful and explicit than enumerability when it comes to controlling how to convert an object to JSON.I’m not sure what the best strategy is for enumerability going forward. If, with ES6, we had started to pretend that it didn’t exist (except for making prototype properties non-enumerable so that old code doesn’t break), we might eventually have been able to deprecate enumerability. However, Object.assign()
considering enumerability runs counter that strategy (but it does so for a valid reason, backward compatibility).
In my own ES6 code, I’m not using enumerability, except (implicitly) for classes whose prototype
methods are non-enumerable.
Lastly, when using an interactive command line, I occasionally miss an operation that returns all property keys of an object, not just the own ones (Reflect.ownKeys
). Such an operation would provide a nice overview of the contents of an object.
This section explains how you can customize basic language operations by using the following well-known symbols as property keys:
Symbol.hasInstance
(method)C
customize the behavior of x instanceof C
.Symbol.toPrimitive
(method)Symbol.toStringTag
(string)Object.prototype.toString()
to compute the default string description of an object obj
:
Symbol.unscopables
(Object)with
statement.Symbol.hasInstance
(method) An object C
can customize the behavior of the instanceof
operator via a method with the key Symbol.hasInstance
that has the following signature:
x instanceof C
works as follows in ES6:
C
is not an object, throw a TypeError
.C[Symbol.hasInstance](x)
, coerce the result to boolean and return it.C
must be callable, C.prototype
in the prototype chain of x
, etc.).The only method in the standard library that has this key is:
Function.prototype[Symbol.hasInstance]()
This is the implementation of instanceof
that all functions (including classes) use by default. Quoting the spec:
This property is non-writable and non-configurable to prevent tampering that could be used to globally expose the target function of a bound function.
The tampering is possible because the traditional instanceof
algorithm, OrdinaryHasInstance()
, applies instanceof
to the target function if it encounters a bound function.
Given that this property is read-only, you can’t use assignment to override it, as mentioned earlier.
As an example, let’s implement an object ReferenceType
whose “instances” are all objects, not just objects that are instances of Object
(and therefore have Object.prototype
in their prototype chains).
Symbol.toPrimitive
(method) Symbol.toPrimitive
lets an object customize how it is coerced (converted automatically) to a primitive value.
Many JavaScript operations coerce values to the types that they need.
*
) coerces its operands to numbers.new Date(year, month, date)
coerces its parameters to numbers.parseInt(string , radix)
coerces its first parameter to a string.The following are the most common types that values are coerced to:
true
for truthy values, false
for falsy values. Objects are always truthy (even new Boolean(false)
).null
→ 0
, true
→ 1
, '123'
→ 123
, etc.).null
→ 'null'
, true
→ 'true'
, 123
→ '123'
, etc.).b
via new Boolean(b)
, numbers n
via new Number(n)
, etc.).Thus, for numbers and strings, the first step is to ensure that a value is any kind of primitive. That is handled by the spec-internal operation ToPrimitive()
, which has three modes:
The default mode is only used by:
==
)+
)new Date(value)
(exactly one parameter!)If the value is a primitive then ToPrimitive()
is already done. Otherwise, the value is an object obj
, which is converted to a primitive as follows:
obj.valueOf()
if it is primitive. Otherwise, return the result of obj.toString()
if it is primitive. Otherwise, throw a TypeError
.toString()
is called first, valueOf()
second.This normal algorithm can be overridden by giving an object a method with the following signature:
In the standard library, there are two such methods:
Symbol.prototype[Symbol.toPrimitive](hint)
prevents toString()
from being called (which throws an exception).Date.prototype[Symbol.toPrimitive](hint)
This method implements behavior that deviates from the default algorithm. Quoting the specification: “Date objects are unique among built-in ECMAScript object in that they treat 'default'
as being equivalent to 'string'
. All other built-in ECMAScript objects treat 'default'
as being equivalent to 'number'
.”The following code demonstrates how coercion affects the object obj
.
Symbol.toStringTag
(string) In ES5 and earlier, each object had the internal own property [[Class]]
whose value hinted at its type. You could not access it directly, but its value was part of the string returned by Object.prototype.toString()
, which is why that method was used for type checks, as an alternative to typeof
.
In ES6, there is no internal slot [[Class]]
, anymore, and using Object.prototype.toString()
for type checks is discouraged. In order to ensure the backward-compatibility of that method, the public property with the key Symbol.toStringTag
was introduced. You could say that it replaces [[Class]]
.
Object.prototype.toString()
now works as follows:
this
to an object obj
.tst
of obj
.'[object ' + tst + ']'
.The default values for various kinds of objects are shown in the following table.
Value | toString tag |
---|---|
undefined |
'Undefined' |
null |
'Null' |
An Array object | 'Array' |
A string object | 'String' |
arguments |
'Arguments' |
Something callable | 'Function' |
An error object | 'Error' |
A boolean object | 'Boolean' |
A number object | 'Number' |
A date object | 'Date' |
A regular expression object | 'RegExp' |
(Otherwise) | 'Object' |
Most of the checks in the left column are performed by looking at internal slots. For example, if an object has the internal slot [[Call]]
, it is callable.
The following interaction demonstrates the default toString tags.
If an object has an (own or inherited) property whose key is Symbol.toStringTag
then its value overrides the default toString tag. For example:
Instances of user-defined classes get the default toString tag (of objects):
One option for overriding the default is via a getter:
In the JavaScript standard library, there are the following custom toString tags. Objects that have no global names are quoted with percent symbols (for example: %TypedArray%
).
JSON[Symbol.toStringTag]
→ 'JSON'
Math[Symbol.toStringTag]
→ 'Math'
M
: M[Symbol.toStringTag]
→ 'Module'
ArrayBuffer.prototype[Symbol.toStringTag]
→ 'ArrayBuffer'
DataView.prototype[Symbol.toStringTag]
→ 'DataView'
Map.prototype[Symbol.toStringTag]
→ 'Map'
Promise.prototype[Symbol.toStringTag]
→ 'Promise'
Set.prototype[Symbol.toStringTag]
→ 'Set'
get %TypedArray%.prototype[Symbol.toStringTag]
→ 'Uint8Array'
etc.WeakMap.prototype[Symbol.toStringTag]
→ 'WeakMap'
WeakSet.prototype[Symbol.toStringTag]
→ 'WeakSet'
%MapIteratorPrototype%[Symbol.toStringTag]
→ 'Map Iterator'
%SetIteratorPrototype%[Symbol.toStringTag]
→ 'Set Iterator'
%StringIteratorPrototype%[Symbol.toStringTag]
→ 'String Iterator'
Symbol.prototype[Symbol.toStringTag]
→ 'Symbol'
Generator.prototype[Symbol.toStringTag]
→ 'Generator'
GeneratorFunction.prototype[Symbol.toStringTag]
→ 'GeneratorFunction'
All of the built-in properties whose keys are Symbol.toStringTag
have the following property descriptor:
As mentioned earlier, you can’t use assignment to override those properties, because they are read-only.
Symbol.unscopables
(Object) Symbol.unscopables
lets an object hide some properties from the with
statement.
The reason for doing so is that it allows TC39 to add new methods to Array.prototype
without breaking old code. Note that current code rarely uses with
, which is forbidden in strict mode and therefore ES6 modules (which are implicitly in strict mode).
Why would adding methods to Array.prototype
break code that uses with
(such as the widely deployed Ext JS 4.2.1)? Take a look at the following code. The existence of a property Array.prototype.values
breaks foo()
, if you call it with an Array:
Inside the with
statement, all properties of values
become local variables, shadowing even values
itself. Therefore, if values
has a property values
then the statement in line * logs values.values.length
and not values.length
.
Symbol.unscopables
is used only once in the standard library:
Array.prototype[Symbol.unscopables]
with
statement): copyWithin
, entries
, fill
, find
, findIndex
, keys
, values
super
in object literals? Yes you can! Details are explained in the chapter on classes.