Categories
JavaScript JavaScript Basics

More Useful DOM Traversal Methods

The main use of client-side JavaScript is to manipulate web pages dynamically. We can do this with the DOM traversal methods and properties available to DOM Node objects.

Adding or changing child and sibling elements is easy for any given element since there are properties built into DOM node objects to do so. The following are methods of a DOM node object for getting parent, child and sibling nodes or elements.

Below are more useful DOM traversal methods.

hasChildNodes

The hasChildNodes method returns a boolean that indicates whether the node it’s called on has any child nodes. Nodes include all kinds of nodes like element, text and comment nodes

For example, given the following HTML:

<div></div>

Then we can call hasChildNodes on it as follows:

const div = document.querySelector('div');  
console.log(div.hasChildNodes())

We should get false since the div has no child nodes.

On the other hand, if we have:

<div>  
  foo  
</div>

Then hasChildNodes returns true since there’s a text node inside the div.

insertBefore

The insertBefore method lets us add a node before the node that this method called on. It takes 2 arguments. The first is the new node to be inserted. The second is the node before the new node that it’s inserted, which is called the reference node.

If the reference node is null , then it’ll be inserted at the end of the list of child nodes.

It returns the add child except when the new node is a DocumentFragement . In that case, only DocumentFragement is returned.

For example, given the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
</div>

We can add a new div element after the div with ID bar inside the one with ID foo by writing:

const foo = document.querySelector('#foo');
const newDiv = document.createElement('div');  
newDiv.textContent = 'new';
foo.insertBefore(newDiv, null);

The HTML structure looks like:

<div id="foo">  
  foo  
  <div id="bar">  
    bar  
  </div>  
  <div>new</div>  
</div>

after insertBefore is called.

If we pass in the second argument, then the element in the first argument will be inserted before the second one.

For example, given the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
</div>

Then we can insert an element before the one with ID bar at the same level by writing:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');
const newDiv = document.createElement('div');  
newDiv.textContent = 'new';
foo.insertBefore(newDiv, bar);

Then we get the following HTML after insertBefore is called:

<div id="foo">  
  foo  
  <div>new</div>  
  <div id="bar">  
    bar  
  </div>  
</div>

isEqualNode

The isEqualNode method checks whether 2 nodes are equal. They’re equal when they have the same type, defining characteristics like ID, the number of children, the same attributes, etc. The data points for comparison vary depending on the types of nodes.

It takes one argument, which is the node that we want to compare with.

For example, given the following HTML:

<div id='foo'>  
  foo  
</div>  
<div id='bar'>  
  bar  
</div>

Then we can write the following JavaScript to check if the div with ID foo and the div with ID bar are the same:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');
console.log(foo.isEqualNode(bar));

The console.log should log false since they don’t have the same ID or content.

On the other hand, if we have the following HTML:

<div class='foo'>  
  foo  
</div>  
<div class='foo'>  
  foo  
</div>

Then we can compare them by writing:

const foo1 = document.querySelector('.foo:nth-child(1)');  
const foo2 = document.querySelector('.foo:nth-child(2)');
console.log(foo1.isEqualNode(foo2));

and the console.log should log true since they have identical class value and content.

isSameNode

The isSameNode method checks whether 2 Node objects reference the same node.

It takes one argument, which is the other node to test and returns a boolean indicating whether they’re the same.

For example, given the following HTML:

<div>  
  foo  
</div>

We can call isSameNode by writing:

const div = document.querySelector('div');console.log(div.isSameNode(div));

On the other hand, if we have:

<div>  
  foo  
</div>  
<div>  
  foo  
</div>

Then the following call to isSameNode will return false :

const div1 = document.querySelector('div:nth-child(1)');  
const div2 = document.querySelector('div:nth-child(2)');console.log(div1.isSameNode(div2));

This is because even though they look the same, they don’t reference the same node.

normalize

The normalize method cleans up the sub-tree of a Node by removing adjacent text nodes.

For example, if we have the following HTML:

<div>  
  foo  
</div>

Then before call normalize, we get that there’re 3 child nodes inside the div element after add 2 more text nodes to it as child nodes of the div. After we called it, it’s reducing the number of child nodes to 1.

We call as in the following code:

