Things I Learned from Learn to Code HTML & CSS, Lesson 6

Here are some things I learned from Lesson 6: Working with Typography of Learn to Code HTML & CSS by Shay Howe.

Typeface vs. Font

A typeface is what we see. It is the artistic impression of how text looks, feels, and reads.

A font is a file that contains a typeface. Using a font on a computer allows the computer to access the typeface.

It’s like comparing a song and an MP3.

Changing Font Properties

Font Variant

The font-variant property accepts three values: normal, small-caps, and inherit. Use this to switch typefaces between normal and small caps variants.

Font Weight

Typeface Weights

Attempting to use a weight that’s not available for a given typeface will cause those styles to default to the closest value.

Line Height

The value line-height: 150% equals line-height: 1.5 and is relative to the font-size of the element. You can also use other units, such as pixels.

You can also use line-height to vertically center a single line of text within an element, using the same property value for line-height and height:

.btn {
  height: 22px;
  line-height: 22px;
}

This technique may be seen with buttons, alert messages, and other single-line text blocks.

Shorthand Font Properties

You can use shorthand font properties: font: font-style font-variant font-weight font-size/line-height font-family.

Every property value is optional except font-size and font-family.

For example:

html {
  font: italic small-caps bold 14px/22px "Helvetica Neue", Helvetica, Arial, sans-serif;
}

Applying Text Properties

Text Decoration

Multiple text-decoration values may be applied to an element at once by space-separating each keyword within the value.

Text Indent

The text-indent property can be used to indent the first line of text within an element. Positive values will indent text inward, while negative values will indent text outward.

Text shadow

The text-shadow property takes four values. The first determines the shadow’s horizontal offset, the second determines the shadow’s vertical offset, and the third determines the shadow’s blur radius. The fourth is the shadow’s color.

Multiple text shadows can be chained together using comma-separated values.

Box Shadow

If we’d like to place a shadow on the element as a whole, we can use the box-shadow property.

In addition to the three length values and the color value, the box-shadow property also accepts an optional fourth length value, before the color value, for the spread of a shadow. A positive value will expand the shadow larger than the size of the element it’s applied to, and a negative value will shrink the shadow to be smaller than the element.

Lastly, the box-shadow property may include an optional inset value at the beginning of the value to place the shadow inside an element as opposed to outside the element.

Letter Spacing

Using a relative length value with the letter-spacing property will help ensure that we maintain the correct spacing between letters as the font-size of the text is changed.

Embedding Web Fonts

The safest of the web-safe fonts are listed here:

  • Arial
  • Garamond
  • Lucida Sans, Lucida Grande, Lucida
  • Tahoma
  • Trebuchet
  • Courier New, Courier
  • Georgia
  • Palatino Linotype
  • Times New Roman, Times
  • Verdana

Including Citations & Quotes

In general, follow these rules:

  • <cite>: Used to reference a creative work, author, or resource
  • <q>: Used for short, inline quotations
  • <blockquote>: Used for longer external quotations

Dialogue & Prose Citation

An optional attribute to include on the <q> (or <blockquote>) element is the cite attribute. It acts as a citation reference to the quote in the form of a URL. This attribute doesn’t alter the appearance of the element; it simply adds value for screen readers and other devices. Because of that, it’s also helpful to provide a hyperlink to this source next to the actual quotation.

Things I Learned from Learn to Code HTML & CSS, Lesson 5

Here are some things I learned from Lesson 5: Positioning Content of Learn to Code HTML & CSS by Shay Howe.

Positioning with Floats

Floats in Practice

Floats May Change an Element’s Display Value

The float property relies on an element having a display value of block, and may alter an element’s default display value if it is not already displayed as a block-level element.

Clearing & Containing Floats

Containing Floats

To contain floats, the floated elements must reside within a parent element. It will act as a container, leaving the flow of the document completely normal outside of it.

.group:before,
.group:after {
  content: "";
  display: table;
}
.group:after {
  clear: both;
}
.group {
  clear: both;
  *zoom: 1;
}

The :before and :after pseudo-elements ara displayed as table-level elements, much like block-level elements. The :after element is clearing the floats within the container element. Lastly, the container element itself also clears any floats that may appear above it. It also includes a little trickery to get older browsers to play nicely.

The technique is known as a “clearfix”. We’ve chosen to use the class name group, as it expresses the content better.

Positioning with Inline-Block

The display: inline-block value will display elements within a line while allowing them to accept all box model properties. This allows us to take full advantage of the box model without having to worry about clearing any floats.

Inline-Block in Practice

Because inline-block elements are displayed on the same line as one another, they include a single space between them.

Removing Spaces Between Inline-Block Elements

The first solution is to put each new <section> element’s opening tag on the same line as the previous <section> element’s closing tag:

<section>
...
</section><section>
...
</section><section>
...
</section>

Writing inline-block elements this way ensures that the space between inline-block elements within HTML doesn’t exist.

Another way is to open a HTML comment directly after an inline-block element’s closing tag:

<section>
...
</section><!--
--><section>
...
</section><!--
--><section>
...
</section>

Creating Reusable Layouts

One approach is to use inline-block elements to create the grid of a page and then use floats to wrap content around a given element.

Things I Learned from Learn to Code HTML & CSS, Lesson 4

Here are some things I learned from Lesson 4: Opening the Box Model of Learn to Code HTML & CSS by Shay Howe.

Working with the Box Model

Width & Height

Width

Inline-level elements cannot have a fixed size, thus the width and height properties are only relevant to non-inline elements.

Margin & Padding

Margin

Vertical margins, margin-top and margin-bottom, are not accepted by inline-level elements. They are, however, accepted by block-level and inline-block elements.

Padding

The padding property, unlike the margin property, works vertically on inline-level elements. This vertical padding may blend into the line above or below the given element, but it will be displayed.

Box Sizing

CSS3 introduced the box-sizing property, which allows us to change exactly how the box model works and how an element’s size is calculated.

Content Box

The content-box value is the default value, leaving the box model as an additive design. The size of an element begins with the width and height properties, and then any padding, border, or margin property values are added on from there.

Padding Box (Deprecated)

The padding-box value alters the box model by including any padding property values within the width and height of an element. If an element has a width of 400 pixels and a padding of 20 pixels around every side, the actual width will remain 400 pixels. As any padding values increase, the content size within an element shrinks proportionately.

If we add a border or margin, those values will be added to the width and height properties to calculate the full box size.

The padding-box value has been deprecated and should not be used.

Border Box

Lastly, the border-box value alters the box model so that any border or padding property values are included within the width and height of an element. If an element has a width of 400 pixels, a padding of 20 pixels around every side, and a border of 10 pixels around every side, the actual width will remain 400 pixels.

If we add a margin, those values will need to be added to calculate the full box size.

Picking a Box Size

The best box-sizing value to use is border-box. It makes the math much easier.

Things I Learned from Learn to Code HTML & CSS, Lesson 3

Here are some things I learned from Lesson 3: Getting to Know CSS of Learn to Code HTML & CSS by Shay Howe.

Calculating Specificity

Every selector in CSS has a specificity weight that, along with its placement in the cascade, identifies how its styles will be rendered.

  • The type selector has the lowest specificity weight and holds a point value of 0-0-1.
  • The class selector has a medium specificity weight and holds a point value of 0-1-0.
  • The ID selector has a high specificity weight and holds a point value of 1-0-0.

The first column counts ID selectors, the second column counts class selectors, and the third column counts type selectors.

The higher the specificity weight of a selector, the more superiority the selector is given when a styling conflict occurs. For example, if a paragraph element is selected using a type selector in one place and an ID selector in another, the ID selector will take precedence over the type selector regardless of where the ID selector appears on the cascade.

Combining selectors

Spaces Within Selectors

The best practice is not to prefix a class selector with a type selector. Generally we want to select any element with a given class, not just one type of element.

Specificity Within Combined Selectors

The selector .hotdog p has one class selector (0-1-0) and one type selector (0-0-1). The total combined point value would be 0-1-1.

The selector .hotdog p.mustard has two class selectors and one type selector. The combined point value is 0-2-1.

Comparing the two selectors, the second one has a higher specificity weight and point value. As such it will take precedence within the cascade.

The higher our specificity weights rise, the more likely our cascade is to break.

Layering Styles with Multiple Classes

One way to keep the specificity weights of our selectors low is to be as modular as possible, sharing similar styles from element to element. And one way to be as modular as possible is to layer on different styles by using multiple classes.

Common CSS Property Values

Colors

HSL & HSLa Colors

HSL stands for hue, saturation, and lightness.

The hue is a unitless number from 0 to 360. The value identifies the degree of a color on the color wheel.

The saturation value identifies how saturated with color the hue is, with 0 being grayscale and 100% being fully saturated.

The lightness identifies how dark or light the hue value is, with 0 being completely black and 100% being completely white.

Things I Learned from Learn to Code HTML & CSS, Lesson 2

Here are some things I learned from Lesson 2: Getting to Know HTML of Learn to Code HTML & CSS by Shay Howe.

Identifying Divisions & Spans

<div>s and <span>s are HTML elements that act as containers solely for styling purposes.

Block vs. Inline Elements

Block-level elements begin on a new line, stacking on top of the other, and occupy any available width. They may wrap inline elements. We’ll most commonly see them used for larger pieces of content, such as paragraphs.

Inline-level elements do not begin on a new line. They fall into the normal flow of the document, lining up one after the other, and only maintain the width of their content. They cannot wrap block elements. We’ll usually see them with smaller pieces of content, such as a few words.

Choose <class> and <id> attribute values based on content, not style.

Building Structure

