JavaScript-mancy: Getting Started Preview - JavaScript Functions

The Basics Of JavaScript Functions

functions,
functions everywhere

- Buzz Lightyearvascript,
Explorer

The Basics Of Functions

Functions are the most fundamental building blocks of JavaScript applications. They are the safekeepers of the logic within our programs but also the primitives upon which we build programmatic constructs such as classes and modules.

JavaScript provides different ways to declare and use functions, each with their own nuances and limitations. So given the fact that they are such a fundamental part of the language it is important that you are aware of these characteristics when you are writing JavaScript.

Welcome to the first step in your journey to JavaScript mastery! Let’s get started!

/* 
 
 ...In the previous chapter...
 stranger.says('...err... who the hell are you?' + 
               'and whaaaat is a kender?!');
 
 */

randalf.says(`Well... I am Randalf. Randalf the Red... 
 JavaScriptmancer of the First Order... Guardian of the 
 Sacred Flame... Freedom fighter and Poet...`);

stranger.says('uh?');

randalf.says(`Yes! And you are the Chosen one! The Child of 
Prophecy! The one that will bring balance to the force! 
Muad'Dib! The Dragon reborn! Brought to this land in the 
twelfth moon of the twelfth month of the twelfth year of 
the Wyrm to save the world from certain destruction!`);

stranger.says(`I am Mooleen actually... 
and this is the weirdest dream I've ever had...`);
let mooleen = stranger;

randalf.says(`There's no time for this child. You need to 
learn how to defend yourself, it isn't safe here... It all 
starts with functions, functions are the key...`);

Functions are the Key

Did you know that there are not one but two ways to write functions in JavaScript? Well, now you do. You can either write them as function expressions or function declarations. It will be very helpful for you to understand the difference and the implications of writing functions in either of these two styles because they work in a very different manner that will impact the readability of your code and how easy or hard it is to debug.

This is a function expression:

// anonymous function expression
var castFireballSpell = function(){
  // chaos and mayhem
};

And this is a function declaration:

// function declaration
function castFireballSpell(){
  // it's getting hot in here...
}

As you can appreciate, in their most common incarnations function expressions and function declarations look very similar. That’s why it is especially important to understand that they are actually very different and behave in disparate ways from each other.

Let’s examine each of them in greater detail.

Function Expressions

We use the function expression style whenever we declare a function like an expression, either by assigning it to a variable:

// an anonymous function expression
var castFireballSpell = function(){
    console.log('...chaos and mayhem');
};
castFireballSpell();
// => ...chaos and mayhem

A property within an object:

// another anonymous function expression as a property of an object
var magician = {
    castFireballSpell: function() {
        console.log('muahaha! Eat this fireball!');
    }
};
magician.castFireballSpell();
// => muahaha! Eat this fireball!

Or passing it as an argument to another function (like a lambda - note that I am using the term lambda in the sense of function as value):

// yet another anonymous function expression passed as an argument
var castCombo = function(spellOne, spellTwo){
    console.log('Combo Magic!!!!');
    spellOne();
    spellTwo();
}

castCombo(function(){
    console.log('FireBalllllzzz!!');
}, function(){
    console.log('And Spectral Shield!!');
});

// => Combo Magic!!!!
// => FireBalllllzzz!!
// => And Spectral Shield!!

There are a couple of important considerations you need to take into account when using function expressions like the ones above: they are all anonymous functions - they don’t have a name - and if stored within a variable they are subjected to the same hoisting rules that apply to any other variable in JavaScript. But what is hoisting? And how a function being anonymous can affect our programs?.

W> ### JavaScript Arcana: Function Scope and Hoisting W> W> One of the things that confuses us the most when we start learning JavaScript is that while JavaScript looks a lot like C#, in many ways it does not behave like it. W> W> These JavaScript Arcana bits will help you understand these hairy parts where JavaScript behaves in a completely different way to what you would expect. You’ll discover them throughout the book as we venture deeper into the world of JavaScript. W> W> Oftentimes you’ll see JavaScript Arcana paired with JavaScript Arcana Resolved sections that will provide tips and solutions to help you tackle JavaScript strange behaviors.

