Using a new operator - from John Resig #36
This example code below is #36 in John Resig`s Learning Advnaced JavaScript. http://ejohn.org/apps/learn/#36
It is called We need to make sure the new operator is always used.
Six Questions - I would appreciate as much detail as you can provide
1) Is function User
ever actually called in this code? I note that when it says assert(user...)
, user is lower case. If the function gets called, how? does it get called when it asserts the variable user, which has a function call attached to it i.e. User("John," name)
2) If Im correct in assuming that function User is never called, is there a way that the code
this.name = first + " " + last;` is run?
3) If the function User is called, or if it were to be called, can 开发者_C百科you please explain the order of operations inside the function User. For example, it returns new User before it does this.name = first + " " + last;
how would that work if this function were called or is called?
4) in what way could !(this instanceof User)
if be true. since the function User is the object, wouldn`t "this" always be an instance of itself?
5) regarding the first assert i.e. assert(user, "this was defined correctly, even if it was by mistake")
, can you please explain how it was defined correctly, and, importantly, please explain how it was a mistake? How should it have been done so it`s not a mistake?
6) regarding the second assert, why is it a noteworthy that the right name was maintained? Isnt it as simple as variable
namehaving been assigned
Resig`. In what way might you have expected name to change?
function User(first, last){
if ( !(this instanceof User) )
return new User(first, last);
this.name = first + " " + last;
}
var name = "Resig";
var user = User("John", name);
assert( user, "This was defined correctly, even if it was by mistake." );
assert( name == "Resig", "The right name was maintained." );
This example is demonstrating a fundamental flaw in JavaScript's design, which is that calling functions intended to be used as constructors without the new
operator can cause unintentional modification of the global object.
User
is called on this line:var user = User("John", name);
. Lowercaseuser
holds a reference to the new uppercaseUser
instance.See #1.
If
User
is called withoutnew
, thenthis
will not be aninstanceof
User
. When this happens, we immediately callnew User(...)
. In this second call,this
will be aninstanceof
User
, so the conditional block is skipped and we simply continue on with the constructor, creating a new instance ofUser
as originally intended.When it's called without the
new
operator, the value ofthis
in a function simply refers to the global object, which iswindow
in a browser. This is a serious mistake in the design of JavaScript, for which this exercise is demonstrating a workaround.Because there is no explict return from the
User
constructor (without theinstanceof
check's block) , ifUser
were called withoutnew
it would returnundefined
and theassert
would fail. Theinstanceof
check prevents this.Without the protection of the
instanceof
check, callingUser
withoutnew
would cause unexpected results. Sincethis
would refer towindow
, the assignment tothis.name
would updatewindow.name
, not thename
property of a newUser
instance, and theassert
would fail.
Any given function object can be used two ways.
- As a function
- As a constructor
The difference being the value of the this
binding. When you use a function as a constructor the this
binding will be a new object that inherits from the constructor's public prototype. Normally calling a function will set the this
binding to the global object. (With strict mode in ES5 this will not happen.)
The second assert is very important now that you know that the this
binding can be coerced to the global object.
A Simplification of Calling a Constructor
function ConstructUser(fname, lname) {
var obj = {}; // Empty Object
// In reality a special internal Prototype property
// is assigned rather than 'obj.prototype'.
if (User.prototype instanceof Object) {
obj.prototype = User.prototype;
} else {
obj.prototype = Object.prototype;
}
// Now call the User function with the
// new object as the 'this' binding.
User.call(obj, fname, lname);
return obj;
}
1) Is function User ever actually called in this code? I note that when it says assert(user...), user is lower case. If the function gets called, how? does it get called when it asserts the variable user, which has a function call attached to it User("John," name)
Yes. It is first called with this
bound to the global object (see caveat below), and then it calls itself with a blank object whose prototype is User.prototype
bound to this
.
The first "call" invokes the builtin function call operator, and the second "call" invokes the builtin function construct operator. For user defined functions the two are the same, but the spec does distinguish between the two.
2) If I'm correct in assuming that function User is never called, is there a way that the code
this.name = first + " " + last;
is run?
Yes. The second invocation will reach that since in the second invocation this
is a User
.
3) If the function User is called, or if it were to be called, can you please explain the order of operations inside the function User. For example, it returns new User before it does this.name = first + " " + last; how would that work if this function were called or is called?
A call does not return until the body finishes. So the "call" invocation starts, which starts the "construct" invocation, which returns, yielding the new User
which is then returned by the first invocation.
4) in what way could !(this instanceof User) if be true. since the function User is the object, wouldn`t "this" always be an instance of itself?
No. If the if
statement were not there, User.call({}, "John", "Doe")
would cause User
to be "called" with this
bound to a blank object. Try running
var notAUser = {};
User.call(notAUser, "John", "Doe")
alert(JSON.stringify(notAUser));
and you should get
{ "name": "John Doe" }
5) regarding the first assert i.e. assert(user, "this was defined correctly, even if it was by mistake"), can you please explain how it was defined correctly, and, importantly, please explain how it was a mistake? How should it have been done so it`s not a mistake?
It is the author's opinion that not putting new
before User
was a programmer error.
6) regarding the second assert, why is it a noteworthy that the right name was maintained? Isnt it as simple as variablenamehaving been assignedResig`. In what way might you have expected name to change?
The global variable name
is a property of the global object. If this
is bound to the global object, then assigning this.name
would change the global variable.
Caveat:
For language-lawyers,
According to the language spec when you call a function as a function, not as a method, this
is bound to null
. Another part of the spec says that when you read this
, if it is null
, what you get back is the global object instead. This difference is significant in the case of EcmaScript 5 strict mode, because that changes the second part so that no funny stuff is done when reading this
. If this
is null, reading it will give you back the value null
.
EDIT:
The construct operator is only defined in the JavaScript spec. The new
operator in the JavaScript language is the way you invoke it.
For user defined functions, it works as follows:
- Code calls
new f(arg1, arg2)
- JS interpreter allocates space for a new object and sets its
prototype
tof.prototype
. - JS interpreter invokes
f
withthis
set to the new object and witharg1
andarg2
as the actual parameters. - If
f
returns an object, that is used as the result ofnew
instead. - Otherwise, the new object is used. If you return any non-object value, even a string or number, then the new object is the result of
new
.
1.the function User is called at var user = User("John", name);
2.pass
3.I didn't understand your question.
4.
user = User("a", "b"); would do that.
5.user = new User("a", "b"); is the correct one.
6.I don't know.
精彩评论