The most essential closure article in the eastern hemisphere

zxg_ God says light 2021-05-03 13:17:55
essential closure article eastern hemisphere

First statement , This is not the title party , yes ssh Sealed [ Eat the melon ].

stay JavaScript Inside , function 、 block 、 Modules can form scopes ( A separate space for variables ), They can be nested with each other , There is a reference relationship between scopes , This chain is called scope chain .

What is the scope chain like ?

Static scope chain

For example, a piece of code like this

 function func() {
const guang = 'guang';
function func2() {
const ssh = 'ssh';
function func3 () {
const suzhe = 'suzhe';
 Copy code 

among , Yes guang、ssh、suzhe 3 A variable , Yes func、func2、func3 3 A function , There's another piece , The scope chain between them can be used babel Check it out. .

const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const code = `
function func() {
const guang = 'guang';
function func2() {
const ssh = 'ssh';
function func3 () {
const suzhe = 'suzhe';
const ast = parser.parse(code);
traverse(ast, {
FunctionDeclaration (path) {
if (path.get('').node === 'func3') {
 Copy code 

The result is

Visualize it with a graph, and that's it


Variable declarations within the scope of functions and blocks will be in the scope (scope) Create a binding inside ( The variable name is bound to a specific value , That is to say binding), And then the rest of the world can quote (refer) This binding, This is the variable access order of the static scope chain .

Why call “ static state ” Well ?

Because this nesting relationship can be obtained by analyzing the code , No need to run , The chain that accesses variables in this order is the static scope chain , The advantage of this chain is that you can intuitively know the reference relationship between variables .

Relative , And dynamic scope chains , That is to say, the reference relation of the scope has nothing to do with the nesting relation , It's about the order of execution , Different functions will be created dynamically during execution 、 The reference relationship of the scope of the block . The disadvantage is that it's not intuitive , No static analysis .

Static scope chain can be used for static analysis , For example, we just used babel Analytical scope The chain is . So the vast majority of programming languages are scope chain design, are to choose the static order .

however ,JavaScript In addition to the static scope chain , Another feature is that functions can be used as return values . such as

function func () {
const a = 1;
return function () {
const f2 = func();
 Copy code 

This leads to a problem , Originally created in order to call a layer of functions , It's nice to create and destroy scopes in order , But if the inner function returns or is exposed through something else , So the outer functions are destroyed , The inner functions are not destroyed , How to deal with scope at this time , Parent scope pin does not destroy ? ( Like here func Do you want to destroy the scope at the end of the call )

Unordered function calls and closures

For example, the above code to do the next transformation , Returns the internal function , And then call... Outside :

function func() {
const guang = 'guang';
function func2() {
const ssh = 'ssh';
function func3 () {
const suzhe = 'suzhe';
return func3;
return func2;
const func2 = func();
 Copy code 

When calling func2 When func1 It's done , It will not be destroyed at this time ? therefore JavaScript We designed the mechanism of closure .

How to design a closure ?

Don't look at the answer first , Consider how we can solve the problem that the parent scope in the static scope chain is destroyed before the child scope .

First , Whether the parent scope should be destroyed ? If the parent scope is not destroyed ?

No good , There are a lot of things in the parent scope that have nothing to do with child functions , Why is the sub function always resident in memory before it ends . There must be a performance problem , So we still need to destroy . However, destroying the parent scope does not affect the child function , So create another object , You need to refer to the sub function (refer) The variables of the parent scope of the , Package the subfunction and take it away .

How to package and take away the sub function ?

Design is a unique property , such as [[Scopes]] , Use this to put the environment used by the function package . And this property has to be a stack , Because functions have subfunctions 、 Subfunctions may also have subfunctions , Every time you pack, you put one bag here , So we need to design a stack structure , It's like a lunch box with multiple layers .

The solution we're considering : After destroying the parent scope , Wrap the variables you use , Package it to a subfunction , Put it on an attribute . This is the mechanism of closure .

Let's experiment with the properties of closures :


This func3 Do you need to pack something ? Will there be closures ?


Actually, there are closures , Closures contain at least global scope .

But why? guang、ssh、suzhe None ? suzhe It's because it's not external , Only external variables are generated , Let's change the code , Print this 3 A variable .


Look again [[Scopes]] ( The closure environment of packing away ):


There are two closures , Why? ? suzhe Where is it ?

First , What we need to package is what we don't have in the environment , That is, closures only hold external references . Then it is saved to the function property when creating the function , The created function will be packaged to the function when it returns , however JS How does the engine know which external references it uses , Need to do AST scanning , quite a lot JS The engine will do Lazy Parsing, Go to at this time parse function , You can also know exactly what external references it uses , And then we'll package these external products into Closure Closure , Add to [[scopes]] in .

therefore , When a closure returns a function, it scans the identifier reference in the function , Type the variables used in this scope into Closure package , Put it in [[Scopes]] in .

So the function above will be in func3 Scan the identifier in the function on return , hold guang、ssh The scan came out , Just follow the scope chain to find these two variables , Filter it out and pack it in two Closure( Because it belongs to two scopes , So two Closure), Plus the outermost layer Global, Set to function func3 Of [[scopes]] attribute , Let it go .

call func3 When ,JS engine Will take out [[Scopes]] In the package Closure + Global chain , Set to a new scope chain , This is all the external environment that the function uses , With the external environment , Naturally, it will work .

Here's a question : When debugging code, why did you encounter a variable that can be accessed in the scope , But there is no relevant information ?


This traverse, It's clearly accessible , Why not display information ? yes debugger It's too bad ?

No, it isn't , If you don't know why , That's because you don't understand closures yet , Because of this FunctionDeclaration Is a callback function , Obviously called inside another function , You need to package and take away the things in this environment at the time of creation , According to the principle of packing only the necessary environment ( Don't waste memory ),traverse Not quoted (refer), Of course, it's not packed . Not at all debugger Yes bug 了 .

So we just need to visit , You can access it during debugging .


Do you suddenly know why you can't read some variable information when debugging , Can explain this phenomenon clearly , Even if you understand closures .


Let's think about one more question : Closures need to scan the identifier inside the function , Do static analysis , that eval What do I do , It's possible that his content was recorded from the Internet , Read from disk, etc , The content is dynamic . It's impossible to use static to analyze dynamic bug Of . What do I do ?

you 're right ,eval There is no way to analyze external references , There's no way to pack closures , This is a special treatment , Just package the whole scope .

Check it out :


This is like the above , Will package the external references as closures


This is eval The implementation of the , Because there's no way to analyze dynamic content statically, it's all packaged as closures , The original purpose of closure is not to save all the contents of the scope chain , result eval Cause it's all saved , So try not to use eval. Will cause the closure to save too much .


however JS The engine only deals with direct calls , That is to say, call eval To package the entire scope , If you don't call eval, There's no way to analyze references , There's no way to form a closure .

Sometimes this kind of special situation can be used to complete some black magic , For example, the use of not directly call eval No closures are generated , Features that will be executed in a global context .

Give closure a definition

Let's use our experiment just now to define closures :

Closures are created when functions are created , Let the function be packed and taken away, and filter the scope chain according to the external reference inside the function, and the remaining chain . It's a subset of the scope chain generated at function creation time , It's the external environment of packaging .evel Because there's no way to analyze the content , So a direct call will package the entire scope ( So try not to use eval, It's easy to keep too many useless variables in closures ), Without calling directly, there is no closure .

Filtering rules :

  1. Global scopes are not filtered out , It must contain . So you can access it wherever you call the function .

  2. Other scopes will be filtered out according to whether there are internal variables referenced by the current function . Not every returned subfunction generates a closure .

  3. Referenced scopes also filter out unreferenced binding ( Variable declarations ). Just package the variables you use .

Disadvantages of closures

JavaScript It's the design of static scopes , Closure is to solve the problem that the child function is destroyed later than the parent function , When the parent function is destroyed , The variable to which the subfunction is referred achieves Closure The package is placed in the function's [[Scopes]] On , Let it calculate the destruction of the parent function, but also anytime and anywhere access to the external environment .

This design really solves the problem , But are there any shortcomings ?

In fact, this is the problem [[Scopes]] Attribute

We know JavaScript The engine will divide the memory into function call stacks 、 Global scope and heap , The heap is used to put some dynamic objects , Call stack each stack frame puts a function's execution context , There's a local The variable environment is used to place some variables declared internally , If it's an object , Space is allocated on the heap , Then save the reference in the stack frame local Environment . The same is true for global scopes , It's just used for static things , Sometimes it's also called static domain .


The execution context of each stack frame contains all the environments that function execution needs to access , Include local Environmental Science 、 Scope chain 、this etc. .

So what happens if the subfunction returns ?

First, the stack frame of the parent function is destroyed , The sub function has not been called at this time , So it's still an object in the heap , There is no corresponding stack frame , At this time, the parent function filters out the scope chain that needs to be used , Form a closed envelope chain , Set to the sub function of [[Scopes]] Attribute .


The parent function is destroyed , The memory corresponding to the stack frame is released immediately , Use of ssh Obj Will be gc Recycling , The returned function will filter out the references used by the scope chain to form a closed package chain and put it in the heap . This leads to a hidden danger : The scope is destroyed at the end of the function call , Because the entire stack frame will be destroyed immediately . And after the closure is formed , Moved to heap memory . When you run this subfunction , Sub functions create stack frames , If this function is running all the time , Then its closure in heap memory is always occupying memory , Will reduce the available memory , If it's serious enough, it's a memory leak . So don't misuse closures , Pack less stuff into heap memory .


Let's start with static scope , Define what scope is , adopt babel Static analysis of the scope , Learn about static and dynamic scopes , Then we introduce the problem that the child function is destroyed before the parent function , Think about the next plan , Then we introduce the concept of closure , Analyze the process of closure generation , Saved in . We also use the characteristics of closure to analyze why we can't view variable information when debugging , After that, I analyzed eval Why can't we generate closures exactly , When to package all scopes 、 When not to generate a closure , eval Why does it lead to too much memory . Then we analyze the characteristics of functions with closures in memory , Explains why there may be a memory leak .

A closure is when it returns a function , In order to save the environment, download , Create a snapshot of , The scope chain is tree shking, Only the necessary closed envelope chains are left , Keep it in the pile , As the object [[scopes]] attribute , Let the function go wherever it goes , You can access the external environment anytime, anywhere . When executing this function , Will use this “ snapshot ”, Restore the scope chain .

Because the function hasn't been executed yet , So statically analyze identifier references . Static analysis and dynamic analysis have been proved impossible by countless frameworks , So the function returned is eval Can only package all or not generate closures . similar webpack The dynamics of the import There's no way to analyze the same .

本文为[zxg_ God says light]所创,转载请带上原文链接,感谢

  1. HTML + CSS + JavaScript to achieve cool Fireworks (cloud like particle text 3D opening)
  2. HTML + CSS + JavaScript realizes 520 advertising love tree (including music), which is necessary for programmers to express themselves
  3. Solve the problem of Web front-end deployment server (it can be deployed online without a server)
  4. HTML + CSS + JS make wedding countdown web page template (520 / Tanabata Valentine's Day / programmer advertisement)
  5. What else can driverless minibus do besides "Park connection"?
  6. Cloud native leads the era of all cloud development
  7. NRM mirror source management tool
  8. Bring it to you, flex Jiugong
  9. Lolstyle UI component development practice (II) -- button group component
  10. Deconstruction assignment in ES6
  11. Luo 2 peerless Tang clan was officially launched. The official gave a key point, and the broadcast time was implied
  12. 20初识前端HTML(1)
  13. 当新零售遇上 Serverless
  14. 20 initial knowledge of front-end HTML (1)
  15. When new retail meets serverless
  16. [golang] - go into go language lesson 5 type conversion
  17. [golang] - go into go language lesson 6 conditional expression
  18. HTML5(八)——SVG 之 path 详解
  19. HTML5 (8) -- detailed explanation of SVG path
  20. 需要开通VIP以后页面内容才能复制怎么办?控制台禁用javascript即可
  21. Web前端|CSS入门教程(超详细的CSS使用讲解,适合前端初学者)
  22. 实践积累 —— 用Vue3简单写一个单行横向滚动组件
  23. Serverless 全能选手,再下一城
  24. What if you need to open a VIP to copy the page content? Just disable JavaScript on the console
  25. Web front end | CSS introductory tutorial (super detailed CSS explanation, suitable for front-end beginners)
  26. Practice accumulation - write a single line horizontal scroll component simply with vue3
  27. Dili Reba is thin again. She looks elegant and high in a strapless hollow skirt, and her "palm waist" is beautiful to a new height
  28. Serverless all-round player, next city
  29. The difference between MySQL semi synchronous replication and lossless semi synchronous replication
  30. Vue表单设计器的终极解决方案
  31. The ultimate solution for Vue form designer
  32. Nginx从理论到实践超详细笔记
  33. Yu Shuxin's red backless swimsuit is split to the waist and tail, with a concave convex figure and excessive color matching, and his face is white to dazzling
  34. Nginx ultra detailed notes from theory to practice
  35. 【动画消消乐|CSS】086.炫酷水波浪Loading过渡动画
  36. typecho全站启用https
  37. CCTV has another popular employee. The off-site interpretation is very professional, and the appearance ability is no less than that of Wang Bingbing
  38. [animation Xiaole | CSS] 086. Cool water wave loading transition animation
  39. Enable HTTPS in Typecho
  40. 50天用JavaScript完成50个web项目,我学到了什么?
  41. 根据JavaScript中原生的XMLHttpRequest实现jQuery的Ajax
  42. What have I learned from completing 50 web projects with JavaScript in 50 days?
  43. "My neighbor doesn't grow up" has hit the whole network. There are countless horse music circles, and actor Zhou Xiaochuan has successfully made a circle
  44. 根据JavaScript中原生的XMLHttpRequest实现jQuery的Ajax
  45. Implement the Ajax of jQuery according to the native XMLHttpRequest in JavaScript
  46. Implement the Ajax of jQuery according to the native XMLHttpRequest in JavaScript
  47. 30 + women still wear less T-shirts and jeans. If they wear them like stars, they will lose weight
  48. 数栈技术分享前端篇:TS,看你哪里逃~
  49. Several stack technology sharing front end: TS, see where you escape~
  50. 舍弃Kong和Nginx,Apache APISIX 在趣链科技 BaaS 平台的落地实践
  51. Abandon the landing practice of Kong and nginx, Apache apisik on the baas platform of fun chain technology
  52. 浪迹天涯king教你用elementui做复杂的表格,去处理报表数据(合并表头,合并表体行和列)
  53. 前端HTML两万字图文大总结,快来看看你会多少!【️熬夜整理&建议收藏️】
  54. Wandering around the world king teaches you to use elementui to make complex tables and process report data (merge header, merge table body rows and columns)
  55. 路由刷新数据丢失 - vuex数据读取的问题
  56. Front end HTML 20000 word graphic summary, come and see how much you can【 Stay up late to sort out & suggestions]
  57. Route refresh data loss - vuex data reading problem
  58. Systemctl系统启动Nginx服务脚本
  59. Systemctl system startup nginx service script
  60. sleepless