Contents

As mentioned earlier, the contents of an object consist of values (any type) stored at specifically named locations, which we call properties.

It’s important to note that while we say “contents” which implies that these values are actually stored inside the object, that’s merely an appearance. The engine stores values in implementation-dependent ways, and may very well not store them in some object container. What is stored in the container are these property names, which act as pointers (technically, references) to where the values are stored.

Consider:

  1. var myObject = {
  2. a: 2
  3. };
  4. myObject.a; // 2
  5. myObject["a"]; // 2

To access the value at the location a in myObject, we need to use either the . operator or the [ ] operator. The .a syntax is usually referred to as “property” access, whereas the ["a"] syntax is usually referred to as “key” access. In reality, they both access the same location, and will pull out the same value, 2, so the terms can be used interchangeably. We will use the most common term, “property access” from here on.

The main difference between the two syntaxes is that the . operator requires an Identifier compatible property name after it, whereas the [".."] syntax can take basically any UTF-8/unicode compatible string as the name for the property. To reference a property of the name “Super-Fun!”, for instance, you would have to use the ["Super-Fun!"] access syntax, as Super-Fun! is not a valid Identifier property name.

Also, since the [".."] syntax uses a string’s value to specify the location, this means the program can programmatically build up the value of the string, such as:

  1. var wantA = true;
  2. var myObject = {
  3. a: 2
  4. };
  5. var idx;
  6. if (wantA) {
  7. idx = "a";
  8. }
  9. // later
  10. console.log( myObject[idx] ); // 2

In objects, property names are always strings. If you use any other value besides a string (primitive) as the property, it will first be converted to a string. This even includes numbers, which are commonly used as array indexes, so be careful not to confuse the use of numbers between objects and arrays.

  1. var myObject = { };
  2. myObject[true] = "foo";
  3. myObject[3] = "bar";
  4. myObject[myObject] = "baz";
  5. myObject["true"]; // "foo"
  6. myObject["3"]; // "bar"
  7. myObject["[object Object]"]; // "baz"

Computed Property Names

The myObject[..] property access syntax we just described is useful if you need to use a computed expression value as the key name, like myObject[prefix + name]. But that’s not really helpful when declaring objects using the object-literal syntax.

ES6 adds computed property names, where you can specify an expression, surrounded by a [ ] pair, in the key-name position of an object-literal declaration:

  1. var prefix = "foo";
  2. var myObject = {
  3. [prefix + "bar"]: "hello",
  4. [prefix + "baz"]: "world"
  5. };
  6. myObject["foobar"]; // hello
  7. myObject["foobaz"]; // world

The most common usage of computed property names will probably be for ES6 Symbols, which we will not be covering in detail in this book. In short, they’re a new primitive data type which has an opaque unguessable value (technically a string value). You will be strongly discouraged from working with the actual value of a Symbol (which can theoretically be different between different JS engines), so the name of the Symbol, like Symbol.Something (just a made up name!), will be what you use:

  1. var myObject = {
  2. [Symbol.Something]: "hello world"
  3. };

Property vs. Method

Some developers like to make a distinction when talking about a property access on an object, if the value being accessed happens to be a function. Because it’s tempting to think of the function as belonging to the object, and in other languages, functions which belong to objects (aka, “classes”) are referred to as “methods”, it’s not uncommon to hear, “method access” as opposed to “property access”.

The specification makes this same distinction, interestingly.

Technically, functions never “belong” to objects, so saying that a function that just happens to be accessed on an object reference is automatically a “method” seems a bit of a stretch of semantics.

It is true that some functions have this references in them, and that sometimes these this references refer to the object reference at the call-site. But this usage really does not make that function any more a “method” than any other function, as this is dynamically bound at run-time, at the call-site, and thus its relationship to the object is indirect, at best.