JavaScript Arcana: Function Scope and Hoisting

An interesting quirk about JavaScript is that, unlike many other languages, variables in JavaScript have function scope (as opposed to block scope). That is, it is the functions themselves that create scopes for variables and not the blocks. This can be better illustrated by an example:

function openPandoraBox(){

    if (true){
        var treasure = 'mouse';
    }
    console.log('the pandora box holds a: **' + treasure + '**');
}
openPandoraBox();
// => the pandora box holds a: **mouse**
// WAT!? x is declared inside an if block. 
// How can it be picked up by the console.log??
// one word: function-scope 

What happens in this piece of code above is that the JavaScript runtime hoists (moves up) all variables within a function definition to the beginning of the function body. And does it even with variables declared within blocks of code such as if statements. This means that the function that you see above is equivalent to this one:

function openPandoraBox(){
    // The variable definition is hoisted up here
    var treasure = undefined;
    if (true){
        treasure = 'mouse';
    }
    console.log('the pandora box holds a: **' + treasure + '**');
    // => the pandora box holds a: **mouse**
}

Where the scope of the treasure variable isn’t only the if statement but the whole function. In yet another example of hoisting the variable i that we declare inside the for loop is actually part of the entire function (Shocker!!):

function aMagicFunctionToIllustrateHoisting(){
    // in reality
    // var i = undefined

    console.log('before i =' + i);
    // => before i = undefined

    for(var i = 0; i < 10; i++){
        // jara jara
    }

    console.log('after i = ' + i);
    // => after i = 10
}

This is the reason why JavaScript developers - seasoned JavaScriptmancers - usually write all variable declarations at the beginning of a function. They do it in order to be more explicit about what is really happening when a function is evaluated and to avoid unnecessary bugs.

If you take a look at jQuery for instance, a popular JavaScript open source library, you’ll be able to appreciate this technique everywhere:

/**
 * Load a url into a page
 */
jQuery.fn.load = function( url, params, callback ) {
    var selector, type, response,
        self = this,
        off = url.indexOf(" ");

    //... more codes here
}

Functions being the stewards of scope of an application is pretty interesting because, all of the sudden, a function is not only used for encapsulating and abstracting a piece of logic but also for structural reasons. That is, a way to organize and distribute bits and pieces of functionality and code. For instance, you’ll often see functions being declared inside other functions:

// declaring a function inside a function?
// Ain't that weird????
function blessMany(many){
  many.forEach(bless);
  
  function bless(target){
    console.log('You bless ' + target + '. (+5 WillPower) ');
  }
}

blessMany(['john snow', 'sansa stark']);
// => You bless John Snow (+5 Willpower) 
// => You bless Sansa Stark (+5 Willpower)

This probably looks very weird if you are coming from C#. But up until the recent advent of ES6 modules this was the only way we had to group pieces of loosely related functionality.

JavaScript Arcana Resolved: Variables with Block Scope With ES6 let and const

Happily for you and happily for me ES6 comes with not one, but two ways to declare variables with block scope: the new let and const keywords. From this point forward I invite you to start using let and achieve a more C#-like style of writing code where you declare variables closer to where you use them.

And so, if we rewrite the example we used to illustrate function scope with let, we’ll obtain a very different yet more familiar result:

function openPandoraBoxWithBlockScope(){ // new scope for function block

    if (true){ // new scope for if block
        let treasure = 'mouse';
    }
    console.log('the pandora box holds a: **' + treasure + '**');
}
openPandoraBoxWithBlockScope();
// ReferenceError: treasure is not defined
// fiuuuu now everything makes sense again

Now the treasure variable only exists within the if statement block.

Alternatively, you can use the const keyword to declare constant variables with block scope.