Structurally based elements provide semantic value.

Header

The <header> element identifies the top of a page, article, section, or other segment of a page.

Navigation

The <nav> element identifies a section of major navigational links on the page, such as global navigation, a table of contents, or previous/next links.

Article

The <article> element identifies a section of independent, self-contained content that may be independently distributed or reused.

Section

The <section> element identifies a thematic grouping of content.

Deciding Between <article>, <section>, or <div> elements

If the content is being grouped solely for styling purposes, use the <div> element.

If the content adds to the document outline and it can be independently redistributed, use the <article> element.

If the content adds to the document outline and represents a thematic group of content, use the <section> element.

Aside

The <aside> element holds content, such as sidebars, inserts, or brief explanations, that is tangentially related to the content surrounding it. When used within an <article> element, for example, the <aside> element may identify content related to the author of the article.

Footer

The <footer> element identifies the closing or end of a page, article, section, or other segment of a page. Content within the <footer> element should be relative information and not diverge from the document or section it is included within.

Creating Hyperlinks

Wrapping Block-Level Elements with Anchors

By nature, <a> is an inline element, and, according to web standards, inline-level elements may not wrap block-level elements. With HTML5, however, anchor elements specifically have permission to wrap any level elements. This is a break from the standard convention, but it’s permissible in order to enable entire blocks of content on a page to become links.

Linking to an Email Address

To add a subject line to a mailto link, include the subject= parameter after the email address. The first parameter following the email address must begin with a question mark, ?, to bind it to the hyperlink path. Spaces must be encoded using %20.

To add body text, use the body= parameter. Use the ampersand, &, to separate multiple parameters. Line breaks must be encoded using %0A.

Things I Learned from Learn to Code HTML & CSS, Lesson 1

Here are some things I learned from Lesson 1: Building Your First Web Page of Learn to Code HTML & CSS by Shay Howe.

Setting Up the HTML Document Structure

The document type declaration, or <!DOCTYPE html>, informs web browsers which version of HTML is being used and is placed at the very beginning of the HTML document. Because we’ll be using the latest version of HTML, our document type declaration is simply <!DOCTYPE html>.

All of the visible content within the web page will fall within the <body> element.

The <head> element includes the character encoding of the page via the <meta charset="utf-8"> tag.

Self-Closing Elements

Here’s a list of common self-closing elements:

  • <meta>
  • <br>
  • <img>
  • <meta>
  • <wbr>
  • <embed>
  • <input>
  • <param>
  • <hr>
  • <link>
  • <source>

The structure that makes use of the <!DOCTYPE html> document type and <html>, <head> and <body> elements is quite common. We’ll be using it often as we create new HTML documents.

Code Validation

The W3C has built both HTML and CSS validators that will scan code for mistakes. Validating our code not only helps it render properly across all browsers, but also helps teach us the best practices for writing code.

Using CSS Resets

You can use CSS resets to make various elements look similar on different browsers. Popular ones are Eric Meyer’s reset and Normalize.css.

Things I Learned from You Don’t Know JS: this & Object Prototypes, Appendix A

Here are some things I learned from Appendix A: ES6 class of You Don’t Know JS: this & Object Prototypes by Kyle Simpson.

class

Let’s revisit the Widget / Button example from Chapter 6:

class Widget {
	constructor(width, height) {
		this.width = width || 50;
		this.height = height || 50;
		this.$elem = null;
	}
	render($where) {
		if (this.$elem) {
			this.$elem.css( {
				width: this.width + "px",
				height: this.height + "px"
			} ).appendTo( $where );
		}
	}
}

class Button extends Widget {
	constructor(width, height, label) {
		super( width, height );
		this.label = label || "Default";
		this.$elem = $( "<button>" ).text( this.label );
	}
	render($where) {
		super.render( $where );
		this.$elem.click( this.onClick.bind( this ) );
	}
	onClick(evt) {
		console.log( "Button '" + this.label + "' clicked!" );
	}
}

Beyond this syntax looking nicer, what problems does ES2015 solve?

  1. There’s no more references to .prototype cluttering the code.
  2. Button is declared directly to “inherit from” (aka extends) Widget, instead of having to use Object.create(..) or Object.setPrototypeOf(..).
  3. super(..) gives us a relative polymorphism capability, so that any method at one level of the chain can refer relatively one level up the chain to a method of the same name.
  4. class literal syntax can’t specify properties (only methods). In most cases, a property existing elsewhere but the end-chain “instances” is very likely a mistake, so the class syntax in a way is protecting you from mistakes.
  5. extends lets you extend even built-in object (sub)types, like Array or RegExp. This used to be very complex before ES2015.

class Gotchas

The class syntax may convince you a new “class” mechanism exists in JS as of ES2015. Not so. class is mostly just syntactic sugar on top of the existing [[Prototype]] mechanism.

That means class is not actually copying definitions statically at declaration time the way it does in traditional class-oriented languages.

For example:

class C {
	constructor() {
		this.num = Math.random();
	}
	rand() {
		console.log( "Random: " + this.num );
	}
}

var c1 = new C();
c1.rand(); // "Random: 0.304132..."

C.prototype.rand = function() {
	console.log( "Random: " + Math.round( this.num * 1000 ));
};

var c2 = new C();
c2.rand(); // "Random: 587"

c1.rand(); // "Random: 304" -- oops!!!

The question to ask yourself is: why are you choosing class syntax for something fundamentally different from classes?

class syntax does not provide a way to declare class member properties (only methods). If you need to do that to track shared state among instances, you end up going back to the ugly .prototype syntax:

class C {
	constructor() {
		// make sure to modify the shared state, not set a
		// shadowed property on the instances!
		C.prototype.count++;

		// here, `this.count` works as expected
		// via delegation
		console.log( "Hello: " + this.count );
	}
}

// add a property for shared state directly to
// prototype object
C.prototype.count = 0;

var c1 = new C();
// Hello: 1

var c2 = new C();
// Hello: 2

c1.count === 2; // true
c1.count === c2.count; // true

The biggest problem here is that it betrays the class syntax by exposing .prototype as an implementation detail.

Using this.count++ would implicitly create a shadowed .count property on both c1 and c2 objects, rather than updating the shared state. class offers no consolation from that issue, except presumably to imply that you shouldn’t be doing that at all.

Accidental shadowing is still a hazard:

class C {
	constructor(id) {
		// oops, gotcha, we're shadowing `id()` method
		// with a property value on the instance
		this.id = id;
	}
	id() {
		console.log( "Id: " + this.id );
	}
}

var c1 = new C( "c1" );
c1.id(); // TypeError -- `c1.id` is now the string "c1"

You might assume that super would always be bound to one level higher than whatever the current method’s position in the [[Prototype]] chain is. However, for performance reasons, super is not bound dynamically. It’s bound sort of “statically”, at declaration time.

If you assign functions around to different objects (which came from class definitions), the super mechanism under the covers is having to be re-bound each time. Depending on your syntactic approaches, super may not be properly bound (at least, not where you suspect).

class P {
	foo() { console.log( "P.foo" ); }
}

class C extends P {
	foo() {
		super.foo();
	}
}

var c1 = new C();
c1.foo(); // "P.foo"

var D = {
	foo: function() { console.log( "D.foo" ); }
};

var E = {
	foo: C.prototype.foo
};

// Link E to D for delegation
Object.setPrototypeOf( E, D );

E.foo(); // "P.foo"

For performance pragmatism reasons, super is derived at call-time from [[HomeObject]].[[Prototype]], where [[HomeObject]] is statically bound at creation time.

In this particular case, super.foo() is still resolving to P.foo(), since the method’s [[HomeObject]] is still C and C.Prototype is P.

Static > Dynamic?

The biggest problem of class is that it opts you into a syntax which seems to imply that once you declare a class, it’s a static definition of a future instantiated thing. You lose sight of the fact that C is an object, a concrete thing, which you can directly interact with.

In traditional class-oriented languages, you never adjust the definition of a class later, so the class design pattern doesn’t suggest such capabilities. However, the definition of any JS object is a fluid and mutable thing (unless you make it immutable).

ES2015 class is actually making things worse for JS and for clear and concise understanding.

Note: If you use the .bind(..) utility to make a hard-bound function, the function created is not subclassable with ES2015 extends like normal functions are.

Things I Learned from You Don’t Know JS: this & Object Prototypes, Chapter 6

Here are some things I learned from Chapter 6: Behavior Delegation of You Don’t Know JS: this & Object Prototypes by Kyle Simpson.

Towards Delegation-Oriented Design

We need to try to change our thinking from the class/inheritance design pattern to the behavior delegation design pattern.

Class Theory

Let’s say we have several similar tasks (“XYZ”, “ABC”, etc) that we need to model in our software.

With classes, we would define a general parent (base) class like Task, defining shared behavior for all the “alike” tasks. Then, you define child classes XYZ and ABC, both of which inherit from Task, and each of which adds specialized behavior.

To get the most out of inheritance, you would override some general Task method in your XYZ task, perhaps even using super to call to the base version of that method while adding more behavior to it.

After that, you can instantiate one or more copies of the XYZ child class, and use those instances to perform task “XYZ”. These instances have copies both of the general Task defined behavior and the specific XYZ behavior. After construction, you will only interact with these instances (and not the classes), as the instances have copies of all the behavior you need.

Delegation Theory

With behavior delegation, you will first define an object (not a class nor a function) called Task, and it will have methods on it that various tasks can use (read: delegate to). For each task (“XYZ”, “ABC”), you define an object to hold that task-specific data/behavior. You link your task-specific objects to the Task utility object, allowing them to delegate to it.

