As a front end white, I don’t know if you have the same problem as me. After reading the analysis of an interview question, I thought I could at that time, but I couldn’t look again two days later. Blindly pursuing all kinds of new technology, I feel like I can do everything, but I can’t do it once I get started… After reflecting on the pain, I finally realized the problem and began to focus on the training of basic skills. Nearly half a year to read through (in fact is swallowed) “JavaScript Advanced Programming”, “You don’t know JavaScript on, in, under” and other books, this series of articles is my reading process of knowledge points in some summary. Like the students remember to help me point 😁.
Understand wang series (1) thoroughly understand JavaScript function execution mechanism understand Wang series (2) thoroughly understand JavaScript scope understand Wang series (3) thoroughly understand JavaScript objects understand Wang series (4) thoroughly understand JavaScript classes To understand this, we need to understand JavaScript data types. To understand this, we need to understand JavaScript data types Complete JavaScript type conversion
1. Lexical scope
1.1 the eval
In the JavaScript eval (..) A function can take a string as an argument and treat its contents as if they existed at that point in the program when it was written. In other words, you can programmatically generate code in the code you’re writing and run it as if it were written in that location.
function foo(str, a) {
eval( str ); / / cheating!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;".1 ); / / 1 and 3
Copy the code
In the real world, it is very easy to dynamically concatenate characters together and pass them in according to program logic. eval(..) Often used to execute dynamically created code.
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError: a is not defined
}
foo( "var a = 2" );
Copy the code
In strict mode programs, eval(..) It has its own lexical scope at runtime, meaning that declarations in it cannot change the scope in which they reside.
The use of dynamically generated code in a program is rare because the benefits do not offset the loss in performance.
Methods that accept a code string:
- setTimeout(..) And setInterval (..)
- new Function(..)
- eval
1.2 with
With is often used as a shortcut to repeatedly reference multiple properties of the same object without having to repeat reference the object itself.
function foo(obj) {
with(obj) { a = 2; }}var o1 = { a: 3 };
var o2 = { b: 3 };
foo(o1);
console.log(o1.a); / / 2
foo(o2);
console.log(o2.a); // undefined
console.log(a); // 2 -- a is leaking into the global scope!
Copy the code
When we pass o1 in, a = 2 the assignment finds o1.a and assigns 2 to it. When O2 is passed in, o2 does not have a property, so this property is not created, and O2.a is left undefined. And the a = 2 assignment creates a global variable a.
Although the with block can process an object into lexical scope, the normal var declaration inside the block is not restricted to the scope of the block, but is added to the scope of the function in which the with block is located.
When we pass o1 to with, the scope declared by with is o1, and that scope contains an identifier that matches the o1.a attribute. But when we scoped O2, there was no A identifier, so the normal LHS identifier lookup was performed. The scope of O2, foo(..) The identifier a was not found in either the global or the scope of, so when a = 2 executes, a global variable is automatically created (because of non-strict mode).
1.3 performance
If the engine finds an eval(..) in your code Or with, which simply assume that the judgment about the identifier position is invalid because eval(..) cannot be explicitly known during the lexical analysis phase. What code is received, how that code changes the scope, and what exactly is the content of the object passed to with to create the new lexical scope. The most pessimistic scenario is that if eval(..) occurs Or with, all optimizations may be meaningless, so the easiest thing to do is to do no optimizations at all. If you use eval(..) heavily in your code Or with, it must run very slowly.
2. Function scope and block scope
2.1 Execute Function Expressions immediately (IIFE)
var a = 2;
(function foo() {
var a = 3;
console.log(a); / / 3}) ();console.log(a); / / 2
Copy the code
Since the function is enclosed within a pair of () parentheses, it becomes an expression that can be executed immediately by adding another () at the end, such as (function foo(){.. }) (). The first () turns the function into an expression, and the second () executes the function.
A very common advanced use of IIFE is to call them as functions and pass arguments to them
var a = 2;
(function IIFE(global) {
var a = 3;
console.log(a); / / 3
console.log(global.a); / / 2}) (window);
console.log(a); / / 2
Copy the code
We pass in a reference to the window object, but we name the parameter global, so the reference to the global object becomes clearer in code style than a reference to a variable without the word “global.”
Another variation of IIFE is to invert the order in which code is run, putting the functions that need to be run in the second place and passing them as arguments after IIFE execution. This pattern is widely used in the Universal Module Definition (UMD) project
var a = 2;
(function IIFE(def) {
def(window); }) (function def(global) {
var a = 3;
console.log(a); / / 3
console.log(global.a); / / 2
});
Copy the code
2.2 Block scope
2.2.1 with
Scopes created from objects with with are valid only in the WITH declaration and not in the outer scope
2.2.2 try/catch
The catch clause of try/catch creates a block scope in which declared variables are valid only within the catch
try {
undefined(a);// Perform an illegal operation to force an exception}
catch (err) {
console.log(err); // It works!
}
console.log(err); // ReferenceError: err not found
Copy the code
2.2.3 the let, const
The let keyword binds variables to whatever scope they are in (usually {.. } inside). In other words, a let implicitly creates the block scope in which it declares variables.
if (foo) {
let bar = foo * 2;
bar = something(bar);
console.log(bar);
}
console.log(bar); // ReferenceError
Copy the code
3. Improve
3.1 Function priority
Both function declarations and variable declarations are promoted. But one notable detail (which can occur in code with multiple “duplicate” declarations) is that functions are promoted first, variables second.
Function declarations inside a normal block are usually promoted to the top of their scope, and this process is not controlled by conditional judgments as implied by the following code:
foo(); // "b"
var a = true;
if (a) {
function foo() { console.log("a"); }}else {
function foo() { console.log("b"); }}Copy the code
4. The closure
4.1 Closure Problem
Closures occur when a function can remember and access the lexical scope in which it is located, even if the function is executed outside the current lexical scope.
- There’s a reference outside fn to inside FN
- Local members of the FN scope are accessed in another scope
function foo() {
var a = 2;
function bar() {
console.log(a); / / 2
}
bar();
}
foo();
Copy the code
For purely academic purposes, in the above code fragment, the function bar() has a closure that covers the scope of foo() (in fact, all the scopes it has access to, such as the global scope).
function foo() {
var a = 2;
function bar() { console.log(a); }
return bar;
}
var baz = foo();
baz(); // 2 -- That's what closures do, my friend.
Copy the code
Bar () can obviously be executed properly. But it executes outside the scope of its own defined lexicon. After foo() is executed, you would normally expect the entire internal scope of foo() to be destroyed. The “magic” of closures is to prevent this from happening. In fact, the inner scope still exists, and bar() itself uses this inner scope.
function foo() {
var a = 2;
function baz() {
console.log(a); / / 2
}
bar(baz);
}
function bar(fn) {
fn(); // Mom, look! This is the closure!
}
Copy the code
Regardless of the means by which an internal function is passed outside its lexical scope, it holds a reference to the original definition scope, and the closure is used wherever the function is executed.
Whenever you use callbacks in timers, event listeners, Ajax requests, cross-window communications, Web Workers, or any other asynchronous (or synchronous) task, you’re actually using closures!
4.2 Loops and closures
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
Copy the code
Use IIFE to immediately execute a function to create the scope.
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
Copy the code
Using IIFE within an iteration generates a new scope for each iteration, so that the delay function callback can enclose the new scope within each iteration, and each iteration contains a variable with the correct value for us to access.
4.3 IIFE
var a = 2;
(function IIFE() {
console.log( a ); }) ();Copy the code
Although this code works fine, it’s not technically a closure. Why is that? This is because functions are not executed outside of their own lexical scope. It executes in the scope in which it was defined (while the outer scope, the global scope, also holds a). A is found by ordinary lexical scoping lookup rather than closure.
IIFE itself is not a good example to look at closures, but it does create closures, and it is the most commonly used tool for creating closures that can be closed. So IIFE is really relevant to scopes, even if it doesn’t actually create them itself
4.4 module
function CoolModule() {
var something = "cool";
var another = [1.2.3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join(! "" "));
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); / / 1! 2! 3
Copy the code
CoolModule() is just a function that must be called to create an instance of the module. Neither the inner scope nor the closure can be created if the outer function is not executed. CoolModule() returns an object literal syntax {key: value,… } to represent an object. The returned object contains references to internal functions rather than internal data variables. We keep the internal data variables hidden and private. You can think of the return value of this object type as essentially the module’s public API.
The module mode requires two requirements:
- There must be an external enclosing function, which must be called at least once (each call creates a new module instance).
- The enclosing function must return at least one inner function so that the inner function can form a closure in the private scope and can access or modify the private state.
var foo = (function CoolModule(id) {
function change() {
// Modify the public API
publicAPI.identify = identify2;
}
function identify1() {
console.log(id);
}
function identify2() {
console.log(id.toUpperCase());
}
var publicAPI = {
change: change,
identify: identify1
};
returnpublicAPI; }) ("foo module");
foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE
Copy the code
We convert the module function to IIFE, immediately call the function and assign the return value directly to the singleton’s module instance identifier foo. In addition, by keeping internal references to public API objects inside the module instance, you can modify the module instance from the inside, including adding or removing methods and properties, as well as modifying their values.
4.4 Modern Module Mechanism
Most modules rely on loaders/managers that essentially encapsulate the module definition into a friendly API.
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get }; }) ();Copy the code
Here’s how to use it to define a module:
MyModules.define("bar"[],function() {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
});
MyModules.define("foo"["bar"].function(bar) {
var hungry = "hippo";
function awesome() {
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome: awesome
};
});
var bar = MyModules.get("bar");
var foo = MyModules.get("foo");
console.log(bar.hello("hippo")); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO
Copy the code
5. Block-scoping alternatives
{
let a = 2;
console.log(a); / / 2
}
console.log(a); // ReferenceError
Copy the code
ES5 uses catch implementations:
try {
throw 2;
} catch (a) {
console.log(a); / / 2
}
console.log(a); // ReferenceError
Copy the code
Why not just use IIFE to create scopes? IIFE and try/catch are not completely equivalent, because if you wrap any part of a piece of code in a function, you change the meaning of that piece of code. This, return, break, and contine will all change. IIFE is not a universal solution, it is only suitable for manual operation in certain situations.