Proto + Templates
// WEB DEV · MISSED CLASS RECOVERY

Prototypal Inheritance
& Template Engines

Everything from tonight's lecture, rebuilt as a hands-on lab. Read each section, poke the interactive demos, run code in the playgrounds, then prove it to yourself with the quiz. Check off sections as you go; your progress saves in this browser.

00Start here

The whole lecture in 60 seconds, plus a study plan.

Prototypal inheritance, in one breath

Every JavaScript object has a hidden link to another object called its prototype. When you ask for a property the object doesn't have, JavaScript follows that link and checks the prototype, then its prototype, and so on until it finds the property or hits null. That linked list is the prototype chain, and it's how objects share methods. class is just friendlier syntax on top of it.

Template engines, in one breath

A template engine takes an HTML file with placeholders (like <%= user.name %>) plus a data object, and produces finished HTML. On a Node/Express server you call res.render('page', data) and the engine (EJS, Handlebars, Pug...) fills in the blanks, loops over arrays, and escapes user content so it's safe.

Suggested study plan (about 90 minutes)

01Objects & the hidden [[Prototype]] link

JavaScript doesn't copy behavior from classes. Objects delegate to other objects.

In languages like Java or C#, a class is a blueprint and objects are stamped out from it. JavaScript works differently: objects are linked to other live objects. If an object can't answer a property request itself, it delegates the question to its prototype.

jsconst animal = {
  eats: true,
  describe() { return `I eat: ${this.eats}`; }
};

// Make a new object whose prototype IS animal
const rabbit = Object.create(animal);
rabbit.jumps = true;

console.log(rabbit.jumps);      // true   (own property)
console.log(rabbit.eats);       // true   (found on animal via the chain)
console.log(rabbit.describe()); // "I eat: true"  (method borrowed from animal)
console.log(Object.getPrototypeOf(rabbit) === animal); // true

The three names that confuse everyone

NameWhat it isWhere it lives
[[Prototype]]The actual hidden internal link from an object to its prototype. You can't type this name directly; it's spec language.Every object
__proto__An old getter/setter that exposes [[Prototype]]. Works in browsers, but it's legacy. Prefer Object.getPrototypeOf(obj) and Object.setPrototypeOf(obj, proto).Inherited from Object.prototype
.prototypeA regular property on functions (and classes). It's the object that will become the [[Prototype]] of anything created with new ThatFunction().Only on functions/classes (not arrow functions)
Memory trick: .prototype is a "future parent" slot on a constructor. __proto__ / [[Prototype]] is an object's "actual parent". So for const d = new Dog(): Object.getPrototypeOf(d) === Dog.prototype.

Own vs inherited properties

jsrabbit.hasOwnProperty('jumps'); // true
rabbit.hasOwnProperty('eats');  // false (it's inherited)
Object.hasOwn(rabbit, 'eats');   // false (modern version of the same check)
'eats' in rabbit;                // true  ("in" checks the whole chain)
Object.keys(rabbit);              // ['jumps'] (own enumerable only)
for (const k in rabbit) console.log(k); // jumps, eats, describe (for...in walks the chain!)

02The prototype chain, visualized

Pick a property and watch JavaScript hunt for it, one link at a time.

jsclass Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
  bark() { return `${this.name} says woof`; }
}
const rex = new Dog('Rex');
// click a property lookup above
Key rules: (1) Reading a property walks the chain. (2) Writing a property (rex.speak = ...) always creates an own property on rex, which shadows the inherited one. It never modifies the prototype. (3) The chain always ends at Object.prototype, whose prototype is null. A missing property returns undefined; calling a missing method throws TypeError: ... is not a function.

Why put methods on the prototype?

If you create 10,000 dogs, they all share one copy of bark living on Dog.prototype. Data that differs per object (like name) lives on each instance. Shared behavior lives on the prototype. That's the whole design.

03Three ways to set up inheritance

Same result under the hood. Your instructor may ask you to write all three.

The most direct form: "make a new object and link it to this one." No constructors, no new.