For example:

var Task = {
	setID: function(ID) { this.id = ID; },
	outputID: function() { console.log( this.id ); }
};

// make `XYZ` delegate to `Task`
var XYZ = Object.create( Task );

XYZ.prepareTask = function(ID, label) {
	this.setID( ID );
	this.label = label;
};

XYZ.outputTaskDetails = function() {
	this.outputID();
	console.log( this.label );
};

In the code above, Task and XYZ are not classes (or functions), they’re just objects. Let’s call this type of coding “OLOO” (Objects Linking to Other Objects) and the class-oriented approach “OO” (Object Oriented).

Some key points to notice:

  • Both id and label are data properties directly on XYZ, not on Task. In general, with [[Prototype]] delegation, you want state to be on the delegator (XYZ), not on the delegate (Task).
  • We avoid naming things the same at different levels of the [[Prototype]] chain because we want to avoid explicit pseudo-polymorphism. This design pattern also calls for more descriptive method names that can create easier to understand/maintain code.
  • The general utility methods on Task are available to us while interacting with XYZ,
    because XYZ can delegate to Task. Furthermore, implicit call-site this binding rules make sure that this in delegated Task methods points to XYZ.

Rather than organizing the objects in your mind vertically, with parents flowing down to children, think of objects side-by-side, as peers.

Note: Delegation is more properly used as an internal implementation than exposed directly in the API design.

Mutual Delegation (Disallowed)

You cannot create a cycle where two or more objects are mutually delegated to each other. If you make B linked to A, and then try to link A to B, you will get an error.

In theory, if all references you make were strictly present on one of the objects, mutual delegation would work. However, engine implementors have observed that it’s more performant to reject the possible infinite loops once at set-time than during run-time.

Debugged

In general, JS specification doesn’t control how browser developer tools should represent specific values/structures to a developer. As such, browsers/tools don’t always agree.

For example:

function Foo() {}

var a1 = new Foo();

a1;
// `Foo {}` on Chrome,
// `Object {  }` on Firefox

Chrome is saying “{} is an empty object that was constructed by a function named `Foo`”. Firefox is saying “{} is an empty object of general construction from Object”. The difference is that Chrome is actively tracking, as an internal property, the name of the actual function that did the construction, whereas other browsers don’t track that information.

Even if you change Foo.prototype.constructor to another function, Chrome will still output Foo. However, using OLOO-style code (Object.create(..) instead of new) and changing the Foo constructor would make Chrome refer to the new function! This is actually a bug that’s not a big deal in OLOO-style code because the constructor (which function was called with new?) is irrelevant.

Mental Models Compared

Let’s write an example program in both OO and OLOO style.

OO:

function Foo(who) {
	this.me = who;
}

Foo.prototype.identify = function() {
	return "I am " + this.me;
};

function Bar(who) {
	Foo.call( this, who );
}

Bar.prototype = Object.create( Foo.prototype );

Bar.prototype.speak = function() {
	alert( "Hello, " + this.identify() + "." );
};

var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );

b1.speak(); // "Hello, I am b1."
b2.speak(); // "Hello, I am b2."

OLOO:

var Foo = {
	init: function(who) {
		this.me = who;
	},
	identify: function() {
		return "I am " + this.me;
	}
};

var Bar = Object.create( Foo );

Bar.speak = function() {
	alert( "Hello, " + this.identify() + "." );
};

var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );

b1.speak(); // "Hello, I am b1."
b2.speak(); // "Hello, I am b2."

We take exactly the same advantage of [[Prototype]] delegation from b1 to Bar to Foo in the OLOO example as we do between b1, Bar.prototype and Foo.prototype in the OO example. In other words, we have the same three objects linked together.

But, importantly, we’ve greatly simplified all the other stuff going on. We don’t make things look like (but not behave) like classes, with constructors, prototypes and new calls.

If you can get the same functionality with OLOO style code as with “class” style code, but OLOO is simpler and has less things to think about, isn’t OLOO better?

Note: There’s an amazing amount of internal consistency in JS’s mechanisms. For instance, the ability of a JS function to access call(..), apply(..) and bind(..) is because functions themselves are objects, and function-objects also have a [[Prototype]] linkage, to the Function.prototype object.

Classes vs. Objects

Let’s look at more concrete code scenarios. We’ll first examine creating UI widgets (buttons, drop-downs, etc.).

Widget “Classes”

The OO design pattern would imply creating a parent class (perhaps called Widget) with all the common base widget behavior, and then child derived classes for specific widget types (like Button).

Let’s examine how we’d implement the “class” design in pure JS without any “class” helper library or syntax:

// Parent class
function Widget(width, height) {
	this.width = width || 50;
	this.height = height || 50;
	this.$elem = null;
}

Widget.prototype.render = function($where) {
	if (this.$elem) {
		this.$elem.css( {
			width: this.width + "px",
			height: this.height + "px"
		} ).appendTo( $where );
	}
};