Every time you access a property on an object, that is a property access, regardless of the type of value you get back. If you happen to get a function from that property access, it’s not magically a “method” at that point. There’s nothing special (outside of possible implicit this binding as explained earlier) about a function that comes from a property access.

For instance:

  1. function foo() {
  2. console.log( "foo" );
  3. }
  4. var someFoo = foo; // variable reference to `foo`
  5. var myObject = {
  6. someFoo: foo
  7. };
  8. foo; // function foo(){..}
  9. someFoo; // function foo(){..}
  10. myObject.someFoo; // function foo(){..}

someFoo and myObject.someFoo are just two separate references to the same function, and neither implies anything about the function being special or “owned” by any other object. If foo() above was defined to have a this reference inside it, that myObject.someFoo implicit binding would be the only observable difference between the two references. Neither reference really makes sense to be called a “method”.

Perhaps one could argue that a function becomes a method, not at definition time, but during run-time just for that invocation, depending on how it’s called at its call-site (with an object reference context or not — see Chapter 2 for more details). Even this interpretation is a bit of a stretch.

The safest conclusion is probably that “function” and “method” are interchangeable in JavaScript.

Note: ES6 adds a super reference, which is typically going to be used with class (see Appendix A). The way super behaves (static binding rather than late binding as this) gives further weight to the idea that a function which is super bound somewhere is more a “method” than “function”. But again, these are just subtle semantic (and mechanical) nuances.

Even when you declare a function expression as part of the object-literal, that function doesn’t magically belong more to the object — still just multiple references to the same function object:

  1. var myObject = {
  2. foo: function foo() {
  3. console.log( "foo" );
  4. }
  5. };
  6. var someFoo = myObject.foo;
  7. someFoo; // function foo(){..}
  8. myObject.foo; // function foo(){..}

Note: In Chapter 6, we will cover an ES6 short-hand for that foo: function foo(){ .. } declaration syntax in our object-literal.

Arrays

Arrays also use the [ ] access form, but as mentioned above, they have slightly more structured organization for how and where values are stored (though still no restriction on what type of values are stored). Arrays assume numeric indexing, which means that values are stored in locations, usually called indices, at non-negative integers, such as 0 and 42.

  1. var myArray = [ "foo", 42, "bar" ];
  2. myArray.length; // 3
  3. myArray[0]; // "foo"
  4. myArray[2]; // "bar"

Arrays are objects, so even though each index is a positive integer, you can also add properties onto the array:

  1. var myArray = [ "foo", 42, "bar" ];
  2. myArray.baz = "baz";
  3. myArray.length; // 3
  4. myArray.baz; // "baz"

Notice that adding named properties (regardless of . or [ ] operator syntax) does not change the reported length of the array.

You could use an array as a plain key/value object, and never add any numeric indices, but this is a bad idea because arrays have behavior and optimizations specific to their intended use, and likewise with plain objects. Use objects to store key/value pairs, and arrays to store values at numeric indices.

Be careful: If you try to add a property to an array, but the property name looks like a number, it will end up instead as a numeric index (thus modifying the array contents):

  1. var myArray = [ "foo", 42, "bar" ];
  2. myArray["3"] = "baz";
  3. myArray.length; // 4
  4. myArray[3]; // "baz"

Duplicating Objects

One of the most commonly requested features when developers newly take up the JavaScript language is how to duplicate an object. It would seem like there should just be a built-in copy() method, right? It turns out that it’s a little more complicated than that, because it’s not fully clear what, by default, should be the algorithm for the duplication.

For example, consider this object:

  1. function anotherFunction() { /*..*/ }
  2. var anotherObject = {
  3. c: true
  4. };
  5. var anotherArray = [];
  6. var myObject = {
  7. a: 2,
  8. b: anotherObject, // reference, not a copy!
  9. c: anotherArray, // another reference!
  10. d: anotherFunction
  11. };
  12. anotherArray.push( anotherObject, myObject );

What exactly should be the representation of a copy of myObject?

