Using Ruby to learn JavaScript

I’ve written about why I believe learning to program is hard. If you’ve made it to the other side, rejoice! It’s time for the easy part. Learning a new language. Let’s see how knowing Ruby can help us learn a little JavaScript. (I’m assuming you know Ruby for the duration of this post)

Ruby to JavaScript

First, let’s find some similarities. Both are:

Let’s look at these first

Dynamically Typed

1
2
my_account = {}
my_account = []

vs

1
2
var myAccount = {};
myAccount = [];

With these 2 lines of code, we can start to identify some differences between the languages.

The var keyword

Variables in JavaScript must be declared. You must communicate this intention, otherwise weird things start to happen. The following code works, but not like you think.

Run this in Developer Tools

1
2
3
4
5
6
7
8
var varTest = function(){
  var totallyFine = 17;
  ohMy = 100;
}
varTest();

console.log(ohMy); // 100
console.log(totallyFine); // explosion!  

Why’s this a big deal? This means that if any code, anywhere creates a variable in a function without the var keyword, it’s with you for the rest of your app. Other things can change it. For a real life case of this biting someone, checkout this post.

Strict mode prevents this tomfoolery.

1
2
3
4
5
6
var varTest = function(){
  'use strict';
  var totallyFine = 17;
  ohMy = 100;
}
varTest(); // blows up here since ohMy is not defined

Another difference are those semicolons! Semicolons are used to end a statement. It may be your first impulse to throw semicolons everywhere but don’t. JavaScript also uses camelCase for its variables, vs Ruby’s snake case.

Object Orientation

Both languages can create structures that act as templates for instances of itself.

1
2
3
class Student
end
bob = Student.new

vs

1
2
var Student = function(){};
var bob = new Student;

In JavaScript, we supply what’s called a Constructor Function. One that responds to the new keyword by returning an instance of the function. Let’s flesh this out some more.

1
2
3
4
5
6
7
8
class Student
  attr_reader :name
  def initialize(name)
    @name = name
  end
end
bob = Student.new("Bob")
bob.name # => "Bob"

vs

1
2
3
4
5
6
var Student = function(name){
  this.name = name;
};

var bob = new Student("Bob");
bob.name; // "Bob"

The Constructor Function can take arguments like any other function.

To add methods to the Factory, add them to the prototype. Then all instances will have access to those methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Student = function(name){
  this.name = name;
};

Student.prototype.grade = function(){
  if(this.name == "Steven"){
    return "A";
  }else{
    return "F";
  }
};

var bob = new Student("Bob");
bob.grade() // "F" sorry Crouton
var steven = new Student("Steven");
steven.grade() // "A" obviously

Inside the Constructor Function and prototype methods you have access to this mysterious this thing. A new this is created whenever you call a function with the new keyword. By convention, Constructor Functions start with a capital letter.

1
2
3
4
5
6
var ThisOrThat = function(){
  this.thang = "This thang";
}

var thisThing = new ThisOrThat();
thisThing.thang; // "This Thang"

There’s another way to make objects in JavaScript by using an object literal.

1
2
3
4
5
6
7
8
9
var myObject = {
  value1: "I'm a value",
  action: function(){
    return "Live Action! " + this.value1 ;
  }
}

myObject.value1; // I'm a value
myObject.action(); // "Live Action! I'm a value""

Here, the action method has access to other properties in the object through this.

1
2
3
4
5
6
7
var myObject = {
  getThis: function(){
    return this;
  }
}

myObject === myObject.getThis(); // true

This is akin to creating an Object instance in Ruby and attaching singleton methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
my_object = Object.new
def my_object.value1
  "I'm a value"
end

def my_object.action
  "Live Action #{self.value1}"
end

def my_object.get_self
  self
end

my_object.value1 # Same
my_object.action # As above
my_object == my_object.get_self # => true

Properties in Ruby

One thing you’ll notice is that Ruby has no concept of ‘properties’ on objects. Everything in Ruby is responding to a message sent. Nothing is stopping you from adding new properties on an existing object in JavaScript.

1
2
3
var myObject = {};
myObject.value1 = "I'm a value";
myObject.value1;

In Ruby, it would break.

1
2
my_object = Object.new
my_object.value1 = "I'm a value" # => "NoMethodError"

They behave more like Ruby’s OpenStruct class

1
2
3
4
require 'ostruct'
my_object = OpenStruct.new
my_object.value1 = "I'm a value"
my_object.value1

First Class Functions

What the hell does that even mean?

Specifically, this means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures.

