Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@

This repository was created with the intention of helping developers master their concepts in JavaScript. It is not a requirement, but a guide for future studies. It is based on an article written by Stephen Curtis and you can read it [here](https://medium.com/@stephenthecurt/33-fundamentals-every-javascript-developer-should-know-13dd720a90d1).

## Examples (runnable)

This repository now includes a small set of concise, runnable examples that illustrate a few of the most important concepts. You can find them in the `examples/` folder. Each file prints a short output that demonstrates the concept.

- `examples/call-stack.js` — simple nested calls to show the call stack order.
- `examples/primitives-vs-references.js` — shows how primitives are copied by value and objects by reference.
- `examples/closures.js` — demonstrates how closures retain lexical scope.
- `examples/map-filter-reduce.js` — quick usage of `map`, `filter` and `reduce`.
- `examples/event-loop.js` — microtask vs macrotask ordering (Promises vs setTimeout).
- `examples/promises-async-await.js` — basic Promise and `async/await` example.
- `examples/this-call-bind.js` — `this` behavior and `call` / `bind` usage.

Try one quickly from the project root:

```
node examples/event-loop.js
```

Or run the default examples script:

```
npm run examples
```

## Community

Feel free to submit a PR by adding a link to your own recaps or reviews. If you want to translate the repo into your native language, please feel free to do so.
Expand Down
25 changes: 25 additions & 0 deletions examples/call-stack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Example: Call Stack (LIFO order)
function a() {
console.log('a start');
b();
console.log('a end');
}

function b() {
console.log('b start');
c();
console.log('b end');
}

function c() {
console.log('c');
}

a();

// Expected output:
// a start
// b start
// c
// b end
// a end
15 changes: 15 additions & 0 deletions examples/call-stack.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { expectOutput } = require('./test-utils');

function testCallStack() {
return expectOutput(() => {
require('./call-stack');
}, [
'a start',
'b start',
'c',
'b end',
'a end'
]);
}

module.exports = testCallStack;
12 changes: 12 additions & 0 deletions examples/closures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Example: Closures — a function that remembers its lexical scope
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}

const counter = makeCounter();
console.log('closure:', counter()); // 1
console.log('closure:', counter()); // 2
12 changes: 12 additions & 0 deletions examples/closures.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { expectOutput } = require('./test-utils');

function testClosures() {
return expectOutput(() => {
require('./closures');
}, [
'closure: 1',
'closure: 2'
]);
}

module.exports = testClosures;
14 changes: 14 additions & 0 deletions examples/event-loop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Example: Event Loop ordering (macrotasks vs microtasks)
console.log('script start');

setTimeout(() => console.log('timeout callback (macrotask)'), 0);

Promise.resolve().then(() => console.log('promise callback (microtask)'));

console.log('script end');

// Expected order:
// script start
// script end
// promise callback (microtask)
// timeout callback (macrotask)
14 changes: 14 additions & 0 deletions examples/event-loop.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { expectOutputAsync } = require('./test-utils');

function testEventLoop() {
return expectOutputAsync(() => {
require('./event-loop');
}, [
'script start',
'script end',
'promise callback (microtask)',
'timeout callback (macrotask)'
], 100);
}

module.exports = testEventLoop;
10 changes: 10 additions & 0 deletions examples/map-filter-reduce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Example: map, filter and reduce
const arr = [1, 2, 3, 4, 5];

const squares = arr.map(n => n * n);
const evens = arr.filter(n => n % 2 === 0);
const sum = arr.reduce((acc, n) => acc + n, 0);

console.log('map -> squares:', squares); // [1,4,9,16,25]
console.log('filter -> evens:', evens); // [2,4]
console.log('reduce -> sum:', sum); // 15
13 changes: 13 additions & 0 deletions examples/map-filter-reduce.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { expectOutput } = require('./test-utils');

function testMapFilterReduce() {
return expectOutput(() => {
require('./map-filter-reduce');
}, [
'map -> squares: [1,4,9,16,25]',
'filter -> evens: [2,4]',
'reduce -> sum: 15'
]);
}

module.exports = testMapFilterReduce;
12 changes: 12 additions & 0 deletions examples/primitives-vs-references.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Example: Primitive vs Reference types
// Primitives are copied by value
let a = 1;
let b = a;
b = 2;
console.log('primitives:', { a, b }); // a stays 1, b is 2

// Objects are copied by reference
const obj1 = { x: 1 };
const obj2 = obj1;
obj2.x = 2;
console.log('references:', { obj1, obj2 }); // both reflect the change
12 changes: 12 additions & 0 deletions examples/primitives-vs-references.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { expectOutput } = require('./test-utils');

function testPrimitivesVsReferences() {
return expectOutput(() => {
require('./primitives-vs-references');
}, [
'primitives: {"a":1,"b":2}',
'references: {"obj1":{"x":2},"obj2":{"x":2}}'
]);
}

