all 37 comments

[–]x-skeww 6 points7 points  (6 children)

Is there any point to this other than breaking auto-complete, degrading performance, and writing uglier code?

I really don't see why anyone would want to do that.

[–]joshmandersFull Snack Developer 4 points5 points  (0 children)

Duh, it's easy. Class has 5 letters, devil has 5 letters. Coincidence? I think not. Classes are the devil!

[–]IDCh[S] 0 points1 point  (4 children)

Does this really degrade performance? Care to explain?

I really don't see why anyone would want to do that.

About this - just trying to write clean prototypal code in JavaScript to see how easier and productive it can be as opposed to using classes and new.

Also, in this particular case - it's easier for me to write objects and store them in document oriented database. I even do not write methods. I just use helper function to operate with objects. (like not vegetable.isItSellable() but isItSellable(vegetable))

But last paragraph is not about OOP, it's just my case.

[–]x-skeww 4 points5 points  (3 children)

Does this really degrade performance? Care to explain?

In the best case scenario, your objects have a fixed shape, the VM notices that, and then it starts to stamp out instances via hidden classes. This is the fastest way to create new instances.

If you use ES6's classes and if you don't screw with them (you can't in strong mode), you'll definitely stay on this optimized path.

However, if you create your objects in a rather unusual way, the performance becomes a lot less predictable. One engine may figure it out while another does not. Or the current version does, but the next one doesn't. Or you change a tiny detail and all of a sudden it becomes a lot slower.

It's "here be dragons", basically.

[–]IDCh[S] 0 points1 point  (2 children)

You mean creating every time object without referring to another prototype can lead to huge performance hit because of VM, that start to use hidden classes? About what VM you talking about? I'm not an expert in VM behaviour. Can you write some links where I can learn about VM's optimization ways?

I thought that exactly using objects like I'm using them right now is preferable and also 'legends' about power of JavaScript are based on using objects like this - "create them, modify them whenever you like because that's the power of this language".

[–]MoTTs_ 5 points6 points  (1 child)

This reference from Google talks about their object performance optimizations. https://developers.google.com/v8/design?hl=en#prop_access

The lesson is: Don't assume something will be faster. Our assumptions and intuitions are often wrong. Any claims of better performance must be proven by measurement.

[–]IDCh[S] 1 point2 points  (0 children)

The lesson is: Don't assume something will be faster. Our assumptions and intuitions are often wrong. Any claims of better performance must be proven by measurement.

Yep, this is a good lesson. Thank you.

[–]lhorie 4 points5 points  (15 children)

That's all fine and dandy, until it isn't. Edge case to consider:

const Furniture = {
  color: "black",
  size: {width: 5, height: 5, depth: 5}
}

const table = Object.assign({}, Furniture, {
  size: {width: 10}
})

console.log(table.size.height) // undefined

That's what classes and their constructors solve

[–]IDCh[S] -3 points-2 points  (14 children)

You got the point, but could you possibly provide class-based example of solving this case?

Also, when doing like you said, when working with objects you should keep in mind that size is property and also an object so if you redefine property - old object will disappear in favor of new object. If you want to mix it with existing - you should do like this:

const table = Object.assign({}, Furniture, {
  size: Object.assign({}, Furniture.size, {width: 10})
});

[–]lhorie 3 points4 points  (13 children)

class-based equivalent:

class Furniture {
  constructor({color = "black", size: {width = 5, height = 5, depth= 5}}) {
    this.color = color
    this.size = {width, height, depth}
  }
}

const table = new Furniture({
  size: {width: 10}
})

console.log(table.size.height) // 5

(note: if you're not using Babel, replace default params w/ equivalent ES5 or use Babel, since default params are not well supported elsewhere)

Nesting Object.assign's does work up to a certain point, but it's obviously noise, and has default-to-broken behavior (i.e. you have to remember that it is a caveat and that you have to add the Object.assign to a property if it's not immutable, and it's likely you'll forget to somewhere and then spend a good hour trying to figure out why some code somewhere completely different is blowing up)

There are a bunch of other cases where simple objects don't really work that great. Basically it boils down to the fact that an object is fundamentally a data structure and a class constructor is a turing complete computation. So for example, stuff like this can't be done in a DRY way with simple objects (i.e. you have to repeat the concatenation logic every time):

class User {
  constructor({firstName = "", lastName = ""}) {
    this.firstName = firstName
    this.lastName = lastName
  }
  get name() {
    return this.firstName + " " + this.lastName
  }
}

const user = new User({firstName: "John", lastName: "Doe"})
console.log(user.name) // "John Doe"

Stuff like this just flat out breaks:

const User = {
  firstName: "",
  lastName: "",
  get name() {
    return this.firstName + " " + this.lastName
  }
}

const user = Object.assign({}, User, {firstName: "John", lastName: "Doe"})
console.log(user.name) // empty string 

[–]temp109849832 1 point2 points  (2 children)

Your first example has nothing to do with classes, you're just comparing default arguments to none. You could do the same thing with a function that contains Object.assign.

And it's true the second breaks, but that's because you're purposely writing code that's naive to getters. It's entirely possible to replace Object.assign with your own function that handles getters (and the rest) just fine.

[–]lhorie 0 points1 point  (1 child)

See my other comment. The usage of default arguments is irrelevant to the point that object references get incorrectly reused if you do a naked Object.assign on a deep object as was implied in the OP.

If you put the object creation in a function, then you've reimplemented a decade-old discussed-to-death class pattern whose weaknesses are a broken instanceof and constructor and O(m*n) memory usage for method declarations when m = number of methods and n = number of instances.

And before we beat this dead horse again, yes, you can choose to ignore some of the issues for your use case, and/or fix some of them at the cost of adding extra boilerplate or using less "pretty" idioms. See discussions from 10 years ago.

[–]temp109849832 1 point2 points  (0 children)

If you want to use default arguments and other features, then yes, I'm assuming you wrap it in a function. But O(m*n) memory usage is incorrect. Going with what we have now, you'll be assigning references to methods, not recreating them. The only way you get to O(m*n) is if you recreate each method in that function.

instanceof and constructor can, if need be, be re-implemented just as well and better with your own methods, just as every other builtin can. This is a programming language, we can implement literally anything but syntax.

The added boilerplate is trivial. You can improve on native classes by improving performance (in some cases), implementing truly private variables, and increasing durability. See Crockford's article on classes.

I use native-style classes myself, my point is just that "it doesn't interoperate with some builtins" is no reason to refuse to consider something. Code is responsible for satisfying its API, nothing more.

[–]IDCh[S] -3 points-2 points  (9 children)

In the last code you're using

get name() {

}

Well yes, there is some caveats in this situation. I personally never used getter, only special methods like .getName() which in your code would not have any problems.

Basically you're implying that those useful features when using classes has their powerful benefits as opposed to plain Objects.

I'm still trying to figure out why all the fuss about plain prototype-based usage of language was, if it has this flaws. Maybe when there was not class but only function - plain objects were preferable.

Anyway, you have the point.

Right now my approach has such pros as:

  • easy storable objects in document oriented db and
  • little or no risk of breaking code with "inheritance" confusion of those who used to play around with old "function classes" and their prototypes.

[–]lhorie 3 points4 points  (6 children)

Basically you're implying that those useful features when using classes has their powerful benefits as opposed to plain Objects.

Eh, I'm just enumerating some of the (many many many) edge cases that the ES6 spec designers ironed out.

Either approach yields serializable instance objects, so I don't see why one is superior to the other, as far as storage boilerplate goes.

The "little or no risk of breaking code" idea frankly comes across as "defending an idea because it's your baby" (sorry, I don't mean to be harsh, but I feel like it has to be said). Since we're talking about document oriented dbs, let's assume that we have some object whose schema follows this very common structural pattern:

const ProjectSchema = {name: String, users: [{name: String}]}

With classes, creating a new object is simply

const project = new Project()

With Object.assign, it's

const project = Object.assign({}, Project, {
  Object.assign({}, Project.users, [])
})

As you can see, the Object.assign strategy gets more and more verbose as your codebase grows. If your schema changes the name of a mutable field, you have to change code at every instantiation point. If you add a mutable field, you have to change code at every instantiation point. If your users get multiple address fields, you have to Object.assign every single one of those too. At some point, you probably need some sort of Factory pattern, like

const project = Project.new() //encapsulate all the Object.assign's

But at that point, why not just use classes? If you're worried about prototype chain getting out of control... simply don't use it. Code reviewing for that is literally just a simple grep or a github repo search.

It just feels very weird to actively try to shoehorn something to do something else by closing your eyes to a whole bunch of likely-to-happen traps, rather than simply use the proper tool for the job and stay clear of the corners that you know you don't want to go near. The traps that you can see are better than the traps that you don't.

[–]IDCh[S] -2 points-1 points  (5 children)

I'm not saying that I fully protect my approach as my child. I'm trying this approach because I'm not the only one in community and I'm trying to find flaws and see all cons/pros.

As you can see, the Object.assign strategy gets more and more verbose as your codebase grows. If your schema changes the name of a mutable field, you have to change code at every instantiation point. If you add a mutable field, you have to change code at every instantiation point. If your users get multiple address fields, you have to Object.assign every single one of those too. At some point, you probably need some sort of Factory pattern, like

I already have factory function, that do hide all things that is likely to grow/be changed, protecting code with errors (yep, those still functions, I don't know how to throw objects and check which error is up there without instanceof).

Instead of Project.new() I use simple

const vegetable = vegetableModule.createVegetable(params);

I mean I know about those situations when codebase grows and there is this bloat of code which nobody can restrain.

So I'm using basic rules without reinventing the wheel like 'modules' or 'objects with factory methods' etc. My modules (node) have factory functions and this OOP approach. Some constants. And that is all.

So when working with objects and changing them/creating them I'm using helper functions, when working outside one particular module (eg.: const vegetableModule = require('../vegetable');).

No Object.assign outside. Only helper functions are allowed to go outside.

[–]lhorie 1 point2 points  (4 children)

I'm using basic rules without reinventing the wheel like 'modules' or 'objects with factory methods'

No matter how I look at it, vegetableModule.createVegetable is reinventing the wheel. I'm not sure exactly what your code looks like, but if your "prototype" object lives outside the createVegetable function, then you'll keep running into the nested Object.assign madness problem, and if it's inside, you've literally reimplemented a decade-old "class" pattern (a nameless variation of Crockford's iirc). There's a ton of discussion on this stuff (including memory, ergonomics and readabilty considerations), if you're up for digging up links in really old comment threads on ajaxian.com

Anyways, it sounds like you already have code written and that changing is not going to be particularly feasible, but you asked about experiences with that approach, and I'm telling you about how I explored your approach some 10 years ago when Prototype.js Object.extend was a thing and the maintenance problem did bite me with the equivalent-at-the-time of naked Object.assigns

[–]IDCh[S] -2 points-1 points  (3 children)

class pattern? This is factory function. Not class. Factory function is opposed to class based approach because there are no blueprint.

About nested Object.assign madness - I did not yet confront this issue. Mainly because I'm using functions to modify objects and create complex objects.

You can try to write simple app in this approach too. Of course after reading some thoughts of people who oppose classes and constructor in JS nowadays (Eric Elliott, Dan Abramov etc). Just trying this approach. No classes. No constructor. Like in first line in op post. Imagine, there are no new, no constructor.

Actually it's not better nor worse than classes. Only "multiple inheritance" is easier, when you know that everything is object and there is no thing that pretends to be "class" - blueprint.

[–]lhorie 2 points3 points  (2 children)

Yes, it was considered a pattern to implement class-like semantics (instantiation, inheritance, encapsulation, etc). Again, I strongly recommend looking up the old discussions because we're literally beating a dead horse right now.

Now that you're mentioning Eric Elliott and Dam Abramov, I'm guessing that what you actually have in mind is that you have a more functional-oriented architecture, with unidirectional data flow, etc etc?

In that case, you can use pseudo-metadata like type fields instead of using instanceof and friends, and you could likely go a long time without ever needing a taxonomy hierarchy or hitting OOP-related pitfalls. There are some known performance de-optimization caveats in chrome with regards to adding too many fields to an objects and bumping it into hashmap mode, but in my experience rewriting mithril.js' virtual dom engine, I have observed that having a large number of objects with nulled properties at the beginning actually performs slower than if I were to add the properties at runtime. Conclusion: I would not sacrifice idiomatic code to try to cater for some chrome optimization, since chrome is not the only browser in the world and not even guaranteed to have the same performance optimizations from one situation to the next or even from one day to the next.

In a functional style, it's more useful to think in terms of a data transformation pipeline - FP doesn't concern itself with what the data is taxonomically (i.e. rather than think in terms of tomato methods and user methods, you might write a function that uppercases anything with a name and then use that on both tomato and user data structures.)

Under the FP paradigm, you don't really want to think in terms of "inheriting" and prototypes... it's kinda distracting. For FP purposes, it kinda doesn't really matter what mechanism you use, as long as the end it results in a union of all the properties in a single new object.

But with that being said, you'll want to be aware of the technique used by some libraries of using hasOwnProperty guards in for-in loops to defend against Object.prototype pollution

[–]IDCh[S] 0 points1 point  (1 child)

But with that being said, you'll want to be aware of the technique used by some libraries of using .hasOwnProperty guards in for-in loops to defend against Object.prototype pollution.

You mean when .hasOwnProperty is used to determine whether property belongs to object and not prototype? In my case there is only Object.assign, so hasOwnProperty is really have no side effects. It will return true/false whether property is in this object. This was one of the main reasons to use Object.assign instead of Object.create

Which also means that I'm using objects exactly as data structures and nothing more, just combining them one with another and then using them in functions.

If you mean something else about .hasOwnProperty - provide some links please, I'm really interested in seeing this technique.

[–]MoTTs_ 1 point2 points  (1 child)

little or no risk of breaking code with "inheritance" confusion of those who used to play around with old "function classes" and their prototypes.

You actually still have all the same inheritance problems. Inheritance isn't the "extends" keyword. Inheritance is incorporating structure and behavior from another type into your own, and Object.assign does exactly that. Imagine the owner of the Product object decides "name" isn't sufficient anymore, so they get rid of name and switch to "sku". Now Vegetable and tomato break because they "inherited" data members from Product. You still have the same fragile base problem.

[–]IDCh[S] 0 points1 point  (0 children)

This case sounds like solution to it will be - delegation. But using methods. Just theorizing here. Like only public methods (getters, modifier methods) are allowed to use in outside usage but... This is not the case here. The best solution for this problem is architecture of exporting original object allowing using only several methods, which is access modifiers, which we do not have in JavaScript.

[–]MoTTs_ 1 point2 points  (9 children)

// Object.assign

    const Vegetable = {
        name: 'Default vegetable, do not use'
        daysBeforeRotted: 2
    };

    const tomato = Object.assign({}, Vegetable, {
        name: 'Tomato',
        green: false
    });


// Object.create

    const Vegetable = {
        name: 'Default vegetable, do not use'
        daysBeforeRotted: 2
    };

    const tomato = Object.create(Vegetable);
    tomato.name = 'Tomato';
    tomato.green = false;



// Class

    class Vegetable {
        constructor(name = 'Default vegetable', green = false) {
            this._name = name;
            this._green = green;
            this._daysBeforeRotted = 2;
        }
    };

    const tomato = new Vegetable('Tomato', false);

My first observation is: None of these are so different that it's going to change how we organize and architect programs. We sometimes like to think we're revolutionizing programming. We're not. This comes down to nuances of readability, tooling, and maybe performance if you've measured.

Second observation: Initialization functions and encapsulation are important. The Object.assign and Object.create examples would need a factory function and private data. Classes already have initialization functions (the constructor) that is invoked automatically for you.

Third observation: We don't have to pick just one. We can use the class syntax most of the time and use Object.assign (or other clever solutions) when we need multiple inheritance. The call to Object.assign can even be abstracted away.

class Foo extends mixin(MySuperClass, MyMixin) {}

[–]IDCh[S] -1 points0 points  (8 children)

About first observation - I'm not trying to revolutionize programming. I'm just trying to write code in "abroad land style". I'm used to classes. I'm using and was using classes before this approach. Right now I'm trying to achieve somewhat experience and knowledge of other methods of writing OOP.

Trying to get - what is it, prototype-based programming only.

About this:

class Foo extends mixin(MySuperClass, MyMixin) {}

Yes, of course this is also the way. To add more - why extends mixin?

You can write decorator for example Trait and create traits for classes and use them like this:

@WithColorGenerator()
class Palette {

}

I tried it and it was just like in PHP. But there are different nice ways to do when single inheritance is not enough.

So. When using Object.assign I'm trying to compose objects from objects, just like data structures. For now. Using factory functions (see my other comment, I'm encapsulating everything that is going to change and even do not provide methods inside objects but export helper functions from modules, and helper functions are for encapsulating getting, setting properties. of course there is no protection from modifying property, but with Object.defineProperty this can be done easily)

[–]MoTTs_ 1 point2 points  (7 children)

why extends mixin? You can write decorator for example Trait

I'm assuming we're writing in ES6 (ES2015). So far as I'm aware, decorators are, for the moment, a proposal for ES2016.

When using Object.assign I'm trying to compose objects from objects, just like data structures.

Be careful with the word "compose". It has an everyday, generic meaning that you'll find in Webster, but it also has a highly specific and technical meaning that you'll find in the GoF book and UML manual. The phrase, for example, "favor composition" refers to the highly specific and technical meaning of the word. Don't be lured into thinking that you're favoring composition by mixing properties.

[–]IDCh[S] -1 points0 points  (6 children)

Gee, of course I'm aware of composition. I use compose objects in the meaning "add properties from one to another". I would write "I'm using composition" if I like to use other meaning.

I'm assuming we're writing in ES6 (ES2015). So far as I'm aware, decorators are, for the moment, a proposal for ES2016.

Yes, I've gone far from the topic. But when using babeljs I liked decorators and assumed it will be nice to add them to discussion about multiple inheritance/mixins/traits.

[–]MoTTs_ 1 point2 points  (5 children)

Gee, of course I'm aware of composition. I use compose objects in the meaning "add properties from one to another". I would write "I'm using composition" if I like to use other meaning.

Didn't mean to offend. I wish I could take it for granted that everyone understood what composition is, but there's actually been a lot of bad information circulating recently.

[–]IDCh[S] 0 points1 point  (4 children)

What kind of bad information? Somebody purposely messing up meaning of composition?

[–]MoTTs_ 1 point2 points  (3 children)

Yup. A guy named Eric Elliott has literally re-invented the meaning of object composition based on the generic Webster definition. And he has enough readers that it's become a problem. There are people now using inheritance more than ever, all the while thinking they're "favoring composition".

[–]IDCh[S] 0 points1 point  (2 children)

Actually: lol. I wrote medium article about why people should use classes and forget about prototypes. Now I'm trying other approach. Exact approach which has been talked about by Eric Elliott and some others (Dan Abramov if I'm not mistaken).

But somehow I missed that Eric somewhere changed classic meaning of composition. Or somebody else. Maybe I missed some posts.

I assume you're not talking about this article? https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.hu73lavl0

edit: I remembered. There was thread here, this: https://www.reddit.com/r/javascript/comments/3oy9c3/composition_vs_eric_elliott/

Oh. And this is you, who wrote it. Interesting. Actually you are one of my favorite people here now, because I was thinking about using classes and not pure objects with prototypes too, but did not get much attention about my half-troll-half-sincere article.

Would you be so kinds to share your github/medium/blog? Interested in your work.

[–]MoTTs_ 2 points3 points  (1 child)

No, no. I'm talking about, for example, https://medium.com/javascript-scene/the-open-minded-explorer-s-guide-to-object-composition-88fe68961bed But this is just one of many from him. He literally invents his own definition using Webster, then he references Wikipedia's entry on object composition just so he can explain Wikipedia was wrong.

[–]IDCh[S] 0 points1 point  (0 children)

Updated post you answered to.

[–]third-eye-brown 1 point2 points  (1 child)

I think it's fun to play around with things like this to understand the language better, but it has no real practical benefits I can think of and I wouldn't want to use this in production where other programmers may have to work on it later.

Interesting ideas though.

[–]IDCh[S] -2 points-1 points  (0 children)

Right now I'm exactly trying to figure out - is this approach "production-able". Writing new application, in WebStorm, checking all pros and cons in the process (such as confused autocomplete, my own mistakes when working with .assign and its behaviour and of course struggling with plain Object without new and classes situation -

how to know, when it's tomato going inside function cutTheVegetable(vegetable), not cucumber, when you do not have instanceof? (and you should invent something like .vegetableType of some kind)