
It is time to mentally return four years ago to the blog “
Simple“ Class ”Instantiation ” from the blog of John Rezig, the famous creator of the unusually convenient jQuery library. And back.
However, since I don’t see it
at all in the search results on Habrahabr by the word “Resig”, then one has to think that this useful blog post nobody has bothered to translate (or at least retell) over the past four years - I will have to , to self-retell the Rezig blog post before I fulfill my main intention: to think out loud why the method of solving the problem indicated by him proposed by Rezyg has not become common. And retell. (This retelling would have been useful to the reader, even if I hadn’t added anything to him. I’ll add.)
On December 6, 2007, Rezig considered what happens when using the “new” operation in the javascript to create an object (in languages with classes we would say “class instance”):
')
function User(first, last){ this.name = first + " " + last; } var user = new User("John", "Resig");
Rezig fairly noted that for novice programmers in javascript it is not quite obvious that the appearance of “this” in the function code indicates that the object constructor is in front of us. (I add myself in parentheses: if the function is in the depths of a library, then this circumstance also needs documentation - and not that the library user will differ from the newbie not much: the source code with the function body is not read by everyone, especially since often used in a minified, not readable form.)
Therefore, Rezig reasoned, sooner or later
someone will try to call "
User () "
without " new " and so will get two unpleasant problems on their head at once.
First, the “
user ” variable will remain vague: the “
User () ” function is intended as a constructor, and it does not return any values.
Secondly, and even worse, attempts to refer
to “ this ” from within such (improperly caused) constructor will inevitably lead to clogging of the global namespace - and this is fraught with ominous and hardly perceptible consequences. Both problems John Rezig demonstrated by example:
var name = "Resig"; var user = User("John", name);
But nevertheless, Rezig pointed out further, the constructor call is useful. It has the advantage that prototype inheritance (obtaining the properties of an object from a prototype), that is, calling this constructor, is much faster than obtaining the same properties as an object “constructed” by calling a simple function:
Rezig made a natural conclusion from here that it would be nice to write such a function each time, which, on the one hand, could work as a designer (providing fast prototypical inheritance), and on the other hand, could be called
without “ new ” and in that case resort to yourself as a constructor. Using the example of the eigenfunction
$ () from the jQuery own library, Rezig reasonably shows: would it really be convenient if the users of the library had to write
“ new $ (" div ") " instead
of $ ("div") ? Of course not.Fortunately, Rezig continued,
all these problems lend themselves to a simple solution with the help of conditional recording of the function's body:
function User(first, last){ if ( this instanceof User ) {
The “
instanceof ” operator here serves as the primary means for detecting whether the
“ new ” operator was involved in the function call — and this is not difficult to show with a simple example:
function test(){ alert( this instanceof test ); } test();
Finding this solution and making sure it works, Rezig said: now let's wrap this solution in a generalized tool for creating “classes” constructors, which could be used whenever there is a need for such functions. For this purpose, John Rezig laid out this piece of free code:
To use this code, the previous example must be rewritten so that the body of the former constructor becomes the body of the
init method in the prototype:
var User = makeClass(); User.prototype.init = function(first, last){ this.name = first + " " + last; }; var user = User("John", "Resig"); user.name
The logic of the "
makeClass " John Rezig also explained in some detail. The function “
makeClass () ” is not a constructor, but creates a constructor — this constructor is the anonymous function
“ function (args) ” returned
from “ makeClass ”. Since the name of the “class” (the name that will be given to this function as a result) is not known in advance, at the time of execution it resorts to the javascript script property “
arguments.callee ”, it is from there that it takes its name. Another trick is that if this anonymous function is called
without “ new ”, then its arguments
(“ arguments ”) are re-passed into it when it calls itself as a constructor
(“ return new arguments.callee (arguments) ”) - and then it is this set of arguments that becomes the
args parameter and is passed
to the init method.
The retelling of John Rezig’s thoughtful blog post is over; now I can finally tell you about where he himself
seemed to be too smart.
The unpleasant element of his
“ makeClass ” idea is the use of the
“ arguments.callee ” property
. This property is considered problematic in relation to the performance acceleration in browsers (modern interpreters optimizations
for some reason are not able to cope with it), so the new version of the language (ECMAScript 5) even introduced the so-called “strict mode”, one of the nuances which is a complete rejection
of " arguments.callee ". (In May 2009, John Rezig himself
mentioned this and was
transferred to Habrahabr .)
It seems to me that this dislike of
“ arguments.callee ” in the community of authors of various javascripts and libraries eventually shifted in part to the very Resig idea of the self-invoking constructor - although this idea personally seems to me sound and useful, and the juxtaposition of
“ $ (" div ") " And" new $ ("div") " seems to me a weighty and convincing argument in favor of this idea.
Another cause of hostility towards the self-invoking constructor is,
apparently, the notion
of the “ new ” operator as an important part of the JavaScript language, the ignorance of which is so shameful that the errors caused by the absence of this operator are not necessary and prevented. (In relation to a computer programmer to a not-so-convenient tool, there is always something masochistic, and this feeling sometimes creates a burning dislike for beginners in need of help: “No, it would be too, too simple; I have sex - now and you have sex”. )
I saw it more than once.
I remember that in May of this (2011) year
in the JavaScript FAQ , compiled
azproduction , saying:
- Better, more familiar and ideological to create objects through new. Designers should be called with a capital letter.
- I prefer to base on agreements and do not check this inside the constructor - the constructor called without new and therefore leaked to the globals - it means “fool himself”. And in no case I do not encourage an error with new - some check if this is a global means the user has called a constructor without new and creates an object inside the constructor and returns it - this is an error promotion and an ideologically incorrect approach.
(End of quote.)
I also remember the case of Vladimir Agafonkin, the creator of the excellent Leaflet library for displaying maps,
mentioned more than once in Habrahabr. In August of the current (2011) year, a
request for a merger came to him on Github, the author of which offered to thrust such code at the beginning of each designer:
if ( !(this instanceof arguments.callee) ){ return new arguments.callee(arguments); }
Agafonkin answered this to him:
- Keeping novice JS authors from making mistakes is very useful, but I don’t like the idea of such a stupid language that allows incorrect syntax instead of telling the user that he was mistaken.
“Instead of creating an instance of an object even without a“ new ”, it seems to me that it would be better to do something like
throw new Error (" you forgot to put the new keyword before the class constructor. ") .
- And here's another thing: I read somewhere that
arguments.callee is now considered malicious, and it’s safer to write the class name explicitly.
(End of quote.)
The author of the request then went, read
about arguments.callee , and removed his request. It turns out that the shortcomings of
arguments.callee and respect
for new once again prevented the implementation of the self-invoking constructor.
Who among the Leaflet users will read at least the “
Quick Start Guide ” will probably notice that the global object defined by this library is called (obviously, for brevity)
simply “ L ” - and not “ Leaflet ”, for example. Six letters are saved. But after all it is possible, it would be possible to save four more characters if not to write down
" new " and a space before each call of the designer.
Sometimes I want to think that John Rezig would have acted far-sightedly, if he had abstained
from arguments.callee at all
, confining himself to just a vivid example (template, pattern) of the self-invoking constructor's record:
function User(first, last){ if ( this instanceof User ) {
But only, of course, in order not to create an extra
if- wrapper around the entire function, this example should be simplified:
function User(first, last){ if (!(this instanceof User)) return new User(first, last);
And we must pay tribute to the
JavaScript FAQ compiled by
azproduction : there this simplification is also given. It is simply not recommended there.
It is easy and pleasant to follow such a good example. He is also more comprehensible than the constructor constructor - including for optimizers javascript more understandable.
If you want to finally see a similar positive example from the life, then look
at the zip.js code from the package that provides
ZIP-archives unzipping
- and which is written in pure javascript under Node.js (without a single
C ++ line; I didn’t know that there are such cross-platform masterpieces!). There you will see exactly the same constructor callout:
var Reader = exports.Reader = function (data) { if (!(this instanceof Reader)) return new Reader(data); this._data = data; this._offset = 0; }
The conclusion is simple: study the works of John Rezig, follow his advice, act on his instructions. But only up to a certain limit of complexity.
Appendage. By the end of July 2012, Vladimir Agafonkin
( Mourner ) nevertheless
introduced into his Leaflet library the opportunity to do
without the operator “new”. But I must say that it was not I who convinced him in the end,
but forgotten , who composed and laid out
his own critical review of Leaflet on Habrahabr.