简体   繁体   中英

can you use call or apply with new?

related to How can I call a javascript constructor using call or apply?

but not the same, I'm trying to apply the SO answers to John Resig's forcing a constructor when not called properly.

function User(first, last){ 
  if ( !(this instanceof User) )
    // the line I want to replace, or remove the redundancy from:
    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." );

The target line of code means every time the constructor changes, someone has to remember to change the internal self-call arguments. I've already had a project trip over this issue 3 times in the last 3 days.

All the examples in the linked question talk about passing the constructor , but what is the constructor in this case? It's not even finished being defined yet.

but so far all attempts do not pass the test, or throw a stackoverflow.

How do I make sure the constructor being called results in something that responds properly to instanceof User even when called without the new keyword, while eliminating the repetition of argument parameters?

Some options for you, all using Object.create :

Option 1:

function User(first, last){ 
  var rv;

  if ( !(this instanceof User) ) {
    // They called us without `new`: Create an object backed by `User.prototype`:
    rv = Object.create(User.prototype);

    // Now, call this function applying the arguments
    User.apply(rv, arguments);

    // Return the object
    return rv;
  }

  // Normal constructor stuff    
  this.name = first + " " + last; 
} 

Of course, all of that logic doesn't have to be repeated for every constructor function you create, you can use a helper function:

function constructWith(obj, ctor, args) {
    if (obj instanceof ctor) {
        return null;
    }
    obj = Object.create(ctor.prototype);
    ctor.apply(obj, args);
    return obj;
}

then

function User(first, last){ 
  var rv;

  if ((rv = constructWith(this, User, arguments)) != null) {
      return rv;
  }

  // Normal constructor stuff    
  this.name = first + " " + last; 
} 

Option 2: Don't use this much:

function User(first, last){ 
  var rv;

  if (this instanceof User) {
    // They (probably) used `new`, all is good, use `this`
    rv = this;
  } else {
    // They didn't use `new`, create an object backed by `User.prototype`
    rv = Object.create(User.prototype);
  }

  // ...use `rv`, not `this`, from here on

  rv.name = first + " " + last; 

  // This is important for the case where they didn't use `new`, and harmless
  // in the case where they did.
  return rv;
} 

As you can see, this is a lot simpler, but if you really like your syntax highlighting (seriously, I have a client to whom it really matters that this jumps out), etc...

And of course, you can wrap that up in a helper:

function useOrConstruct(obj, ctor) {
    return obj instanceof ctor ? obj : Object.create(ctor.prototype);
}

Then

function User(first, last){ 
  var rv = useOrConstruct(this, User);

  // ...use `rv`, not `this`, from here on

  rv.name = first + " " + last; 

  // This is important for the case where they didn't use `new`, and harmless
  // in the case where they did.
  return rv;
} 

Option 3: constructOMatic

Of course, if we're going to define helpers, maybe we should go whole-hog:

function User() {
    return constructOMatic(this, User, arguments, function(first, last) {
        this.name = first + " " + last;
    });
}

...where constructOMatic is:

function constructOMatic(obj, ctor, args, callback) {
    var rv;
    if (!(obj instanceof ctor)) {
        obj = Object.create(ctor.prototype);
    }
    rv = callback.apply(obj, args);
    return rv !== null && typeof rv === "object" ? rv : obj;
}

Now, you can use this to your heart's content within the callback. That fiddling with rv vs. obj in the return at the end is to emulate the behavior of new (the result of a new expression is the object created by the new operator unless the constructor function returns a non- null object reference, in which case that takes precedence).


Object.create is an ES5 feature found on all modern browsers, but the single-argument version of it used above can be shimmed for out-of-date browsers:

if (!Object.create) {
    Object.create = function(proto, props) {
        if (typeof props !== "undefined") {
            throw "The two-argument version of Object.create cannot be shimmed.";
        }
        function ctor() { }
        ctor.prototype = proto;
        return new ctor; // Yes, you really don't need () (but put them on if you prefer)
    };
}

Copy and paste are very easy and the code is clean. You need not to change it.

If you accept eval , you can do it like this:

function User(first, last){ 
  if ( !(this instanceof arguments.callee) ) {
    var name = arguments.callee.name;
    var param = [].map.call(arguments,function(e,i){return 'arguments['+i+']';});
    return eval('new '+name+'('+ param +')');
  }

  this.name = first + " " + last;
}

//test
var user1 = User("John", "Resig");
var user2 = new User("John", "Resig");

Without eval , you can do it like this:

function instantiate(C,a){
    switch(a.length){
        case 0: return new C();
        case 1: return new C(a[0]);
        case 2: return new C(a[0],a[1]);
        case 3: return new C(a[0],a[1],a[2]);
        case 4: return new C(a[0],a[1],a[2],a[3]);
        default : throw("too many arguments");
    }
}

function User(first, last){ 
  if ( !(this instanceof arguments.callee) ) {
    return instantiate(arguments.callee, arguments);
  }

  this.name = first + " " + last;
}


//test
var user1 = User("John", "Resig");
var user2 = new User("John", "Resig");

In ECMAScript 6, you can use the spread operator to apply a constructor with the new keyword to an array of arguments:

"use strict";
function User(first, last){ 
  if ( !(this instanceof User) ) {
    return new User(...arguments);
  }

  this.name = first + " " + last;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM