“This is the fourth day of my participation in the First Challenge 2022. For details: First Challenge 2022”
A thought triggered by closures
One of the most confusing and inescapable concepts in JavaScript is closures. He can’t get away with it because it’s an outgrowth of the language’s design philosophy, and it’s confusing for beginners because its definitions are so abstract that it’s easy to get the feeling that you understand it but don’t fully understand it.
Today we are going to start with the very basic concept of peeling away the closings to reveal their true colors.
What is a closure?
So let’s look at the definition, just read the following definition, don’t go into it right now.
Let’s take a look at how this works in computer science, using the Wikipedia definition:
In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created. Unlike a plain function, a closure allows the function to access those captured variables through the closure’s copies of their values or references, even when the function is invoked outside their scope.
In computer science, closures (English: Closure), also known as Lexical Closure or function closures, are a technique for implementing Lexical binding in programming languages that support first-class functions. A closure is implemented as a structure that stores a function (usually its entry address) and an associated environment (equivalent to a symbol lookup table). The context is a set of symbols and values, including both constrained variables (the symbols bound within the function) and free variables (defined outside the function but referenced within the function). Some functions may not have free variables. The biggest difference between a closure and a function is that when a closure is captured, its free variables are determined at capture time, so that it can operate without being captured in context. The processing of values during capture can be either value copy or name reference, which is usually determined by the language designer or may be specified by the user (as in C++).
Let’s take a look at the MDN documentation explaining closures
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, A closure gives you access to an outer function’s scope from an inner function. closures are created every time a function is created, at function creation time.
A combination of a function bound to (or surrounded by) references to its surrounding state (lexical environment) is a closure. That is, closures allow you to access the scope of an outer function within an inner function. In JavaScript, whenever a function is created, the closure is created at the same time the function is created.
Let’s take a look at closures in the front Little Red Book:
Closures are functions that have access to variables from another function’s scope. This is often closure by creating a function inside a function.
Closures are functions that refer to variables in the scope of another function, usually implemented in nested functions.
Finally, let’s take a look at the definition of closures:
Closure is observed when a function uses variable(s) from outer scope(s) even while running in a scope where those variable(s) wouldn’t be accessible.
A closure is formed when a function accesses a variable in some scope from which the external (lexical) variable cannot be accessed.
For the above definition, if you are a JavaScript veteran, it is probably easy to understand what it means, whereas for some new JS users, it may be problematic. For the above mentioned lexical environment, scope, lexical scope and so on is not very clear understanding, such a combination, is a blur.
Anyway, once you read through the above definition, closures and Functions & scopes seem to have the following relationship.
What makes closures difficult to understand is not the complexity of their definition, but the fact that they require a lot of pre-knowledge, which would be difficult to understand if we were to talk about this abstract concept without the pre-knowledge.
In this case, let’s start by understanding closures bit by bit from the perspective of a newcomer to JS.
Front knowledge
Originally, I wanted to explain these pre-contents one concept at a time, but it was still foggy, making it difficult to string together a clear knowledge route. So we will start from the COMPILATION principle of JS, let the whole process string together, in the whole cycle, what concepts need, we will talk about what concepts.
Although JavaScript is often categorized as a “dynamic” or “interpreted execution” language, it is in fact a compiled language.
Why is there such ambiguity? Unlike other languages, the compilation process for JavaScript does not take place before build. With JavaScript, most of the time compilation happens a few microseconds (or less!) before the code executes. In time.
JS engine in the running process is mainly divided into two steps
- Compile time (this time the JS engine will invite their good gay friends
The compiler
To be responsible for parsing and code generation, etc.) - Execution time (this is mainly done by the JS engine, where the execution context is created, etc.)
The specific JS engine in the execution processCompile time
andDesignating a
What did they do? Let’s do it one by one. First of all to give you a picture of the whole experience. A picture is worth a thousand words. Look at the picture first and see how much you can understand.
Compile time
The compile phase mainly goes through three “compile” steps
- This process breaks a string of characters into blocks of code that make sense (to the programming language). These blocks of code are called lexical units (tokens).
- Parsing converts the flow of lexical units (arrays) into a tree of nested elements that represents the syntax of the program. This Tree is called the Abstract Syntax Tree (AST).
- Executable code generation The process of converting an AST into executable code is called code generation. This process is related to language, target platform, etc.
It is worth mentioning that in the three steps above we will determine scope, what is scope?
scope
A scope is responsible for collecting and maintaining a series of queries made up of all declared identifiers (variables) and enforcing a very strict set of rules that determine access to these identifiers by currently executing code and that govern the visibility and life cycle of variables and functions.
Speaking of scopes, let’s look at a few concepts of scopes
Lexical scope/static scope
Lexical scope: Lexical scope is also called static scope. When a function defined using lexical scope encounters a variable that is neither a parameter nor a local variable defined inside the function, go to the context in which the function was defined.
JS uses lexical scope.
Dynamic scope
Dynamic scope: When a dynamically scoped function encounters a variable that is neither a parameter nor a local variable defined within the function, the function is queried into the context in which the function is called.
There is no dynamic scope in JS, but the performance of this in JS is surprisingly similar to dynamic scope. You can understand the direction of this to understand dynamic scope, if you do not understand dynamic scope and this, please leave a message, I will write a special topic if necessary.
The most striking difference between the two
The lexical scope is because the environment used is the defining environment, and there is only one; And the dynamic scope environment is the runtime environment, there are N, we can’t be sure.
Block-level scope
Block-level scopes: in ES5 there are only global and functional scopes, but no block-level scopes. Variables declared using let can only be accessed in block-level scopes and have the property of “temporary dead zones” (that is, not available before declaration). Block scopes are included by {}. The {} in the if and for statements are also block scopes.
JS scope mechanism
JavaScript uses lexical scoping, also known as static scoping. The Scope chain is stored on the function’s internal property [[Scope]]. Internal properties are used by JavaScript engines and are not accessible to developers.
Note: there are methods in JS that cheat on lexical scope (eval and with). These methods can still modify scope even after being processed by the lexical parser, but this mechanism can be a bit tricky to understand and is not the focus of today’s article
Designating a
The execution period is mainly performed by the JS engine, which can be divided into the following steps
1. Create an execution context
An execution context describes the environment in which code is executed. It defines access to variables or functions by code statements. JS as an associated internal (variable objects) variable object.
All JS code is run in the corresponding execution thread. According to the call location, it can be divided into
- Global execution context
- Function execution context
- Eval Execution context
Let’s take a function call in code as an example:
Before the function is called, the execution context of the function is created, which does several things:
- Create an internal variable object
- Create scope chains.
- Make sure this points to.
What is a scope chain?
Let’s take an example to understand the scope chain of JS: we create A function A, and within that function we create A function B. There are three scopes: global, A, and B. The global scope contains scope A, and scope A contains scope B. When B searches for A variable, it will first search from its scope, and then search for the scope of A at the upper level. If it has not found the variable, it will search for the global scope, thus forming the scope chain.
2. Execution code: After the execution context is created, the internal code is executed by the engine.
And in this process, it basically involves
- Assignment of a variable
- Function reference and execution
- .
3. Recycling
Without going into too much detail here, we just need to know that after the code is executed, the engine will automatically recycle according to the specific algorithm strategy. The engine recycling strategy is generally determined by
- Reference counting
- Mark clear
So far we have put the JS engine compilation and running process roughly understand a time. Of course, the specific process is far more complicated than what we said above, but as a front-end student, it is enough to understand so much first. After all, we focus on closures today. If you are interested, you can read and dig deeper.
Now what is a closure
Now that we know what lexical scope is, we can see what the closure definition just means. Let’s do a simple analysis. Take the explanation in the MDN documentation for analysis
A combination of a function bound to (or surrounded by) references to its surrounding state (lexical environment) is a closure. That is, closures allow you to access the scope of an outer function within an inner function. In JavaScript, whenever a function is created, the closure is created at the same time the function is created.
Because JS uses lexical scope, the reference to its scope chain is determined at the time of its definition, and it does not matter where the function is executed, so the closure is generated as a whole. So, closures are a summary definition of the JS language’s reference phenomenon in lexical scoped construction and execution, nothing special.
Closure usage scenarios
I chuckled at the subheading “What I write closures for.” It’s like writing about the effects of eating. First, we looked at the previous analysis and realized that closures are everywhere, lurking in the code you write, in the marrow of the JS language. The benefits of closures are basically the benefits of JS as a language; The use of closures is equivalent to analyzing the advantages and best practices of the JS language.
I’ve summarized the closure usage scenarios below.
1. Implementation of corrification function, partial function
First of all, if you’ve never heard of a curation or partial function, don’t be intimidated by these fanciful names, they’re just simple concepts.
Currying (attacking)
Convert a function that takes n arguments to n functions that take 1 argument.
This literal name is really too hard to remember, if changed to flat I think it will be easier to understand.
For example
const curry = function(fn){
return function curryFn(. args){
if(args.length<fn.length){
return function(. newArgs){
returncurryFn(... args,... newArgs) } }else{
returnfn(... args) } } }let add = (a,b,c) = >a+b+c
/ / curry
add = curry(add)
console.log(add(1) (2) (3)) / / output 6
Copy the code
Partial functions
Take a single function that takes n arguments and arbitrarily fix a arguments, and return the function that takes the remaining n-a arguments.
For example
function getPerson(name,age){
return function (height) {
return `${name}This year,${age}I'm old and tall${height}cm`; }}let person = getPerson('Bamboo'.'18');
person('182'); Bamboo is 18 years old, 178cm tall.
Copy the code
The core of this type of operation is that JS uses static lexical scopes.
2. Just-in-time Function (IIFF)
Immediate Functions are Functions that can be called immediately after they are defined.
The function prefix operator converts an anonymous function or function declaration into a function expression.
Use scenarios for just-in-time functions
- The most common feature of IIFE is isolation scope. Prior to ES6, JS did not have the concept of block-level scope, so function scope is needed to simulate.
// IIFF
const MyLibrary = (function (global) {
const myData = '* * *';
function feature1(params) {
console.log(params, global);
}
function feature2(params) {
console.log(params);
}
function ReturnFunction(params) {
// do something;
return {
myData,
feature1,
};
}
ReturnFunction.myData = myData;
ReturnFunction.feature2 = feature2;
returnReturnFunction; }) (typeof window! = ='undefined' ? window : this);
Copy the code
- The inertia function
For example, DOM event adding, in order to be compatible with modern browsers and Internet Explorer, we need to make a judgment call to the browser environment.
- Simulate block-level scopes
function foo(){
var result = [];
for(var i = 0; i<10; i++){ result[i] =function(){
console.log(i)
}
}
return result;
}
var result = foo();
result[0] ();/ / 10
result[1] ();/ / 10
function foo(){
var result = [];
for(var i = 0; i<10; i++){ (function(i){
result[i] = function(){
console.log(i)
}
})(i)
}
return result;
}
var result = foo();
result[0] ();/ / 0
result[1] ();/ / 1
Copy the code
- Implement singleton module encapsulation
// singleton
const MyLibrary3 = (function (global) {
const myData = '* * *';
let instance;
function feature1(params) {
console.log(params, global);
}
function feature2(params) {
console.log(params);
}
function ReturnFunction(params) {
// do something;
return {
myData,
feature1,
};
}
ReturnFunction.myData = myData;
ReturnFunction.feature2 = feature2;
return function () {
if (instance) {
return instance;
}
instance = new ReturnFunction();
returninstance; }; }) (typeof window! = ='undefined' ? window : this);
Copy the code
Let’s take a quick look at how the IIFF and classic closures apply to Webpack
Webpack as a whole is a self-executing function that takes advantage of closures to privatize internal variables without contaminating global variables. We also set an object in this self-executing function that emulates and holds the contents of exports in the module. And a function to simulate the require method. The execution of each module is also put into a self-executing function.
The following code we can only look at IIFF, if you don’t understand the place don’t go into depth, if you are interested in this part of the code, you can also leave a message, we will do a detailed analysis of the article.
; (function (modules) {
// 01 defines objects for caching loaded objects in the future
let installedModules = {}
// 02 Define a __webpack_require__ method to replace the import require load operation
function __webpack_require__(moduleId) {
// 2-1 Check whether the current cache contains the contents of the module to be loaded, if so, return directly
if (installedModules[moduleId]) {
return installedModules[moduleId].exports
}
// 2-2 If the current cache does not exist, we need to define {} to perform the imported module content load
let module = (installedModules[moduleId] = {
i: moduleId,
l: false.exports: {}})// 2-3 Call the function corresponding to the current moduleId and finish loading the content
modules[moduleId].call(
modules.exports,
module.module.exports,
__webpack_require__
)
// 2-4 After the above method call is complete, we can change the value of l to indicate that the current module content has been loaded
module.l = true
// 2-5 After the load is complete, the retrieved content is returned to the place where it was called
return module.exports
}
// 03 Define the m attribute to store modules
__webpack_require__.m = modules
// 04 Define the c attribute to store the cache
__webpack_require__.c = installedModules
// 05 Defines the o method to determine whether the specified attribute exists on an object
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty(object, property)
}
// 06 defines the d method to add the specified property to an object and provide a getter for that property
__webpack_require__.d = function (exports, name, getter) {
if(! __webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true.get: getter })
}
}
// 07 Defines the r method to indicate that the current module is of es6 type
__webpack_require__.r = function (exports) {
if (typeof Symbol! = ='undefined' && Symbol.toStringTag) {
Object.defineProperty(exports.Symbol.toStringTag, { value: 'Module'})}Object.defineProperty(exports.'__esModule', { value: true})}// 08 defines the n method to set specific getters
__webpack_require__.n = function (module) {
let getter =
module && module.__esModule
? function getDefault() {
return module['default']} :function getModuleExports() {
return module
}
__webpack_require__.d(getter, 'a', getter)
return getter
}
// 11 Defines the t method to load the module content of the specified value, process the content and return it
__webpack_require__.t = function (value, mode) {
// 01 Load module contents corresponding to value (value is usually a module ID)
// The loaded content is reassigned to the value variable
if (mode & 1) {
value = __webpack_require__(value)
}
if (mode & 8) {
// Load the content that can be returned to use directly
return value
}
if (mode & 4 && typeof value === 'object' && value && value.__esModule) {
return value
}
// If 8 and 4 are not true, you need to define ns to return the content via the default attribute
let ns = Object.create(null)
__webpack_require__.r(ns)
Object.defineProperty(ns, 'default', { enumerable: true.value: value })
if (mode & 2 && typeofvalue ! = ='string') {
for (var key in value) {
__webpack_require__.d(
ns,
key,
function (key) {
return value[key]
}.bind(null, key)
)
}
}
}
// 09 Defines the p attribute, which is used to save the resource access path
__webpack_require__.p = ' '
// 10 Invoke the __webpack_require__ method to import and load modules
return __webpack_require__((__webpack_require__.s = './src/index.js')) ({})'./src/c.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, 'age'.function () {
return age
})
__webpack_exports__['default'] = 'aaaaaa'
const age = 90
},
'./src/index.js': function (
module,
__webpack_exports__,
__webpack_require__
) {
'use strict'
__webpack_require__.r(__webpack_exports__)
var _c_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
/ *! ./c.js */ './src/c.js'
)
console.log('index.js')
console.log(
_c_js__WEBPACK_IMPORTED_MODULE_0__['default'].The '-',
_c_js__WEBPACK_IMPORTED_MODULE_0__['age'])}})Copy the code
conclusion
Finally, let’s review the MDN definition of a closure: a combination of a function bundled with references to its surrounding state (lexical environment) is called a closure. That is, closures allow you to access the scope of an outer function within an inner function. In JavaScript, whenever a function is created, the closure is created at the same time the function is created.
Now you have a better understanding of this saying?