const div = document.querySelector('div');  
div.appendChild(document.createTextNode("foo "));  
div.appendChild(document.createTextNode("bar"));console.log(div.childNodes.length);  
div.normalize();  
console.log(div.childNodes.length);

The first console.log should log 3 and the second one should log 1.

removeChild

To remove the child node from a given node, we can use the removeChild method to remove the child from the DOM tree.

It takes a child element of the given node as an argument and then returns the node that was removed from the DOM tree.

For example, given that we have the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
</div>

We can remove the div with ID bar by writing:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');foo.removeChild(bar);

Then we should no longer see ‘bar’ in the browser window.

It only works with child nodes. For example, if we have:

<div id='foo'>  
  foo  
</div>  
<div id='bar'>  
  bar  
</div>

and run the same code, then we’ll get the error ‘Uncaught DOMException: Failed to execute ‘removeChild’ on ‘Node’: The node to be removed is not a child of this node.’

It’ll also throw an error if the node doesn’t exist.

replaceChild

The replaceChild method replaces the current one with the second one given in the parameter.

For example, if we have the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
</div>

Then we can create a new element to replace the div with ID bar:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');const baz = document.createElement('div');  
baz.textContent = 'baz';  
foo.appendChild(baz);foo.replaceChild(baz, bar);

The first argument has the new element and the second argument has the original one.

It also works for using elements that already exist to replace another one that exists.

For instance, if we have the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
  <div id='baz'>  
    baz  
  </div>  
</div>

Then we can write:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');  
const baz = document.querySelector('#baz');
foo.replaceChild(baz, bar);

to replace the div with ID bar with the one with ID baz.

The DOM traversal and manipulation methods are very useful. We can use them to check if they have the same content or referencing the same element. Also, there’re methods to replace nodes and clean up extraneous text nodes. There are also handy methods for inserting nodes and removing nodes.

Categories
JavaScript JavaScript Basics

The JavaScript Global Variable — When Should We Use It?

In JavaScript, there’s a global variable depending on the environment. In the browser, there’s the window object. The worker context has the self object as the global object. Node.js has the global object.

In this article, we’ll look at the global object, what it does, and how and when to use it.

Scope of the Global Object

The global object is always in the topmost scope. There’s nothing else above it. Therefore, it can be accessed by anything. Consider the name, this makes sense.

In JavaScript, scopes don’t change at runtime, so we can get the scope of something from their location in the code.

Since the global object is in the topmost scope, it can be accessed by anything. For example, we can console.log the window object from anywhere and we can get the value for it.

The Global Object

A global object is an object where the properties of it are global variables. It’s called the window object in browsers, self in web workers, and global in Node.js

There’s also a proposal to have a globalThis global object that’s available in all contexts.

The global object contains all built-in global variables.

Creating Global Variables

We can create global variables by creating a property of the global object. In the browser context, this means that we create it as a property of the window object.

There’re a few ways to do this. One way is to define as a property of the window object as follows:

window.foo = 1;

Then we can reference as foo or window.foo anywhere in our code.

Another way is to use the var keyword in the top level of our code. That is, it’s not nested in any block. We can define it as follows:

var foo = 1;

Getting or Setting Variables

We can get and set global variables just like how we declared it. For example, if we declared:

window.foo = 1;

Then we can reference window.foo and assign values to them. If it’s declared with var at the top level as in:

var foo = 1;

Then we can either write:

foo = 1;

or:

window.foo = 1;

Module

Each module has its own environment that isn’t exposed to the outside unless something is explicitly exported. However, we can still access global variables from there like in any other environment.

Caveats

The global object is considered a mistake. It’s easy to create global variables that clash with the names of other variables.

Also, we can easily change existing built-in global variables or ones that are defined by us. This is a problem which is avoided by using newer constructs like let and const to declare variables and constants and also using modules to protect private data and namespace them so that we can’t modify anything accidentally.

It was only created to make simple scripting on the browser easy. Now that we’re building full-fledged apps with JavaScript, using the global object too much will create lots of problems like we described before.

However, the global object in the browser environment has lots of properties that we can use to add features to our apps like window.SubtleCrypto for cryptography, customElements variable to declare Web Components and many more properties. location is also a property of the window object.

Likewise Node.js’ global object has properties like __dirname and __filename, which is handy for accessing data about our current code. The console object is useful for debugging, and Process is useful for getting data about the currently running process.

setTimeout, setInterval are also part of the Node.js global object.

The full list of properties is at Mozilla Developer Network.

It was also used to access variables from external scripts before JavaScript has modules. But now that we have modules, we definitely don’t need to use the global object for that purpose.

Also, testing is a lot harder since global variables are changed by everything. So, it’s hard to reason what’s going on with them.

Conclusion

Nowadays, we just use global variables like the window object mostly for their built-in properties.

In our own code, we use what’s provided by the global object, but there isn’t any good use case to add any properties to the global object. It’s messy and hard to trace issues since everything modifies the global variable since everything can access it.

The better way is to modularize everything and then make expose the things that we need by exporting them.

However, the window object does have lots of useful properties that we can use. This is the same for Node.js’ global object.

Categories
JavaScript JavaScript Basics

Sorting JavaScript Arrays

Sorting arrays is a common operation that’s done in many apps. The sort method that comes with JavaScript arrays makes this easy. However, there are lots of intricacies when it comes to sorting arrays.

In this article, we’ll look at how to sort arrays with the sort method. Also, we’ll look at how to compare objects to order them in the way that we desire.

Basic Sorting

The most basic sorting operation is probably sorting numbers. With the sort method, we can do this easily. For example, if we have the following array:

let arr = [3, 4, 5, 2, 5, 8, 9];

Then we can sort is with the sort method as follows:

arr.sort();

The arr array is sorted in place, so we don’t have to assign it to a new variable.

After calling sort, we get that the array is:

[2, 3, 4, 5, 5, 8, 9]

We can see that the sorting is done by ordering numbers in ascending order.

Sorting number arrays in ascending order are the simplest cases. We didn’t have to modify the sort method’s default functionality to do this.

We can also sort strings by ascending Unicode code point order. The sort method will sort by the total code points of each string. The full list of code points for Unicode characters are listed at https://en.wikipedia.org/wiki/List_of_Unicode_characters.

For example, given the following array:

let strings = ['foo', 'bar', 'baz'];

We can call sort by writing:

strings.sort();

to sort in ascending total Unicode code points.

Copying an Array Before Sorting

As we can see, sort will sort the array it’s called on in place. We can avoid this by copying the array to a new array before sorting.

To do this, we can either use the slice method as follows:

let sortedStrings = strings.slice().sort();

Or more conveniently, we can use the spread operator to do the same thing:

let sortedStrings = [...strings].sort();

Sorting with Compare Functions

The sort method can take an optional callback function to let us compare the content of the array to let us sort it the way we want.

The callback function takes 2 arguments. The first one is the array entry that comes first, the second is the one that comes after the one in the first argument.

We compare them and then return a positive number, negative number, or 0 depending on our preference.

Given that we have 2 entries, a and b for the first and second parameter for the callback respectively, we return a negative number to make a come before b. To make b come before a we can return a positive number.

To keep their order unchanged with respect to each other, we return 0.

For example, we can use this to sort numbers in reverse as follows:

arr.sort((a, b) => b - a);

We want to make the bigger entry come before the smaller entry, so we return b — a to make b come before a if b is bigger than a since b bigger than a means that b — a will be positive.

Otherwise, if b is smaller than a, it’ll be negative, and it’ll be zero if they’re the same.

It’s also handy for sorting by fields in an array of objects. If we want to sort by a field of objects, then we’ve to write a compare function to do it. For example, given the following array:

let arr = [{  
    name: 'Joe',  
    age: 20  
  },  
  {  
    name: 'Mary',  
    age: 16  
  },  
  {  
    name: 'Jane',  
    age: 22  
  },  
];

We can sort by the age field by ascending age as follows:

arr.sort((a, b) => a.age - b.age);

It works the same as sorting other numbers. If a.age is less than b.age , then we return a negative number, so a comes before b . If it’s the reverse, then b comes before a, where a and b are neighboring entries of the array.

Otherwise, if they’re the same, then a and b’s order stays the same.

Then we get the following if we log the array:

{name: "Mary", age: 16}  
{name: "Joe", age: 20}  
{name: "Jane", age: 22}

We can use the comparison operators like >, <, <=, or >=to compare strings.

For example, if we want to sort the array above by name, we can write:

arr.sort((a, b) => {  
  if (a.name.toLowerCase() < b.name.toLowerCase()) {  
    return -1;  
  } else if (a.name.toLowerCase() > b.name.toLowerCase()) {  
    return 1;  
  }  
  return 0;  
});

Then we get the following result:

{name: "Jane", age: 22}  
{name: "Joe", age: 20}  
{name: "Mary", age: 16}

Comparing Non-English Strings

The callback for sorting strings above works great sorting English strings. However, it doesn’t work very well for non-English strings since it doesn’t take into account things like punctuation or accents.

To compare these kinds of strings, we can use the localeCompare method that comes with strings.

It has lots of optional that we can adjust for changing the comparison of strings, like whether to check accents, case, and other variants of a letter. The locale for comparison can also be adjusted.

The full list of options are at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare

We can use it as follows:

let arr = ['réservé', 'Premier', 'Cliché', 'communiqué', 'café', 'Adieu'];  
arr.sort((a, b) => a.localeCompare(b, 'fr', {  
  ignorePunctuation: true,  
  sensitivity: 'base'  
}));

In the code above, we passed a string b to compare with a in the comparison function of the localeCompare method as the first argument. Then we set the second argument to the 'fr' locale to indicate that we’re comparing French string. Finally, we passed in some basic options, which include ignoring punctuation and adjusting the sensitivity of the string comparison.

sensitivity set to 'base' means that strings that differ in letters are unequal.

After the sort method call above, we get that the value for arr is:

["Adieu", "café", "Cliché", "communiqué", "Premier", "réservé"]

Sorting JavaScript arrays have its tricky parts. The easiest case is sorting numbers and string code points in ascending order, which requires no comparison function.

For more complex comparisons, we’ve to pass in a comparison function. In it, returning a negative number will make the first value come before the second. Positive will make the second value come before the first. Returning 0 will keep the existing order.

Comparing non-English strings requires more care. We can use the localeCompare method that comes with strings to do the comparison by passing in various options.

Categories
JavaScript JavaScript Basics

How To Use LocalStorage to Store Data in the Browser

Storing data in a browser is something we do often for web apps. Often, we need to store some temporary data that’s user-specific on the browser. We can do this with the browser’s local-storage capabilities.

With local storage, we store data on the browser as strings. It’s a key value–based storage medium, allowing us to get data by keys and also setting values with the key as the identifier. All data is stored as strings. If a piece of data is not a string, then it’ll be converted to a string before being stored.

Once they’re saved, they’re there as long as we don’t delete the data. We can remove data by its key or remove them all at the same time. Every app that’s hosted on the same domain can access the data, so we can host multiple web apps under the same domain and still get the same data in all the apps.

This means we can easily modularize apps into smaller apps, and we won’t have a problem with browser data access as long as all the apps are hosted in the same domain.

Unlike cookies, local-storage data has no expiration time, so it’ll still be there if we don’t do anything to delete it.

We access the browser’s local storage with the localStorage object. It’ll thrown a SecurityError if we don’t access localStorage with the http protocol. This means that any URL starting with protocols like file: or data: will fail with this error.


Saving Data

We can use the setItem method to save data to a browser’s local storage. It takes two arguments. The first argument is a string with the key of the data, and the second argument is a string with the value of the corresponding key we passed into the first argument.

It throws an exception if the storage is full. Safari has the storage quota set to zero bytes in private mode, but it allows storage in private mode using separate data containers. This means we should catch exceptions from setItem.

For example, we can write:

localStorage.setItem('foo', 'bar');

Then once we run that, we’ll see the entry in the Application tab, under the Local Storage section of Chrome.

We can also write …

localStorage.foo = 'bar';

… to save data.

Bracket notation also works for assigning values to local storage. For example, we can write …

localStorage['foo'] = 'bar';

… to set the local storage item with the key 'foo' and the value 'bar' .

However, we shouldn’t use the dot or bracket notation instead of setItem. We don’t want to accidentally override things like the setItem method by setting a string to it, like trying to save data by using setItem as the key.

Saving object data

If we try to save objects, we’ll get [object Object] as the value. To actually save the content, we have to use the JSON.stringify method. For example, instead of writing …

localStorage.setItem('foo', {foo: 'bar'});

… we write:

localStorage.setItem('foo', JSON.stringify({foo: 'bar'}));

Then, we get {“foo”:”bar”} as the value for the entry with the foo key.

Repeated calling

