Movatterモバイル変換


[0]ホーム

URL:


14. New OOP features besides classes
Table of contents
Please support this book:buy it (PDF, EPUB, MOBI) ordonate
(Ad, please don’t block.)

14.New OOP features besides classes

Classes (which are explained inthe next chapter) arethe major new OOP feature in ECMAScript 6. However, it also includes new features for object literals and new utility methods inObject. This chapter describes them.



14.1Overview

14.1.1New object literal features

Method definitions:

constobj={myMethod(x,y){···}};

Property value shorthands:

constfirst='Jane';constlast='Doe';constobj={first,last};// Same as:constobj={first:first,last:last};

Computed property keys:

constpropKey='foo';constobj={[propKey]:true,['b'+'ar']:123};

This new syntax can also be used for method definitions:

constobj={['h'+'ello'](){return'hi';}};console.log(obj.hello());// hi

The main use case for computed property keys is to make it easy to use symbols as property keys.

14.1.2New methods inObject

The most important new method ofObject isassign(). Traditionally, this functionality was calledextend() in the JavaScript world. In contrast to how this classic operation works,Object.assign() only considersown (non-inherited) properties.

constobj={foo:123};Object.assign(obj,{bar:true});console.log(JSON.stringify(obj));// {"foo":123,"bar":true}

14.2New features of object literals

14.2.1Method definitions

In ECMAScript 5, methods are properties whose values are functions:

varobj={myMethod:function(x,y){···}};

In ECMAScript 6, methods are still function-valued properties, but there is now a more compact way of defining them:

constobj={myMethod(x,y){···}};

Getters and setters continue to work as they did in ECMAScript 5 (note how syntactically similar they are to method definitions):