Firstly, we should answer if it should be a shallow or deep copy. A shallow copy would end up with a on the new object as a copy of the value 2, but b, c, and d properties as just references to the same places as the references in the original object. A deep copy would duplicate not only myObject, but anotherObject and anotherArray. But then we have issues that anotherArray has references to anotherObject and myObject in it, so those should also be duplicated rather than reference-preserved. Now we have an infinite circular duplication problem because of the circular reference.

Should we detect a circular reference and just break the circular traversal (leaving the deep element not fully duplicated)? Should we error out completely? Something in between?

Moreover, it’s not really clear what “duplicating” a function would mean? There are some hacks like pulling out the toString() serialization of a function’s source code (which varies across implementations and is not even reliable in all engines depending on the type of function being inspected).

So how do we resolve all these tricky questions? Various JS frameworks have each picked their own interpretations and made their own decisions. But which of these (if any) should JS adopt as the standard? For a long time, there was no clear answer.

One subset solution is that objects which are JSON-safe (that is, can be serialized to a JSON string and then re-parsed to an object with the same structure and values) can easily be duplicated with:

  1. var newObj = JSON.parse( JSON.stringify( someObj ) );

Of course, that requires you to ensure your object is JSON safe. For some situations, that’s trivial. For others, it’s insufficient.

At the same time, a shallow copy is fairly understandable and has far less issues, so ES6 has now defined Object.assign(..) for this task. Object.assign(..) takes a target object as its first parameter, and one or more source objects as its subsequent parameters. It iterates over all the enumerable (see below), owned keys (immediately present) on the source object(s) and copies them (via = assignment only) to target. It also, helpfully, returns target, as you can see below:

  1. var newObj = Object.assign( {}, myObject );
  2. newObj.a; // 2
  3. newObj.b === anotherObject; // true
  4. newObj.c === anotherArray; // true
  5. newObj.d === anotherFunction; // true

Note: In the next section, we describe “property descriptors” (property characteristics) and show the use of Object.defineProperty(..). The duplication that occurs for Object.assign(..) however is purely = style assignment, so any special characteristics of a property (like writable) on a source object are not preserved on the target object.

Property Descriptors

Prior to ES5, the JavaScript language gave no direct way for your code to inspect or draw any distinction between the characteristics of properties, such as whether the property was read-only or not.

But as of ES5, all properties are described in terms of a property descriptor.

Consider this code:

  1. var myObject = {
  2. a: 2
  3. };
  4. Object.getOwnPropertyDescriptor( myObject, "a" );
  5. // {
  6. // value: 2,
  7. // writable: true,
  8. // enumerable: true,
  9. // configurable: true
  10. // }

As you can see, the property descriptor (called a “data descriptor” since it’s only for holding a data value) for our normal object property a is much more than just its value of 2. It includes 3 other characteristics: writable, enumerable, and configurable.

While we can see what the default values for the property descriptor characteristics are when we create a normal property, we can use Object.defineProperty(..) to add a new property, or modify an existing one (if it’s configurable!), with the desired characteristics.

For example:

  1. var myObject = {};
  2. Object.defineProperty( myObject, "a", {
  3. value: 2,
  4. writable: true,
  5. configurable: true,
  6. enumerable: true
  7. } );
  8. myObject.a; // 2

Using defineProperty(..), we added the plain, normal a property to myObject in a manually explicit way. However, you generally wouldn’t use this manual approach unless you wanted to modify one of the descriptor characteristics from its normal behavior.

Writable

The ability for you to change the value of a property is controlled by writable.

Consider:

  1. var myObject = {};
  2. Object.defineProperty( myObject, "a", {
  3. value: 2,
  4. writable: false, // not writable!
  5. configurable: true,
  6. enumerable: true
  7. } );
  8. myObject.a = 3;
  9. myObject.a; // 2