module.exports = testPrimitivesVsReferences;
12 changes: 12 additions & 0 deletions examples/promises-async-await.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Example: Promises and async/await
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function run() {
console.log('before await');
await wait(50);
console.log('after await');
}

run();
12 changes: 12 additions & 0 deletions examples/promises-async-await.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { expectOutputAsync } = require('./test-utils');

function testPromisesAsyncAwait() {
return expectOutputAsync(() => {
require('./promises-async-await');
}, [
'before await',
'after await'
], 100);
}

module.exports = testPromisesAsyncAwait;
31 changes: 31 additions & 0 deletions examples/run-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Test runner
const { expectOutput } = require('./test-utils');

// Run all example tests
async function runTests() {
const tests = [
require('./call-stack.test.js'),
require('./primitives-vs-references.test.js'),
require('./closures.test.js'),
require('./map-filter-reduce.test.js'),
require('./event-loop.test.js'),
require('./this-call-bind.test.js'),
require('./promises-async-await.test.js'),
];

console.log('\nRunning tests...\n');

let passed = 0;
let failed = 0;

for (const test of tests) {
const result = await test();
if (result) passed++;
else failed++;
}

console.log(`\nResults: ${passed} passed, ${failed} failed\n`);
process.exit(failed > 0 ? 1 : 0);
}

runTests().catch(console.error);
81 changes: 81 additions & 0 deletions examples/test-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Simple test harness
function expectOutput(fn, expectedLines) {
const originalLog = console.log;
const actualOutput = [];

console.log = (...args) => {
// Handle object stringification consistently
const formatted = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
actualOutput.push(formatted);
};

try {
fn();
console.log = originalLog;

const passed = expectedLines.every((expected, i) => {
if (!actualOutput[i]) return false;
// Normalize JSON strings and arrays for comparison
const normalizedExpected = expected.replace(/[\s{}[\]]/g, '');
const normalizedActual = actualOutput[i].replace(/[\s{}[\]]/g, '');
return normalizedActual.includes(normalizedExpected);
});

if (passed) {
console.log('\x1b[32m✓\x1b[0m', fn.name || 'Test passed');
return true;
} else {
console.log('\x1b[31m✗\x1b[0m', fn.name || 'Test failed');
console.log('Expected:', expectedLines);
console.log('Got:', actualOutput);
return false;
}
} catch (err) {
console.log = originalLog;
console.error('\x1b[31m✗\x1b[0m Test error:', err);
return false;
}
}

// For async tests
function expectOutputAsync(fn, expectedLines, timeout = 1000) {
return new Promise((resolve) => {
const originalLog = console.log;
const actualOutput = [];

console.log = (...args) => {
const formatted = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
actualOutput.push(formatted);
};

fn();

// Wait for all async operations to complete
setTimeout(() => {
console.log = originalLog;

const passed = expectedLines.every((expected, i) => {
if (!actualOutput[i]) return false;
const normalizedExpected = expected.replace(/[\s{}[\]]/g, '');
const normalizedActual = actualOutput[i].replace(/[\s{}[\]]/g, '');
return normalizedActual.includes(normalizedExpected);
});

if (passed) {
console.log('\x1b[32m✓\x1b[0m', fn.name || 'Test passed');
resolve(true);
} else {
console.log('\x1b[31m✗\x1b[0m', fn.name || 'Test failed');
console.log('Expected:', expectedLines);
console.log('Got:', actualOutput);
resolve(false);
}
}, timeout);
});
}

module.exports = { expectOutput, expectOutputAsync };
13 changes: 13 additions & 0 deletions examples/this-call-bind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Example: this, call, apply and bind
const person = {
name: 'Alice',
greet() {
return `Hello ${this.name}`;
}
};

const greet = person.greet;
console.log('direct method:', person.greet()); // Hello Alice
console.log('extracted function (this lost):', greet()); // undefined or global
console.log('call ->', greet.call({ name: 'Bob' })); // Hello Bob
console.log('bind ->', greet.bind({ name: 'Carol' })()); // Hello Carol
14 changes: 14 additions & 0 deletions examples/this-call-bind.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { expectOutput } = require('./test-utils');

function testThisCallBind() {
return expectOutput(() => {
require('./this-call-bind');
}, [
'direct method: Hello Alice',
'extracted function (this lost): Hello undefined',
'call -> Hello Bob',
'bind -> Hello Carol'
]);
}

module.exports = testThisCallBind;
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@

Any kind of contribution is welcome. Feel free to contribute.
*/

// NOTE: For short runnable examples that illustrate many of the concepts listed
// in this repo, check the `examples/` folder. Each example is a small Node script
// you can run with `node examples/<name>.js` or `npm run examples` (default).
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
},
"homepage": "https://github.com/leonardomso/33#readme",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"examples": "node examples/event-loop.js"
},
"repository": {
"type": "git",
Expand Down