Variables
Variables are named slots for storing values. You define a new variable in Ghost using the = operator, like so:
a = 1 + 2
This creates a new variable a in the current scope and initializes it with the result of the expression following =. Once a variable has been defined, it can be accessed by name as you would expect.
technology = "Micromachines"
console.log(technology) // >> Micromachines
Reading a name that has never been defined is an error, and Ghost reports it as soon as it reaches that line:
console.log(mystery) // name error: `mystery` is not defined
Destructuring
One statement can bind several names at once. A list pattern binds by position:
[first, second] = [10, 20]
console.log(first) // >> 10
console.log(second) // >> 20
Asking for more names than the list has is not an error — the extras are null, the same leniency indexing out of range already has.
A map pattern binds by key. Naming a key on its own binds it to a variable of that name:
{name, version} = {name: "Ghost", version: "1.0.0-beta.3"}
console.log(name) // >> Ghost
console.log(version) // >> 1.0.0-beta.3
key: local binds that key to a different name instead:
{name: title} = {name: "Ghost"}
console.log(title) // >> Ghost
Destructuring is a statement, not an expression — it cannot be used as a call argument. The patterns bind plain names only: no nesting, and no obj.x or list[0] on the left. The right-hand side has to already be a list for a [...] pattern, and a map for a {...} one, or it is a type error.
[ can be read as a continuation of the line above it, since Ghost has no significant newlines. If the previous line ends in something indexable, end it with a ; — see Statements.Scope
Functions introduce a scope. Blocks do not. A variable first assigned inside an if, while, or for body belongs to the scope that surrounds the loop or branch, and is still there after it ends:
if (true) {
message = "assigned inside the block"
}
console.log(message) // >> assigned inside the block
A function body is different. Everything assigned inside it lives and dies with the call:
function foobar() {
a = 123
console.log(a) // >> 123
}
foobar()
console.log(a) // name error: `a` is not defined
Variables defined at the top level of a script are top-level, or global. All other variables are local.
Assignment inside a function is always local
This is the rule that catches people out. = inside a function creates or updates a variable in that function; it never reaches back out to reassign one from an enclosing scope.
count = 0
function bump() {
count = count + 1 // reads the outer `count`, writes a new local one
}
bump()
console.log(count) // >> 0
A function can read a variable from an enclosing scope, so the right-hand side above sees 0. The assignment then defines a local count that disappears when the call returns.
To have a function change state that outlives it, put the state on a map or a class instance and assign through a property. Property assignment mutates the object itself, and every reference to it sees the change:
state = { count: 0 }
function bump() {
state.count = state.count + 1
}
bump()
bump()
console.log(state.count) // >> 2
Shadowing
Declaring a variable in an inner scope with the same name as an outer one is called shadowing and is not an error.
a = "outer"
function foobar() {
a = "inner"
console.log(a) // >> inner
}
foobar()
console.log(a) // >> outer
Loop variables
The counter of a for loop, and the key and value of a for ... in loop, belong to the loop. When the loop finishes, they are removed again — and if the name was already in use, its previous value is put back.
for (i = 0; i < 3; i++) {
last = i
}
console.log(last) // >> 2
console.log(i) // name error: `i` is not defined