As you can see, our modification of the value silently failed. If we try in strict mode, we get an error:

  1. "use strict";
  2. var myObject = {};
  3. Object.defineProperty( myObject, "a", {
  4. value: 2,
  5. writable: false, // not writable!
  6. configurable: true,
  7. enumerable: true
  8. } );
  9. myObject.a = 3; // TypeError

The TypeError tells us we cannot change a non-writable property.

Note: We will discuss getters/setters shortly, but briefly, you can observe that writable:false means a value cannot be changed, which is somewhat equivalent to if you defined a no-op setter. Actually, your no-op setter would need to throw a TypeError when called, to be truly conformant to writable:false.

Configurable

As long as a property is currently configurable, we can modify its descriptor definition, using the same defineProperty(..) utility.

  1. var myObject = {
  2. a: 2
  3. };
  4. myObject.a = 3;
  5. myObject.a; // 3
  6. Object.defineProperty( myObject, "a", {
  7. value: 4,
  8. writable: true,
  9. configurable: false, // not configurable!
  10. enumerable: true
  11. } );
  12. myObject.a; // 4
  13. myObject.a = 5;
  14. myObject.a; // 5
  15. Object.defineProperty( myObject, "a", {
  16. value: 6,
  17. writable: true,
  18. configurable: true,
  19. enumerable: true
  20. } ); // TypeError

The final defineProperty(..) call results in a TypeError, regardless of strict mode, if you attempt to change the descriptor definition of a non-configurable property. Be careful: as you can see, changing configurable to false is a one-way action, and cannot be undone!

Note: There’s a nuanced exception to be aware of: even if the property is already configurable:false, writable can always be changed from true to false without error, but not back to true if already false.

Another thing configurable:false prevents is the ability to use the delete operator to remove an existing property.

  1. var myObject = {
  2. a: 2
  3. };
  4. myObject.a; // 2
  5. delete myObject.a;
  6. myObject.a; // undefined
  7. Object.defineProperty( myObject, "a", {
  8. value: 2,
  9. writable: true,
  10. configurable: false,
  11. enumerable: true
  12. } );
  13. myObject.a; // 2
  14. delete myObject.a;
  15. myObject.a; // 2

As you can see, the last delete call failed (silently) because we made the a property non-configurable.

delete is only used to remove object properties (which can be removed) directly from the object in question. If an object property is the last remaining reference to some object/function, and you delete it, that removes the reference and now that unreferenced object/function can be garbage collected. But, it is not proper to think of delete as a tool to free up allocated memory as it does in other languages (like C/C++). delete is just an object property removal operation — nothing more.

Enumerable

The final descriptor characteristic we will mention here (there are two others, which we deal with shortly when we discuss getter/setters) is enumerable.

The name probably makes it obvious, but this characteristic controls if a property will show up in certain object-property enumerations, such as the for..in loop. Set to false to keep it from showing up in such enumerations, even though it’s still completely accessible. Set to true to keep it present.

All normal user-defined properties are defaulted to enumerable, as this is most commonly what you want. But if you have a special property you want to hide from enumeration, set it to enumerable:false.

We’ll demonstrate enumerability in much more detail shortly, so keep a mental bookmark on this topic.

Immutability

It is sometimes desired to make properties or objects that cannot be changed (either by accident or intentionally). ES5 adds support for handling that in a variety of different nuanced ways.

It’s important to note that all of these approaches create shallow immutability. That is, they affect only the object and its direct property characteristics. If an object has a reference to another object (array, object, function, etc), the contents of that object are not affected, and remain mutable.

  1. myImmutableObject.foo; // [1,2,3]
  2. myImmutableObject.foo.push( 4 );
  3. myImmutableObject.foo; // [1,2,3,4]

We assume in this snippet that myImmutableObject is already created and protected as immutable. But, to also protect the contents of myImmutableObject.foo (which is its own object — array), you would also need to make foo immutable, using one or more of the following functionalities.