// Child class
function Button(width, height, label) {
	// "super" constructor call
	Widget.call( this, width, height );
	this.label = label || "Default";

	this.$elem = $( "<button>" ).text( this.label );
}

// make `Button` "inherit" from `Widget`
Button.prototype = Object.create( Widget.prototype );

// override base "inherited" `render(..)`
Button.prototype.render = function($where) {
	// "super" call
	Widget.prototype.render.call( this, $where );
	this.$elem.click( this.onClick.bind( this ) );
};

Button.prototype.onClick = function(evt) {
	console.log( "Button '" + this.label + "' clicked!" );
};

$( function documentReady() {
	var $body = $( document.body );
	var btn1 = new Button( 125, 30, "Hello" );
	var btn2 = new Button( 150, 40, "World" );

	btn1.render( $body );
	btn2.render( $body );
} );

OO design patterns tell us to declare a base render(..) in the parent class, then override it in our child class, but not to replace it. This way, it can augment the base functionality with button-specific behavior.

Notice the ugliness of explicit pseudo-polymorphism with Widget.call and Widget.prototype.render.call references for faking “super” calls from the child “class” methods.

ES6 class sugar

We’d implement the same code using ES2015 class like this:

class Widget {
	constructor(width, height) {
		this.width = width || 50;
		this.height = height || 50;
		this.$elem = null;
	}
	render($where) {
		if (this.$elem) {
			this.$elem.css( {
				width: this.width + "px",
				height: this.height + "px"
			} ).appendTo( $where );
		}
	}
}

class Button extends Widget {
	constructor(width, height, label) {
		super(width, height);
		this.label = label || "Default";
		this.$elem = $( "<button>" ).text( this.label );
	}
	render($where) {
		super.render( $where );
		this.$elem.click( this.onClick.bind( this ) );
	}
	onClick(evt) {
		console.log( "Button '" + this.label + "' clicked!" );
	}
}

$( function documentReady() {
	var $body = $( document.body );
	var btn1 = new Button( 125, 30, "Hello" );
	var btn2 = new Button( 150, 40, "World" );

	btn1.render( $body );
	btn2.render( $body );
} );

This syntax is more elegant than the previous one (especially the presence of super(..)) but still these are not real classes, as they operate on top of the [[Prototype]] mechanism.

Using any type of class-like syntax with JavaScript is a choice that is likely to cause extra headache.

Delegating Widget Objects

Finally, here’s the OLOO style implementation:

var Widget = {
	init: function(width, height) {
		this.width = width || 50;
		this.height = height || 50;
		this.$elem = null;
	},
	insert: function($where) {
		if (this.$elem) {
			this.$elem.css( {
				width: this.width + "px",
				height: this.height + "px"
			} ).appendTo( $where );
		}
	}
};

var Button = Object.create( Widget );

Button.setup = function(width, height, label) {
	// delegated call
	this.init( width, height );
	this.label = label || "Default";

	this.$elem = $( "<button>" ).text( this.label );
};
Button.build = function($where) {
	// delegated call
	this.insert( $where );
	this.$elem.click( this.onClick.bind( this ) );
};
Button.onClick = function(evt) {
	console.log( "Button '" + this.label + "' clicked!" );
};

$( function documentReady() {
	var $body = $( document.body );

	var btn1 = Object.create( Button );
	btn1.setup( 125, 30, "Hello" );

	var btn2 = Object.create( Button );
	btn2.setup( 150, 40, "World" );

	btn1.build( $body );
	btn2.build( $body );
} );

Now we don’t think of Widget as a parent and Button as a child. They’re both just objects.

We didn’t share the same method name render(..) in both objects, but instead chose different names insert(..) and build(..) that were more descriptive of what task each does specifically.

This also allows simple, relative and delegated calls to this.init(..) and this.insert(..) instead of explicit pseudo-polymorphic Widget.call and Widget.prototype.render.call.

Syntactically, we also don’t have any constructors, .prototype or new present, as they are unnecessary.