function shallIPass(){ // new scope for youShallNotPass block
    let youShallPass = 'you Shall Pass!', 
        youShallNotPass = 'You Shall Not Pass!';
    // console.log(canIPass); // => ReferenceError

    if (true){ // new scope for if block
        const canIPass = youShallNotPass;
        console.log(canIPass); // => 'You Shall Not Pass!'
        canIPass = youShallPass; 
        // => TypeError: Assignment to a constant variable
    }

    console.log(canIPass); // => undefined
    // ReferenceError: x is not defined
}
shallIPass();
// => you Shall not Pass!
// => TypeError: Assignment to a constant variable

Variables declared with the const keyword behave in a very similar way to C#. Attempting to make the constant refer to a different value will cause an exception.

However, it is important to understand that if the constant refers to an object, you can still modify its properties:

const fourHorsemen = ['conquest', 'war', 'famine', 'death'];
fourHorsemen.push('jaime');
console.log(`${fourHorsemen} waaat`);
// => ['conquest', 'wat', 'famine', 'death', 'jaime'] waaat

This means that const only affects the act of binding a value to a variable, and not the act of modifying that value itself. In order to make an object immutable you need to use Object.freeze but that’s knowledge best kept for another chapter about the beauty of objects. We’ll stick to functions for a little bit longer.

Another aspect of let and const that is interesting is that they do not hoist variables to the top of a block. Instead, if you attempt to use a variable before it has been defined you’ll get an exception (cheers for some sane behavior):

function openPandoraBoxWithBlockScopeAndHoisting(){ 
    // new scope for function block

    if (true){ 
        // new scope for if block
        console.log('the pandora box holds a: **' + treasure + '**');
        // => ReferenceError: treasure is not defined;
        let treasure = 'mouse';
    }
}
openPandoraBoxWithBlockScopeAndHoisting();
// => ReferenceError: treasure is not defined;

Now that you’ve learnt some of the main characteristics of function expressions let’s take a look at the two types of function expressions that are available to you in JavaScript: anonymous function expressions and named function expressions.

Anonymous Function Expressions

Anonymous function expressions are particularly interesting because even though you read the following:

var castFireballSpell = function(){
    console.log('...chaos and mayhem');
};

And you may be tempted to think that the name of the function is castFireballSpell, it is not!?! castFireballSpell is just a variable we use to store an anonymous function. You can appreciate this anonymity by inspecting the name property of the function itself:

var castFireballSpell = function(){
    console.log('...chaos and mayhem');
};
console.log(castFireballSpell.name);
// => ""
// no name!

Luckily for us, as long as an anonymous function is bound to a variable, the developer tools in modern browsers will use that variable when displaying errors in call stacks (which is a savior when debugging):

// inspecting a call stack for an anonymous function bound 
// to a variable
var castFireballSpellWithError = function(){
    console.log('...chaos and mayhem');
    try {
        throw new Error();
    } catch (e) {
        console.log('stack: ', e.stack);
    }
};
castFireballSpellWithError();
//=> stack:  Error
// at castFireballSpellWithError (somefile:53:15)
// at window.onload (somefile:58:1)

Even if we use this function as a argument:

// If you use this function as a lambda the name is 
// still shown in the call stack:
var spellLauncher = function(f){ f(); }
spellLauncher(castFireballSpellWithError);
// => stack:  Error
// at castFireballSpellWithError (somefile:56:15)
// at spellLauncher (somefile:68:35)
// at window.onload (somefile:69:1)

However, an unbound anonymous function will show as completely anonymous within the call stack making it harder to debug when errors occur (I will refer to these functions as strict anonymous function from now on):

// strict anonymous function don't appear in the call stack
spellLauncher(function(){
    console.log('...chaos and mayhem');
    try {
        throw new Error();
    } catch (e) {
        console.log('stack: ', e.stack);
    }
});
//=> stack:  Error
// at somefile:76:15
// at spellLauncher (somefile:68:35)
// at window.onload (somefile:73:1)

This lack of name will also affect the ability to use recursion because a function that doesn’t have a name cannot call itself from inside its body. In spite of that and just like in the previous examples, if we have the anonymous function bound to a variable we take a free pass and can take advantage of JavaScript lexical scope to access the function through that variable:

// you can use recursion when an anonymous function is bound to a variable
var castManyFireballsSpell = function(n){
    // this function encloses the castManyFireballsSpell variable
    // and thus becomes a closure
    console.log('... SHOOOOOOOOOSH ....');
    if (n > 0)
        castManyFireballsSpell(n-1);
};
castManyFireballsSpell(3);
// => ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....

Notice that this is a pretty brittle way to use of recursion. In this example we are using the variable castManyFireballsSpell to access the anonymous function from within itself. If, at some later point in time, you happen to set the variable to another function you’ll get into a pickle. A tricky situation with a very subtle bug where the original function will call this new function instead of itself (so no more recursion and weird unexpected stuff happening).

A strict anonymous function, on the other hand, has no way to refer to itself and thus you lose the ability to use recursion. For instance, this is the case when we define an anonymous function expression and we invoke it right away:

// but there's no way for an anonymous function 
// to call itself in any other way
(function(n){
    console.log('... SHOOOOOOOOOSH ....');
    if (n > 0) {
        // I cannot call myself... :(
    }
}(5));
// => ... SHOOOOOOOOOSH ....

In summary, the fact that common function expressions are anonymous makes them harder to debug and complicates the use of recursion. And the fact that they are hoisted as variables, a trait common to all function expressions, also makes them less readable as we’ll see in a bit with a larger program as example.

Let’s see some ways to ameliorate or lessen these issues.

Named Function expressions

You can solve the problem of anonymity that we’ve seen thus far by using named function expressions. You can declare named function expressions by adding a name after the function keyword:

// named function expression
var castFireballSpell = function castFireballSpell(){
  // mayhem and chaos
};
console.log("this function's name is: ", castFireballSpell.name);
// => this function's name is castFireballSpell

The example above shows a variable and a function expression both named castFireballSpell. A named function expression always appears correctly represented in the call stacks even when used as an argument (and not bound to a variable):

// A named function expression always appears in the call stack
spellLauncher(function spreadChaosAndMayhem(){
  console.log('...chaos and mayhem');
  try {
    throw new Error();
  } catch (e) {
    console.log('stack: ', e.stack);
  }
});
// stack:  Error
// at spreadChaosAndMayhem (somefile:134:15)
// at spellLauncher (somefile:68:35)
// at window.onload (somefile:131:1)

This helps while debugging and makes your code more readable since you can read the name of the function and understand what it’s meant to do without looking at its implementation.

An interesting fact about named function expressions is that you cannot call them by their name from the outside:

var castFireballSpell = function cucumber(){
    // cucumber?
};
cucumber();
// => ReferenceError: cucumber is not defined

The name of a function expression is more of an internal identifier that can be used from inside the function body. This is very useful when working with recursion. For instance, if we declare a recursive named function expression and invoke it right away it just works:

// but you can call it from the function body 
// which is helpful for recursion
(function fireballSpellWithRecursion(n){
  console.log('... SHOOOOOOOOOSH ....');
  if (n > 0) {
    fireballSpellWithRecursion(n-1);
  }
}(5));
// => ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ..... etc..

In summary, named function expressions improve on anonymous function expressions by increasing readability, improving the debugging process and allowing for a function to call itself.

Function Expressions are Hoisted as Variables

Function expressions are still problematic because they are hoisted like variables. But what does this mean exactly? It means that you can only use a function expression after you have declared it and therefore it forces you to write your code starting from the implementation details and continuing into higher levels of abstraction.

This is the opposite of what you want. Think about it. When you write a class as a C# developer, you start with the public API at the top of the class definition. Then you write the implementation of each method from top to bottom, from higher to lower levels of abstraction so that reading becomes natural. You open a file at the top, understand what it does at a glance by reading the intentional names that compose its public API and then you traverse the file down looking at the implementation details only when you need or want to.

Being forced to start from the opposite direction will have a negative impact in the readability of your code:

(function (magic){
  // this function represents a module 'magic'
  // it's just a way to group like-minded pieces of code

  var oven = {
    open: function(){},
    placeBaking: function(){},
    increaseTemperature: function(){},
    claimVictory: function(){ return 'awesome cake';}
  };

  var mix = function mix(ingredients){
    console.log('mixin ingredients:', ingredients.join(''));
    return 'un-appetizing mixture';
  }

  var work = function work(mixture){
    console.log('working ' + mixture);
    return 'yet more un-appetizing dough';
  };

  var bake = function bake(dough){
    oven.open();
    oven.placeBaking(dough);
    oven.increaseTemperature(200);
    // insta-oven!
    return oven.claimVictory();
  };

  var enchant = function enchant(ingredients){
    var mixture = mix(ingredients),
      dough = work(mixture),
      cake = bake(dough);
    return cake;
  };

  // This is the public API of this module
  // and it's almost hidden down here
  magic.enchant = enchant;

}(window.magic || (window.magic = {})));

var cake = magic.enchant(['flour', 'mandragora', 'dragon', 'chicken foot']);
// => mixin ingredients:  flour mandragora dragon chicken foot
// => working un-appetizing mixture
console.log(cake);
// => awesome cake

If you try to reorder the different functions within the module so that they start from the public API and continue from top to bottom, from higher to lower levels of abstraction you’ll encounter many issues:

(function (magic){
  // this function represents a module 'magic'
  // it's just a way to group like-minded pieces of code

  // exposing enchant as the API for the 'magic' module
  magic.enchant = enchant;
  // => hoisting issue, enchant is undefined at this point
  // so we are just exposing an undefined variable thinking it is a function

  // if uncommented this would cause an exception
  // enchant();
  // => TypeError: enchant is not a function
  // => hoisting issue, enchant is undefined at this point

  var enchant = function enchant(ingredients){
    var mixture = mix(ingredients),
      dough = work(mixture),
      cake = bake(dough);
    return cake;
  };

  // if uncommented this would cause an exception
  // enchant();
  // => TypeError: mix is not a function (it's undefined at this point)
  // hoisting issue, mix is undefined at this point

  /* rest of the code...
  var mix = function mix(ingredients){}
  var work = function work(mixture){};
  var bake = function bake(dough){};
  var oven = {};
  */

}(window.magic || (window.magic = {})));

try {
  var cake = magic.enchant(['flour', 'mandragora', 'dragon', 'chicken foot']);
  console.log(cake);
} catch (e) {
  console.warn('ups!!!!!!', e);
  // => ups!!!!!! TypeError: magic.enchant is not a function
}

In this example we use function expressions to define every function. Because they are hoisted like variables when we try to assign the enchant function to our magic.enchant object its value is undefined. This results in us exposing an undefined value to the outside world instead of a helpful function to enchant delicious cakes. In a similar way, when we attempt to call the enchant function before either enchant or mix have been initialized we get a TypeError exception.

In summary, both named and anonymous function expressions are hoisted as variables. This affects their readability and can cause bugs when we try to run a function or expose it as part of the public API of a module before it has been defined. Although you could use let and const to prevent hoisting, there’s a better way you can declare your functions: Function declarations.

Function Declarations

Function declarations have some advantages over function expressions:

  • They are named, and you can use that name from outside and from within the function.
  • They are not hoisted as variables but as a whole, which makes them impervious to hoisting problems.

This is how you write a function declaration:

// function declaration
function castFireballSpell(){
  // it's getting hot in here...
}

As you learned just a moment ago, function declarations are hoisted in a very special way. When the JavaScript runtime processes this piece of code:

var message = "hello";

// some more code here...

// function expression
var sayHi = function(){console.log('hi!')}; 

// some more code here...

// function declaration
function sayHello(){ 
  console.log(msg);
}

It’s going to rearrange it by hoisting the whole sayHello function and only the declaration of the variables message and sayHi:

// hoisted as variables
var message, sayHi;

// hoisted as a whole
function sayHello(){
console.log(msg);
}

message = "hello";
// some more code here...
sayHi = function(){console.log('hi!')};
// some more code here...

Because of this special hoisting behavior, function declarations will enable you to write your JavaScript modules from higher to lower levels of abstraction just like we discussed earlier and as you can see in this example below:

// with function declarations you can write functions like this
// and don't worry about hoisting issues at all
(function(magic){

  // public API of the magic module
  magic.enchant = enchant;

  // functions from higher to lower level of abstraction
  function enchant(ingredients){
    var mixture = mix(ingredients),
      dough = work(mixture),
      cake = bake(dough);
    return cake;
  }

  // these are private functions to this module
  function mix(ingredients){
    console.log('mixin ingredients:', ingredients.join(''));
    return 'un-appetizing mixture';
  }

  function work(mixture){
    console.log('working ' + mixture);
    return 'yet more un-appetizing dough';
  }

  function bake(dough){
    oven.open();
    oven.placeBaking(dough);
    oven.increaseTemperature(200);
    // insta-oven!
    return oven.claimVictory();
  }

  var oven = {
    open: function(){},
    placeBaking: function(){},
    increaseTemperature: function(){},
    claimVictory: function(){ return 'awesome cake';}
  };

}(window.magic || (window.magic = {})));

var cake = magic.enchant(['flour', 'mandragora', 'dragon', 'chicken foot']);
// => mixin ingredients:  flour mandragora dragon chicken foot
// => working un-appetizing mixture
console.log(cake);
// => awesome cake

And, just like function expressions, you can also use function declarations as values (and arguments). Notice the disintegrate function below:

var orc = {toString: function(){return 'a mighty evil orc';}};
var warg = {toString: function(){return 'a terrible warg';}};
var things = [1, orc, warg, false];

// using the disintegrate function declaration as an argument
// nice and readable
things.forEach(disintegrate);

function disintegrate(thing){
  console.log(thing + ' suddenly disappears into nothingness...');
}

// => 1 suddenly disappears into nothingness...
// a mighty evil orc suddenly disappears into nothingness...
// a terrible warg suddenly disappears into nothingness...
// false suddenly disappears into nothingness...

There’s something interesting happening in this example above that is worthy of note: type coercion. When the body of the disintegrate function is evaluated, the expression below:

thing + ' suddenly disappears into nothingness...'

Coerces the thing variable to a string type. In the case of the orc and warg which are objects that means calling the toString method and obtaining a string representation of either of these objects.

Concluding: Prefer Function Declarations and Named Function Expressions

Function expressions have some limitations:

  1. They are anonymous, which can make them less readable, harder to debug and use in recursion
  2. They are hoisted as variables which can lead to bugs and forces you to declare them before you can use them

Named function expressions solve the problem of anonymity. They make your vanilla function expressions more readable, easier to debug and enable recursion.

Function declarations solve both problems of anonymity and hoisting (they are hoisted as a whole), and even allow you to write code from higher to lower levels of abstraction.

You can use the let and const keywords to solve the problems with hoisting related to function expressions but you don’t get the nice top to bottom narrative that you get with function declarations. That is, with let and const you cannot use a function before you have declared it, if you attempt to do so you’ll get an exception.

In summary, and based on the characteristics of functions in JavaScript, prefer named function expressions and function declarations over anonymous function expressions.

Hope you have enjoyed this chapter about the different ways you can write functions in JavaScript. Up next, We will discuss the most common patterns to achieve default values, multiple arguments and function overloading when writing functions in JavaScript and we’ll see how some of the new features in ES6 provide native support for some of these.

/* 

 <Grrrrrr...>

 */

randalf.says('Are you hungry?');

mooleen.says('What? That was you!!?!');

randalf.says('All right... all right...' + 
             ' no need to start pointing fingers...');