Note: It is not terribly common to create deeply entrenched immutable objects in JS programs. Special cases can certainly call for it, but as a general design pattern, if you find yourself wanting to seal or freeze all your objects, you may want to take a step back and reconsider your program design to be more robust to potential changes in objects’ values.

Object Constant

By combining writable:false and configurable:false, you can essentially create a constant (cannot be changed, redefined or deleted) as an object property, like:

  1. var myObject = {};
  2. Object.defineProperty( myObject, "FAVORITE_NUMBER", {
  3. value: 42,
  4. writable: false,
  5. configurable: false
  6. } );

Prevent Extensions

If you want to prevent an object from having new properties added to it, but otherwise leave the rest of the object’s properties alone, call Object.preventExtensions(..):

  1. var myObject = {
  2. a: 2
  3. };
  4. Object.preventExtensions( myObject );
  5. myObject.b = 3;
  6. myObject.b; // undefined

In non-strict mode, the creation of b fails silently. In strict mode, it throws a TypeError.

Seal

Object.seal(..) creates a “sealed” object, which means it takes an existing object and essentially calls Object.preventExtensions(..) on it, but also marks all its existing properties as configurable:false.

So, not only can you not add any more properties, but you also cannot reconfigure or delete any existing properties (though you can still modify their values).

Freeze

Object.freeze(..) creates a frozen object, which means it takes an existing object and essentially calls Object.seal(..) on it, but it also marks all “data accessor” properties as writable:false, so that their values cannot be changed.

This approach is the highest level of immutability that you can attain for an object itself, as it prevents any changes to the object or to any of its direct properties (though, as mentioned above, the contents of any referenced other objects are unaffected).

You could “deep freeze” an object by calling Object.freeze(..) on the object, and then recursively iterating over all objects it references (which would have been unaffected thus far), and calling Object.freeze(..) on them as well. Be careful, though, as that could affect other (shared) objects you’re not intending to affect.

[[Get]]

There’s a subtle, but important, detail about how property accesses are performed.

Consider:

  1. var myObject = {
  2. a: 2
  3. };
  4. myObject.a; // 2

The myObject.a is a property access, but it doesn’t just look in myObject for a property of the name a, as it might seem.

According to the spec, the code above actually performs a [[Get]] operation (kinda like a function call: [[Get]]()) on the myObject. The default built-in [[Get]] operation for an object first inspects the object for a property of the requested name, and if it finds it, it will return the value accordingly.

However, the [[Get]] algorithm defines other important behavior if it does not find a property of the requested name. We will examine in Chapter 5 what happens next (traversal of the [[Prototype]] chain, if any).

But one important result of this [[Get]] operation is that if it cannot through any means come up with a value for the requested property, it instead returns the value undefined.

  1. var myObject = {
  2. a: 2
  3. };
  4. myObject.b; // undefined

This behavior is different from when you reference variables by their identifier names. If you reference a variable that cannot be resolved within the applicable lexical scope look-up, the result is not undefined as it is for object properties, but instead a ReferenceError is thrown.

  1. var myObject = {
  2. a: undefined
  3. };
  4. myObject.a; // undefined
  5. myObject.b; // undefined

From a value perspective, there is no difference between these two references — they both result in undefined. However, the [[Get]] operation underneath, though subtle at a glance, potentially performed a bit more “work” for the reference myObject.b than for the reference myObject.a.

Inspecting only the value results, you cannot distinguish whether a property exists and holds the explicit value undefined, or whether the property does not exist and undefined was the default return value after [[Get]] failed to return something explicitly. However, we will see shortly how you can distinguish these two scenarios.

[[Put]]

Since there’s an internally defined [[Get]] operation for getting a value from a property, it should be obvious there’s also a default [[Put]] operation.

It may be tempting to think that an assignment to a property on an object would just invoke [[Put]] to set or create that property on the object in question. But the situation is more nuanced than that.

