Skip to content

Instantly share code, notes, and snippets.

@belen-albeza
Last active February 3, 2019 21:17
Show Gist options
  • Save belen-albeza/eec2ba8ed7b9b5f35f4fa77436600b6e to your computer and use it in GitHub Desktop.
Save belen-albeza/eec2ba8ed7b9b5f35f4fa77436600b6e to your computer and use it in GitHub Desktop.
JS review for game workshop

JS review

JavaScript objects

We can create an Object with {}:

let chara = {};

Objects can have properties:

chara.x = 0;
chara.y = 0;
> chara
{ x: 0, y: 0 }

Objects can have methods we can invoke:

chara.hi = function () {
    console.log('Hello, stranger');
};
> chara.hi();
Hello, stranger

this

More about this at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

this in method calls

Unless changed, when we call a function as an object's method, this points to that object instance:

chara.move = function () {
   this.x += 10;
};
> chara.move();
> chara
{ x: 10, y: 0, move: [function] }

This can be changed with apply, call, or bind:

let other = {x: 100, y: 100};
> (chara.move.bind(other))();
> other
{ x: 110, y: 100 }
> chara
{ x: 10, y: 0, move: [Function] }

this outside method calls

this will point to the window (in a browser), or to the global object (in Node).

When a in a function (that is not a method), if we are in strict mode, this will be set to undefined; if we are not in strict mode, this will point to that window or global.

function waka() { console.log(this); }
use strict;

this in event handlers, callbacks or functions as parameters

Sometimes, this can be changed by an API we are using.

For instance, Array.forEach, which takes a function as a first parameter, by the default will leave this untouched. It will be either the global object or undefined (as seen previously).

let printer = {};
printer.prompt = '>>';
printer.array = function (arr) {
    arr.forEach(function (x) {
        console.log(this.prompt, x);
    });
};
> printer.array([1, 2, 3]);
TypeError: Cannot read property 'prompt' of undefined

But it can be changed with bind:

printer.array = function (arr) {
    arr.forEach(function (x) {
        console.log(this.prompt, x);
    }.bind(this));
};
> printer.array([1, 2, 3]);
>> 1
>> 2
>> 3

Or, better yet, with the second argument of forEach, that is able to set the this for us!

printer.array = function (arr) {
    arr.forEach(function (x) {
        console.log(this.prompt, x);
    }, this);
};
> printer.array([1, 2, 3]);
>> 1
>> 2
>> 3

Note: keep in mind that bind returns a new function. Be careful when using it inside a loop, or while creating indefinite loops (such as requestAnimationFrame), because you might end up consuming a lot of memory.

Events handlers in the browser also modify this. For instance, giving this object:

let dog = {name: 'Conan' };
dog.hi = function () {
    document.body.innerHTML += '<p>Hello, ' + this.name + '.</p>';
};

When subscribing to click, if we do nothing, this will be the Element that has triggered the event:

document.querySelector('button').addEventListener('click', dog.hi);

But we can manually set the this that we want:

document.querySelector('button').addEventListener('click', dog.hi.bind(dog));

See online at https://jsfiddle.net/jaox55ck/.

Objects and prototypes

Constructors

{} is a shortcut for this:

new Object();

A good way of code reusing, if we need to have several instances that behave in the same way, is to use a function as a constructor. We can get this with the new operator:

function Character(name) {
    this.name = name;
}
> new Character('Binky');
Character { name: 'Binky' }

In a constructor, this will point to the object that has been instantiated, and it will be returned automatically implictly.

Prototypes

We could add methods to that object in the constructor…

function Character(name) {
    this.name = name;
    this.hi = function () { console.log("Hi, I'm", this.name); }
}

And if we create two instances, it would work as expected:

> let wizard = new Character('Gandalf');
> let warrior = new Character('Aragorn');
> warrior.hi();
Hi, I'm Aragorn

However, we would be wasting memory because two different functions have been created for each instance:

> wizard.hi === warrior.hi
false

To solve this, JavaScript introduces a mechanism known as prototypes. All objects have a prototype, which is itself an object, and is stored in the property __proto__:

> warrior.__proto__
Character {}

The key is that prototypes are shared between objects created with the same constructor.

> warrior.__proto__ === wizard.__proto__
true

So if we add methods to that prototype, it will be shared by all objects and only one function would be created. This not only saves memory, but allow us to edit the prototype later on and have those changes "propagated" to all instances.

We can modify the prototype of a constructor accessing its prototype property:

Character.prototype.alive = true;
> warrior.alive;
true
> wizard.alive;
true

So this is great to define the methods we want all the instances of the same constructor to share:

Character.prototype.die = function () {
    this.alive = false;
};
> warrior.die();
> warrior.alive
false

Note that the new ES6 class declaration (See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) is just syntactic sugar over this prototype model. This is why it is important to understand what a prototype is.

Inheritance

Inheritance is another re-using mechanism. It allows an instance to extend another one, sharing most of the code but providing custom behaviour.

For example, we could need a new type Enemy that extends Character.

To share the methods and properties declared in the prototype, we need to follow these steps:

  1. Make a copy of it and establish that copy as the prototype of the new constructor.
  2. Override the constructor property of the copy and set it to the new constructor.
  3. If we need it, we can call the parent's constructor with apply or call:
function Enemy(name, level) {
    // call the parent constructor
    Character.call(this, name);
    this.level = level;
}

Enemy.prototype = Object.create(Character.prototype);
Enemy.prototype.constructor = Enemy;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment