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 applyed/called, 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.