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
| Name | What it is | Where 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 |
.prototype | A 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) |
.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');
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
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(callingPerson()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');
- Create a brand-new empty object:
{} - Link its
[[Prototype]]toDog.prototype(this is where inheritance happens) - Run the constructor with
thispointing at the new object, sothis.name = 'Rex'sets an own property - 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);
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.
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
HTML + placeholders
from your route / DB
2. call it with data
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<script>.
08EJS syntax (Embedded JavaScript)
The most common engine in Express courses. If you know JavaScript, you already know 90% of it.
| Tag | What it does | Example |
|---|---|---|
<%= %> | 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') %>
<%- %> 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).{ 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
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.
| EJS | Handlebars | Pug | |
|---|---|---|---|
| Philosophy | Real JavaScript inside HTML | "Logic-less": minimal logic, use helpers | Indentation-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 setup | npm i ejs | npm i express-handlebars | npm 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}}
{{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, '&').replace(/</g, '<').replace(/>/g, '>') .replace(/"/g, '"').replace(/'/g, '''); } 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 <Weston></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 %>
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.
16Cheat sheet
Prototypes
Object.create(proto): new object linked to protoObject.getPrototypeOf(o): read the linkObject.setPrototypeOf(o, p): change the link (slow, avoid)Fn.prototype: parent of objects made bynew Fn()o.hasOwnProperty(k)/Object.hasOwn(o, k): own onlyk in o: own or inheritedo instanceof Fn: isFn.prototypein 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 ejsapp.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?