0

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.name)

I don't get this. How is the new object item instantiated from the Food function when it's not a constructor, but it only contains another function which is then invoked with the call() method that passes this which refers to the newly instantiated object? How is the Product function becoming the constructor of the item object? I don't understand how is that transfer happening, since Food is not a constructor, and Product.call() does not return the content of the Product function which would've made the Food function a constructor.

Can somebody explain this to me?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
happy_story
  • 1
  • 1
  • 7
  • 17
  • Steps list seems explaining this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new#description – vsemozhebuty Aug 01 '21 at 16:43
  • FYI `item instanceof Product` is `false`, whereas `item instanceof Food` is `true`. `Food` _is_ a constructor function. – Patrick Roberts Aug 01 '21 at 17:04
  • "*when it's not a constructor*" - what makes you think `Food` is not a constructor? – Bergi Aug 01 '21 at 18:32
  • "*How is the `Product` function becoming the constructor of the `item` object?*" - it is not. Try `console.log(item.constructor)` - `item` inherits from `Food.prototype`. – Bergi Aug 01 '21 at 18:34
  • @Bergi because nothing is assigned inside `Food`. There is just one function which is invoked, but I find it difficult to understand how does invoking the `Product()` function and passing to it the newly instantiated object whose constructor is.. supposedly `Food()`, makes the `this` of the `Food()` get assigned to the properties of `Product()`. It's hard to see the connection and how it all get's assigned. – happy_story Aug 01 '21 at 20:16
  • @PeterSeliger I did read your answer, and then asked a question about it. I specifically don't get how does passing the object instance `item` to `Product()` which is a non-instantiated constructor assigns the `this` to the `this` properties of the `Product()`. How do they get assigned to `this`. `Call()` is supposed to change the object owner, but how does `item` becomes the object owner of `Product()` if `Product()` is not even instantiated? And does `this.name = name` inside `Product()` means that `this` is the `item` instance? How does that reference happen? – happy_story Aug 01 '21 at 20:45
  • 1
    @IloveCoffee It doesn't really matter what `Food` does, it's invoked with `new`, that makes it a constructor. And `Food` *does* create properties on its `this` object - by invoking `Product` (as a function, not as a constructor) with its `this` value set to the same `this` value as in `Food`. – Bergi Aug 01 '21 at 23:25
  • `And Food does create properties on its this object - by invoking Product with its this value set to the same this value as in Food.` This is the part that I am trying to understand! HOW exactly is that assignment happening? `Product()` is invoked, but does not return anything to the `Food()` function, so how is `this` of `Food()` assigning inside `Food()` values from `Product()`? How does that reference happen? When I say `item.name` does the `Food()` constructor makes a reference to the `Product()` function every time? – happy_story Aug 02 '21 at 11:45
  • @IloveCoffee There is no magic. It behaves just like the `setProductProperties` in my answer. When you say `item.name`, it just accesses the `.name` property of the object, it doesn't matter where and how that property was created. – Bergi Aug 02 '21 at 12:27
  • @IloveCoffee ... 1/2 The first argument of `call` / `apply` is reserved for whatever one wants to get treated as the very function's / method's `this` context, one is going to invoke `call`/`apply` on. Of cause it makes sense for ***objects***, which are ***passed as references***. Thus, for the `Product` example, whatever reference on does provide as `this` context to the `Product` function, it will get assigned the two additional properties, ... – Peter Seliger Aug 02 '21 at 12:49
  • @IloveCoffee 2/2 ...because this is exactly what gets executed by invoking `Product()`. ***In words*** ...create and/or assign each a value to both the `name` and the `price` property of whatever is in this moment the function's `this` context, hence the reference which was provided via `call`/`apply`. Thus, if one writes `const myObj = {};` followed by `Product.call(myObj, 'foo', 'bar');` and then does `console.log(myObj);` one sees at the command line `{name: "foo", price: "bar"}`. *For how this exactly works with the `Food` constructor please read carefully again the 1st paragraph of my A.* – Peter Seliger Aug 02 '21 at 13:09

3 Answers3

1

When you call a function with the new keyword, it creates an object instance. Lets call it this. Inside the function, we can assign stuff to this and it will hence be properties of this.

Every function could essentially be used as a constructor, but if we don't assign anything to this, it wont have any properties. Therefore, in your case, we are using the Food function as a constructor (with the help of the new keyword) and instead of manually assigning stuff to this in the function, we call a different function with this as object we want the properties to be assigned to.

Yossi Sternlicht
  • 740
  • 1
  • 6
  • 17
  • You cannot assign to `this` directly - you can only assign to properties of `this`, thereby creating or updating them. – Bergi Aug 01 '21 at 18:36
  • So, when we invoke `Product.call(this)` we pass to the `Product()` function the just now instantiated object `item` which doesn't have any properties assigned to it yet, and then.. what happens when the `Product()` is invoked? How does passing to it `this` reference to the `item` object assigns the `this.name` to the `name` of the `Food()` function? Can you please try to sort of simplify it and illustrate it for me? – happy_story Aug 01 '21 at 20:20
  • 1
    @IloveCoffee There is no "*`.name` of the `Food()` function*". There's just a `.name` property of the object, and it doesn't matter where it was created - all properties are equal in JS, they don't "belong" to a certain class or something. – Bergi Aug 01 '21 at 23:26