What was previously one call (var btn1 = new Button(..)) is now two calls (var btn1 = Object.create(Button) and btn1.setup(..). This is actually a good thing because you can do the construction and initialization of objects separately. This supports the principle of separation of concerns.

Simpler Design

Behavior delegation as a pattern can actually lead to simpler code architecture. Let’s examine an example of handling authentication on a web page.

Here’s the OO version:

// Parent class
function Controller() {
	this.errors = [];
}
Controller.prototype.showDialog = function(title, msg) {
	// display title & message to user in dialog
};
Controller.prototype.success = function(msg) {
	this.showDialog( "Success", msg );
};
Controller.prototype.failure = function(err) {
	this.errors.push( err );
	this.showDialog( "Error", err );
};
// Child class
function LoginController() {
	Controller.call( this );
}
// Link child class to parent
LoginController.prototype = Object.create( Controller.prototype );

LoginController.prototype.getUser = function() {
	return document.getElementById( "login_username" ).value;
};
LoginController.prototype.getPassword = function() {
	return document.getElementById( "login_password" ).value;
};
LoginController.prototype.validateEntry = function(user, pw) {
	user = user || this.getUser();
	pw = pw || this.getPassword();

	if (!(user && pw)) {
		return this.failure( "Please enter a username & password!" );
	}
	else if (pw.length < 5) {
		return this.failure( "Password must be 5+ characters!" );
	}

	// got here? validated!
	return true;
};
// Override to extend base `failure()`
LoginController.prototype.failure = function(err) {
	// "super" call
	Controller.prototype.failure.call( this, "Login invalid: " + err );
};
// Child class
function AuthController(login) {
	Controller.call( this );
	// in addition to inheritance, we also need composition
	this.login = login;
}
// Link child class to parent
AuthController.prototype = Object.create( Controller.prototype );

AuthController.prototype.server = function(url, data) {
	return $.ajax( {
		url: url,
		data: data
	} );
};
AuthController.prototype.checkAuth = function() {
	var user = this.login.getUser();
	var pw = this.login.getPassword();

	if (this.login.validateEntry( user, pw )) {
		this.server( "/check-auth", {
			user: user,
			pw: pw
		} )
		.then( this.success.bind( this ) )
		.fail( this.failure.bind( this ) );
	}
};
// Override to extend base `success()`
AuthController.prototype.success = function() {
	// "super" call
	Controller.prototype.success.call( this, "Authenticated!" );
};
// Override to extend base `failure()`
AuthController.prototype.failure = function(err) {
	// "super" call
	Controller.prototype.failure.call( this, "Auth Failed: " + err );
};
var auth = new AuthController(
	// in addition to inheritance, we also need composition
	new LoginController()
);
auth.checkAuth();

We have base behaviors that all controllers share, which are success(..), failure(..) and showDialog(..). Our child classes LoginController and AuthController override failure(..) and success(..) to augment the default base class behavior.

There's also some composition: AuthController needs to know about LoginController, so we instantiate it and keep a class member property this.login to reference it.

Note: We could've made AuthController inherit from LoginController, or vice versa. However, this would've made little sense because neither of them are specializing base behavior of the other. This is the reason we chose to use composition instead.

De-class-ified

Here's the OLOO version of the example:

var LoginController = {
	errors: [],
	getUser: function() {
		return document.getElementById( "login_username" ).value;
	},
	getPassword: function() {
		return document.getElementById( "login_password" ).value;
	},
	validateEntry: function(user, pw) {
		user = user || this.getUser();
		pw = pw || this.getPassword();

		if (!(user && pw)) {
			return this.failure( "Please enter a username & password!" );
		}
		else if (pw.length < 5) {
			return this.failure( "Password must be 5+ characters!" );
		}

		// got here? validated!
		return true;
	},
	showDialog: function(title, msg) {
		// display success message to user in dialog
	},
	failure: function(err) {
		this.errors.push( err );
		this.showDialog( "Error", "Login invalid: " + err );
	}
};
// Link `AuthController` to delegate to `LoginController`
var AuthController = Object.create( LoginController );

AuthController.errors = [];
AuthController.checkAuth = function() {
	var user = this.getUser();
	var pw = this.getPassword();

	if (this.validateEntry( user, pw )) {
		this.server( "/check-auth", {
			user: user,
			pw: pw
		} )
		.then( this.accepted.bind( this ) )
		.fail( this.rejected.bind( this ) );
	}
};
AuthController.server = function(url, data) {
	return $.ajax( {
		url: url,
		data: data
	} );
};
AuthController.accepted = function() {
	this.showDialog( "Success", "Authenticated!" );
};
AuthController.rejected = function(err) {
	this.failure( "Auth Failed: " + err );
};

Since AuthController is just an object (so is LoginController), we don't need to instantiate it to perform our task. All we need is:

AuthController.checkAuth();

AuthController and LoginController are just objects, horizontal peers of each other. We chose to have AuthController delegate to LoginController, and it would've been just as valid to choose the other way around.

We only have two entities, not three as before. We didn't need a base Controller class to "share" behavior between the two. We also don't need to instantiate our classes, because there are no classes, just the objects. Furthermore, there's no need for composition as delegation gives the two objects the ability to cooperate differentially as needed.

Lastly, we avoided the polymorphic pitfalls of class-oriented design by not having the names success(..) and failure(..) be the same on both objects.

Bottom line: we end up with the same capability, but a significantly simpler design.

Nicer Syntax

One of the things that makes ES2015's class so attractive is the short-hand syntax for declaring class methods:

class Foo {
	methodName() { /* .. */ }
}

As of ES2015, we can use concise method declarations in any object literal, like this:

var LoginController = {
	errors: [],
	getUser() { // no `function`!
		// ...
	}, // <-- note the comma as opposed to the `class` syntax
	getPassword() {
		// ...
	}
	// ...
};

In addition, the clunkier syntax (like for the AuthController definition) where you're assigning properties individually and not using an object literal can be re-written like this:

// use nicer object literal syntax w/ concise methods!
var AuthController = {
	errors: [],
	checkAuth() {
		// ...
	},
	server(url, data) {
		// ...
	}
	// ...
};

// NOW, link `AuthController` to delegate to `LoginController`
Object.setPrototypeOf( AuthController, LoginController );

You don't have to opt for class complexity to get nice clean object syntax.

Unlexical

There is one drawback to concise methods. Consider:

var Foo = {
	bar() { /*..*/ },
	baz: function baz() { /* .. */ }
};

This is equal to:

var Foo = {
	bar: function() { /*..*/ },
	baz: function baz() { /*..*/ }
};

The bar() short-hand became an anonymous function expression. Compare that to the manually specified named function expression function baz() which has a lexical name identifier baz.

Lack of a name identifier on an anonymous function:

  1. makes debugging stack traces harder
  2. makes self-referencing (recursion, event (un)binding, etc) harder
  3. makes code harder to understand

Items 1 and 3 don't apply to concise methods, but item 2 is a problem. Consider:

var Foo = {
	bar: function(x) {
		if (x < 10) {
			return Foo.bar( x * 2 );
		}
		return x;
	},
	baz: function baz(x) {
		if (x < 10) {
			return baz( x * 2 );
		}
		return x;
	}
};

A manual Foo.bar( x * 2 ) type of reference wouldn't be possible in many cases, such as when the function is being shared in delegation across different objects, using this binding, etc.

If you run into such issues, forgo the concise method syntax just for that declaration in favor of the manual named function expression declaration form.

Introspection

In class oriented programming, type introspection means inspecting an instance to find out what kind of object it is, or how it was created.

Consider:

function Foo() {
	// ...
}
Foo.prototype.something = function() {
	// ...
};

var a1 = new Foo();

// later

if (a1 instanceof Foo) {
	a1.something();
}

Because Foo.prototype (not Foo!) is in the [[Prototype]] chain of a1, the instanceof operator confusingly pretends to tell us that a1 is an instance of the Foo "class".

Of course, there is no Foo class, only a normal function Foo, which happens to have a reference to an arbitrary object Foo.prototype that a1 happens to be delegation-linked to. instanceof is actually telling us whether a1 and (the arbitrary object referenced by) Foo.prototype are related.

This means that you have to have a function that holds a reference to that object; you can't just directly ask if the two objects are related.

For type introspection, here are the various checks you might need to perform:

function Foo() { /*..*/ }
Foo.prototype.something = function() { /*..*/ };

function Bar() { /*..*/ }
Bar.prototype = Object.create( Foo.prototype );

var b1 = new Bar( "b1" );

// relating `Foo` and `Bar` to each other
Bar.prototype instanceof Foo; // true
Object.getPrototypeOf( Bar.prototype ) === Foo.prototype; // true
Foo.prototype.isPrototypeOf( Bar.prototype ); // true

// relating `b1` to both `Foo` and `Bar`
b1 instanceof Foo; // true
b1 instanceof Bar; // true
Object.getPrototypeOf( b1 ) === Bar.prototype; // true
Foo.prototype.isPrototypeOf( b1 ); // true
Bar.prototype.isPrototypeOf( b1 ); // true

Another common pattern for type instrospection is called "duck typing". This term comes from the adage, "if it looks like a duck, and it quacks like a duck, it must be a duck".

Example:

if (a1.something) {
	a1.something();
}

Rather than inspecting for a relationship between a1 and an object that holds the delegatable something() function, we test whether a1 has the capability to call .something() (regardless of if the method is directly on a1 or delegated to some other object).

One problem comes with ES2015 Promises. Testing whether any object reference is a Promise relies on whether it has a then() function on it. If you have any non-Promise object that happens to have a then() method on it, you should keep away from the ES2015 Promise mechanism to avoid broken assumptions.

Let's take a look at type introspection with OLOO-style code:

var Foo = { /*..*/ };
var Bar = Object.create( Foo );
var b1 = Object.create( Bar );

// relating `Foo` and `Bar` to each other
Foo.isPrototypeOf( Bar ); // true
Object.getPrototypeOf( Bar ) === Foo; // true

// relating `b1` to both `Foo` and `Bar`
Foo.isPrototypeOf( b1 ); // true
Bar.isPrototypeOf( b1 ); // true
Object.getPrototypeOf( b1 ) === Bar; // true

We're not using instanceof anymore, because it's pretending to have something to do with classes. We just ask the question, "are you a prototype of me?" These checks are less complicated and confusing than the previous set of introspection checks, so OLOO wins once again.