Built-in JavaScript classes - JavaScript

Built-in JavaScript classes - JavaScript

JavaScript has built-in objects: Date, Array, Object and others. They use prototypes and demonstrate the organization of "pseudo-classes" in JavaScript, which we can fully apply for ourselves.

Where does the method of {}?

We start by creating an empty object and outputting it.

var obj = {};
alert( obj ); // "[object Object]" ?

Where is the code that generates the string representation for alert (obj)? The object is empty.

Object.prototype

...Of course, this was done by the toString method, which is ... Of course, not in the object itself (it's empty), but in it's prototype obj .__ proto__, you can even derive it:

alert( {}.__proto__.toString ); // function toString

Where does the new object obj get this __proto__?

  1. The obj = {} entry is a short form of obj = new Object, where Object is the built-in constructor function for objects.
  2. When the new Object is executed, the object being created is put __proto__ by the prototype of the constructor, which in this case is equal to the built-in Object.prototype.
  3. In the future, when you access obj.toString(), the function will be taken from Object.prototype.

This can be easily verified:

var obj = {};

// The method is taken from the prototype?
alert( obj.toString == Object.prototype.toString ); // true, yes

// Check whether it's true that __proto__ is Object.prototype?
alert( obj.__proto__ == Object.prototype ); // true

// Is __proto__ from Object.prototype?
alert( obj.__proto__.__proto__ ); // null, no

Built-in JavaScript classes

The exact same approach is used in Array arrays, Function functions and other objects. The built-in methods for them are in Array.prototype, Function.prototype, and so on.

For example, when we create an array, [1, 2, 3], then this is an alternative variant of the new Array syntax, so that the arrays have a standard prototype Array.prototype.

But in it there are methods only for arrays, and for common methods of all objects there is an Array.prototype .__ proto__ link equal to Object.prototype.

Similarly, for functions.

Only for numbers (as well as for other primitives) everything is a little different, but more on this later.

The Object.prototype object is the vertex of the hierarchy, the only one whose __proto__ is null.

Therefore, they say that "all objects inherit from Object", and more precisely, from Object.prototype.

A "pseudo-class" or, more briefly, a "class", is called a constructor function together with its prototype. This way of declaring classes is called a "prototype OOP style".

When inheriting, some methods are overridden, for example, Array has its own toString, which prints the elements of the array separated by commas:

var arr = [1, 2, 3]
alert( arr ); // 1,2,3 <-- result of Array.prototype.toString

As we saw earlier, Object.prototype has its own toString, but since in Array.prototype it is searched first, then the option for arrays is taken:

Calling methods via call and apply from a prototype

Earlier we talked about the application of array methods to "pseudo-massives", for example, you can use [].join for arguments:

function showList() {
  alert( [].join.call(arguments, " - ") );
}

showList("Jack", "Steve", "Mary"); // Jack - Steve - Mary

Since the join method is in Array.prototype, you can call it directly from there, like so:

function showList() {
  alert( Array.prototype.join.call(arguments, " - ") );
}

showList("Jack", "Steve", "Mary"); // Jack - Steve - Mary

This is more efficient because the extra object of the array [] is not created, although, on the other hand, more letters are written.

Primitives

Primitives are not objects, but methods are taken from the corresponding prototypes: Number.prototype, Boolean.prototype, String.prototype.

By standard, if you access the property of a number, a string, or a boolean value, an object of the appropriate type will be created, for example, new String for a string, new Number for numbers, new Boolean for booleans.

Next, an operation will be performed with a property or a method call using normal rules, with a search in the prototype, and then this object will be destroyed.

This is how the code works below:

var user = "Jack"; // created string (primitive)

alert( user.toUpperCase() ); // JACK

// a temporary object was created new String
// called method
// new String destroyed, result returned

You can even try to write a property into this temporary object:

// Try to write the property into a string:
var user = "Jack";
user.age = 30;

alert( user.age ); // undefined

The age property was written into a temporary object, which was immediately destroyed, so there is little point in such a record.

Constructors String / Number / Boolean - for internal use only

Technically, you can create objects for primitives manually, for example, the new Number. But in a number of cases, frankly delusional behavior will turn out. For example:

alert( typeof 1 ); // "number"

alert( typeof new Number(1) ); // "object" ?!?

Or, even stranger:

var zero = new Number(0);

if (zero) { // object - true, alert was done
  alert( "zero -- true?!?" );
}

Therefore, explicitly new String, new Number, and new Boolean are never called.

The values null and undefined do not have properties

The values null and undefined are stand alone. The above does not apply to them.

For them there are no corresponding classes, they can not write a property (there will be an error), in general, at the contest "the most primitive value" they would exactly divide the first place.

Changing the built-in prototypes

Built-in prototypes can be changed. Including - to add their own methods.

We can write a method for repeated repetition of a string, and it will immediately become available for all strings:

String.prototype.repeat = function(times) {
  return new Array(times + 1).join(this);
};

alert( "la".repeat(3) ); // lalala

Similarly, we could create an Object.prototype.each(func) method that will apply func to each property:

Object.prototype.each = function(f) {
  for (var prop in this) {
    var value = this[prop];
    f.call(value, prop, value); // called f(prop, value), this=value
  }
}

// Try! (Attention, so far this is not working!)
var user = {
  name: 'Jack,
  age: 25
};

user.each(function(prop, val) {
  alert( prop ); // name -> age -> (!) each
});

Note - the example above does not work correctly. Together with the properties of the user object, it prints out our each property. Technically, this is correct, since for..in loop iterates through the properties in the prototype too, but not very convenient.

Of course, it's easy to fix it by adding a hasOwnProperty check:

Object.prototype.each = function(f) {

  for (var prop in this) {

    // Skip properties from the prototype
    if (!this.hasOwnProperty(prop)) continue;

    var value = this[prop];
    f.call(value, prop, value);

  }

};

// Now everything will be all right
var obj = {
  name: 'Jack',
  age: 25
};

obj.each(function(prop, val) {
  alert( prop ); // name -> age
});

Here it worked, now the code works correctly. But we do not want to add hasOwnProperty to the loop on any object! Therefore, either do not add properties to Object.prototype, or you can use the property descriptor and the enumerable flag.

This, of course, will not work in IE8-:

Object.prototype.each = function(f) {

  for (var prop in this) {
    var value = this[prop];
    f.call(value, prop, value);
  }

};

// Correct the property declaration by setting the flag enumerable: false
Object.defineProperty(Object.prototype, 'each', {
  enumerable: false
});

// Now everything will be all right
var obj = {
  name: 'Jack',
  age: 25
};

obj.each(function(prop, val) {
  alert( prop ); // name -> age
});

There are several "for" and "against" modifications of the built-in prototypes:

Advantages

  1. Methods in the prototype are automatically available everywhere, their challenge is simple and beautiful.

Disadvantages

  1. New properties added to the prototype from different places can conflict with each other. Imagine that you have connected two libraries that added the same property to the prototype, but defined it differently. Conflict is inevitable.
  2. Changes in built-in prototypes affect globally, all-all scripts, do not do them well from an architectural point of view.

As a rule, the cons are weightier, but there is one exception when changes to the built-in prototypes are not only allowed but also welcomed.

It is possible to change the prototype of built-in objects, which adds support for the method from modern standards to those browsers where it does not exist yet.

For example, add Object.create(proto) to older browsers:

This is how the es5-shim library works, which provides many functions of modern JavaScript for older browsers. They are added to the built-in objects and their prototypes.

Total

  • The methods of embedded objects are stored in their prototypes.
  • Embedded prototypes can be extended or changed.
  • Adding methods to Object.prototype, if it is not accompanied by Object.defineProperty with the enumerable setting (IE9 +), will "break" the for..in loops, so try not to add methods to this prototype.

Other prototypes are less dangerous to change, but still not recommended to avoid conflicts.

Separately, there is a change to add modern methods to old browsers, such as Object.create, Object.keys, Function.prototype.bind, etc. This is acceptable and is just doing es5-shim.