jsconst vehicle = {
  start() { return `${this.make} is starting`; }
};
const car = Object.create(vehicle);
car.make = 'Honda';
car.honk = function() { return 'Beep!'; };

const myCar = Object.create(car);   // chain: myCar -> car -> vehicle -> Object.prototype -> null
myCar.make = 'Civic';
console.log(myCar.start()); // "Civic is starting"
console.log(myCar.honk());  // "Beep!"

// Bonus: an object with NO prototype at all (useful as a pure dictionary)
const bare = Object.create(null);
console.log(bare.toString); // undefined

The pre-2015 way, and still what class compiles down to conceptually. Know this one for exams: there are 4 required steps.

js// 1. Parent constructor: per-instance data
function Person(name) {
  this.name = name;
}
// 2. Shared methods go on the prototype
Person.prototype.greet = function() {
  return `Hi, I'm ${this.name}`;
};

function Student(name, school) {
  Person.call(this, name);   // 3a. borrow parent's constructor (like super())
  this.school = school;
}
// 3b. Link Student.prototype -> Person.prototype
Student.prototype = Object.create(Person.prototype);
// 4. Repair the constructor pointer (Object.create wiped it)
Student.prototype.constructor = Student;

Student.prototype.study = function() {
  return `${this.name} studies at ${this.school}`;
};

const s = new Student('Weston', 'UMKC');
console.log(s.greet()); // "Hi, I'm Weston"  (from Person.prototype)
console.log(s.study()); // "Weston studies at UMKC"
console.log(s instanceof Person); // true
Classic mistake: Student.prototype = Person.prototype (no Object.create). Now both names point at the same object, so adding study to Student also adds it to every Person. Always use Object.create.

Modern syntax. Cleaner, but it's still prototypes underneath: typeof Dog is "function", and methods still land on Dog.prototype.

jsclass Person {
  constructor(name) { this.name = name; }
  greet() { return `Hi, I'm ${this.name}`; }
  static species() { return 'Homo sapiens'; } // lives on Person itself, not instances
}

class Student extends Person {
  constructor(name, school) {
    super(name);            // MUST call before using "this"
    this.school = school;
  }
  study() { return `${this.name} studies at ${this.school}`; }
  greet() {                   // override...
    return super.greet() + ' (student)'; // ...and still call the parent version
  }
}

const s = new Student('Weston', 'UMKC');
console.log(s.greet());                                  // "Hi, I'm Weston (student)"
console.log(typeof Student);                            // "function"
console.log(Object.getPrototypeOf(Student.prototype) === Person.prototype); // true

What class adds beyond sugar

  • Must be called with new (calling Person() throws).
  • Class bodies run in strict mode automatically.
  • Methods are non-enumerable, so they don't show up in for...in.
  • Classes are not usable before their declaration line (no hoisting like function declarations).
  • Extras: static, private #fields, getters/setters, super.

04What the new keyword actually does

Four steps. Click through them, then see it reimplemented by hand.

jsconst rex = new Dog('Rex');
  1. Create a brand-new empty object: {}
  2. Link its [[Prototype]] to Dog.prototype (this is where inheritance happens)
  3. Run the constructor with this pointing at the new object, so this.name = 'Rex' sets an own property
  4. Return the new object automatically (unless the constructor explicitly returns a different object)
js · new, by handfunction myNew(Constructor, ...args) {
  const obj = {};                                          // step 1
  Object.setPrototypeOf(obj, Constructor.prototype);       // step 2
  const result = Constructor.apply(obj, args);             // step 3
  return (result !== null && typeof result === 'object') ? result : obj; // step 4
}
// Steps 1 + 2 in one line: const obj = Object.create(Constructor.prototype);
And instanceof? rex instanceof Animal just walks rex's chain asking "is Animal.prototype somewhere in here?" That's all it does.

05Common pitfalls (and exam traps)

1. Arrow functions as prototype methods

Dog.prototype.bark = () => this.name; // ✗
rex.bark(); // undefined: arrows don't get their own "this"
Dog.prototype.bark = function() { return this.name; }; // ✓

2. Methods inside the constructor

function Dog(n) {
  this.name = n;
  this.bark = function() {...}; // ✗ a new copy per dog
}

Works, but wastes memory. Shared behavior belongs on Dog.prototype.

3. Mutable data on the prototype

Dog.prototype.tricks = [];
a.tricks.push('sit');
b.tricks; // ['sit']  ✗ shared by ALL dogs!

Arrays/objects on a prototype are shared. Put per-instance data in the constructor.

4. Forgetting the constructor fix

Student.prototype = Object.create(Person.prototype);
new Student().constructor === Person; // ✗ true
Student.prototype.constructor = Student; // ✓

5. Forgetting super()

constructor(name) {
  this.x = 1; // ✗ ReferenceError: must call super first
  super(name);
}

6. Extending built-in prototypes

Array.prototype.last = function(){...}; // ✗ avoid

It affects every array in every library on the page and can clash with future JavaScript features. Write a helper function instead.

06JavaScript playground

Edit and run real code. console.log output appears below. Try breaking things on purpose.

playground.js · Ctrl/⌘ + Enter to run
// output appears here

07Why template engines exist

Because building HTML with string concatenation gets ugly, fast, and dangerous.

without a template engine ✗let html = '<ul>';
for (const u of users) {
  html += '<li class="' + (u.admin ? 'admin' : '')
       + '">' + u.name + '</li>';
}
html += '</ul>';
res.send(html);
// hard to read, easy to break,
// and u.name is NOT escaped (XSS risk)
with EJS ✓<ul>
  <% users.forEach(u => { %>
    <li class="<%= u.admin ? 'admin' : '' %>">
      <%= u.name %>
    </li>
  <% }) %>
</ul>

// reads like HTML, auto-escapes u.name

How it works

Templateviews/users.ejs
HTML + placeholders
+
Data{ users: [...] }
from your route / DB
→
Engine1. compile to a function
2. call it with data
→
HTMLplain string sent
to the browser
  • Server-side rendering (SSR): the browser receives finished HTML. It never sees your template syntax.
  • Separation of concerns: routes handle data and logic; templates handle presentation.
  • Reuse: partials/includes let you write a header or nav once and use it on every page.
  • Security: engines escape output by default, turning <script> into harmless &lt;script&gt;.
Big picture: React/Vue render in the browser (client side). Template engines like EJS usually render on the server. Both solve "turn data into HTML."

08EJS syntax (Embedded JavaScript)

The most common engine in Express courses. If you know JavaScript, you already know 90% of it.

TagWhat it doesExample
<%= %>Output a value, HTML-escaped. Your default.<h1><%= title %></h1>
<%- %>Output raw, unescaped HTML. Use for includes or trusted HTML only.<%- include('partials/nav') %>
<% %>Run JavaScript, output nothing (loops, ifs, variables).<% if (user) { %>
<%# %>Comment. Not included in output.<%# TODO: fix %>
-%>Trim the newline after the tag (cleaner output).<% } -%>
views/profile.ejs<%- include('partials/header', { title: user.name }) %>

<h1>Welcome, <%= user.name %>!</h1>

<% if (user.isAdmin) { %>
  <a href="/admin">Admin panel</a>
<% } else { %>
  <p>Standard account</p>
<% } %>

<h2>Your courses (<%= courses.length %>)</h2>
<ul>
  <% courses.forEach((course, i) => { %>
    <li><%= i + 1 %>. <%= course.title %></li>
  <% }) %>
</ul>

<%- include('partials/footer') %>
Security rule: never use <%- %> on anything a user typed (comments, names, bios). If user.bio is <script>stealCookies()</script>, <%= prints it as harmless text, but <%- runs it. That attack is called XSS (cross-site scripting).
Watch the braces: every { opened in a <% %> tag needs a matching } in a later tag. Most EJS errors in class are a missing <% } %> or <% }) %>.

