- Biết 4 cách định nghĩa function và phân biệt rõ qua bảng so sánh: hoisting,
this,arguments,new,prototype. - Hiểu "function là first-class" — gán biến, truyền argument, return, lưu trong cấu trúc dữ liệu.
- Master parameters modern: default, rest
...args, destructuring params. - Phân biệt arrow function với function thường — 4 điểm khác biệt then chốt.
- Master
thisbinding 4 rule: default, implicit, explicit,new— và ngoại lệ arrow function. - Hiểu scope chain, hoisting và Temporal Dead Zone trên function declaration vs expression.
- Định nghĩa closure chính xác và áp dụng 5 pattern: data privacy, factory, memoization, once, currying.
- Biết IIFE — vẫn còn trong code legacy, đôi khi cần.
1. Bốn cách định nghĩa function
JavaScript cho phép viết function theo ít nhất 4 cú pháp, mỗi cú pháp có hành vi khác nhau. Hiểu khác biệt là điều kiện tiên quyết để đọc bất kỳ codebase nào.
1.1. Function declaration
Cú pháp truyền thống. Bắt đầu bằng keyword function ở vị trí statement.
function add(a, b) {
return a + b;
}
add(2, 3); // 5
// Hoisted toàn bộ — gọi trước dòng khai báo vẫn chạy:
sayHi(); // 'Xin chào!' ← vẫn chạy
function sayHi() {
console.log('Xin chào!');
}
1.2. Function expression
Function nằm ở vị trí biểu thức (ví dụ vế phải của =). Có thể named hoặc anonymous.
Không hoisted như declaration.
// Anonymous
const add = function(a, b) {
return a + b;
};
// Named (tên chỉ dùng được bên trong function — hữu ích cho recursion + debug)
const fact = function factorial(n) {
return n <= 1 ? 1 : n * factorial(n - 1);
};
// KHÔNG hoisted — gọi trước dòng khai báo lỗi:
greet(); // ReferenceError (let/const) hoặc TypeError (var)
const greet = function() { console.log('hi'); };
1.3. Arrow function
Cú pháp ES6, ngắn gọn. Khác function thường ở 4 điểm: không có this riêng, không có
arguments, không new được, không có prototype. Chi tiết ở
mục 4.
// Body block
const add = (a, b) => {
return a + b;
};
// Body biểu thức — implicit return (không cần `return` keyword)
const add2 = (a, b) => a + b;
// 1 tham số — bỏ ngoặc
const square = x => x * x;
// 0 tham số — vẫn cần ngoặc rỗng
const hi = () => console.log('hi');
// Trả về object literal — phải bọc ngoặc tròn để JS không hiểu là block
const point = (x, y) => ({ x, y });
// Multi-line body block:
const processUser = (user) => {
const name = user.name.trim();
const age = user.age ?? 0;
return { name, age };
};
1.4. Method shorthand (object & class)
Cú pháp ES6 viết method gọn trong object literal hoặc class body. Hành vi giống function expression thường, chỉ khác cách viết.
// Cách cũ:
const user = {
name: 'An',
greet: function() { console.log(this.name); }
};
// Shorthand ES6:
const user2 = {
name: 'An',
greet() { console.log(this.name); } // ← bỏ `: function`
};
// Trong class:
class User {
constructor(name) { this.name = name; }
greet() { console.log(this.name); }
}
1.5. Bảng so sánh 4 cách
| — | Declaration | Expression | Arrow | Method shorthand |
|---|---|---|---|---|
| Hoisting | Full (cả tên + body) | Chỉ tên biến (var = undefined; let/const TDZ) | Như expression | N/A (nằm trong object) |
this riêng | Có | Có | Không (lexical) | Có |
arguments | Có | Có | Không | Có |
new được | Có | Có | Không | Có |
prototype | Có | Có | Không | Có |
| Có tên cho stack trace | Có | Khi named hoặc gán biến | Khi gán biến | Có |
- Top-level helper dùng nhiều chỗ →
function declaration(hoisted, dễ debug). - Callback ngắn truyền vào
map/filter/setTimeout→ arrow. - Method của object/class cần
this→ method shorthand. - Function gán điều kiện (vd:
const fn = cond ? a : b) → expression.
2. Function là first-class citizen
Trong JS, function là giá trị như string hay number. Có thể:
- Gán cho biến.
- Truyền làm argument cho function khác.
- Trả về từ function (higher-order function).
- Lưu trong array, object, Map.
// 1. Gán cho biến
const double = x => x * 2;
// 2. Truyền làm argument — callback
[1, 2, 3].map(double); // [2, 4, 6]
// 3. Return function — higher-order function
function multiplier(n) {
return x => x * n; // trả về function
}
const triple = multiplier(3);
triple(10); // 30
// 4. Lưu trong array/object
const ops = [
x => x + 1,
x => x * 2,
x => x - 3,
];
const result = ops.reduce((acc, fn) => fn(acc), 10);
// (10+1)*2 - 3 = 19
const handlers = {
onClick: () => console.log('click'),
onHover: () => console.log('hover'),
};
handlers.onClick();
Dart cũng coi function là first-class — có type Function và arrow
(x) => x * 2. Tuy nhiên Dart phân biệt rõ tham số kiểu num Function(num).
Python tương tự (def hoặc lambda), function là object —
có thể gán, truyền, trả về. Nhưng lambda Python chỉ chứa 1 biểu thức.
3. Parameters: default, rest, destructuring
ES6 thêm 3 cú pháp parameter rất tiện. Cần master cả 3.
3.1. Default parameter
Gán giá trị mặc định khi argument là undefined.
function greet(name = 'khách', greeting = 'Xin chào') {
return `${greeting}, ${name}!`;
}
greet(); // 'Xin chào, khách!'
greet('An'); // 'Xin chào, An!'
greet('An', 'Chào'); // 'Chào, An!'
// Quan trọng: default chỉ áp dụng với `undefined`, KHÔNG với `null`
greet(null); // 'Xin chào, null!' 🔥 (null không trigger default)
greet(undefined); // 'Xin chào, khách!'
// Default có thể là biểu thức, thậm chí gọi function khác:
function log(msg, time = Date.now()) {
console.log(time, msg);
}
3.2. Rest parameter ...args
Gom các argument còn lại thành 1 array. Khác arguments cũ (không phải array thực, không
có trong arrow function).
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15
// Kết hợp tham số thường + rest (rest phải ở CUỐI):
function tag(label, ...items) {
return `${label}: ${items.join(', ')}`;
}
tag('fruits', 'apple', 'banana'); // 'fruits: apple, banana'
// So với arguments cũ:
function oldSum() {
// arguments là array-like, không có .reduce/.map
const arr = Array.from(arguments);
return arr.reduce((a, b) => a + b, 0);
}
3.3. Destructuring parameter
"Tháo dỡ" object/array ngay tại signature. Cực kỳ tiện cho config object.
// Object destructuring
function createUser({ name, age = 18, role = 'user' }) {
return { name, age, role };
}
createUser({ name: 'An', age: 25 });
// { name: 'An', age: 25, role: 'user' }
// Default cho cả object (tránh lỗi destructure undefined)
function init({ debug = false } = {}) {
return debug;
}
init(); // false (không lỗi nhờ `= {}`)
// Array destructuring
function swap([a, b]) {
return [b, a];
}
swap([1, 2]); // [2, 1]
// Rename khi destructure
function printName({ name: fullName }) {
console.log(fullName);
}
4. Arrow function vs function thường
Arrow function trông giống "function viết gọn" nhưng khác về 4 điểm hành vi. Hiểu kỹ là điều bắt buộc.
4.1. Không có this riêng — lexical this
Arrow function không tạo binding this mới. Nó "thừa kế" this
từ scope bao ngoài tại thời điểm định nghĩa (lexical).
const user = {
name: 'An',
hobbies: ['code', 'gym'],
// ❌ Function thường: this = undefined trong callback
printBad() {
this.hobbies.forEach(function(h) {
console.log(this.name, h); // `this` ở đây ≠ user
});
},
// ✅ Arrow: kế thừa this từ printGood
printGood() {
this.hobbies.forEach(h => {
console.log(this.name, h); // `this` = user
});
},
};
4.2. Không có arguments
function normal() {
console.log(arguments); // Arguments(3) [1, 2, 3]
}
normal(1, 2, 3);
const arrow = () => {
console.log(arguments); // ReferenceError (hoặc kế thừa từ scope ngoài nếu có)
};
// Thay thế: dùng rest params
const arrowFix = (...args) => console.log(args);
arrowFix(1, 2, 3); // [1, 2, 3] (array thực)
4.3. Không new được
function Point(x, y) {
this.x = x;
this.y = y;
}
const p = new Point(1, 2); // OK
const ArrowPoint = (x, y) => {
this.x = x;
};
new ArrowPoint(1, 2); // ❌ TypeError: ArrowPoint is not a constructor
4.4. Không có prototype
function foo() {}
console.log(foo.prototype); // { constructor: foo }
const bar = () => {};
console.log(bar.prototype); // undefined
- Arrow: callback ngắn (
map,filter,setTimeout), khi muốn giữthiscủa scope ngoài, function không cầnnew. - Function thường: method của object/class (cần
thisriêng), constructor, khi cần hoisting, khi cầnarguments.
5. this binding — 4 rule
this trong JavaScript không phải "instance của class hiện tại" như Java.
Nó được quyết định tại thời điểm gọi function, dựa trên cách gọi. Có 4 rule
theo thứ tự ưu tiên:
| Ưu tiên | Rule | Cú pháp | this là gì |
|---|---|---|---|
| 1 (cao nhất) | new binding | new Foo() | Object mới được tạo |
| 2 | Explicit binding | fn.call(obj), fn.apply(obj), fn.bind(obj)() | Object truyền vào |
| 3 | Implicit binding | obj.fn() | Object trước dấu chấm |
| 4 (mặc định) | Default binding | fn() độc lập | undefined (strict mode) hoặc globalThis (sloppy) |
| Ngoại lệ | Arrow function | Không tạo binding mới | Kế thừa từ scope bao ngoài (lexical) |
5.1. Default binding
'use strict';
function show() {
console.log(this);
}
show(); // strict: undefined; sloppy: window/global
// Hidden case — vẫn là default binding:
const obj = {
name: 'An',
greet() { console.log(this); }
};
const fn = obj.greet;
fn(); // undefined — vì không có obj. trước fn()
5.2. Implicit binding
Khi gọi với cú pháp obj.method(), this = obj (object trước dấu chấm).
const user = {
name: 'An',
greet() { console.log(this.name); }
};
user.greet(); // 'An' (this = user)
// Chain — `this` luôn là object cuối cùng trước dấu chấm:
const a = { name: 'A', fn: user.greet };
const b = { name: 'B', inner: a };
b.inner.fn(); // 'A' (this = a, không phải b)
5.3. Explicit binding — call/apply/bind
function greet(greeting, punct) {
console.log(`${greeting}, ${this.name}${punct}`);
}
const user = { name: 'An' };
// call(thisArg, ...args)
greet.call(user, 'Xin chào', '!'); // 'Xin chào, An!'
// apply(thisArg, [args])
greet.apply(user, ['Chào', '.']); // 'Chào, An.'
// bind(thisArg, ...args) — trả về function MỚI, không gọi ngay
const greetAn = greet.bind(user, 'Hi');
greetAn('?'); // 'Hi, An?'
greetAn('!'); // 'Hi, An!' (this đã bị "khóa" với user vĩnh viễn)
5.4. new binding
Khi gọi với new, JS thực hiện 4 bước:
- Tạo object mới rỗng.
- Link
__proto__của object mới tớiFoo.prototype. - Gán
this= object mới, chạy body function. - Nếu function không
returnobject, tự động return object mới.
function User(name) {
// this = {} mới
this.name = name;
// return this ngầm
}
const u = new User('An');
console.log(u); // User { name: 'An' }
5.5. Ngoại lệ: arrow function
Arrow function bỏ qua mọi rule trên. this luôn lấy từ scope bao ngoài
tại thời điểm định nghĩa.
const obj = {
name: 'An',
arrow: () => console.log(this?.name),
regular() { console.log(this.name); }
};
obj.arrow(); // undefined (this kế thừa từ global, không phải obj)
obj.regular(); // 'An'
// Cả bind cũng KHÔNG đổi được this của arrow:
const bound = obj.arrow.bind({ name: 'B' });
bound(); // vẫn undefined
Mọi lúc gán const fn = obj.method rồi gọi fn(), bạn mất implicit
binding. Đây là nguồn bug kinh điển.
3 cách fix:
const fn = obj.method.bind(obj)const fn = () => obj.method()(closure)- Trong class, gán method trong constructor:
this.method = this.method.bind(this)
6. Scope: global, function, block
"Scope" = vùng nhìn thấy biến. JavaScript có 3 loại:
| Scope | Tạo bởi | Áp dụng cho |
|---|---|---|
| Global | Top-level của script/module | Mọi khai báo top-level |
| Function | Mỗi function body | var, function declaration, parameters, this, arguments |
| Block | { } — if, for, while, block thường | Chỉ let, const, class |
const g = 'global'; // global scope
function outer() {
const f = 'function'; // function scope
if (true) {
var v = 'var-in-block'; // FUNCTION scope (var ignore block!)
let l = 'let-in-block'; // block scope
const c = 'const-in-block'; // block scope
}
console.log(v); // 'var-in-block' ← var leak ra function
console.log(l); // ReferenceError
console.log(c); // ReferenceError
}
Mỗi scope có "tham chiếu" tới scope cha. Khi truy cập biến, JS tìm theo chuỗi:
local scope → parent scope → ... → global scope. Tìm thấy ở đâu thì dùng ở đó,
không tìm thấy thì throw ReferenceError.
Quan trọng: scope chain xác định tại thời điểm định nghĩa, không phải khi gọi (lexical scoping).
const a = 1; // global
function outer() {
const b = 2; // outer
function inner() {
const c = 3; // inner
console.log(a, b, c); // 1 2 3 — chain: inner → outer → global
}
inner();
}
outer();
7. Scope chain & hoisting
Hoisting = JS engine "đẩy" khai báo lên đầu scope trước khi chạy code. Nhưng cách hoist khác nhau giữa các loại khai báo.
| Khai báo | Hoisted | Init value khi hoist | Truy cập trước dòng khai báo |
|---|---|---|---|
function fn() {} | Full (cả body) | function object | OK, chạy được |
var x | Có | undefined | Trả undefined (không lỗi) |
let x / const x | Có | (không init) | ReferenceError (TDZ) |
var fn = function(){} | Chỉ tên var | undefined | TypeError: fn is not a function |
const fn = () => {} | Chỉ tên const, TDZ | (không init) | ReferenceError |
// ✅ Function declaration — hoist full
sayHi(); // 'hi'
function sayHi() { console.log('hi'); }
// ⚠️ var + function expression — chỉ hoist tên
sayHo(); // ❌ TypeError: sayHo is not a function
var sayHo = function() { console.log('ho'); };
// Lúc gọi: sayHo là undefined → undefined() → TypeError
// ❌ const/let arrow — TDZ
sayHey(); // ❌ ReferenceError: Cannot access 'sayHey' before initialization
const sayHey = () => console.log('hey');
Khai báo trước khi dùng — luôn đúng dù ngôn ngữ nào. Đừng dựa vào function hoisting để viết code "gọi trước, định nghĩa sau", rất khó đọc.
8. Closure — function nhớ scope nó được tạo
Closure = function + reference tới scope nơi nó được tạo ra. Khi function được trả ra khỏi scope cha, scope đó không bị thu hồi — function vẫn truy cập được các biến bên trong.
Ví dụ kinh điển — counter:
function makeCounter() {
let n = 0; // biến local của makeCounter
return () => ++n; // inner function "đóng gói" biến n
}
const c1 = makeCounter();
c1(); // 1
c1(); // 2
c1(); // 3 — n vẫn tồn tại sau khi makeCounter return!
const c2 = makeCounter(); // scope mới, n mới
c2(); // 1 — c1 và c2 độc lập
c1(); // 4
Trong garbage-collected language, biến bị thu hồi khi không còn ai tham chiếu.
Khi makeCounter return, biến n vẫn được inner function tham chiếu — nên
không bị thu hồi.
Đây không phải "leak". Khi biến c1 bị gán giá trị khác hoặc ra khỏi
scope, inner function bị thu hồi → n mới được thu hồi.
9. Closure patterns
9.1. Data privacy — biến private
Trước class field #private (ES2022), closure là cách duy nhất tạo state thật sự private.
function createAccount(initialBalance) {
let balance = initialBalance; // private — không cách nào truy cập trực tiếp
return {
deposit(amount) { balance += amount; },
withdraw(amount) {
if (amount > balance) throw new Error('Insufficient');
balance -= amount;
},
getBalance() { return balance; }
};
}
const acc = createAccount(100);
acc.deposit(50);
acc.getBalance(); // 150
acc.balance; // undefined — không có property này
9.2. Factory function
Tạo nhiều function "biến thể" từ 1 template.
function multiplier(factor) {
return x => x * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
const half = multiplier(0.5);
[1, 2, 3].map(double); // [2, 4, 6]
[10, 20].map(half); // [5, 10]
9.3. Memoization — cache kết quả
function memoize(fn) {
const cache = new Map(); // private cache, sống cùng wrapped fn
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const slowSquare = (n) => {
for (let i = 0; i < 1e7; i++) {} // giả lập chậm
return n * n;
};
const fastSquare = memoize(slowSquare);
fastSquare(5); // chậm lần đầu
fastSquare(5); // instant — đọc cache
9.4. Once — gọi 1 lần duy nhất
function once(fn) {
let called = false;
let result;
return (...args) => {
if (!called) {
result = fn(...args);
called = true;
}
return result;
};
}
const init = once(() => console.log('Khởi tạo'));
init(); // 'Khởi tạo'
init(); // (không in gì, trả lại undefined cached)
9.5. Currying
Chuyển fn(a, b, c) thành fn(a)(b)(c). Cho phép partial application.
// Curry thủ công cho 3 tham số:
const add = a => b => c => a + b + c;
add(1)(2)(3); // 6
// Curry tự động cho bất kỳ arity:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...next) => curried(...args, ...next);
};
}
const sum3 = (a, b, c) => a + b + c;
const curriedSum = curry(sum3);
curriedSum(1)(2)(3); // 6
curriedSum(1, 2)(3); // 6
curriedSum(1)(2, 3); // 6
curriedSum(1, 2, 3); // 6
10. IIFE — Immediately Invoked Function Expression
Function được "định nghĩa và gọi ngay lập tức". Trước ES6 module, đây là cách phổ biến để tạo scope riêng.
// Cú pháp truyền thống — bọc trong ngoặc
(function() {
const secret = 42; // scope riêng, không leak ra global
console.log(secret);
})();
// Arrow IIFE
(() => {
const x = 1;
console.log(x);
})();
// Truyền argument vào IIFE
((name) => {
console.log(`Hello ${name}`);
})('JS');
// Trả về giá trị
const config = (() => {
const env = process.env.NODE_ENV;
return { debug: env !== 'production' };
})();
Trước ES6, JS không có module — tất cả script chia sẻ global scope. IIFE giải quyết bằng cách tạo
scope riêng. Bây giờ với import/export (chương 8), mỗi module đã có
scope riêng → IIFE ít cần thiết. Tuy vậy vẫn hữu ích cho:
- Async top-level cũ:
(async () => { await ... })()(trước top-level await). - Khởi tạo có logic:
const value = (() => { ... return ...; })(); - Code phải chạy trong
<script>tag không cótype="module".
Bài tập
Bài 1 — 3 cách viết add + hoisting
Viết function add(a, b) theo cả 3 cách: function declaration, function expression
(gán const), arrow function. Trong mỗi file/snippet, thử gọi trước dòng khai báo
và quan sát:
- Cách nào chạy được, cách nào throw, throw lỗi gì?
Đáp án
// 1. Declaration — hoisted full, gọi trước OK
console.log(add1(2, 3)); // 5
function add1(a, b) { return a + b; }
// 2. Expression với const — TDZ, gọi trước → ReferenceError
console.log(add2(2, 3)); // ReferenceError: Cannot access 'add2' before initialization
const add2 = function(a, b) { return a + b; };
// 3. Arrow với const — cũng TDZ → ReferenceError
console.log(add3(2, 3)); // ReferenceError
const add3 = (a, b) => a + b;
Mẹo: nếu thay const bằng var ở case 2 và 3, lỗi sẽ thành
TypeError: add2 is not a function (vì var hoist tên với value
undefined, gọi undefined(2,3) sinh TypeError).
Bài 2 — Mất this và 3 cách fix
Cho:
const user = {
name: 'An',
greet() { console.log(`Hi, ${this.name}`); }
};
const fn = user.greet;
fn(); // ?
Hỏi: this trong fn() là gì? Fix bằng 3 cách độc lập:
bind, arrow wrapper, closure capture.
Đáp án
this = undefined (strict mode) hoặc globalThis
(sloppy). Khi tách method khỏi object, mất implicit binding.
Fix 1 — bind:
const fn = user.greet.bind(user);
fn(); // 'Hi, An'
Fix 2 — arrow wrapper:
const fn = () => user.greet();
fn(); // 'Hi, An' (arrow giữ this=user qua implicit call)
Fix 3 — closure capture:
const fn = function() {
const self = user; // capture qua closure
self.greet();
};
fn(); // 'Hi, An'
Bài 3 — once(fn)
Viết once(fn): trả function chỉ chạy fn lần đầu, các lần sau trả
kết quả đã cache. Test với:
const init = once(() => {
console.log('init!');
return { ready: true };
});
init(); // in 'init!', trả { ready: true }
init(); // KHÔNG in, trả { ready: true } đã cache
init(); // idem
Đáp án
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
result = fn.apply(this, args);
called = true;
}
return result;
};
}
Mẹo: dùng function thường + fn.apply(this, args) để pass-through
this nếu caller gọi như method. Nếu không cần, arrow + fn(...args)
đủ.
Bài 4 — curry(fn)
Viết curry(fn) chuyển fn(a, b, c) thành dạng có thể gọi
fn(a)(b)(c), fn(a, b)(c), fn(a)(b, c) hay
fn(a, b, c) đều ra kết quả.
Hint: dùng fn.length (arity) và đệ quy.
Đáp án
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...next) => curried.apply(this, [...args, ...next]);
};
}
const sum = (a, b, c) => a + b + c;
const c = curry(sum);
c(1)(2)(3); // 6
c(1, 2)(3); // 6
c(1)(2, 3); // 6
c(1, 2, 3); // 6
Ý tưởng: gom args qua mỗi lần gọi, khi nào đủ arity thì gọi fn.
Bài 5 — Fix bug var i trong setTimeout
Code dưới in 3 3 3 thay vì 0 1 2. Giải thích vì sao và fix bằng 2 cách:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
Giải thích + 2 cách fix
Vì sao: var i là function scope (hoặc global), không phải block.
Cả 3 callback setTimeout chia sẻ cùng 1 binding i.
Khi loop kết thúc, i === 3. Sau đó callback chạy, đọc i hiện tại
→ in 3 3 3.
Fix 1 — đổi var sang let:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 0 1 2 — let tạo binding MỚI mỗi iteration
Lý do: let trong header for được spec đặc biệt — mỗi iteration
tạo binding mới. Closure capture binding riêng → in đúng.
Fix 2 — IIFE tạo scope riêng:
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 0);
})(i);
}
// 0 1 2 — mỗi iteration IIFE tạo `j` mới
Cách 3 dùng setTimeout(fn, ms, ...extraArgs): setTimeout((j) =>
console.log(j), 0, i) — capture qua tham số runtime.
Quiz
Function declaration và function expression khác nhau gì về hoisting?
Xem đáp án
Function declaration được hoist toàn bộ (cả tên + body) — có thể gọi trước dòng khai báo.
Function expression chỉ hoist tên biến (theo rule của var/let/const).
Body chưa tồn tại. Gọi trước dòng khai báo:
var → TypeError (undefined không phải function),
let/const → ReferenceError (TDZ).
Arrow function có this riêng không?
Xem đáp án
Không. Arrow function không tạo binding this mới — nó dùng
this của scope bao ngoài tại thời điểm định nghĩa (lexical this).
Hệ quả: call/apply/bind không thay đổi được
this của arrow. Cũng không thể dùng new với arrow.
Code sau in gì (strict mode)?
function foo() { console.log(this); }
foo();
Xem đáp án
undefined. Đây là default binding — gọi function độc lập (không qua
obj.fn(), new, call/apply/bind)
trong strict mode thì this = undefined.
Trong sloppy mode (không có 'use strict'), this = globalThis (window trong browser).
Closure có giải phóng biến của scope cha sau khi function cha trả về không?
Xem đáp án
Không — chừng nào inner function vẫn còn được tham chiếu, các biến trong scope cha mà nó "đóng gói" vẫn còn sống. Đó chính là cơ chế closure.
Khi inner function bị thu hồi (mất hết reference), scope cha mới được garbage collector dọn dẹp. Engine hiện đại (V8) còn tối ưu hơn: chỉ giữ các biến thực sự được closure tham chiếu, không giữ toàn bộ scope.
Code sau in gì?
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
Xem đáp án
0 1 2. let trong header for tạo binding mới
mỗi iteration — mỗi callback closure capture binding riêng → in giá trị đúng.
Đổi sang var sẽ in 3 3 3 vì cả 3 callback chia sẻ cùng 1 biến.
Dùng new với arrow function được không?
Xem đáp án
Không. Arrow function không có [[Construct]] internal slot, không có
prototype property, không tạo this binding. new trên arrow
throw TypeError: X is not a constructor.
Để tạo constructor, dùng function declaration/expression hoặc class (chương 4).
const fn = obj.method; fn.call(obj) và obj.method() khác nhau gì?
Xem đáp án
Về kết quả: giống nhau — this trong cả 2 đều là obj.
Về cơ chế: obj.method() dùng implicit binding (rule 3) — JS
tự set this = object trước dấu chấm. fn.call(obj) dùng
explicit binding (rule 2) — bạn truyền tay obj vào.
Khác biệt thực tế: nếu chỉ gọi fn() (không có .call), bạn sẽ mất
this → default binding (undefined trong strict mode). Đây là lý do
const fn = obj.method; fn() hay sinh bug.
Tổng kết
Sau chương 3, bạn nên đã master:
- 4 cách định nghĩa function: declaration, expression, arrow, method shorthand — và bảng so sánh hoisting/
this/arguments/new/prototype. - First-class function: gán biến, truyền arg, return, lưu trong cấu trúc.
- Parameters modern: default (chỉ áp dụng cho
undefined), rest...args(mảng thực), destructuring trực tiếp ở signature. - Arrow vs regular: 4 khác biệt — không
this, khôngarguments, khôngnew, khôngprototype. - 4 rule của
this:new> explicit > implicit > default; arrow là ngoại lệ (lexical). - Scope: global / function / block;
varignore block scope. - Hoisting: declaration hoist full; expression chỉ hoist tên;
let/constở TDZ. - Closure = function + scope nó được tạo. Counter, data privacy, memoize, once, curry.
- IIFE — dù module hiện đại đã giảm vai trò, vẫn xuất hiện trong code base lớn.
Kết nối
- Chương 2 (Variables, Types) — củng cố hiểu biết về
var/let/constvà TDZ trước khi đào sâu hoisting ở đây. - Chương 4 (Objects, Prototypes & Classes) —
thisbinding áp dụng vào method của class;newbinding kết nối với prototype chain. - Chương 6 (Async) — callback và Promise chain dựa hoàn toàn vào closure và arrow function để giữ
this. - Chương 9 (TypeScript) — type signature của function (
(a: number) => number) và overload phụ thuộc cú pháp ở đây. - Dart Chương 3 — Dart cũng có closure và arrow, nhưng
thisđơn giản hơn (không có 4 rule). So sánh để thấy JS phức tạp ở đâu và vì sao.