randalf.says('We'll need to make some time for break...');

/*

 A huge fire explosion suddenly obliterates part of the forest 
 right beside you. It rapidly extinguishes in a very unnatural 
 way and a figure emerges from the smoke and cinders.

 */

randalf.says('oh great...');
great.says(`Do you really think you could hide your schemes
    and machinations from the great-est javascriptmancer!?`) 
great.says('It's time to burn...');

/*

 Great (really?) lifts his arm and an igneous ball of fire 
 starts forming on his hand...

 */

Exercises

randalf.says('Oh no... we are going to die.');
mooleen.says('Wait? What? This is just a dream...');

randalf.says('Does the unbearable heat of that torrent of flames ' + 
             'feel dreamy to you?');
mooleen.says('Damn...');
mooleen.says(`Well... JavaScriptmancer of the First Order,
  guardian of the secret flame, can you do something!?`);

randalf.says('Sacred Flame...')
randalf.says('hmm I kind of ...ehem.. have lost my power');
mooleen.says('Shit');
castMagicShield("Mooleen");
// => The air shimers around Mooleen as Mooleen is surrounded 
//    by a magic shield
castMagicShield("cat");
// => The air shimers around cat as cat is surrounded by a 
//    magic shield

Solution

var castMagicShield = function(target){
  console.log('The air shimmers around ' + target + ' as ' + 
  target + ' is surrounded by a magic shield');
  target.defense = 1000000;
}

mooleen.weaves('castMagicShield(mooleen)');
// => The air shimmers around Mooleen as Mooleen is surrounded 
//    by a magic shield
mooleen.weaves('castMagicShield(randalf)');
// => The air shimmers around Randalf, the Red as Randalf, 
//    the Red is surrounded by a magic shield

great.weaves('fireball(mooleen, randalf)');
// => A giant fireball blasts Mooleen and Randalf, the Red

/*

While all the surrounding vegetation springs into flames
the space surrouding Mooleen and Randalf remains eerily 
intact.

*/

great.says(`What!? How are you alive?! It can't be! 
You old fool lost the ability to weave long ago`);

great.says(`It doesn't matter, If I cannot burn you, 
            I'll bury you both`);

/*

The earth starts rumbling under your feet as Great 
concentrates...

*/
(function(source){

 source.supercharge = supercharge;
 
 var supercharge = function(spell){
   console.log('you tap into the One True Source an are ' + 
               'inundated by power');
   console.log('you concentrate all your power in ' + spell.name);
   spell.mana = 1000000;
 }

}(window.source = {}));

Solution

(function(source){

// the bug was here
// at this point supercharge is undefined
// it is the equivalent to:
// var supercharge;
//source.supercharge = supercharge;
  
var supercharge = function(spell){
  console.log('you tap into the One True Source an are ' +
              'inundated by power');
  console.log('you concentrate all your power in ' + spell.name);
  spell.mana = 1000000;
}
  
source.supercharge = supercharge;

}(window.source = {}));

function fireball(...targets){
  if (!fireball.mana || fireball.mana < 1000000)
    world.spell(`A giant fireball blasts ${targets.join(' & ')}`);
  else
    world.spell(`An immense and unending torrent of molten lava 
      surges from your fingers and impacts ${targets.join(' & ')}`);
}

mooleen.weaves('source.supercharge(fireball)');
// => you tap into the One True Source and are inundated by Power
// => you concentrate all your power in fireball
mooleen.weaves('fireball(great)');
// => An inmense and unending torrent of molten lava 
//    surges from your fingers and impacts Great
    
mooleen.weaves('source.supercharge(fireball)');
// => you tap into the One True Source and are inundated by Power
// => you concentrate all your power in fireball
mooleen.weaves('fireball(great)');
// => An inmense and unending torrent of molten lava 
//    surges from your fingers and impacts Great

great.says('Glurrrp');
randalf.says("Omg! You are awesome! Now, let's flee!");
teleport("out of the forest", "Mooleen");
// => Mooleen teleports out of the forest
teleport("to Fiji", "Mooleen");
// => Mooleen teleports to Fiji

Solution

function teleport(where, ...who){
	console.log(`${who.join(' and ')} teleport ${where}`);
}

mooleen.weaves('teleport("out of the forest", mooleen, randalf)');
// => Mooleen and Randalf, the Red teleport out of the forest
mooleen.says('I feel so tired...');

/* 
Mooleen collapses into the ground...
*/