My post on closures shows that Ruby has passable functions in the form of lambdas, but it’s used a lot more in JavaScript!!

Look at this JavaScript code that takes a function, and returns a new function based on the return value of the former function.

1
2
3
4
5
6
7
8
var poppaFunction = function(poppa){
  return function(msg){
    console.log('Hello, ' + poppa + "! " + msg);
  }
}

var babyFunction = poppaFunction("Big Papa");
babyFunction("Have a great night!");

and the same in Ruby

1
2
3
4
5
6
poppa_lambda = lambda do |poppa|
  lambda {|msg| puts "Hello #{poppa}! #{msg}"}
end

baby_lambda = poppa_lambda.call("Big Papa")
baby_lambda.call("Have a great night!")

One important distinction is the the differences in return values. In JavaScript, functions must return a value, otherwise they return undefined. In ruby, the last executed line of a method/lambda/block is what’s returned, unless you use and explicit return.

Differences

We’ve seen a few differences already. Some with syntax (semicolon use, variable declarations), others with the object model (Ruby only responding to messages, and JavaScript taking any old property). In this section I want to dive into 2 things, primarily how these languages represent the current object, and equality.

self and this

Ruby’s rules for what changes self are pretty straight forward.

1
2
3
4
5
6
7
class RubySelf
  self # => RubySelf
  def my_instance_method
    self # => the instance
  end
end
self # => main

That’s pretty much it. There’s also instance_eval and class_eval. A topic for another time.

In JavaScript it’s not so simple.

We’ve seen that this points to current object inside an object.

1
2
3
4
5
6
7
var myObject = {
  getThis: function(){
    return this;
  }
}

myObject === myObject.getThis(); // true

But what about here?

1
2
3
4
5
6
7
8
var myFunction = function(){
  this.value = "A value";
  return this
};

var result = myFunction();
value; // "A value" We put it on the global scope!!!!
result === window // true OMG!!!

this inside a regular function points to the root object. In browser that’s window.

Again, strict mode saves our butts here.

1
2
3
4
5
6
7
var myFunction = function(){
  'use strict';
  this.value = "A value";
  return this
};

var result = myFunction(); // TypeError: Cannot set property 'value' of undefined

How about this one?

1
2
3
4
5
6
7
8
9
10
11
12
var myObject = {
  collection: [1,2,3],
  iterate: function(){
    this.collection.forEach(function(item){
      this.collection.push(item * item); // This gon break.
    })
  }
};

myObject.collection;
myObject.iterate(); // TypeError: Cannot read property 'push' of undefined
myObject.collection;

This new function changed this back to the default this, the window in the case of the browser.

In cases like this we have to keep track of our previous self in a variable. Usually this variable is named _that or self.

1
2
3
4
5
6
7
8
9
10
11
12
13
var myObject = {
  collection: [1,2,3],
  iterate: function(){
    var self = this;
    this.collection.forEach(function(item){
      self.collection.push(item * item); // This gon break.
    })
  }
};

myObject.collection;
myObject.iterate(); // TypeError: Cannot read property 'push' of undefined
myObject.collection; // [1, 2, 3, 1, 4, 9]

Sharp viewers know forEach takes an optional argument for the object to stand in as this.

1
2
3
this.collection.forEach(function(item){
  this.collection.push(item * item); // This gon break.
}, this) // this this is the original this not that this. :-)

Equality

Ruby’s equality testing has 2 forms: == and ===. The == tests for equality as defined by the object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
5 == 5
"Steven" == "Steven"

class MyObject
  attr_reader :description
  def initialize(description)
    @description = description
  end

  def ==(other)
    other.is_a?(MyObject) && self.description == other.description
  end
end

obj1 = MyObject.new("A plain object")
obj2 = MyObject.new("A plain object")
obj1 == obj2 # => true

And Case equality, used for case statements. In a new object, === delegates to ==

1
2
(1..10) === 5
/even/ === "Steven"

JavaScript has no way to override equality on objects. You CAN use equality on primitives like strings and numbers, but here lies the danger. This is from “JavaScript: The Good Parts”

1
2
3
4
0 == '' // true
false == 'false' // false
false == '0' // true
null == undefined // true

Wat. The == in JavaScript tries to coerce or convert the things being compared to be the same type, which might sound helpful, but it’s not. Use === and !== to ensure you’re comparing type and value.

1
2
3
4
0 === '' // false
false === 'false' // false
false === '0' // false
null === undefined // false as it should be!

Learning your first programming language is hard. I hope I’ve shown you that learning your second one, isn’t that bad.

Happy Clacking!

comments powered by Disqus