6

Update 2: This question is a mess, because I thought the ES6 class doesn't modify .protototype, which it does, and hence this is exactly what I wanted.

I accepted the broadest answer even if all the answers and comments should have pointed me to the right direction in the very beginning :)

Thank you all!

Old:

In old JS, pre ES6, when we learned about making "classes" with:

function X() {
    this.foo = function(){
    }
}; 
var x = new X();

we also knew that every time we do x = new X(); we get a copy of the 'foo' method, in every instance This was one of the reasons why using prototype was a good idea.

Now, in ES6 we have this new cool class syntax, but with the same 'issue', i.e. here the 'f' method is copied. How do we avoid it in a ES6 way? Or do we still need to use .prototype?

class X{
  f(){
    return 'f';
  }
}

console.clear();
let x1 = new X();
let x2 = new X();
x2.f = function() {return 'changed f';};

console.log(x1.f());//f
console.log(x2.f());//changed f

Update

I do understand we can still use .prototype. My question was more about using a more modern way to achieve what I wanted, i.e. not having copies of methods. I checked the very first tutorial about JS prototypes to find a citation, if my English is that bad :) - Found on https://hackernoon.com/prototypes-in-javascript-5bba2990e04b :

(...) i.e. every object created using the constructor function will have it’s own copy of properties and methods. It doesn’t make sense to have two instances of function fullName that do the same thing. Storing separate instances of function for each objects results into wastage of memory. We will see as we move forward how we can solve this issue.

And you also mentioned that class ... is only a syntactic sugar, but then why... with function FC below, we can see can see the "f" method directly in fc1, and

function FC() {
    this.v = 42;
    this.f = function(){
    }
}; 

var fc1 = new FC();

console.log('fc1, we can see the "f" directly in fc1: ', 
    Object.getOwnPropertyNames(fc1)
);


//////////////////////////////////

class X{
  constructor(){
    this.v  = 'bar';
  }
  f(){
    return 'f';
  }
}


let x1 = new X();

