Passing a constructor to Array.map?
Asked Answered
R

4

21

How can i do something like this:

var a = [1,2,3,4];
a.map(Date.constructor);

This code throws an Error on Google V8:

SyntaxError: Unexpected number

I'am also tried:

a.map(Date.constructor, Date.prototype)

with the same result.

Remise answered 28/6, 2011 at 7:24 Comment(3)
That code should not result in a SyntaxError. (Even though the operation is questionable otherwise: if new is not used during the invocation then the constructor function is ... just a normal function.) Is that all the code? Can you create a minimal jsfiddle test-case? Does it have a SyntaxError in FF/IE?Adnate
There is a mistake in my code. The Date.constructor is the Function object constructor. The Date object constructor is Date.prototype.constructor or just Date.Remise
Found an answer. I will post it in a few hours.Remise
R
-4

The Date is a function, so the Date.constructor is a constructor of a function. Proper call of the Date object constructor looks like this:

Date.prototype.constructor();

Or just:

Date();

The problem here is to create an array of Date objects with time values from array a, but it is impossible to call the Date object constructor and pass an arguments to it without a new operator (ECMA-262 15.9.2).

But it is possible for any object constructors that can be called as a functions with the same result as if i use the new operator (for instance the Error object constructor (ECMA-262 15.11.1)).

$ var a = ['foo','bar','baz'];
$ a.map(Error);
> [ { stack: [Getter/Setter], arguments: undefined, type: undefined, message: 'foo' },
    { stack: [Getter/Setter], arguments: undefined, type: undefined, message: 'bar' },
    { stack: [Getter/Setter], arguments: undefined, type: undefined, message: 'baz' } ]
Remise answered 28/6, 2011 at 19:15 Comment(3)
that’s wrong, those are just objects, not instances of Error. and if you map .prototype.constructor instead, suddenly this isn’t defined when calling functions added to the prototype.Airship
@Antoniossss because the author accepted their own answer.Defendant
Right its authors answser.Clamatorial
A
8

I think what the OP was looking for is strictly analogous to this:

var nums = [1, 2, 3];
var strs = nums.map(String);
//=> ['1', '2', '3']; // array of strings

I assume the reason is that this is really elegant, both in simple type-casting operations like above, and in more interesting tasks like converting one representation of something into a different representation, like so:

function MyCoolObject(oldObject) {
    // generates new object by consuming old one
    // maybe attach some cool class methods via prototype
    return this;
}

var newList = oldList.map(MyCoolObj);
//=> array of MyCoolObj based on oldObject

The problem with this appears to be that the new object, when created by passing the constructor to Array.map, is an extended version of the window; that is, this within the constructor refers to the global scope, which sucks because (1) it wasn't your goal to hang props on window, and (2) the objects you create this way are not unique instances.

For what it's worth, the original type-casting example isn't all it's cracked-up to be, either, because:

strs[0] instanceof String
//=> false // UGH!

The only solution I've come up with so far requires writing the constructor differently -- which you obviously can't do for native types like Date:

function Human(animal) {
    var h = new Object();
    h.species = 'human';
    h.name = animal.name;
    return h;
}

var humans = animals.map(Human);

By defining the return value as a new object, we sever the connection between the global scope and this; at least, that's what I assume is going on here. (You could also return a JSON literal instead of invoking Object.)

If I want these objects to have an interesting prototype, I have to define it separately and then attach it explicitly:

// this object will be used as the prototype for new Human instances
var HumanProto = {
    species: 'human',
    speak: function() { console.log('My name is ' + this.name); },
    canDrink: function() { return this.age >= 21; }
}

// then, in Human, make this change
var h = new Object(HumanProto);

In this case, it's not just as good to return JSON, because there don't appear to be any effective ways to set the prototype of an object-literal; and even if you could, you never want this to be true:

myObject.hasOwnProperty('prototype');
//=> true // only if myObject = { prototype: HumanProto }

I think the best way to ensure the new object has the desired prototype is to pass the would-be prototype as the argument to new Object().

Is this pattern ideal? I don't know. It seems a little weird, since there are now two symbols associated with creating humans: Human the constructor function, and HumanProto the explicit prototype. More importantly, this seems like a real barrier if you've already got an ecosystem of fun custom classes that weren't written to be compatible with this pattern.

There is probably a better way out there. Maybe someone will post it.

Adsorb answered 12/5, 2015 at 1:40 Comment(1)
Another problem with my solution is that you can't say myHuman instanceof Human -- you have to compare it to HumanProto, which is counter-intuitive and exposes the smoke-and-mirrors implementation. GROSS.Adsorb
V
3

Is this what you're trying to do?

var a = [1, 2, 3, 4];
a.map(function(obj) { return new Date(obj); });
Vonnievonny answered 28/6, 2011 at 7:41 Comment(3)
Yes, but without anonymous function.Remise
Why? It's a straightforward, working solution and doesn't create any significant overhead.Vonnievonny
Because if I have an anonymous function which calls the constructor of object with its argument and returns a result, it is reasonable to call the constructor directly. In any case this is not possible in this case (see my answer)Remise
E
0

The map method just iterates an array using the callback function provided (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map). So:

var a = [1,2,3,4];
a.map(function(b) { return b+10; }); // [11, 12, 13, 14]

What are you trying to get from this:

Date.constructor(1);
Date.constructor(2);
Date.constructor(3);

Update from comment:

The problem here is to create an array of Date objects with time values from array a by passing the Date object constructor to a map function. Anyway there is a mistake in the code(see comment to pst) – set

I see, so something like:

var a = [1,2,3,4];
a.map(Date.prototype.constructor);
Emasculate answered 28/6, 2011 at 7:38 Comment(1)
The problem here is to create an array of Date objects with time values from array a by passing the Date object constructor to a map function. Anyway there is a mistake in the code(see comment to pst)Remise
R
-4

The Date is a function, so the Date.constructor is a constructor of a function. Proper call of the Date object constructor looks like this:

Date.prototype.constructor();

Or just:

Date();

The problem here is to create an array of Date objects with time values from array a, but it is impossible to call the Date object constructor and pass an arguments to it without a new operator (ECMA-262 15.9.2).

But it is possible for any object constructors that can be called as a functions with the same result as if i use the new operator (for instance the Error object constructor (ECMA-262 15.11.1)).

$ var a = ['foo','bar','baz'];
$ a.map(Error);
> [ { stack: [Getter/Setter], arguments: undefined, type: undefined, message: 'foo' },
    { stack: [Getter/Setter], arguments: undefined, type: undefined, message: 'bar' },
    { stack: [Getter/Setter], arguments: undefined, type: undefined, message: 'baz' } ]
Remise answered 28/6, 2011 at 19:15 Comment(3)
that’s wrong, those are just objects, not instances of Error. and if you map .prototype.constructor instead, suddenly this isn’t defined when calling functions added to the prototype.Airship
@Antoniossss because the author accepted their own answer.Defendant
Right its authors answser.Clamatorial

© 2022 - 2024 — McMap. All rights reserved.