📜 ⬆️ ⬇️

Javascript: object exploration

The material, the translation of which we are publishing today, is devoted to the study of objects - one of the key entities of JavaScript. It is designed mainly for novice developers who want to streamline their knowledge about objects.



Objects in JavaScript are dynamic collections of properties that, in addition, contain a “hidden” property, which is a prototype of the object. Properties of objects are characterized by keys and values. Let's start the conversation about JS objects with keys.

Object Property Keys


The property key of the object is a unique string. To access the properties, you can use two methods: accessing them through a point and specifying the object key in square brackets. When accessing properties through a dot, the key must be a valid JavaScript identifier. Consider an example:
')
let obj = {  message : "A message" } obj.message //"A message" obj["message"] //"A message" 

If you attempt to access a non-existent property of the error message object, it will return the value undefined :

 obj.otherProperty //undefined 

When used to access the properties of square brackets, you can use keys that are not valid JavaScript identifiers (for example, the key can be a string containing spaces). They can have any value that can be cast to the string:

 let french = {}; french["merci beaucoup"] = "thank you very much"; french["merci beaucoup"]; //"thank you very much" 

If non-string values ​​are used as keys, they are automatically converted to strings (using the toString() method, if possible):

 et obj = {}; //Number obj[1] = "Number 1"; obj[1] === obj["1"]; //true //Object let number1 = { toString : function() { return "1"; } } obj[number1] === obj["1"]; //true 

In this example, the number1 object is used as the key. When attempting to access a property, it is converted to line 1 , and the result of this conversion is used as a key.

Object Property Values


Object properties can be primitive values, objects, or functions.

â–ŤObject as an object property value


Objects can be placed in other objects. Consider an example :

 let book = { title : "The Good Parts", author : {   firstName : "Douglas",   lastName : "Crockford" } } book.author.firstName; //"Douglas" 

A similar approach can be used to create namespaces:

 let app = {}; app.authorService = { getAuthors : function() {} }; app.bookService = { getBooks : function() {} }; 

â–ŤFunction as an object property value


When a function is used as a property value of an object, it usually becomes an object method. Inside the method, to refer to the current object, use the keyword this .

This keyword, however, may have different meanings, depending on how the function was called. Here you can read about situations in which this loses context.

Dynamic nature of objects


Objects in JavaScript, by their nature, are dynamic entities. You can add properties to them at any time, the same applies to deleting properties:

 let obj = {}; obj.message = "This is a message"; //   obj.otherMessage = "A new message"; //    delete obj.otherMessage; //  

Objects as associative arrays


Objects can be considered as associative arrays. The keys of an associative array are the names of the properties of an object. In order to gain access to the key, it is not necessary to look through all the properties, that is, the operation of accessing the key of an associative array based on an object is performed in O (1) time.

Object Prototypes


Objects have a “hidden” link, __proto__ , pointing to a prototype object, from which the object inherits properties.

For example, an object created using an object literal has a reference to the Object.prototype :

 var obj = {}; obj.__proto__ === Object.prototype; //true 

Empty objects


As we have just seen, an “empty” object, {} , is actually not so empty, since it contains a reference to an Object.prototype . In order to create a truly empty object, you need to use the following construction:

 Object.create(null) 

This will create an object without a prototype. Such objects are usually used to create associative arrays.

A prototype chain


Prototype objects can have their own prototypes. If you try to access the property of an object that is not in it, JavaScript will try to find this property in the prototype of this object, and if there is no desired property there, an attempt will be made to find it in the prototype prototype. This will continue until the desired property is found, or until the end of the prototype chain is reached.

Primitive Type Values ​​and Object Wrappers


JavaScript allows you to work with values ​​of primitive types as objects, in the sense that the language allows you to access their properties and methods.

 (1.23).toFixed(1); //"1.2" "text".toUpperCase(); //"TEXT" true.toString(); //"true" 

In this case, of course, the values ​​of primitive types are not objects.

To provide access to the “properties” of values ​​of primitive types of JavaScript, if necessary, create wrapper objects, which, after they are no longer needed, are destroyed. The process of creating and destroying wrapper objects is optimized by the JS engine.

Object wrappers have numeric, string, and boolean values. Objects of the corresponding types are represented by the constructor functions Number , String , and Boolean .

Embedded Prototypes


