Miscellaneous
Proxy and Reflect
Proxy
A Proxy wraps an object and lets you intercept operations on it:
let target = { name: "Alice", age: 30 };
let handler = {
get(obj, prop) {
console.log(`Getting ${prop}`);
return prop in obj ? obj[prop] : "Not found";
},
set(obj, prop, value) {
console.log(`Setting ${prop} = ${value}`);
obj[prop] = value;
return true;
}
};
let proxy = new Proxy(target, handler);
console.log(proxy.name); // β "Getting name" then "Alice"
console.log(proxy.email); // β "Getting email" then "Not found"
proxy.age = 31; // β "Setting age = 31"
Proxy Traps
let handler = {
get(target, prop, receiver) {}, // Reading property
set(target, prop, value, receiver) {}, // Writing property
has(target, prop) {}, // 'prop' in obj
deleteProperty(target, prop) {}, // delete obj.prop
ownKeys(target) {}, // Object.keys/for...in
apply(target, thisArg, args) {}, // Function call
construct(target, args) {}, // new operator
getPrototypeOf(target) {}, // Object.getPrototypeOf
setPrototypeOf(target, proto) {}, // Object.setPrototypeOf
isExtensible(target) {}, // Object.isExtensible
preventExtensions(target) {}, // Object.preventExtensions
defineProperty(target, prop, desc) {}, // Object.defineProperty
getOwnPropertyDescriptor(target, prop) {} // Object.getOwnPropertyDescriptor
};
Practical Proxy Examples
// Validation with Proxy
let user = {
name: "Alice",
age: 30
};
let validator = {
set(target, prop, value) {
if (prop === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("Age must be an integer");
}
if (value < 0 || value > 150) {
throw new RangeError("Age must be 0-150");
}
}
if (prop === "name" && value.length < 2) {
throw new Error("Name must be at least 2 characters");
}
target[prop] = value;
return true;
}
};
let protectedUser = new Proxy(user, validator);
protectedUser.age = 25; // β
OK
// protectedUser.age = -5; // β RangeError
// protectedUser.name = "A"; // β Error
// Property defaults with Proxy
function withDefaults(defaults) {
return {
get(target, prop) {
if (prop in target) {
return target[prop];
}
return defaults[prop];
}
};
}
let config = { theme: "dark" };
let configProxy = new Proxy(config, withDefaults({
theme: "light",
fontSize: 14,
lang: "en"
}));
console.log(configProxy.theme); // β "dark" (own property)
console.log(configProxy.fontSize); // β 14 (from defaults)
console.log(configProxy.lang); // β "en" (from defaults)
// Read-only view
function readOnly(obj) {
return new Proxy(obj, {
set() { throw new Error("Read-only"); },
deleteProperty() { throw new Error("Read-only"); },
defineProperty() { throw new Error("Read-only"); }
});
}
let data = { secret: "1234" };
let safeData = readOnly(data);
// safeData.secret = "5678"; // Error: Read-only
Reflect
Reflect is a built-in object with methods that correspond to proxy traps:
let obj = { name: "Alice", age: 30 };
// Instead of:
console.log(obj.name); // Property access
console.log("age" in obj); // in operator
delete obj.name; // delete
// Use Reflect:
console.log(Reflect.get(obj, "name")); // β "Alice"
console.log(Reflect.has(obj, "age")); // β true
Reflect.deleteProperty(obj, "name");
Proxy + Reflect = Forwarding
let target = { name: "Alice" };
let proxy = new Proxy(target, {
get(target, prop, receiver) {
// Do something custom
console.log(`Accessed: ${prop}`);
// Forward to the target with proper receiver
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Set: ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
});
proxy.name; // β "Accessed: name" then "Alice"
proxy.age = 5; // β "Set: age = 5"
Eval: run a code string
eval() executes a string as JavaScript code. Avoid it unless absolutely necessary.
let x = 10;
let y = 20;
let code = "x + y";
console.log(eval(code)); // β 30
Why avoid eval?
// 1. Security β code injection
let userInput = "alert('Hacked!')";
// eval(userInput); // DANGEROUS
// 2. Performance β can't be optimized by JS engines
// 3. Debugging β harder to trace
// 4. Scope β can access/modify local variables
Alternatives to eval
// Instead of eval for parsing JSON:
// β eval('(' + jsonString + ')')
// β
JSON.parse(jsonString)
// Instead of eval for dynamic property access:
// β eval('obj.' + propName)
// β
obj[propName]
// Instead of eval for dynamic function creation:
// β eval('function sum(a, b) { return a + b; }')
// β
new Function('a', 'b', 'return a + b')
Currying
Currying transforms a function that takes multiple arguments into a series of functions that each take one argument.
// Normal function
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // β 6
// Curried version
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(curriedAdd(1)(2)(3)); // β 6
Currying with Arrow Functions
let add = a => b => c => a + b + c;
console.log(add(1)(2)(3)); // β 6
Why Currying?
// Partial application β pre-fill arguments
let addFive = add(5); // Fix first argument
console.log(addFive(10)(20)); // β 35 (5 + 10 + 20)
// Useful for configuration
function createLogger(level) {
return function(message) {
console.log(`[${level}] ${message}`);
};
}
let info = createLogger("INFO");
let error = createLogger("ERROR");
info("System started"); // β "[INFO] System started"
error("Failed!"); // β "[ERROR] Failed!"
Generic Currying Function
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
console.log(curriedSum(1, 2, 3)); // β 6
console.log(curriedSum(1)(2, 3)); // β 6
console.log(curriedSum(1)(2)(3)); // β 6
Reference Type
The Reference Type is an internal JavaScript mechanism that explains how method calls lose their this.
let user = {
name: "Alice",
greet() {
console.log("Hello, " + this.name);
}
};
// Direct call β user.greet has the reference type (user, greet)
user.greet(); // β "Hello, Alice"
// Assigned β loses the reference type
let fn = user.greet;
fn(); // β "Hello, undefined" (this lost)
Why this happens
obj.method() passes two pieces of information:
- The object (
obj) - The method name (
method)
obj.method alone returns just the function, losing the object reference.
BigInt
BigInt represents integers larger than 2^53 - 1 (the safe integer limit for Number).
Creating BigInt
// With 'n' suffix
let big = 123456789012345678901234567890n;
// With BigInt() function
let big2 = BigInt("123456789012345678901234567890");
BigInt Operations
let a = 100n;
let b = 200n;
a + b; // β 300n
a - b; // β -100n
a * b; // β 20000n
b / a; // β 2n (division truncates toward zero)
b % a; // β 0n
a ** 2n; // β 10000n
Cannot Mix with Regular Numbers
// β Error
// let result = 100n + 50;
// β
Must convert
let result = 100n + BigInt(50); // β 150n
let result2 = Number(100n) + 50; // β 150
// Comparisons work across types:
100n > 50; // β true
100n == 100; // β true
100n === 100; // β false (different types)
BigInt Operations
// Math operations
let n = 1n;
n++; // β 2n
++n; // β 3n
// Bitwise operations
5n & 3n; // β 1n
5n | 3n; // β 7n
5n ^ 3n; // β 6n
~5n; // β -6n
// NOT supported with BigInt:
// Math.floor(5n) // TypeError
// +5n // TypeError
// parseInt(5n) // OK (converts to number)
BigInt Conversions
String(100n); // β "100"
Boolean(0n); // β false
Boolean(1n); // β true
Number(100n); // β 100 (might lose precision for large BigInts)
BigInt("100"); // β 100n
BigInt(true); // β 1n
Unicode, String Internals
Unicode in JavaScript
JavaScript strings are UTF-16 encoded. Characters outside the Basic Multilingual Plane (BMP) use surrogate pairs (2 code units).
let emoji = "π";
console.log(emoji.length); // β 2 (surrogate pair!)
// Code points vs code units
for (let char of emoji) {
console.log(char); // β "π" (iterates by code point, correct)
}
// Surrogate pair breakdown
console.log(emoji.charCodeAt(0)); // β 55357 (high surrogate)
console.log(emoji.charCodeAt(1)); // β 56832 (low surrogate)
String Methods for Unicode
let str = "Hello π World";
// Code point at position
console.log(str.codePointAt(6)); // β 128512 (the emoji code point)
// From code point
console.log(String.fromCodePoint(128512)); // β "π"
// Correct length (in code points)
console.log([...str].length); // β 13 (correct)
console.log(str.length); // β 14 (includes surrogate pair as 2)
Unicode Escape Sequences
// Hex escape (BMP only)
console.log("\u00E9"); // β "Γ©"
// Code point escape (any Unicode)
console.log("\u{1F600}"); // β "π"
console.log("\u{1F431}"); // β "π±"
// Named escape (in some environments)
// console.log("\N{LATIN SMALL LETTER E WITH ACUTE}"); // Not standard in JS
Normalization
Some characters can be represented in multiple ways:
// Precomposed vs decomposed
let n1 = "\u00F1"; // "Γ±" (single code point)
let n2 = "n\u0303"; // "n" + combining tilde
console.log(n1 === n2); // β false (visually identical, different bytes)
// Normalize
console.log(n1.normalize() === n2.normalize()); // β true
localeCompare
// String comparison respecting locale
let words = ["rΓ©servΓ©", "reserve", "RESERVE"];
words.sort((a, b) => a.localeCompare(b));
// β ["reserve", "RESERVE", "rΓ©servΓ©"] (locale-aware order)
WeakRef and FinalizationRegistry
WeakRef (ES2021)
A WeakRef holds a weak reference to an object β it doesnβt prevent garbage collection:
let obj = { data: "important" };
let weakRef = new WeakRef(obj);
// Get the object (might be undefined if GC'd)
console.log(weakRef.deref()); // β { data: "important" }
console.log(weakRef.deref()?.data); // β "important"
// If obj is set to null and GC runs, deref() returns undefined
obj = null;
// After GC: weakRef.deref() β undefined
FinalizationRegistry
Run a callback when an object is garbage collected:
let registry = new FinalizationRegistry((heldValue) => {
console.log(`Object ${heldValue} was garbage collected`);
});
let obj = { data: "temp" };
registry.register(obj, "someId");
// When obj is GC'd, the callback runs with "someId"
obj = null;
// β After GC: "Object someId was garbage collected"
Use Cases
// Cache with WeakRef β entries can be GC'd when memory is needed
class WeakCache {
constructor() {
this.cache = new Map();
}
get(key) {
let ref = this.cache.get(key);
if (ref) {
let value = ref.deref();
if (value !== undefined) return value;
}
return null;
}
set(key, value) {
this.cache.set(key, new WeakRef(value));
}
cleanup() {
for (let [key, ref] of this.cache) {
if (ref.deref() === undefined) {
this.cache.delete(key);
}
}
}
}
Warning: WeakRef and FinalizationRegistry are advanced features. They should be used sparingly β the timing of GC is unpredictable and implementation-dependent.
// BigInt
let big = 12345678901234567890n;
document.write('BigInt: ' + big + '<br>');
document.write('BigInt * 2n = ' + (big * 2n) + '<br>');
// Unicode
let str = 'Hello π!';
document.write('String: ' + str + '<br>');
document.write('Length: ' + str.length + ' (surrogate pairs make it longer)<br>');
document.write('Actual chars: ' + [...str].length + '<br>');
document.write('Unicode escape: ' + '\u{1F30D}' + '<br>');
// Proxy simple example
let handler = {
get(target, prop) {
if (prop in target) {
return target[prop];
}
return 'Property ' + prop + 'not found';
}
};
let obj = new Proxy({ name: 'Alice', age: 30 }, handler);
document.write('<br>Proxy:<br>');
document.write('obj.name: ' + obj.name + '<br>');
document.write('obj.email: ' + obj.email + '<br>');