09Setting up EJS in Express, step by step

Step 1: Install

terminalnpm init -y
npm install express ejs

Step 2: Folder structure

projectmy-app/
├── app.js
├── package.json
├── public/            ← static files (css, images)
│   └── style.css
└── views/             ← Express looks here by default
    ├── index.ejs
    ├── profile.ejs
    └── partials/
        ├── header.ejs
        └── footer.ejs

Step 3: Configure and render

app.jsconst express = require('express');
const app = express();

app.set('view engine', 'ejs');        // tell Express which engine to use
// app.set('views', './views');      // optional: default is already ./views
app.use(express.static('public'));   // serve /style.css etc.

app.get('/', (req, res) => {
  res.render('index', { title: 'Home', items: ['HTML', 'CSS', 'JS'] });
  // finds views/index.ejs (no extension needed), passes the object as variables
});

app.get('/users/:id', (req, res) => {
  const user = { name: 'Weston', isAdmin: false };   // pretend this came from a DB
  res.render('profile', { user, courses: [{ title: 'Web Dev' }] });
});

app.listen(3000, () => console.log('http://localhost:3000'));

Step 4: The view

views/index.ejs<!DOCTYPE html>
<html>
<head><title><%= title %></title><link rel="stylesheet" href="/style.css"></head>
<body>
  <h1><%= title %></h1>
  <ul>
    <% items.forEach(item => { %><li><%= item %></li><% }) %>
  </ul>
</body>
</html>

Step 5: Run it

terminalnode app.js
# open http://localhost:3000, then View Source: you'll see plain HTML, no EJS tags
Errors you'll likely hit: Failed to lookup view "index" means the file isn't in views/ or the name is misspelled. title is not defined means you forgot to pass it in the res.render object. Tip: res.locals.x = ... in middleware makes x available to every view.

10EJS vs Handlebars vs Pug

Same page, three engines. Your class probably uses one, but you should recognize all three.

