This paragraph is taken from the ECMA-262 1st edition: "In a class-based object-oriented language, in general, state is carried by instances, methods are carried by classes,
and inheritance is only of structure and behavior. In ECMAScript, the state and methods are carried by objects,
and structure, behavior, and state are all inherited." Please clarify the following concepts: state, methods, structure, behavior. Are these separate entities, or do they intersect in meaning? With very simple (JS & C/Java) code examples, please.
Disclaimer: I'm just a JS enthusiast and I'm not referencing the spec here so I'm sorry if I said something misleading or totally wrong
Anyway: this passage is discussing class-based OOP concepts in general and how JavaScript works slightly differently because its OOP is prototype-based. I believe that of those terms, "method" is the only one that has a specific meaning in JS, everything else is OOP terminology.
We're talking about 3 places that parts of classes can live:
- Instance objects:
const fido = new Dog()
- The class definition:
class Dog { ....
- A super-class that our class inherits from:
class Dog extends Animal { ...
JS works differently than, say, Java, because it uses prototype-based inheritance. In Java, you can't really reach into a class after it's been created and mess with it; but in JS, a class definition is represented by a prototype object. When you try to access any property of a class instance, whether it's a field or a method, JS first checks the object for that property, then it checks its prototype object (i.e. class definition), then it tries THAT object's prototype (i.e. super-class definition), and up and up the chain until it reaches Object
. These are live objects that can be modified at any time. Since it's objects all the way up, and doesn't inherently distinguish between fields and methods as different kinds of data, this leads to some behaviors that might be surprising to someone coming from the class-based OOP world. But generally, if you only use the newer class syntax (or type-checking) you won't run into most of these peculiarities.
"State"
In class-based OOP languages, state lives on class instances, and this is also true in JS! In JS, we call instance state "class fields." Class fields are any data stored on the class instance. Like in Java, they can be accessed/set via this.key
inside class methods, and outside code can get or set them via object.key
.
...HOWEVER, it's also possible for data to be inherited from a class definition, or even a super-class definition, in a way that looks very similar to normal class fields. As I said before, when you try to access someObject.foo
, first JS checks the properties of someObject
, but then it climbs up the prototype chain looking for a key matching that name.
An example: (almost) all objects in JS inherit from Object
, so you can add properties to every object by adding something to Object.prototype
:
Object.prototype.foo = 'bar';
const emptyObject = {};
console.log(emptyObject.foo); // logs 'bar'
"Methods" / "Behavior"
I think they basically just mean "methods" when they say "behavior" Maybe someone else will have some insight?
In languages like Java, methods only live on the class definition. They are not copied into individual class instances. And this is also true in JS if you use the class method syntax! Because of prototype-based inheritance, this means they live on an object somewhere in the prototype chain. So "methods live on objects ... behavior [is] inherited" is talking about this (these statements are not in opposition to each-other).
It might be worth noting that you can also store a function in a class field, which will behave similarly to a class method except that it is stored on the class instance. And methods on super-classes simply exist farther up the prototype chain. So just like class fields ("state"), methods can either be inherited or stored directly on the object.
"Structure"
By "structure", this quote is pointing out that many class-based OOP languages draw a distinction between declaring and defining a class field. In these languages, when you declare a field in a class definition, that creates a slot on future instance objects where that data will be stored. But the data is never stored on the class definition itself, only in the instance objects. And in these languages, new class fields can't be created "on the fly" by methods or by outside code, the available slots are set at compile time. In JS, everything is free-form objects that can be edited at any time, and thanks to prototype-based inheritance, data can live anywhere you want it to in the inheritance chain (it can be inherited but doesn't have to.)
Methods can also be added to classes "on the fly." Before class syntax, this was the only way to add them, actually:
// this is our class definition
function Dog() {};
// and this is the old way to add a method
Dog.prototype.bark = function() { console.log('Bark!!'); }
const fido = new Dog();
fido.bark(); // logs 'Bark!!'