Number objects inherit properties and methods from the Number.prototype prototype, which is derived from the Object.prototype :

 var no = 1; no.__proto__ === Number.prototype; //true no.__proto__.__proto__ === Object.prototype; //true 

The prototype of the string objects is the String.prototype . The prototype of the boolean objects is the Boolean.prototype . The prototype of the arrays (which are also objects) is Array.prototype .

Functions in JavaScript are also objects that have a prototype Function.prototype . Functions have methods like bind() , apply() and call() .

All objects, functions, and objects representing values ​​of primitive types (with the exception of null and undefined values) inherit properties and methods from Object.prototype . This leads to the fact that, for example, all of them have a toString() method.

Extending embedded objects with polyfills


JavaScript allows you to easily extend built-in objects with new functions using the so-called polyfills. Polyfill is a piece of code that implements features not supported by any browsers.

â–ŤUsing polyfills


For example, there is a polyfill for the Object.assign() method. It allows you to add a new function to Object if it is not available in it.

The same applies to the Array.from Array.from() , which, if there is no from() method in the Array object, equips it with this method.

â–ŤPolythills and prototypes


With the help of polyfills new methods can be added to the prototypes of objects. For example, a polyfill for String.prototype.trim() allows you to equip all string objects with a trim() method:

 let text = "   A text "; text.trim(); //"A text" 

Polyfill for Array.prototype.find() allows you to equip all arrays with the find() method. The polyfill for Array.prototype.findIndex() works in a similar way:

 let arr = ["A", "B", "C", "D", "E"]; arr.indexOf("C"); //2 

Single inheritance


The Object.create() command allows you to create new objects with a given prototype object. This command is used in JavaScript to implement the single inheritance mechanism. Consider an example :

 let bookPrototype = { getFullTitle : function(){   return this.title + " by " + this.author; } } let book = Object.create(bookPrototype); book.title = "JavaScript: The Good Parts"; book.author = "Douglas Crockford"; book.getFullTitle();//JavaScript: The Good Parts by Douglas Crockford 

Multiple inheritance


The Object.assign() command copies properties from one or more objects to the target object. It can be used to implement a multiple inheritance scheme. Here is an example :

 let authorDataService = { getAuthors : function() {} }; let bookDataService = { getBooks : function() {} }; let userDataService = { getUsers : function() {} }; let dataService = Object.assign({}, authorDataService, bookDataService, userDataService ); dataService.getAuthors(); dataService.getBooks(); dataService.getUsers(); 

Immunity objects


The Object.freeze() command allows you to freeze an object. In such an object can not add new properties. Properties cannot be deleted, and their values ​​cannot be changed. Using this command, the object becomes immutable or immutable:

 "use strict"; let book = Object.freeze({ title : "Functional-Light JavaScript", author : "Kyle Simpson" }); book.title = "Other title";//: Cannot assign to read only property 'title' 

The Object.freeze() command performs the so-called “shallow freezing” of objects. This means that objects nested in a “frozen” object can be modified. In order to carry out a “deep freezing” of an object, one must recursively “freeze” all its properties.

Cloning objects


To create clones (copies) of objects, you can use the Object.assign() command:

 let book = Object.freeze({ title : "JavaScript Allongé", author : "Reginald Braithwaite" }); let clone = Object.assign({}, book); 

This command performs shallow copying of objects, that is, it copies only top-level properties. Nested objects are common for original objects and their copies.

Object literal


Object literals give the developer a simple and straightforward way to create objects:

 let timer = { fn : null, start : function(callback) { this.fn = callback; }, stop : function() {}, } 

However, this method of creating objects has disadvantages. In particular, with this approach, all properties of an object are publicly available, object methods can be overridden, they cannot be used to create new instances of identical objects:

 timer.fn;//null timer.start = function() { console.log("New implementation"); } 

Object.create () method


The two problems mentioned above can be Object.create() and Object.freeze() methods.

Apply this technique to our previous example. First, create a frozen prototype timerPrototype containing all the methods needed by different instances of the object. After that, create an object that is a successor of timerPrototype :

 let timerPrototype = Object.freeze({ start : function() {}, stop : function() {} }); let timer = Object.create(timerPrototype); timer.__proto__ === timerPrototype; //true 

If the prototype is protected from changes, the object that is its successor will not be able to change the properties defined in the prototype. Now the start() and stop() methods cannot be redefined:

 "use strict"; timer.start = function() { console.log("New implementation"); } //: Cannot assign to read only property 'start' of object 

The Object.create(timerPrototype) can be used to create multiple objects with the same prototype.

Constructor function


In JavaScript, there are so-called constructor functions, which are “syntactic sugar” for performing the above described actions for creating new objects. Consider an example :

 function Timer(callback){ this.fn = callback; } Timer.prototype = { start : function() {}, stop : function() {} } function getTodos() {} let timer = new Timer(getTodos); 

As a constructor, you can use any function. The constructor is called using the new keyword. An object created using a constructor function named FunctionConstructor will receive the prototype FunctionConstructor.prototype :

 let timer = new Timer(); timer.__proto__ === Timer.prototype; 

Here, to prevent prototype changes, again, you can freeze the prototype:

 Timer.prototype = Object.freeze({ start : function() {}, stop : function() {} }); 

â–Ť Keyword new


When a command like new Timer() is executed, the same actions are performed as the following newTimer() function newTimer() :

 function newTimer(){ let newObj = Object.create(Timer.prototype); let returnObj = Timer.call(newObj, arguments); if(returnObj) return returnObj;   return newObj; } 

This is where a new object is created, the prototype of which is Timer.prototype . Then the Timer function is called, setting the fields for the new object.

Keyword class


In ECMAScript 2015, a new way to perform the actions described above has been introduced, representing another portion of “syntactic sugar”. We are talking about the class keyword and the corresponding constructs associated with it. Consider an example :

 class Timer{ constructor(callback){   this.fn = callback; } start() {} stop() {} } Object.freeze(Timer.prototype); 

An object created using a class -based class keyword named ClassName will have a prototype ClassName.prototype . When creating an object based on a class, you need to use the keyword new :

 let timer= new Timer(); timer.__proto__ === Timer.prototype; 

Using classes does not make prototypes unchanged. If necessary, they will have to be “frozen” just as we have already done:

 Object.freeze(Timer.prototype); 

Prototype Inheritance


In JavaScript, objects inherit properties and methods from other objects. Constructor functions and classes are “syntactic sugar” for creating prototype objects containing all the necessary methods. With their use, new objects are created which are the heirs of the prototype, whose properties that are specific to a particular instance are established using the constructor function or using class mechanisms.

It would be nice if constructor functions and classes could automatically make prototypes unchanged.

The strength of prototype inheritance is memory savings. The fact is that a prototype is created only once, after which it is used by all objects created on its basis.

â–ŤThe problem of the lack of built-in encapsulation mechanisms


The prototype inheritance pattern does not use the separation of the properties of objects into private and public. All properties of objects are publicly available.

For example, the Object.keys() command returns an array containing all property keys of the object. It can be used to iterate through all the properties of an object:

 function logProperty(name){ console.log(name); //  console.log(obj[name]); //  } Object.keys(obj).forEach(logProperty); 

There is one pattern that mimics private properties, relying on the fact that developers will not refer to those properties whose names begin with an underscore ( _ ):

 class Timer{ constructor(callback){   this._fn = callback;   this._timerId = 0; } } 

Factory Functions


Encapsulated objects in JavaScript can be created using factory functions. It looks like this:

 function TodoStore(callback){   let fn = callback;     function start() {},   function stop() {}     return Object.freeze({      start,      stop   }); } 

Here, the fn variable is private. Only start() and stop() methods are publicly available. These methods cannot be modified from the outside. This keyword is not used here, so when using this method of creating objects, the problem of losing the this context is irrelevant.

The return command uses an object literal containing only functions. Moreover, these functions are declared in closure, they share a common state. To “freeze” the public API of an object, the command Object.freeze() already known to you is used.

Here, in the examples, we used the Timer object. In this material you can find its full implementation.

Results


In JavaScript, the values ​​of primitive types, ordinary objects and functions are perceived as objects. Objects have a dynamic nature, they can be used as associative arrays. Objects are the heirs of other objects. Constructor functions and classes are “syntactic sugar”; they allow you to create objects based on prototypes. For the organization of a single inheritance, you can use the Object.create() method, for the organization of multiple inheritance - Object.assign() . You can use factory functions to create encapsulated objects.

Dear readers! If you came to JavaScript from other languages, please tell us what you like or dislike in JS objects, in comparison with the implementation of objects in languages ​​you already know.

Source: https://habr.com/ru/post/420615/


All Articles