Objects¶
Objects are simple collections of properties. Once created, objects can be freely modified by reassigning values to existing properties, adding new properties or removing properties. Due to their lightweight syntax, Objects in JavaScript can be used as simple associative arrays, hashmaps, or dicts found in other languages.
Warning
Objects created using native Imba classes are different than objects created using JavaScript constructors. Classes are covered in level 2.
Cheats¶
- Objects behave the same way as in JavaScript but with differences in syntax.
- Syntactically valid JavaScript object literal is valid in Imba.
- Commas
,
are not needed when properties are listed vertically. - Braces are optional if objects are used as arguments.
- Braces are optional when nested under properties in object literals.
- Properties are accessed with
x:key
. - Methods are called with
x.key
(parentheses are optional even with no arguments). - Property access on a potentially nullable object is done with
x?:key
. - Method call on a potentially nullable object is done with
x?:key
. - Computed member access
x[expr]
works like in JavaScript. - Can assign default value to properties with
x:key ?= y
. - Object properties can be iterated with
for key of obj
orfor key, val of obj
Object literals¶
The simplest way to create an object is the object literal syntax. This may come as a surprise to programmers coming from object-oriented languages where objects must be instantiated using classes.
While objects in Imba can also be instantiated using classes, there is very little difference between such objects and objects created using the object literal syntax.
1 2 3 4 5 | var player = { score: 0 lives: 3 ammo: 100 } |
An object literal is surrounded by a set of curly braces { }
. Inside the
curly braces, zero or more key-value pairs are listed. These are known as
object properties. Each property consists of a key name, a colon :
, and a
value. If a key is present, it must have a value.
Warning
If you are used to JavaScript objects, please note that commas are optional when listing object keys vertically.
A key is always a string, and values are of any type.
When multiple properties are written on the same line, they are separated by commas:
1 | var gun = { type: 'ranged', power: 120, durability: 0.2 } |
An empty object literal is simply a pair of curly braces with no content.
1 | var dead = {} |
Object literals can sometimes be written without curly braces. This is the case in the following situations:
- When nesting objects inside other object literals.
- Used as an argument to a method or a function.
In the following example, hotspot
property is an object with properties x
and y
.
1 2 3 4 5 6 7 | var level2 = { order: 2 exists: 2 hotspot: x: 12.1 y: 44.2 } |
In the next example, we are calling a method startGame
with a single object
that has properties level
and player
.
1 | startGame level: level2, player: player |
Warning
Please note that, unlike JavaScript, there is no shorthand for the cases where keys have the same name as a variable that is used as the value.
Properties on objects can be methods or functions (including do blocks).
1 2 3 4 5 6 7 8 | var game = { level: level2, player: player, def start screen screen.setLevel self:level screen.setPlayer self:player } |
Accessing the properties¶
Properties of an object are referenced by its key. There are three ways to access the properties.
:
- property access.
- method access[ ]
- computed access
The property access operator :
is used to access the value of a property.
1 | level2:order # evaluates to 2 |
The method access operator .
is used to call a method on an object.
1 | game.start screen |
In the above example, the start
method on the game
object is called with
sreen
as an argument.
The computed member access allows us to use any valid expression to calculate the key of an object. This includes keys that cannot be used with the usual property and method access operators (e.g., names that contain spaces).
1 2 3 4 | game['play' + 'er'] var key = 'level' game[key] |
If a key does not exist on an object, there is no error. Instead, the accessed
property will simply evaluate to undefined
. Attempting to access properties
on undefined
or null
is an error, however.
Imba provides two operators to safely access properties on nullable objects (undefined
and null
):
1 2 3 4 | var possiblyNull = null possiblyNull?:lives # `null` (the value of the object itself) possiblyNull?.go 12, 33 # nothing happens |
Manipulating objects¶
Properties can be added to objects at any time by simply assigning to them.
1 2 3 4 5 6 7 8 9 10 | var nightShadow = { type: 'skill' magic: no stealth: yes effect: 'invisibility' } nightShadow:effectDuration = 2 nightShadow:effectDuration # 2 |
It is also possible to only assign the default value (that is, only assign a value to a property that does not exist or is nullable).
1 2 3 | nightShadow:effectDuration ?= 4 nightShadow:effectDuration # still 2 |
To delete a property from an object, a delete
operator can be used:
1 2 3 | delete nightShadow:effectDuration nightShadow:effectDuration # undefined |
Iterating over object properties¶
We can iterate (loop) over object properties using the for of
block.
We can either iterate just the keys or both keys and values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var dragon = { health: 2000 damage: 500 hp: 240 } for key of dragon console.log key # logs 'health', 'damage', 'hp' for key, val of dragon dragon[key] = val - 100 dragon:health # 1900 dragon:hp # 140 |
In the next example, we will merge an object into another one by iterating over properties:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var snail = { speed: 2 hp: 1 } var poisonVomit = { damage: 200 specialAbility: 'resist poison' } for key, val of poisonVomit snail[key] = val snail:damage # 200 snail:specialAbility # 'resist poison' |
You can read more about the for of
block in the Control
structures section.
Mutability and passing by reference¶
Objects are mutable. This means that when they are passed to methods and do blocks, they are passed by reference and any changes made to them are visible both to the method body and the caller.
1 2 3 4 5 6 7 8 9 10 11 12 | var player = { score: 0 lives: 3 health: 100 } def hit dmg, player player.health -= dmg hit 30, player console.log player:health # logs 70 |
The hit
method, instead of returning a copy of the player
object that
is different from the one given to it, changes the player
object in place.
This is called a 'mutation'.
In the example above, the player object was passed to the hit
method, and
was mutated (changed) within it. However, because it is the same object as
the one defined outside the method, we are able to see the effects of the
mutation from outside the hit
method. This is known as a 'side effect'.
It is possible to prevent mutation of objects by using the Object.freeze
function. This locks the object so that any attempt to mutate it will result
in an error.
1 2 3 | Object.freeze player player:score += 100 # error |
Object identity¶
Two objects are only compared equal if they are the exact same object. In this context 'sameness' does not mean the the contents of the two objects, but whether they point to the same thing in memory.
Consider the following examples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var player1 = { score: 0 lives: 3 health: 100 } var player2 = { score: 0 lives: 3 health: 100 } var player3 = player1; player1 is player2 # no player1 is player3 # yes |
Comparing two objects for equality by value requires inspection of individual properties.
1 2 3 4 5 6 7 8 9 10 11 | def equalObj x, y if x is y # optimization in case two objects are identical yes else for key, val in x if val isnt y[key] return no yes equalObj player1, player2 # yes equalObj player1, player3 # yes |
Note
The reason this kind of object comparison is not provided out of the box is that it is (a) expensive, and (b) objects can be arbitrarily nested, in which case it becomes even more complex and expensive.
Object types and classes¶
All objects have a type 'object' regardless of a class or constructor. To
determine whether some object is an instance of a class, we use the isa
operator.
If we create an object using a Date
constructor from the standard
JavaScript API, we are able to tell it is a Date
object.
1 2 3 4 5 | var d = Date.new var notDate = {} d isa Date # yes notDate isa Date # no |
Note that all objects are ultimately of the class Object
because any class
or constructor inherits from this class.
1 | d isa Object # yes |
Inheritance will be discussed in more detail in level 2, when we talk about classes.