Functions & call stack Flashcards
What are the execution context and stack?
Execution context is an internal data structure that contains the context of a function:
- Where the control flow is
- Variables
- The ‘this’ value
- Reference to its outer lexical environment
- …
When a function makes a call to another function:
1. The current function is paused
2. Its execution context is pushed to the execution context stack
3. A new execution context is created for the called function
4. The created execution context is pushed to the stack
5. The created execution context is popped off the stack when the function completes.
What is the lexical environment?
It is a static specification object that only exists ‘theoretically’. It is composed of:
1. Environment record: An object that has all local variables as its properties, and other info like ‘this’ value.
2. A reference to the outer lexical environment
When a variable is referenced, the environment record is looked up first, then the outer one, and so on and so forth.
Because the returned nested function is still reachable after the function has been called, the lexical environment isn’t garbage collected and is still accessible.
What is a closure?
A closure is a function that remembers its outer variables and can access them. In JavaScript, all functions are naturally closures functions except those defined with the ‘new Function()’ syntax
Var vs Let vs Const
- Var is function scoped, while Let and Const are blocked scoped
- Var can be redeclared
- Var and Let can be reassigned but Const cannot
- Var declarations are hoisted but not instantiated
What are some common Function properties that you might access?
- name
- arguments
- length(of args)
What is the output of the following code, or errors if any?
function makeCounter() { function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); alert( counter() ); alert( counter() );
1, 2
‘counter.count’ = 0 is assigning to a function’s properties. This is not the same as declaring a local variable ‘count=0’ in the function, and has the same effect as declaring ‘count=0’ in the outer lexical environment of counter().
The difference is that closure properties cannot be accessed by external code, but function properties can.
What’s the difference between the following pieces of code, and discuss any errors or implications as a result?
let sayHi = function(who) { alert(`Hello, ${who}`); }; let sayHi = function func(who) { alert(`Hello, ${who}`); };
The difference is the second function is assigning a Named Function Expression(NFE) to sayHi.
Both functions are still called with sayHi.
With NFE:
1. The function can reference itself
let sayHi = function func(who) {
if (who) {
alert(Hello, ${who}
);
} else {
func(“Guest”); // use func to re-call itself
}
};
- The name is not visible outside the function.
Note that for 1. we could just as well called sayHi in the else block. The reason why this isn’t preferred is that sayHi can be reassigned then the else block will throw an error.
What’s special about functions created with ‘new Function’, and some benefits they have?
let func = new Function ([arg1, arg2, ...argN], functionBody); // arg1, arg2, functionBody are all strings
- The function is created literally from a string, that is passed at run time, instead of Javascript code.
- Its [[Environment]] is set to reference not the current Lexical Environment, but the global one.
The benefits are because these function have no outer lexical environments, we must pass all the variables as arguments which insures us from errors, and minification(When variables are shortened e.g. const unsubscribablePlanId = 3 -> const a=3;
What are decorators?
Decorators are special functions that take another function and alter its behavior.
Does the following code throw any errors, and if so how to fix them?
let worker = { someMethod() { return 1; }, slow(x) { // scary CPU-heavy task here alert("Called with " + x); return x * this.someMethod(); // (*) } }; function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } let result = func(x); // (**) cache.set(x, result); return result; }; } worker.slow = cachingDecorator(worker.slow); alert( worker.slow(1) );
Yes. When we call ‘let result = func(x)’, in this case func refers to slow(), and when called without any object the ‘this’ value of the nested function which is undefined/globalThis.
Instead, to refer to ‘worker’s this value, we need to change it to ‘let result = func.call(this, x)
DIfference between function.call() and function.apply() and function.bind() and what they are used for
Call and Apply are used to call the function with a given ‘this’ value. The difference is call takes CSV as arguments while Apply takes an array as arguments.
Bind is used to bind the function with a given this value and return the new function.
How might you go above improving the following code?
function readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } return user; } // Working example with try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { alert("Invalid data: " + err.message); // Invalid data: No property: name alert(err.name); // PropertyRequiredError alert(err.property); // name } else if (err instanceof SyntaxError) { alert("JSON Syntax Error: " + err.message); } else { throw err; // unknown error, rethrow it } }
The code is not very forward-compatible. readUser can throw an arbitrary number of types of errors, in which case the catch block would need to be updated to include a conditional for each of them.
Instead what we could do is to wrap the errors, so we don’t have to add code for every possible type of error:
// Error Wrapper class ReadError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'ReadError'; } } class ValidationError extends Error { /*...*/ } class PropertyRequiredError extends ValidationError { /* ... */ } function validateUser(user) { if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } } function readUser(json) { let user; try { user = JSON.parse(json); } catch (err) { if (err instanceof SyntaxError) { throw new ReadError("Syntax Error", err); } else { throw err; } } try { validateUser(user); } catch (err) { if (err instanceof ValidationError) { throw new ReadError("Validation Error", err); } else { throw err; } } } try { readUser('{bad json}'); } catch (e) { if (e instanceof ReadError) { alert(e); // Original error: SyntaxError: Unexpected token b in JSON at position 1 alert("Original error: " + e.cause); } else { throw e; } }