EJSHandlebarsPug
PhilosophyReal JavaScript inside HTML"Logic-less": minimal logic, use helpersIndentation-based shorthand, no closing tags
Escaped output<%= name %>{{name}}p= name or #{name}
Raw output<%- html %>{{{html}}}p!= html
Loop<% items.forEach(i => { %>{{#each items}}...{{/each}}each i in items
Conditional<% if (x) { %>{{#if x}}...{{else}}...{{/if}}if x
Partials<%- include('nav') %>{{> nav}}include nav
Express setupnpm i ejsnpm i express-handlebarsnpm i pug
<h1><%= title %></h1>
<% if (students.length) { %>
  <ul>
  <% students.forEach(s => { %>
    <li><%= s.name %></li>
  <% }) %>
  </ul>
<% } else { %>
  <p>No students yet.</p>
<% } %>
<h1>{{title}}</h1>
{{#if students.length}}
  <ul>
  {{#each students}}
    <li>{{this.name}}</li>
  {{/each}}
  </ul>
{{else}}
  <p>No students yet.</p>
{{/if}}
h1= title
if students.length
  ul
    each s in students
      li= s.name
else
  p No students yet.
//- indentation IS the structure in Pug. Mixing tabs and spaces breaks it.

11Template playground

A working mini engine with Handlebars-style syntax, built right into this page. Edit the template or the data and it re-renders live.

Supports: {{path}} escaped · {{{path}}} raw · {{#each list}}...{{/each}} with {{this}} and {{@index}} · {{#if x}}...{{else}}...{{/if}} · {{#unless x}} · helpers {{upper x}} {{lower x}}

live render● ok
template.hbs
data.json
rendered in browser
generated html source (what the server would send)
Try this: load the "XSS demo" preset and compare how {{bio}} and {{{bio}}} render. Then change the data and watch only the output change, never the template. That's separation of concerns.

12Build your own template engine

Demystify it. A working engine is about 15 lines.

tiny-engine.jsfunction escapeHTML(value) {
  return String(value)
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}

function render(template, data) {
  // find every {{ something.path }}
  return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (match, path) => {
    // walk "user.address.city" one key at a time
    const value = path.split('.').reduce((obj, key) => obj?.[key], data);
    return escapeHTML(value ?? '');
  });
}

console.log(render('<h1>Hi {{ user.name }}</h1>', { user: { name: '<Weston>' } }));
// <h1>Hi &lt;Weston&gt;</h1>

How real engines like EJS do it: compile, then render

Instead of regex-replacing every request, EJS compiles your template once into a real JavaScript function, caches it, and calls it with new data each time. Conceptually:

template → compiled function (simplified)// Template:  <h1><%= title %></h1><% items.forEach(i => { %><li><%= i %></li><% }) %>

function compiled(data) {
  let out = '';
  out += '<h1>' + escape(data.title) + '</h1>';   // <%= %> becomes escape(...)
  data.items.forEach(i => {                          // <% %> code is pasted in as-is
    out += '<li>' + escape(i) + '</li>';
  });
  return out;
}

That's why EJS errors sometimes point at strange line numbers: the error is in the generated function, not your file. It's also why <% %> blocks must form valid JavaScript when glued together.

13How the two topics connect

Why your instructor taught them in the same class.

In a real Express app, the data you pass to res.render is often made of class instances. Their methods live on the prototype, and templates can call them because property lookup walks the chain.

app.jsclass Student {
  constructor(first, last, gpa) { Object.assign(this, { first, last, gpa }); }
  get fullName() { return `${this.first} ${this.last}`; }   // on Student.prototype
  isHonors() { return this.gpa >= 3.5; }                     // on Student.prototype
}

app.get('/roster', (req, res) => {
  const students = [new Student('Ada', 'Lovelace', 3.9), new Student('Alan', 'Turing', 3.2)];
  res.render('roster', { students });
});
views/roster.ejs<% students.forEach(s => { %>
  <li><%= s.fullName %> <%= s.isHonors() ? '🏅' : '' %></li>
<% }) %>
<%# s has no own "fullName" or "isHonors", the chain finds them on Student.prototype %>
Gotcha: if you convert objects to JSON (for an API or JSON.stringify), prototype methods and getters disappear, because JSON only keeps own enumerable data. Some engines (newer Handlebars) also block prototype access by default for security, which is why you may see "Access has been denied to resolve the property" errors with Mongoose documents. The fix is passing plain objects (e.g. Mongoose's .lean()).

14Exercises

Try each one in the playground before opening the solution. Struggling a little is the point.

15Quiz

12 questions. Click an answer to see the explanation.

0 / 12
answer all questions to see your result

16Cheat sheet

Prototypes

  • Object.create(proto): new object linked to proto
  • Object.getPrototypeOf(o): read the link
  • Object.setPrototypeOf(o, p): change the link (slow, avoid)
  • Fn.prototype: parent of objects made by new Fn()
  • o.hasOwnProperty(k) / Object.hasOwn(o, k): own only
  • k in o: own or inherited
  • o instanceof Fn: is Fn.prototype in o's chain?
  • Parent.call(this, ...): old-style super()
  • Child.prototype = Object.create(Parent.prototype) then fix .constructor
  • Chain ends: Object.prototype → null
  • Write = own property (shadowing). Read = walks the chain.

Template engines (EJS)

  • npm i express ejs
  • app.set('view engine', 'ejs')
  • Templates live in views/
  • res.render('name', { data })
  • <%= x %> escaped output (default choice)
  • <%- x %> raw output (trusted only)
  • <% code %> logic, no output
  • <%- include('partials/nav') %>
  • res.locals.x = available to all views
  • Handlebars: {{x}} / {{{x}}} / {{#each}}
  • Pug: indentation, p= x, each i in list

Questions worth asking your instructor next class

  • Which template engine will we use for the project, and do you want partials for header/footer?
  • On exams, should we write inheritance with constructor functions, classes, or both?
  • Did I miss any assignment details or due dates tonight?