When invoking [[Put]], how it behaves differs based on a number of factors, including (most impactfully) whether the property is already present on the object or not.

If the property is present, the [[Put]] algorithm will roughly check:

  1. Is the property an accessor descriptor (see “Getters & Setters” section below)? If so, call the setter, if any.
  2. Is the property a data descriptor with writable of false? If so, silently fail in non-strict mode, or throw TypeError in strict mode.
  3. Otherwise, set the value to the existing property as normal.

If the property is not yet present on the object in question, the [[Put]] operation is even more nuanced and complex. We will revisit this scenario in Chapter 5 when we discuss [[Prototype]] to give it more clarity.

Getters & Setters

The default [[Put]] and [[Get]] operations for objects completely control how values are set to existing or new properties, or retrieved from existing properties, respectively.

Note: Using future/advanced capabilities of the language, it may be possible to override the default [[Get]] or [[Put]] operations for an entire object (not just per property). This is beyond the scope of our discussion in this book, but will be covered later in the “You Don’t Know JS” series.

ES5 introduced a way to override part of these default operations, not on an object level but a per-property level, through the use of getters and setters. Getters are properties which actually call a hidden function to retrieve a value. Setters are properties which actually call a hidden function to set a value.

When you define a property to have either a getter or a setter or both, its definition becomes an “accessor descriptor” (as opposed to a “data descriptor”). For accessor-descriptors, the value and writable characteristics of the descriptor are moot and ignored, and instead JS considers the set and get characteristics of the property (as well as configurable and enumerable).

Consider:

  1. var myObject = {
  2. // define a getter for `a`
  3. get a() {
  4. return 2;
  5. }
  6. };
  7. Object.defineProperty(
  8. myObject, // target
  9. "b", // property name
  10. { // descriptor
  11. // define a getter for `b`
  12. get: function(){ return this.a * 2 },
  13. // make sure `b` shows up as an object property
  14. enumerable: true
  15. }
  16. );
  17. myObject.a; // 2
  18. myObject.b; // 4

Either through object-literal syntax with get a() { .. } or through explicit definition with defineProperty(..), in both cases we created a property on the object that actually doesn’t hold a value, but whose access automatically results in a hidden function call to the getter function, with whatever value it returns being the result of the property access.

  1. var myObject = {
  2. // define a getter for `a`
  3. get a() {
  4. return 2;
  5. }
  6. };
  7. myObject.a = 3;
  8. myObject.a; // 2

Since we only defined a getter for a, if we try to set the value of a later, the set operation won’t throw an error but will just silently throw the assignment away. Even if there was a valid setter, our custom getter is hard-coded to return only 2, so the set operation would be moot.

To make this scenario more sensible, properties should also be defined with setters, which override the default [[Put]] operation (aka, assignment), per-property, just as you’d expect. You will almost certainly want to always declare both getter and setter (having only one or the other often leads to unexpected/surprising behavior):

  1. var myObject = {
  2. // define a getter for `a`
  3. get a() {
  4. return this._a_;
  5. },
  6. // define a setter for `a`
  7. set a(val) {
  8. this._a_ = val * 2;
  9. }
  10. };
  11. myObject.a = 2;
  12. myObject.a; // 4

Note: In this example, we actually store the specified value 2 of the assignment ([[Put]] operation) into another variable _a_. The _a_ name is purely by convention for this example and implies nothing special about its behavior — it’s a normal property like any other.

Existence

We showed earlier that a property access like myObject.a may result in an undefined value if either the explicit undefined is stored there or the a property doesn’t exist at all. So, if the value is the same in both cases, how else do we distinguish them?

We can ask an object if it has a certain property without asking to get that property’s value:

  1. var myObject = {
  2. a: 2
  3. };
  4. ("a" in myObject); // true
  5. ("b" in myObject); // false
  6. myObject.hasOwnProperty( "a" ); // true
  7. myObject.hasOwnProperty( "b" ); // false