console.log('x1, we don\'t, because we have it in prototype:',
    Object.getOwnPropertyNames(x1)
);
konrados
  • 1,047
  • 8
  • 21
  • 6
    The method is not "copied". It's shared via the prototype chain. You code overwrites the property value on an instance of the class; why is it surprising that it works the way it does? – Pointy Apr 24 '18 at 02:47
  • 6
    your logic is flawed ... change `X.prototype.f` after creating x1 and x2 - you'll see it has changed for both. Pre-ES6, your code would behave the same, by the way. think of Class syntax as prototype sugar - transpile some `class`es in babel, and see how classes work in ES5 terms – Jaromanda X Apr 24 '18 at 02:48
  • 4
    In any case, the `class` syntax does not change the way prototype inheritance works in JavaScript. – Pointy Apr 24 '18 at 02:49
  • @JaromandaX - thanks, but yes, I do understand that I can change X.prototype to alter the method for both. That was my question - do I need to, or is there a more modern way, more ES6 way. And yes I did use babel before. I think I didn't express myself correctly :( I wanted to know if there is a better way :) Without .prototype, because I've never liked them :) – konrados Apr 24 '18 at 02:55
  • 2
    Please, explain your case further. What exactly are you trying to do? In one case you're just defining `f` method for all instances and in another case you're re-assigning it to particular instance. – Estus Flask Apr 24 '18 at 02:57
  • @Pointy - ok, but are you sure "It's shared via the prototype chain."? I... always thought otherwise. Please note, that when I do console.log(x1) - the 'f' function is directly in x1, not in __proto__ - https://codepen.io/anon/pen/JvGqOX – konrados Apr 24 '18 at 03:00
  • 2
    @konrados - equivalent code in ES5 behaves the same ... that was my point. – Jaromanda X Apr 24 '18 at 03:00
  • @estus - yes, it seems I failed at explaining what I really wanted :( I'll try to edit it. – konrados Apr 24 '18 at 03:03
  • @JaromandaX -- hmmm now I'm lost, I think I have a new question here... if `class` is just a syntactic sugar, why do I see a difference here: https://codepen.io/anon/pen/LmGovy i.e. with old syntax the `f` function is directly in the object, and with `class` it is in `prototype` o.O Ahhh! Sorry, by old equivalent, you all meant doing `object.prototype = 'something' `", Oh :( OK, nvm. – konrados Apr 24 '18 at 03:43
  • @Pointy ok, I think I understand now this: "The method is not "copied". It's shared via the prototype chain." - I thought that it is "copied" and hence my question. So... is this correct, that with `function X() {this.foo == `... the method is "copied", but when we use `class` it becomes part of prototype? Like here: https://codepen.io/anon/pen/LmGovy – konrados Apr 24 '18 at 03:52
  • No, assigning to `this.x` in the constructor does not affect the prototype. – Pointy Apr 24 '18 at 04:04

3 Answers3

6

JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

Read more on MDN

Your code excerpt translates to as follows:

function X() { }

X.prototype.f = function f() {
  return 'f';
};

let x1 = new X();

let x2 = new X();
x2.f = function() { return 'changed f'; };

console.log(x1.f()); // f
console.log(x2.f()); // changed f

Note the output remains unchanged as expected.

You can play with the Babel repl and check the transpiled code.

Carloluis
  • 4,205
  • 1
  • 20
  • 25
  • So, to clarify, the ES6 `class` does... what I wanted? I.e. it puts the methods into `prototype`, right? Like here - https://codepen.io/anon/pen/LmGovy - so in short ES6 `class` actually **is** the answer to my question, right? BTW, I updated it. Can you take a look again, please?:) – konrados Apr 24 '18 at 03:48
  • 1
    Yes, `ES6` class methods are defined within the object prototype. – Carloluis Apr 24 '18 at 04:23
  • 1
    In your updated code the `f` function is declared within the `FC` function returned object instead of the `FC` **prototype**. That is not equivalent with the later `f` function within the `X` class. – Carloluis Apr 24 '18 at 04:28
6

ES6 classes are syntactic sugar for established inheritance pattern that have been used in ES5.

ES6 classes use prototype methods by design. A method is shared among instances, so it's reasonable to define it once on prototype object and prototypically inherit it in instances. Prototypes are consistently optimized in modern engines and show performance benefits in some cases, so they can be preferred where appropriate.

My question was more about using a more modern way to achieve what I wanted, i.e. not having copies of methods.

Memory footprint can be reduced by reusing a function. This isn't a 'modern' way, just a way to address the issue:

function function f(){}
function FC() {
    this.v = 42;
    this.f = f;
};

A 'modern' way is to use prototype members.

FC function is not a direct counterpart to X class because the former assigns f on instance, not constructor prototype. A direct counterpart (with the exception of f descriptor, which is also non-enumerable in ES6 class) would be:

function FC() {
    this.v = 42;
}; 
FC.prototype.f = function(){};

class X{
  constructor(){
    this.v  = 'bar';
  }
  f(){}
}
// typeof X.prototype.f === 'function'

The reason why this style wasn't used consistently in ES5 is because this.f takes less characters to type and may be more readable than FC.prototype.f, while a developer may be unaware of the benefits and quirks of prototype.

The promotion of prototype members (methods and getters/setters) is one of few problems that class syntactic sugar addresses.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
3

ES6 classes are merely syntactical sugar over the existing prototype-based inheritance. You can still access the prototype of X:

class X{
  f(){
    return 'f';
  }
}

console.clear();
let x1 = new X();
let x2 = new X();
X.prototype.f = function() {return 'changed f';};

console.log(x1.f());//changed f
console.log(x2.f());//changed f
klugjo
  • 19,422
  • 8
  • 57
  • 75
  • 1
    "ES6 classes are merely syntactical sugar over the existing **prototype-based inheritance**" - yeah, the emphasis is what I missed since the beginning, thank you! – konrados Apr 24 '18 at 05:11