If we call the setItem method repeated with the same key, then the existing value with the key is overwritten. For example, if we write …

localStorage.setItem('foo', 1);  
localStorage.setItem('foo', 2);

… then we get 2 as the value for the entry with the key foo since it’s the last value that was saved.


Getting Data

We can get data with the getItem method or use the dot notation to get data like any other object. getItem takes one argument — a string for the key in which we want to get a value with. It returns a string with the value if it’s set for the given key or null if it’s not.

For example, we can write:

localStorage.getItem('foo');

Then, we get the value 'bar' if we run the setItem code from the previous section.

We can also write:

localStorage.foo;

Alternatively, we can write:

localStorage['foo'];

The bracket notation is handy for accessing values with corresponding keys that aren’t valid object-property names.

There’s also a key method to get the name of the key in the local storage given the position number. It takes one argument, which is an integer that’s zero or higher. The first position is numbered 0. For example, if we have the following code…

localStorage.key(1)

… then we get the key name of the second entry in the local storage.


Removing a Single Entry in Local Storage

We can remove a single entry from the local storage given the key with the removeItem method. It takes one argument, which is a string with the key name of the entry.

For example, if we want to remove the entry with the key 'foo', then we can write:

loocalStorage.removeItem('foo');

The code above will remove the local-storage entry with the key name 'foo'.

Alternatively, we can use the delete operator to remove the item given the key name. For example, we can remove the local-storage entry with the key 'foo' by running:

delete localStorage.foo;

We can also use the bracket notation to do the same thing, so we can write …

delete localStorage['foo'];

… to remove the same entry.

However, we shouldn’t use the dot or bracket notation instead of setItem. We don’t want to accidentally override things like the setItem method by setting a string to it, like trying to save data by using setItem as the key.


Clearing Local Storage

We can use the clear() method to clear all entries in local storage.

We can write …

localStorage.clear()

… to clear all entries. We shouldn’t see anything in the browser’s local-storage section in the Application tab once we run this method.


Browser Compatibility

Local storage is available for almost all modern browsers, so it’s safe to use pretty much anywhere. Even Internet Explorer has had support for it since version 8, so it’s not a new technology. It’s much better than browser-side cookies for persistent data since it doesn’t expire and there are methods to manipulate the data.

With local storage on the browser, storing data client-side is easier than ever. We can do a lot by just calling the methods we outlined above. Whatever is saved is going to be saved as strings. If the data passed into the second argument of the setItem isn’t a string, then it’ll automatically be converted to a string.

Categories
JavaScript JavaScript Basics

Using JavaScript’s Object Constructor — Part 2

In JavaScript, the object constructor lets us create object wrapper with the given values. It will create an empty object if null or undefined is passed into the object constructor. If the value passed into the constructor is an object already, then it will return the object.

The object constructor has two properties. It has a length property that is always 1, and like all other objects, the object constructor has a prototype to get all the property additions to the type object.

Continuing from Part 1, the object constructor has many useful methods that can be used without constructing a new object.


Object.fromEntries()

The Object.fromEntries() method accepts an array with arrays of key-value pairs with the key as the first element and the value of the corresponding key as the second element. We can also pass in other iterables with the same kind of arrays into the method.

For example, we can write the following code to pass in an array of key-value pairs to create an object:

const entries = [  
  [  
    ['a', 1],  
    ['b', 2]  
  ]  
];

const obj = Object.fromEntries(entries);
console.log(obj);

The resulting object obj should be {a: 1, b: 2} as we can see from the console.log output when the code above is run. We can pass in other iterables, such as maps:

const entries = new Map([  
  ['a', 1],  
  ['b', 2]  
]);

const obj = Object.fromEntries(entries);
console.log(obj);

We should see the same thing logged. Also, we can convert arrays to objects with the following code:

const arr = [1,2,3];  
const entries = arr.map((value, index) => [index, value]);
const obj = Object.fromEntries(entries);
console.log(obj);

When the code above is run, we get {0: 1, 1: 2, 2: 3} since we mapped the index of each array entry to the key and the value of each array entry to the value.


Object.getOwnPropertyDescriptor()

The Object.getOwnPropertyDescriptor() method gets the property descriptor of a property in the object and returns it. As the name suggests, it only gets the property descriptor of the object that’s in the object itself and not up the prototype chain.