The in operator will check to see if the property is in the object, or if it exists at any higher level of the [[Prototype]] chain object traversal (see Chapter 5). By contrast, hasOwnProperty(..) checks to see if only myObject has the property or not, and will not consult the [[Prototype]] chain. We’ll come back to the important differences between these two operations in Chapter 5 when we explore [[Prototype]]s in detail.

hasOwnProperty(..) is accessible for all normal objects via delegation to Object.prototype (see Chapter 5). But it’s possible to create an object that does not link to Object.prototype (via Object.create(null) — see Chapter 5). In this case, a method call like myObject.hasOwnProperty(..) would fail.

In that scenario, a more robust way of performing such a check is Object.prototype.hasOwnProperty.call(myObject,"a"), which borrows the base hasOwnProperty(..) method and uses explicit this binding (see Chapter 2) to apply it against our myObject.

Note: The in operator has the appearance that it will check for the existence of a value inside a container, but it actually checks for the existence of a property name. This difference is important to note with respect to arrays, as the temptation to try a check like 4 in [2, 4, 6] is strong, but this will not behave as expected.

Enumeration

Previously, we explained briefly the idea of “enumerability” when we looked at the enumerable property descriptor characteristic. Let’s revisit that and examine it in more close detail.

  1. var myObject = { };
  2. Object.defineProperty(
  3. myObject,
  4. "a",
  5. // make `a` enumerable, as normal
  6. { enumerable: true, value: 2 }
  7. );
  8. Object.defineProperty(
  9. myObject,
  10. "b",
  11. // make `b` NON-enumerable
  12. { enumerable: false, value: 3 }
  13. );
  14. myObject.b; // 3
  15. ("b" in myObject); // true
  16. myObject.hasOwnProperty( "b" ); // true
  17. // .......
  18. for (var k in myObject) {
  19. console.log( k, myObject[k] );
  20. }
  21. // "a" 2

You’ll notice that myObject.b in fact exists and has an accessible value, but it doesn’t show up in a for..in loop (though, surprisingly, it is revealed by the in operator existence check). That’s because “enumerable” basically means “will be included if the object’s properties are iterated through”.

Note: for..in loops applied to arrays can give somewhat unexpected results, in that the enumeration of an array will include not only all the numeric indices, but also any enumerable properties. It’s a good idea to use for..in loops only on objects, and traditional for loops with numeric index iteration for the values stored in arrays.

Another way that enumerable and non-enumerable properties can be distinguished:

  1. var myObject = { };
  2. Object.defineProperty(
  3. myObject,
  4. "a",
  5. // make `a` enumerable, as normal
  6. { enumerable: true, value: 2 }
  7. );
  8. Object.defineProperty(
  9. myObject,
  10. "b",
  11. // make `b` non-enumerable
  12. { enumerable: false, value: 3 }
  13. );
  14. myObject.propertyIsEnumerable( "a" ); // true
  15. myObject.propertyIsEnumerable( "b" ); // false
  16. Object.keys( myObject ); // ["a"]
  17. Object.getOwnPropertyNames( myObject ); // ["a", "b"]

propertyIsEnumerable(..) tests whether the given property name exists directly on the object and is also enumerable:true.

Object.keys(..) returns an array of all enumerable properties, whereas Object.getOwnPropertyNames(..) returns an array of all properties, enumerable or not.

Whereas in vs. hasOwnProperty(..) differ in whether they consult the [[Prototype]] chain or not, Object.keys(..) and Object.getOwnPropertyNames(..) both inspect only the direct object specified.

There’s (currently) no built-in way to get a list of all properties which is equivalent to what the in operator test would consult (traversing all properties on the entire [[Prototype]] chain, as explained in Chapter 5). You could approximate such a utility by recursively traversing the [[Prototype]] chain of an object, and for each level, capturing the list from Object.keys(..) — only enumerable properties.