constobj={getfoo(){console.log('GET foo');return123;},setbar(value){console.log('SET bar to '+value);// return value is ignored}};

Let’s useobj:

> obj.fooGET foo123> obj.bar = trueSET bar to truetrue

There is also a way to concisely define properties whose values are generator functions:

const obj = {    * myGeneratorMethod() {        ···    }};

This code is equivalent to:

const obj = {    myGeneratorMethod: function* () {        ···    }};

14.2.2Property value shorthands

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.

constx=4;consty=1;constobj={x,y};

The last line is equivalent to:

constobj={x:x,y:y};

Property value shorthands work well together with destructuring:

constobj={x:4,y:1};const{x,y}=obj;console.log(x);// 4console.log(y);// 1

One use case for property value shorthands are multiple return values (which are explained inthe chapter on destructuring).

14.2.3Computed property keys

Remember that there are two ways of specifying a key when you set a property.

  1. Via a fixed name:obj.foo = true;
  2. Via an expression:obj['b'+'ar'] = 123;

In object literals, you only have option #1 in ECMAScript 5. ECMAScript 6 additionally provides option #2 (line A):

constobj={foo:true,['b'+'ar']:123};

This new syntax can also be used for method definitions:

constobj={['h'+'ello'](){return'hi';}};console.log(obj.hello());// hi

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 inSymbol.iterator. If an object has a method with that key, it becomesiterable: The method must return an iterator, which is used by constructs such as thefor-of loop to iterate over the object. The following code demonstrates how that works.

constobj={*[Symbol.iterator](){// (A)yield'hello';yield'world';}};for(constxofobj){console.log(x);}// Output:// hello// world

obj is iterable, due tothe generator method definition starting in line A.

14.3New methods ofObject

14.3.1Object.assign(target, source_1, source_2, ···)

This method merges the sources into the target: It modifiestarget, by first copying all enumerableown (non-inherited) properties ofsource_1 into it, then all own properties ofsource_2, etc. At the end, it returns the target.

constobj={foo:123};Object.assign(obj,{bar:true});console.log(JSON.stringify(obj));// {"foo":123,"bar":true}

Let’s look more closely at howObject.assign() works:

14.3.1.1Copying all own properties

This is how you would copyall own properties (not just enumerable ones), while correctly transferring getters and setters and without invoking setters on the target:

functioncopyAllOwnProperties(target,...sources){for(constsourceofsources){for(constkeyofReflect.ownKeys(source)){constdesc=Object.getOwnPropertyDescriptor(source,key);Object.defineProperty(target,key,desc);}}returntarget;}

Consult Sect. “Property Attributes and Property Descriptors” in ”Speaking JavaScript” for more information on property descriptors (as used byObject.getOwnPropertyDescriptor() andObject.defineProperty()).

14.3.1.2Caveat:Object.assign() doesn’t work well for moving methods

On one hand, you can’t move a method that usessuper: Such a method has the internal slot[[HomeObject]] that ties it to the object it was created in. If you move it viaObject.assign(), it will continue to refer to the super-properties of the original object. Details are explained ina 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 (otherwiseObject.assign() wouldn’t see them, anyway), but the prototype normally only has non-enumerable methods.

14.3.1.3Use cases forObject.assign()

Let’s look at a few use cases.

14.3.1.3.1Adding properties tothis

You can useObject.assign() to add properties tothis in a constructor:

classPoint{constructor(x,y){Object.assign(this,{x,y});}}
14.3.1.3.2Providing default values for object properties

Object.assign() is also useful for filling in defaults for missing properties. In the following example, we have an objectDEFAULTS with default values for properties and an objectoptions with data.

constDEFAULTS={logLevel:0,outputFormat:'html'};functionprocessContent(options){options=Object.assign({},DEFAULTS,options);// (A)···}

In line A, we created a fresh object, copied the defaults into it and then copiedoptions into it, overriding the defaults.Object.assign() returns the result of these operations, which we assign tooptions.

14.3.1.3.3Adding methods to objects

Another use case is adding methods to objects:

Object.assign(SomeClass.prototype,{someMethod(arg1,arg2){···},anotherMethod(){···}});

You can also manually assign functions, but then you don’t have the nice method definition syntax and need to mentionSomeClass.prototype each time:

SomeClass.prototype.someMethod=function(arg1,arg2){···};SomeClass.prototype.anotherMethod=function(){···};
14.3.1.3.4Cloning objects

One last use case forObject.assign() is a quick way of cloning objects:

functionclone(orig){returnObject.assign({},orig);}

This way of cloning is also somewhat dirty, because it doesn’t preserve the property attributes oforig. If that is what you need, you have to use property descriptors, like we did to implementcopyAllOwnProperties().

If you want the clone to have the same prototype as the original, you can useObject.getPrototypeOf() andObject.create():

functionclone(orig){constorigProto=Object.getPrototypeOf(orig);returnObject.assign(Object.create(origProto),orig);}

14.3.2Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols(obj) retrieves allown (non-inherited) symbol-valued property keys ofobj. It complementsObject.getOwnPropertyNames(), which retrieves all string-valued own property keys. Consulta later section for more details on traversing properties.

14.3.3Object.is(value1, value2)

The strict equals operator (===) treats two values differently than one might expect.

First,NaN is not equal to itself.

> NaN === NaNfalse

That is unfortunate, because it often prevents us from detectingNaN:

> [0,NaN,2].indexOf(NaN)-1

Second, JavaScript hastwo zeros, but strict equals treats them as if they were the same value:

> -0 === +0true

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:

> Object.is(NaN, NaN)true> Object.is(-0, +0)false

Everything else is compared as with===.

14.3.3.1UsingObject.is() to find Array elements

In the following functionmyIndexOf(), we combineObject.is() with the new ES6 Array methodfindIndex() to findNaN in Arrays.

functionmyIndexOf(arr,elem){returnarr.findIndex(x=>Object.is(x,elem));}constmyArray=[0,NaN,2];myIndexOf(myArray,NaN);// 1myArray.indexOf(NaN);// -1

As you can see in the last line,indexOf() does not findNaN.

14.3.4Object.setPrototypeOf(obj, proto)

This method sets the prototype ofobj toproto. The non-standard way of doing so in ECMAScript 5, that is supported by many engines, is via assigning tothe special property__proto__. The recommended way of setting the prototype remains the same as in ECMAScript 5: during the creation of an object, viaObject.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.

14.4Traversing properties in ES6

14.4.1Five operations that traverse properties

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 objectobj:

14.4.2Traversal order of properties

ES6 defines two traversal orders for properties.

Own Property Keys:

Enumerable Own Names:

The order in whichfor-in traverses properties is not defined.Quoting Allen Wirfs-Brock:

Historically, thefor-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.

14.4.2.1Integer indices

Even though you access Array elements via integer indices, the spec treats them as normal string property keys:

constarr=['a','b','c'];console.log(arr['0']);// 'a'// Operand 0 of [] is coerced to string:console.log(arr[0]);// 'a'

Integer indices are special in only two ways: they affect thelength 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:

Further reading:

14.4.2.2Example

The following code demonstrates the traversal order “Own Property Keys”:

constobj={[Symbol('first')]:true,'02':true,'10':true,'01':true,'2':true,[Symbol('second')]:true,};Reflect.ownKeys(obj);// [ '2', '10', '02', '01',//   Symbol('first'), Symbol('second') ]

Explanation:

14.4.2.3Why does the spec standardize in which order property keys are returned?

Answer byTab 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 isimpossible for code to depend on; in other words, we’d have to mandate that the ordering berandom, 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.

14.4.2.4The order of properties in the spec

The following parts of the spec are relevant for this section:

14.5Assigning versus defining properties

This section provides background knowledge that is needed in later sections.

There are two similar ways of adding a propertyprop to an objectobj:

There are three cases in which assigning does not create an own propertyprop – even if it doesn’t exist, yet:

  1. A read-only propertyprop exists in the prototype chain. Then the assignment causes aTypeError in strict mode.
  2. A setter forprop exists in the prototype chain. Then that setter is called.
  3. A getter forprop without a setter exists in the prototype chain. Then aTypeError is thrown in strict mode. This case is similar to the first one.

None of these cases preventObject.defineProperty() from creating an own property. The next section looks at case #3 in more detail.

14.5.1Overriding inherited read-only properties

If an objectobj inherits a propertyprop that is read-only then you can’t assign to that property:

constproto=Object.defineProperty({},'prop',{writable:false,configurable:true,value:123,});constobj=Object.create(proto);obj.prop=456;// TypeError: Cannot assign to read-only 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:

constproto=Object.defineProperty({},'prop',{writable:false,configurable:true,value:123,});constobj=Object.create(proto);Object.defineProperty(obj,'prop',{value:456});console.log(obj.prop);// 456

14.6__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.

14.6.1__proto__ prior to ECMAScript 6

14.6.1.1Prototypes

Each object in JavaScript starts a chain of one or more objects, a so-calledprototype chain. Each object points to its successor, itsprototype via the internal slot[[Prototype]] (which isnull if there is no successor). That slot is calledinternal, because it only exists in the language specification and cannot be directly accessed from JavaScript. In ECMAScript 5, the standard way of getting the prototypep of an objectobj is:

varp=Object.getPrototypeOf(obj);

There is no standard way to change the prototype of an existing object, but you can create a new objectobj that has the given prototypep:

varobj=Object.create(p);
14.6.1.2__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:

14.6.1.3SubclassingArray via__proto__

The main reason why__proto__ became popular was because it enabled the only way to create a subclassMyArray ofArray in ES5: Array instances were exotic objects that couldn’t be created by ordinary constructors. Therefore, the following trick was used:

functionMyArray(){varinstance=newArray();// exotic objectinstance.__proto__=MyArray.prototype;returninstance;}MyArray.prototype=Object.create(Array.prototype);MyArray.prototype.customMethod=function(···){···};

Subclassing in ES6 works differently than in ES5 and supports subclassing builtins out of the box.

14.6.1.4Why__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 viaObject.getPrototypeOf()) is the best approach.

14.6.2The two kinds of__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 inAnnex 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:

14.6.2.1Object.prototype.__proto__

ECMAScript 6 enables getting and setting the property__proto__ via a getter and a setter stored inObject.prototype. If you were to implement them manually, this is roughly what it would look like:

Object.defineProperty(Object.prototype,'__proto__',{get(){const_thisObj=Object(this);returnObject.getPrototypeOf(_thisObj);},set(proto){if(this===undefined||this===null){thrownewTypeError();}if(!isObject(this)){returnundefined;}if(!isObject(proto)){returnundefined;}conststatus=Reflect.setPrototypeOf(this,proto);if(!status){thrownewTypeError();}returnundefined;},});functionisObject(value){returnObject(value)===value;}

The getter and the setter for__proto__ in the ES6 spec:

14.6.2.2The property key__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:

> Object.getPrototypeOf({ __proto__: null })null> Object.getPrototypeOf({ '__proto__': null })null

Using the string value'__proto__' as a computed property key does not change the prototype, it creates an own property:

> const obj = { ['__proto__']: null };> Object.getPrototypeOf(obj) === Object.prototypetrue> Object.keys(obj)[ '__proto__' ]

The special property key'__proto__' in the ES6 spec:

14.6.3Avoiding the magic of__proto__

14.6.3.1Defining (not assigning)__proto__

In ECMAScript 6, if youdefine the own property__proto__, no special functionality is triggered and the getter/setterObject.prototype.__proto__ is overridden:

constobj={};Object.defineProperty(obj,'__proto__',{value:123})Object.keys(obj);// [ '__proto__' ]console.log(obj.__proto__);// 123
14.6.3.2Objects that don’t haveObject.prototype as a prototype

The__proto__ getter/setter is provided viaObject.prototype. Therefore, an object withoutObject.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:

>constdict=Object.create(null);>'__proto__'indictfalse>dict.__proto__='abc';>dict.__proto__'abc'
14.6.3.3__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 calleddict 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:

constdictObj={__proto__:null,yes:true,no:false,};

Note that in ES6, you should normally preferthe built-in data structureMap to dict objects, especially if keys are not fixed.

14.6.3.4__proto__ and JSON

Prior to ES6, the following could happen in a JavaScript engine:

> JSON.parse('{"__proto__": []}') instanceof Arraytrue

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:

> JSON.stringify({['__proto__']: true})'{"__proto__":true}'

14.6.4Detecting support for ES6-style__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__.

14.6.4.1Feature:__proto__ as getter/setter

A simple check for the getter/setter:

varsupported={}.hasOwnProperty.call(Object.prototype,'__proto__');

A more sophisticated check:

vardesc=Object.getOwnPropertyDescriptor(Object.prototype,'__proto__');varsupported=(typeofdesc.get==='function'&&typeofdesc.set==='function');
14.6.4.2Feature:__proto__ as an operator in an object literal

You can use the following check:

varsupported=Object.getPrototypeOf({__proto__:null})===null;

14.6.5__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 beensuggested 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.

14.6.6Recommendations for__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:

14.7Enumerability in ECMAScript 6

Enumerability is anattribute of object properties. This section explains how it works in ECMAScript 6. Let’s first explore what attributes are.

14.7.1Property attributes

Each object has zero or moreproperties. Each property has a key and three or moreattributes, 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):

You can retrieve the attributes of a property viaObject.getOwnPropertyDescriptor(), which returns the attributes as a JavaScript object:

>constobj={foo:123};>Object.getOwnPropertyDescriptor(obj,'foo'){value:123,writable:true,enumerable:true,configurable:true}

This section explains how the attributeenumerable works in ES6. All other attributes and how to change attributes is explained in Sect. “Property Attributes and Property Descriptors” in “Speaking JavaScript”.

14.7.2Constructs affected by enumerability

ECMAScript 5:

ECMAScript 6:

for-in is the only built-in operations where enumerability matters for inherited properties. All other operations only work with own properties.

14.7.3Use cases for enumerability

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.

14.7.3.1Use case: Hiding properties from thefor-in loop

Thefor-in loop traversesall enumerable properties of an object, own and inherited ones. Therefore, the attributeenumerable is used to hide properties that should not be traversed. That was the reason for introducing enumerability in ECMAScript 1.

14.7.3.1.1Non-enumerability in the language

Non-enumerable properties occur in the following locations in the language:

The main reason for making all of these properties non-enumerable is to hide them (especially the inherited ones) from legacy code that uses thefor-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.

14.7.3.2Use case: Marking properties as not to be copied
14.7.3.2.1Historical precedents

When it comes to copying properties, there are two important historical precedents that take enumerability into consideration:

Problems with this way of copying properties:

The only instance property that is non-enumerable in the standard library is propertylength 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).

14.7.3.2.2ES6: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 ofObject.extend() and$.extend().Quoting Yehuda Katz:

Object.assign would pave the cowpath of all of the extend() APIs already incirculation. We thought the precedent of not copying enumerable methods inthose 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.

14.7.3.3Marking properties as private

If you make a property non-enumerable, it can’t by seen byObject.keys() and thefor-in loop, anymore. With regard to those mechanisms, the property is private.

However, there are several problems with this approach:

14.7.3.4Hiding own properties fromJSON.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 usetoJSON():

constobj={foo:123,toJSON(){return{bar:456};},};JSON.stringify(obj);// '{"bar":456}'

I findtoJSON() 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.

14.7.4Naming inconsistencies

In general, a shorter name means that only enumerable properties are considered:

However,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 forObject.keys() would now beObject.names().

14.7.5Looking ahead

It seems to me that enumerability is only suited for hiding properties from thefor-in loop and$.extend() (and similar operations). Both are legacy features, you should avoid them in new code. As for the other use cases:

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 whoseprototype methods are non-enumerable.

Lastly, when using an interactive command line, I occasionally miss an operation that returnsall 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.

14.8Customizing basic language operations via well-known symbols

This section explains how you can customize basic language operations by using the following well-known symbols as property keys:

14.8.1Property keySymbol.hasInstance (method)

An objectC can customize the behavior of theinstanceof operator via a method with the keySymbol.hasInstance that has the following signature:

[Symbol.hasInstance](potentialInstance:any)

x instanceof C works as follows in ES6:

14.8.1.1Uses in the standard library

The only method in the standard library that has this key is:

This is the implementation ofinstanceof 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 traditionalinstanceof algorithm,OrdinaryHasInstance(), appliesinstanceof 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.

14.8.1.2Example: checking whether a value is an object

As an example, let’s implement an objectReferenceType whose “instances” are all objects, not just objects that are instances ofObject (and therefore haveObject.prototype in their prototype chains).

constReferenceType={[Symbol.hasInstance](value){return(value!==null&&(typeofvalue==='object'||typeofvalue==='function'));}};constobj1={};console.log(obj1instanceofObject);// trueconsole.log(obj1instanceofReferenceType);// trueconstobj2=Object.create(null);console.log(obj2instanceofObject);// falseconsole.log(obj2instanceofReferenceType);// true

14.8.2Property keySymbol.toPrimitive (method)

Symbol.toPrimitive lets an object customize how it iscoerced (converted automatically) to a primitive value.

Many JavaScript operations coerce values to the types that they need.

The following are the most common types that values are coerced to:

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 operationToPrimitive(), which has three modes:

The default mode is only used by:

If the value is a primitive thenToPrimitive() is already done. Otherwise, the value is an objectobj, which is converted to a primitive as follows:

This normal algorithm can be overridden by giving an object a method with the following signature:

[Symbol.toPrimitive](hint:'default'|'string'|'number')

In the standard library, there are two such methods:

14.8.2.1Example

The following code demonstrates how coercion affects the objectobj.

constobj={[Symbol.toPrimitive](hint){switch(hint){case'number':return123;case'string':return'str';case'default':return'default';default:thrownewError();}}};console.log(2*obj);// 246console.log(3+obj);// '3default'console.log(obj=='default');// trueconsole.log(String(obj));// 'str'

14.8.3Property keySymbol.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 byObject.prototype.toString(), which is why that method was used for type checks, as an alternative totypeof.

In ES6, there is no internal slot[[Class]], anymore, and usingObject.prototype.toString() for type checks is discouraged. In order to ensure the backward-compatibility of that method, the public property with the keySymbol.toStringTag was introduced. You could say that it replaces[[Class]].

Object.prototype.toString() now works as follows:

14.8.3.1Default toString tags

The default values for various kinds of objects are shown in the following table.

ValuetoString 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.

>Object.prototype.toString.call(null)'[objectNull]'>Object.prototype.toString.call([])'[objectArray]'>Object.prototype.toString.call({})'[objectObject]'>Object.prototype.toString.call(Object.create(null))'[objectObject]'
14.8.3.2Overriding the default toString tag

If an object has an (own or inherited) property whose key isSymbol.toStringTag then its value overrides the default toString tag. For example:

>({}.toString())'[objectObject]'>({[Symbol.toStringTag]:'Foo'}.toString())'[objectFoo]'

Instances of user-defined classes get the default toString tag (of objects):

classFoo{}console.log(newFoo().toString());// [object Object]

One option for overriding the default is via a getter:

classBar{get[Symbol.toStringTag](){return'Bar';}}console.log(newBar().toString());// [object Bar]

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%).

All of the built-in properties whose keys areSymbol.toStringTag have the following property descriptor:

{writable:false,enumerable:false,configurable:true,}

As mentioned earlier, you can’t use assignment to override those properties, because they are read-only.

14.8.4Property keySymbol.unscopables (Object)

Symbol.unscopables lets an object hide some properties from thewith statement.

The reason for doing so is that it allows TC39 to add new methods toArray.prototype without breaking old code. Note that current code rarely useswith, which is forbidden in strict mode and therefore ES6 modules (which are implicitly in strict mode).

Why would adding methods toArray.prototype break code that useswith (such as the widely deployedExt JS 4.2.1)? Take a look at the following code. The existence of a propertyArray.prototype.values breaksfoo(), if you call it with an Array:

functionfoo(values){with(values){console.log(values.length);// abc (*)}}Array.prototype.values={length:'abc'};foo([]);

Inside thewith statement, all properties ofvalues become local variables, shadowing evenvalues itself. Therefore, ifvalues has a propertyvalues then the statement in line * logsvalues.values.length and notvalues.length.

Symbol.unscopables is used only once in the standard library:

14.9FAQ: object literals

14.9.1Can I usesuper in object literals?

Yes you can! Details are explained inthe chapter on classes.

Next:15. Classes

[8]ページ先頭

©2009-2025 Movatter.jp