1

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.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • ` 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.` this is the part that I am not getting. 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? – happy_story Aug 01 '21 at 20:25
  • 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! – happy_story Aug 01 '21 at 23:07
  • @IloveCoffee ... I updated my answer once again according to your still open questions. – Peter Seliger Aug 02 '21 at 08:14
  • @IloveCoffee ... and ... do you understand from the above example code what ... `assignProductFeatures.call(myUnknownType, 'cheese', 50);` ... actually does? This code demonstrates one of the simplest use cases of `call` / `apply`. – Peter Seliger Aug 02 '21 at 08:15
1

You might want to consider the following snippets:

const item = {
    name: 'cheese',
    price: 50,
};

is absolutely equivalent to

const item = {};
item.name = 'cheese';
item.price = 50;

is (apart from the prototype of item) equivalent to

function Food() {}
const item = Object.create(Food.prototype);
item.name = 'cheese';
item.price = 50;

is absolutely equivalent to

function Food() {}
const item = new Food();
item.name = 'cheese';
item.price = 50;

is absolutely equivalent to

function Food(name, price) {
    this.name = name;
    this.price = price;
}
const item = new Food('cheese', 50);

is absolutely equivalent to

function setProductProperties(obj, name, price) {
    obj.name = name;
    obj.price = price;
}
function Food(name, price) {
    setProductProperties(this, name, price); 
}
const item = new Food('cheese', 50);

is absolutely equivalent to the code in your question

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);

They all have the same outcome - an item object with properties .name and .price. The difference is only in how they achieve that, with varying levels of abstraction through function calls. Having constructors just makes it easier to instantiate multiple objects with the same shape, defining that shape only in a single place.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • So, I understand your example, but the thing I struggle to understand is how to look at the change of object ownership. I am looking at it from this perspective. Please look at this https://jsfiddle.net/c84nLqjz/ basic example of `call()` and change of object ownership. In the example, I am invoking the method `showName` with a new object, and now inside `showName`, the `this` refers not to `myObj2` but to `myObj1`, and the properties defined in it. Well, let's apply the same logic and principles to the above example. `Product()` is a method of `window` object, and I am invoking it with ... – happy_story Aug 02 '21 at 15:20
  • ...with a new object owner `item`, and therefore, `this` inside `product()` now doesn't refer to the `window`, but to the new object owner `item`, but `item` does not have any properties defined like `myObj1` has in the jsfiddle example. So this is where I'm mentally suck. Can you please try to somehow add clarity to this image? – happy_story Aug 02 '21 at 15:23
  • I get that my question is slightly different from the original one ,but they're still mostly related. Could any one of you just address it here in the comments? I try to avoid asking too many SO questions, because they'll ban my account again. – happy_story Aug 02 '21 at 18:27
  • @IloveCoffee ... 1/2 *"item does not have any properties "* ... because there is no need for that ... you got pointed several times to code similar to ... `function assignProductFeatures(name, price) { this.name = name; this.price = price;}` ... `const myUnknownType = { type: "unknown" };` ... `console.log({ myUnknownType });` ... with detailed information why the latter reference does feature in the end the two new properties. There is also the explanation that you share `this` contexts mostly as object references, which means that one does mutate/change the shared object itself ... – Peter Seliger Aug 02 '21 at 19:17
  • @IloveCoffee ... 2/2 ... in case of invoking a constructor function with the `new` operator one does create a new instance of such type, hence an object reference. From within this constructor one can do whatever one wants to do to this reference, including passing it around to other functions, either invoking it directly or delegating it as `thisArgs` via `call` / `apply`. Whatever gets done by such functions/methods to this reference will be immediately reflected within/by the constructor's `this`. – Peter Seliger Aug 02 '21 at 19:33
  • But you're focusing on something I'm not asking. You keep repeating something I already get, while glossing over the crux of my confusion. Did you saw the jsfiddle example? This is what I need explained. In that example, `myObj2.showName` is invoked with `myObj1` as its new object, and so, `this` inside `myObj2.showName` now refers to `myObj1`, which has properties, and that's how I am accessing them, but in the above example, I am invoking `window.product()` with `food` as its new object owner, but unlike `myObj1`, `food` does not contain properties! So what is referenced by `this`!? – happy_story Aug 02 '21 at 19:50
  • By the way, what did `{ expression }` mean? Sorry I keep forgetting. – happy_story Aug 02 '21 at 19:50
  • 1
    @IloveCoffee "*I invoke `Product` with a new object owner `item`, but `item` does not have any properties defined like `myObj1` has in the jsfiddle example.*" - yes. So? `item` *is* an empty object when it is being passed to `Product`. Then, `Product` **creates** the properties on the object by assigning to them. Remember that objects can change shape arbitrarily in JS! – Bergi Aug 02 '21 at 19:58
  • I think I understand everything now. The way the new object instance is passed, and properties assigned to it in the `product()` function is what confused me. Really thanks a lot to both of you. I really appreciate all your help. I think we can all agree this example isn't the best way to teach someone how does `call()` work. Just one of the many reasons I dislike MDN. – happy_story Aug 02 '21 at 22:16