Both Product
and Food
at first sight appear like classic constructor functions. Let's look into where the instantiation starts ... new Food
utilizes Food
as constructor function, thus, at construction/instantiation time, the this
context of Food
already refers to a Food
instance. The latter gets passed with the non-instantiating Product.call
as thisArgs
into the call/invocation time of Product
. Thus there is now a Food
instance processed as this
context by Product
which does add/augment the properties name
and price
to the very Food
instance.
Something that looks like a constructor function (featuring a this
context) but is never instantiated but always explicitly applied either via call
or apply
to an object is one of the possible ways of writing a mixin. Actually the example shows one of the most classic purely function-based mixin patterns.
more ... (self promotion) ...
Note ... one needs to be aware that utilizing constructor-like functions as mixins does exclusively work for pure functions. True/real class (syntax based) constructor functions can not be apply
ed/call
ed, but only instantiated via the new
operator.
The logging of the beneath example does prove the above explanation ...
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
}
const item = new Food('cheese', 50);
console.log({ item });
console.log(
'(Object.getPrototypeOf(item) === Product.prototype) ?',
(Object.getPrototypeOf(item) === Product.prototype)
);
console.log(
'(Object.getPrototypeOf(item) === Food.prototype) ?',
(Object.getPrototypeOf(item) === Food.prototype)
);
console.log(
'(item instanceof Product) ?',
(item instanceof Product)
);
console.log(
'(item instanceof Food) ?',
(item instanceof Food)
);
console.log(
'(item instanceof Object) ?',
(item instanceof Object)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit:
OP's Q.
I get that this
inside Food()
refers to the new instance item
, but I'm not getting how is it that when you pass this
to Product()
, now the this.name
inside Product()
gets assigned to the passed this
? First of all, I thought call()
changes the object owner, but Product()
is not instantiated yet, so how can it have a new object owner?
A.
One should not get confused by the naming and the capital letter of Product
. One needs to think of it as a normal function with this
context, hence a method, still "free floating" for it itself is not assigned to an object (and never will be).
One even might rename it to assignProductFeatures
. With call
/ apply
(apparently the OP even took the example from the former) one does execute the function upon the very this
context which one had to pass as the method's first argument. Thus, for the given example, one does assign name
and price
to whatever object/instance was provided as this
context.
function assignProductFeatures(name, price) {
// formerly known as "wan't to be" `Product` constructor.
this.name = name;
this.price = price;
}
const myUnknownType = {
type: "unknown"
};
console.log({ myUnknownType });
assignProductFeatures.call(myUnknownType, 'cheese', 50);
console.log({ myUnknownType });
// ... deconstructing/refactoring the OP's original example code ...
// empty constructor
function Food() {}
// food factory
function createFood(name, price) {
// create `Food` instance.
const foodType = new Food;
// augment the newly created type.
assignProductFeatures.call(foodType, name, price);
// return the newly created augmented type.
return foodType;
}
const item = createFood('cheese', 50);
console.log({ item });
console.log(
'(item instanceof assignProductFeatures) ?',
(item instanceof assignProductFeatures)
);
console.log(
'(item instanceof Food) ?',
(item instanceof Food)
);
console.log(
'(item instanceof Object) ?',
(item instanceof Object)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit:
OP's Q.
For this.name = name
inside Product()
to be assigned to the passed object referenced by this
, then the Product()
function must be acting as a constructor to the item
instance, otherwise, how are the properties defined inside Product()
referenced by this
inside Food()
? call(this)
invokes Product()
with item
as its new object owner, but item
does not have any properties, and Food()
is not instantiating any object... I can't wrap my head around this!!!! I get what you're saying to me, but I can't understand the mechanics, and how it's all working!
From all your Qs ...
"the Product()
function must be acting as a constructor to the item instance"
... no, not at all, because of ...
"Call()
is supposed to change the object owner"
... exactly, but only temporarily exactly once at the delegated function's/method's call time ...
"... if Product() is not even instantiated?"
... there is no need for instantiation. Look into the example above where I try to make you think of Product
as an unbound method, which gets applied at time to whatever object by explicitly invoking call
/apply
, thus calling the former as a method within the applied context.