A property descriptor is an object with the property names as keys and the properties writable, configurable, enumerable, and value as properties of the property name keys.

writable means that the property’s value can be changed. configurable means the property descriptor may be changed and if the property can be deleted from the object.

The enumerable property means that the property shows up during enumeration of the properties with the for...in loop and value is the value of the property.

For example, if we log a property descriptor of an object with:

const obj = {  
  a: 1  
}const descriptor = Object.getOwnPropertyDescriptor(obj, 'a');  
console.log(descriptor);

We get {value: 1, writable: true, enumerable: true, configurable: true} . The value is the value of the property and the rest of the properties are the property descriptor’s properties. If we have property getters and setters, they are also returned with the method call:

let obj = {};  
let value;  
Object.defineProperty(obj, 'a', {  
  get() {  
    return value;  
  },  
  set(a) {  
    value = a;  
  }  
});  
  
const descriptor = Object.getOwnPropertyDescriptor(obj, 'a');  
console.log(descriptor);

When the code above is run, we get the get and set functions back in the descriptor object.


Object.getOwnPropertyDescriptors()

While the Object.getOwnPropertyDescriptor() gets the property descriptor for a single object, the Object.getOwnPropertyDescriptors() gets all the property descriptors of an object in one object—once again without the properties that are inherited by this object up the prototype chain, with the property names as the keys and the property descriptor of the corresponding property name key as the value.

For example, if we have

const obj = {  
  a: 1,  
  b: 2  
}  
const descriptors = Object.getOwnPropertyDescriptors(obj);  
console.log(descriptors);

then we get

{  
  "a": {  
    "value": 1,  
    "writable": true,  
    "enumerable": true,  
    "configurable": true  
  },  
  "b": {  
    "value": 2,  
    "writable": true,  
    "enumerable": true,  
    "configurable": true  
  }  
}

We get all the values and the property descriptor attributes of each property. Like with Object.getOwnPropertyDescriptor(), this method has the same definitions for the property descriptors.

A property descriptor is an object with the property names as keys and the properties writable, configurable, enumerable, and value as properties of the property name keys.

The writable means that the property’s value can be changed. configurable means the property descriptor may be changed and if the property can be deleted from the object.

The enumerable property means that the property shows up during enumeration of the properties with the for...in loop, and value is the value of the property.


Object.getOwnPropertyNames()

The Object.getOwnPropertyNames() method returns an array of property names that are defined in the object itself and not in any object up the prototype chain. Non-enumerable properties are also returned except for those that are symbols. For example, if we have:

let obj = {  
  a: 1,  
  b: 2  
}

Object.defineProperty(obj, 'c', {  
  "value": 2,  
  "writable": true,  
  "enumerable": false,  
  "configurable": true  
})

const names = Object.getOwnPropertyNames(obj);  
console.log(names);

If we run the code above, we get [“a”, “b”, “c”] returned since all properties defined except ones identified with Symbols are returned. If we have Symbols in our object, we won’t see it in the returned array. For example, if we have

let obj = {  
  a: 1,  
  b: 2,  
  [Symbol('foo')]: 3  
}  
Object.defineProperty(obj, 'c', {  
  "value": 4,  
  "writable": true,  
  "enumerable": false,  
  "configurable": true  
})  
const names = Object.getOwnPropertyNames(obj);  
console.log(names);

we still see [“a”, “b”, “c”] if we run the code above since symbols aren’t included in the array. We have the Object.getOwnPropertySymbols() to get properties that are identified with symbols.


Object.getOwnPropertySymbols()

The Object.getOwnPropertySymbols() returns an array of symbols that are used as identifiers in the properties of an object.

It only gets the property identifiers that are named with symbols and nothing else.

Also, it doesn’t traverse up to the prototype chain to get properties of objects up the prototype chain. For example, if we have the following code:

let obj = {  
  a: 1,  
  b: 2,  
  [Symbol('foo')]: 3  
}  
Object.defineProperty(obj, 'c', {  
  "value": 4,  
  "writable": true,  
  "enumerable": false,  
  "configurable": true  
})  
const symbols = Object.getOwnPropertySymbols(obj);  
console.log(symbols);

we get [Symbol(foo)] logged as we expected.


Object.getPrototypeOf()

The Object.getPrototypeOf() method gets the prototype of an object, which is the same as the [[Prototype]] property of the specified object. For example, if we have

const prototype1 = {};  
const obj = Object.create(prototype1);  
console.log(Object.getPrototypeOf(obj) === prototype1);

the console.log will output true since the first argument of Object.create is the prototype object for the object that it returns; likewise, if we use Object.setPrototypeOf() method to set the prototype of an existing object.

const prototype2 = {};  
let obj2 = {};  
Object.setPrototypeOf(obj2, prototype2);  
console.log(Object.getPrototypeOf(obj2) === prototype2);

The console.log will also output true since set the prototype of obj2 explicitly.


Object.is()

The Object.is() method compares whether two objects passed in its argument are the same value. Two values are the same if they’re:

  • both undefined
  • both null
  • both true or both false
  • both strings with the same length and same characters in the same order
  • both objects having the same references
  • both numbers and both +0 , or both -0 or both NaN or both non-zero and both not NaN and they both have the same value

It doesn’t convert types like the == operator to compare objects and don’t convert truthy or falsy values to booleans. Also, it’s not the same as comparing objects with the === operator because with the === operator, -0 and +0 are equal and NaN is not the same as itself.

For example, we can make the following comparisons:

Object.is('a', 'a');             // true  
Object.is(document, document);   // true  
  
Object.is('a', 'b');             // false  
Object.is([], []);               // false  
  
const obj = { a: 1 };  
const obj2 = { a: 1 };  
Object.is(obj, obj);             // true  
Object.is(obj, obj2);            // false  
  
Object.is(null, null);           // true  
  
Object.is(0, -0);                // false  
Object.is(-0, -0);               // true  
Object.is(NaN, 0/0);             // true

As we can see, only object references and values are compared. The object’s content isn’t compared, so even if two objects have the same content, they’re still considered different since their references in memory are different, as they’re defined by two different variables.


Object.isExtensible()

The Object.isExtensible() method checks whether an object’s property is extensible. That is if an object can have new properties added to it. For example, if we have:

const obj = {};  
const obj2 = {};console.log(Object.isExtensible(obj));Object.preventExtensions(obj2);console.log(Object.isExtensible(obj2));

Then console.log(Object.isExtensible(obj)); will log true since we didn’t explicitly prevent adding new properties to obj . However, console.log(Object.isExtensible(obj2)); will log false since we called Object.preventExtensions(obj2); to prevent new properties from being added to obj2 .

If we run the following code, we also get false logged in both console.log statements because we explicitly prevented the objects obj and obj2 from having new properties added with the Object.freeze() and Object.seal() methods respectively:

const obj = {};  
const obj2 = {};  
Object.freeze(obj);  
console.log(Object.isExtensible(obj));Object.seal(obj2);  
console.log(Object.isExtensible(obj2));

Object.isFrozen()

The Object.isFrozen() method determines if an object is frozen. Frozen means that all properties’ values in an object can’t be changed. Also, new properties can’t be added to it, and existing property descriptors for the frozen object can’t be changed. The object is frozen in place. This method doesn’t return a new frozen object. Instead, it returns the original object before it’s frozen. The frozen object’s prototype also can’t be changed. For example, if we have:

const obj = {  
  a: 1  
};console.log(Object.isFrozen(obj));

We get false from the console.log statement because we didn’t explicitly freeze the object by calling Object.freeze(obj). On the other hand, we have frozen the object, then Object.isFrozen() will return true. For example:

const obj = {  
  a: 1  
};Object.freeze(obj);  
console.log(Object.isFrozen(obj));

Then we get true from the console.log statement because we froze the object by calling Object.freeze(obj). If primitive values are passed in, Object.isFrozen() will return true since they’re immutable. For example, if we have:

console.log(Object.isFrozen(1));  
console.log(Object.isFrozen('string'));  
console.log(Object.isFrozen(null));  
console.log(Object.isFrozen(undefined));  
console.log(Object.isFrozen(NaN));  
console.log(Object.isFrozen(true));  
console.log(Object.isFrozen(Symbol('a')));

Then all the console.log statement will be true since they’re all immutable.

The Object constructor has many more methods for constructing objects from an array of an array with key and value of properties and also methods to get property descriptors from objects, property names, property symbols, gets the keys of an object and prevent properties from being added or deleted or modify their property descriptors.