We were introduced to OOP this week. While it may seem very complex, at heart it's still about the same notions of data and operations on data.
As JediScript has a rather complicated OOP system with many moving parts, it's important to distinguish the ideas from the implementation.
-
State and behaviour: Objects comprise state and behaviour. Programs are no longer structured around just functions, but around a central object which specific functions operate on. Computation usually involves updating the state of that central object over time.
-
Aggregation: As with functions, we can compose objects, building large objects out of smaller ones.
-
Classification: Common characteristics in objects can be factored out, leading to the idea that objects can be put into classes, each with their own kinds of state and behaviour.
-
Specialisation: We want to describe concepts as more specific versions of other concepts, so parts of our programs can be reused. The type of specialisation we use is classical inheritance.
-
Encapsulation: The notion that interactions with an object should be confined to a specific interface, and that the parts which should not be accessed should be hidden. Helps us better manage complexity. JediScript has no encapsulation by default, but is flexible enough that we can implement it ourselves.
Object literals {}
- 'classless' objects
- properties can be easily added and changed
a["property"] === a.property
(keys are strings)
new
- creates an instance of a class
- turns a function into a constructor
- sets the
__proto__
property of the object to theprototype
of the constructor
Constructor
- initialises an instance of a class
- usually also stands-in for the class, for example in
is_instance_of
- may
call
a superclass constructor - is a function: any function can serve as a constructor
- is also an object with a
prototype
property
this
- the current object
- like a hidden function parameter which can be set in various ways
- nothing special; can be simulated with an explicit parameter
- rules are actually very simple:
obj.method(...)
:this
is set toobj
in the body ofmethod
method.call(obj, ...)
:this
is set toobj
in the body ofmethod
new Constructor()
:this
is set the new object that is created in the constructor- otherwise:
undefined
Method invocation
- calls a function with the object it is invoked with set to
this
call
- explicitly sets
this
in the body of a function and relays the remaining arguments to it - used to call superclass constructor for inherited properties
- used to call superclass methods
Inherits
- sets up the prototype chain between classes
- more specifically, the
__proto__
property of the subclass constructor'sprototype
is set to theprototype
of the superclass constructor
prototype
- a property of a constructor function
- usually contains methods of an object which are shared across all instances
- the
__proto__
property of objects created with the constructor is set to this
__proto__
- a property of an object created with a constructor
- can also be explicitly set in an object literal
- properties that can't be found on the object are searched for here (prototype chain)
is_instance_of
- checks if an object is an instance of a class (represented by the constructor)
- internally searches the prototype chain to see if the
prototype
of the constructor appears somewhere in it
bind
- like
call
, but returns a function withthis
bound instead
Bank accounts support two operations, deposit
and withdraw
.
function make_bank_account(initial) {
return pair("bank account", initial);
}
var get_balance = tail;
function withdraw(bank_account, amount) {
return make_bank_account(get_balance(bank_account) - amount);
}
function deposit(bank_account, amount) {
return make_bank_account(get_balance(bank_account) + amount);
}
var b = make_bank_account(10);
var b2 = withdraw(b, 4);
get_balance(b2); // 6
var b3 = deposit(b2, 10);
get_balance(b3); // 16
- After each operation, we end up with a new bank account, just like how we ended up with a new list, curve, and jedisound.
- We create new things instead of changing things.
var set_balance = set_tail;
function withdraw(bank_account, amount) {
return set_balance(bank_account, get_balance(bank_account) - amount);
}
function deposit(bank_account, amount) {
return set_balance(bank_account, get_balance(bank_account) + amount);
}
var b = make_bank_account(10);
withdraw(b, 4);
get_balance(b); // 6
deposit(b, 10);
get_balance(b); // 16
- Now, we change things. We mutate the pair that we use to represent a bank account and give it a new value. This changes its state.
We've always written programs by defining some data (lists, curves, jedisounds, bank accounts), then creating operations over the data (append
, connect_rigidly
, consecutively
, withdraw
). OOP is really just about organising these two things together.
function BankAccount(initial) {
this.amount = initial;
}
BankAccount.prototype.get_balance = function () {
return this.amount;
};
BankAccount.prototype.deposit = function (amount) {
this.amount = this.amount + amount;
};
BankAccount.prototype.withdraw = function (amount) {
this.amount = this.amount - amount;
};
var b = new BankAccount(10);
b.withdraw(4);
b.get_balance(); // 6
b.deposit(10);
b.get_balance(); // 16
- Same as the stateful version, just with special syntax
- There are new features (prototypes, inheritance), but exactly the same fundamental ideas: data, and operations over data.