Built-in JavaScript classes - JavaScript
Bài đăng này đã không được cập nhật trong 7 năm
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__
?
- The
obj = {}
entry is a short form ofobj = new Object
, whereObject
is the built-in constructor function for objects. - 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-inObject.prototype
. - In the future, when you access
obj.toString()
, the function will be taken fromObject.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
- Methods in the prototype are automatically available everywhere, their challenge is simple and beautiful.
Disadvantages
- 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.
- 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 byObject.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.
All rights reserved