Home > Article > Web Front-end > What you need to know about JavaScript closures
This article brings you study notes about JavaScript closures, including closures, method stacks, and the role of closures. I hope it will be helpful to you.
By definition, it is a scripting language, and it is a scripting language that is relatively easy to learn. Without much expertise, you can also use js (abbreviation for JavaScript) code to a certain extent.
Of course, if you have learned a front-end knowledge, you should be able to understand the role of this tool. This is a very convenient tool for displaying the spacing between page elements. You see, you just performed some simple browser operations, and you may not even understand the content of the above code, but you have just embedded a piece of js code in the page you are on (obviously it is harmless, Please feel free to use it) Thanks to the video of the up master CodingStartup minimum course [With it, the webpage will be the same as the design drawing] and the reply of the up master ArcRain below the video
The purpose of this study note is to record me I have some insights and experiences on the js learning journey, as well as some tips that I think are not for teaching, so I will not give answers to the principles of some of the content. It may be that I cannot describe it accurately. , maybe I haven’t understood it yet, and my level is quite limited. If there are any mistakes in the text, you are welcome to criticize.
I formally learned JavaScript in a training class. Yes, I came out of a training class, not from a major. It can be said that I am very grassroots. When I was studying, the ES6 standard had not yet become popular, and variables were still named using the very traditional var. The first piece of code I learned was the classic console.log('Hello, world!'), which of course prints on the console. from.
Of course, the JavaScript content in training institutions is very simple, with only the most basic variable definition and naming, function declaration, callback function, ajax and the most basic dom operations. Obviously these contents are completely insufficient for the work.
The opportunity to learn js came from my work. At work, I learned about node for the first time, and also learned that even js can be used as a backend (I do JAVA Training), and gradually came into contact with some ES6 standards. Of course, these are all things for later. The biggest obstacle I encountered at the beginning was this product.
Ah, I only have a little knowledge of it, and I can't understand the jsonp code encapsulated by our company at all. It looks like this .
var jsonp = (function(){ var JSONP; return function(url){ if (JSONP) { document.getElementsByTagName("head")[0].removeChild(JSONP); } JSONP = document.createElement("script"); JSONP.type = "text/javascript"; JSONP.src = url; document.getElementsByTagName("head")[0].appendChild(JSONP); } }())
Of course, this method can no longer be used directly through the console on the browser. In order to prevent XSS attacks, the browser has banned such injection of code, but it can still be used on the server. Of course, these are Not the point.
The key point is here
if (JSONP) { //dosome }
If you are like me and don’t know what closure is or have only a limited understanding of closure, then you should also have questions about this. The idea is roughly like this The
line 2 defines JSONP but does not assign a value. Now the JSONP value is null. The third line returns a method. The fourth line detects whether the JSONP value is empty. If it is not empty, it does something. , okay, you don’t need to read it later, this if is written in vain, it is 100% impossible to enter!
You see, there is no assignment before, and then it is judged directly, then it is obviously null. But when you actually use it, you will find that the first call to this place will indeed not enter this branch, but as long as you call it the second time, it will 100% enter this branch.
// 这个是一个可以在控制台输出的闭包版本,你可以自己试一下 var closedhull = (function() { let name = null; // 这里直接赋值为null return function(msg){ if(name) { console.log('name:', name) return name += msg; } return name = msg; } }()) closedhull('我是第一句。') //我是第一句。 closedhull('我是第二句。') //我是第一句。我是第二句。
After running the above example, it is not difficult to see from console.log() or the return value that it has indeed entered the branch of if(name). This is the performance of closure. Here is the definition of closure
A closure is a function that can read the internal variables of other functions.
Okay, now that I have seen what a closure is, let’s not talk about whether it can be used. At least, I have seen it. The package has a distinctive feature return function(){}
No!
Its distinctive feature is the function within the function!
Observe the following methods
/*第一个案例*/ function test1(){ // a应该在方法运行结束后销毁 let a = 1; return { add: function(){ return ++a; } } } let a = test1(); a.add()//2 a.add()//3 /*第二个案例*/ (function(){ // b应该在方法运行结束后销毁 let b = 1, timer = setInterval(()=>{ console.log(++b) }, 2000) setTimeout(()=>{ clearInterval(timer) }, 10000) })()// 2 3 4 5 6 /*第三个案例*/ function showMaker(obj){ // obj应该在方法运行结束后销毁 return function(){ console.log(JSON.stringify(obj)) } } let shower = showMaker({a:1}) // 显然这里你还能看到他 shower(); // {"a":1} /*第四个案例*/ let outObj = (function(){ let c = 'hello', obj = {}; Object.defineProperty(obj, 'out', { get(){ return c; }, set(v){ c = v; } }); return obj })() outObj.out // 可以读取并设置c的值
These four are closures, and they all have the characteristic of methods within methods.
The definition of closure, 1. The variable can be accessed outside the scope of the variable. 2. Extend the life cycle of a local variable by some means. 3. Let the survival time of a local variable exceed its time loop execution time.
3 involves the concept of event loop, which will be discussed later. Here we mainly discuss the definitions of the first two methods.
If you know what the method stack is, you can skip it
局部作用域:在ES6之前,一般指一个方法内部(从参数列表开始,到方法体的括号结束为止),ES6中增加let关键字后,在使用let的情况下是指在一个{}中的范围内(显然,你不能在隐式的{}中使用let,编译器会禁止你做出这种行为的,因为没有{}就没有块级作用域),咱们这里为了简化讨论内容,暂且不把let的块级作用域算作闭包的范畴(其实应该算,不过意义不大,毕竟,你可以在外层块声明它。天啊,JS的命名还没拥挤到需要在一个方法内再去防止污染的程度。)
局部变量:区别于全局变量,全局变量会在某些时候被意外额创造和使用,这令人非常的...恼火和无助。局部变量就是在局部作用域下使用变量声明关键字声明出来的变量,应该很好理解。
局部变量的生命周期:好了,你在一个局部作用域中通过关键字(var const let等)声明了一个变量,然后给它赋值,这个局部变量在这个局部作用域中冒险就开始了,它会被使用,被重新赋值(除了傲娇的const小姐外),被调用(如果它是个方法),这个局部变量的本质是一个真实的值,区别在于如果它是个对象(对象,数组,方法都是对象)那么,它其实本质是一个地址的指针。如果它一个基础类型,那么它就是那个真实的值。它之所以存活是因为它有个住所。内存。
局部作用域与内存:每当出现一个局部作用域,一个方法栈就被申请了出来,在这个方法栈大概长这样子
| data5 | | data4 | | data3 | | data2 | |__data1_|
当然,它是能够套娃的,长这个样子
| | d2 | | | |_d1_| | | data3 | | data2 | |__data1___|
如果上面的东西是在太过于抽象,那么,我可以用实际案例展示一下
function stack1(){ var data1, data2, data3, data4, data5 } function stack2(){ var data1, data2, data3; function stackInner(){ var d1, d2; } }
如果方法栈能够直观的感受的话,大约就是这个样子,咱们重点来分析stack2的这种情况,同时写一点实际内容进去
function stack2(){ var data1 = '1', data2 = {x: '2'}, data3 = '3'; function stackInner(){ var d1 = '4', d2 = {y: '5'}; } stackInner() } stack2()
显然其中data1,data3,d1持有的是基本类型(string),data2,d2持有的是引用类型(object),反应到图上
运行时的方法栈的样子
|------>{y: '5'} | |->{x: '2'} | | d2-| || | | |_d1='4'_|| | | data3='3' | | | data2 ----| | |__data1='1'___|
画有点抽象...就这样吧。具体对象在哪呢?他们在一个叫堆的地方,不是这次的重点,还是先看方法栈内的这些变量,运行结束后,按照先进后出的原则,把栈内的局部变量一个一个的销毁,同时堆里的两个对象,由于引用被销毁,没了继续存在的意义,等待被垃圾回收。
接下来咱们要做两件事情:
d1不再等于4了,而是引用data1
return stackInner 而不是直接调用
这样闭包就完成了
function stack2(){ var data1 = {msg: 'hello'}, data2 = {x: '2'}, data3 = '3'; function stackInner(){ var d1 = data1, d2 = {y: '5'}; } return stackInner } var out = stack2()
这里有一个要点,d2赋值给data1一定是在stackInner中完成的,原因?因为再stackInner方法中d2才被声明出来,如果你在stack2中d1 = data1那么恭喜你,你隐式的声明了一个叫d1的全局变量,而且在stackInner由于变量屏蔽的原因,你也看不到全局上的d2,原本计划的闭包完全泡汤。
变量屏蔽:不同作用域中相同名称的变量就会触发变量屏蔽。
看看栈现在的样子
运行时的方法栈的样子
|------>{y: '5'} out<---| | |----| | | | d2-| | | | | | |--|_d1---|_| | | | data3='3' | | | data2(略) | | |_____data1<------|__|
好了,这个图可以和我们永别了,如果有可能,我后面会用画图工具替代,这么画图实在是太过邪典了。
这里涉及到了方法栈的一个特性,就是变量的穿透性,外部变量可以在内部的任意位置使用,因为再内部执行结束前,外部变量会一直存在。
由于stackInner被外部的out引用,导致这个对象不会随着方法栈的结束而销毁,接下来,最神奇的事情来了,由于stackInner这对象没有销毁,它内部d1依然保有data1所对应数据的引用,d1,d2一定会活下来,因为他们的爸爸stackInner活下来了,data1也以某种形式活了下来。
为什么说是某种形式,因为,本质上来说data1还是被销毁了。没错,只不过,data1所引用的那个对象的地址链接没有被销毁,这个才是本质。栈在调用结束后一定是会销毁的。但是调用本体(方法对象)只要存在,那么内部所引用的链接就不会断。
这个就是闭包的成因和本质。
OK,我猜测上一个章节估计很多人都直接跳过了,其实,跳过影响也不多,这个部分描述一下结论性的东西,闭包的作用。
它的最大作用就是给你的变量一个命名空间,防止命名冲突。要知道,你的框架,你export的东西,你import进来的东西,在编译的时候都会变成闭包,为的就是减少你变量对全局变量的污染,一个不依赖与import export的模块的代码大概长这个样子
(function(Constr, global){ let xxx = new Constr(env1, env2, env3) global.NameSpace = xxx; })(function(parm1, parm2, parm3) { //dosomeing reutrn { a: 'some1', b: 'some2', funcC(){ //dosome }, funcD(){ //dosome } } }, window)
当然这种封装代码的风格有多种多样的,但是大家都尽量把一套体系的内容都放到一个命名空间下,避免与其他框架产生冲突
相关推荐:javascript学习教程
The above is the detailed content of What you need to know about JavaScript closures. For more information, please follow other related articles on the PHP Chinese website!