Let’s look at some definitions of closures:
A closure refers to a function that has access to a variable in the scope of another function - - "JS Advanced Programming Third Edition" p178
Function objects can be related through scope chains, and variables inside the function body can be saved in the function scope. This feature Called 'closure'. -- "The Definitive Guide to JS" p183
Internal functions can access the parameters and variables of the external functions in which they are defined (except this
and arguments
). --"JS Language Essence" p36
Let’s summarize the definition
You can access variables in the external function scope The function
The variables of the external function accessed by the internal function can be saved in the scope of the external function without being recycled---this is the core, we will meet later When thinking about closures, we need to focus on the variable referenced by the closure.
To create a simple closure
var sayName = function(){var name = 'jozo';return function(){ alert(name); } };var say = sayName(); say();
To interpret the following two statements:
var say = sayName()
: Returns an anonymous internal function stored in the variable say, and references the variable name of the external function. Due to the garbage collection mechanism, after the sayName function is executed, the variable name is not Not destroyed.
say()
: Execute the returned internal function, you can still access the variable name, and output 'jozo'.
Understanding scope chain is also helpful for understanding closure.
You should be familiar with the way variables are searched in the scope. In fact, this is searching up the scope chain.
When the function is called:
First create an execution context and the corresponding scope chain;
Add the values of arguments and other named parameters to the function’s activation object
Scope chain: the activation object priority of the current function The highest, followed by the active objects of external functions, and the active objects of external functions of external functions decrease in order until the end of the scope chain - the global scope. The priority is the order in which variables are searched;
Let’s first look at a common scope chain:
function sayName(name){return name; }var say = sayName('jozo');
This code contains two scopes: a.
Global scope;b.
The scope of the sayName function, that is, there are only two variable objects. When the corresponding execution environment is executed, the variable object will become the active object and be Pushed to the front end of the execution environment scope chain, that is, the one with the highest priority. Look at the picture and talk:
This picture is also in the JS advanced programming book. I drew it again.
When creating the sayName() function, a scope chain that pre-contains the variable object is created, which is the scope chain with index 1 in the figure, and is saved to the internal [[Scope]] attribute. , when the sayName() function is called, an execution environment is created, and then the active domain chain is built by copying the object in the [[Scope]] attribute of the function. After that, there is another active object (index 0 in the figure) ) is created and pushed to the front of the execution environment scope chain.
Generally speaking, when the function is executed, the local active object will be destroyed, and only the global scope will be saved in the memory. However, the situation with closures is different:
Let’s take a look at the scope chain of closures:
function sayName(name){return function(){return name; } }var say = sayName('jozo');
This closure instance has more instances than the previous one After the anonymous function is returned from the sayName() function, its scope chain is initialized to Contains the active object and global variable object of the sayName() function. In this way, the anonymous function can access all variables and parameters defined in sayName(). More importantly, after the sayName() function is executed, its active object will not be destroyed because of the scope chain of the anonymous function. This active object is still being referenced. In other words, after the sayName() function is executed, the scope chain of its execution environment will be destroyed, but its active object will remain in the memory until the anonymous function is destroyed. This is also the memory leak problem that will be discussed later.
I won’t write so much about the scope chain issue, and it’s very tiring to write the things in the book o(╯□╰)o3. Examples of closures
Example 1: Implement accumulation
// 方式1var a = 0;var add = function(){ a++; console.log(a) }add();add();//方式2 :闭包var add = (function(){ var a = 0; return function(){ a++; console.log(a); } })(); console.log(a); //undefinedadd();add(); 相比之下方式2更加优雅,也减少全局变量,将变量私有化
var oli = document.getElementsByTagName('li'); var i; for(i = 0;i < 5;i++){ oli[i].onclick = function(){ alert(i); } } console.log(i); // 5 //执行匿名函数 (function(){ alert(i); //5 }());
a. 先来分析没用闭包前的情况:for循环中,我们给每个li点击事件绑定了一个匿名函数,匿名函数中返回了变量i的值,当循环结束后,变量i的值变为5,此时我们再去点击每个li,也就是执行相应的匿名函数(看上面的代码),这是变量i已经是5了,所以每个点击弹出5. 因为这里返回的每个匿名函数都是引用了同一个变量i,如果我们新建一个变量保存循环执行时当前的i的值,然后再让匿名函数应用这个变量,最后再返回这个匿名函数,这样就可以达到我们的目的了,这就是运用闭包来实现的!
b. 再来分析下运用闭包时的情况:
var oli = document.getElementsByTagName('li'); var i; for(i = 0;i < 5;i++){ oli[i].onclick = (function(num){ var a = num; // 为了说明问题 return function(){ alert(a); } })(i) } console.log(i); // 5
这里for循环执行时,给点击事件绑定的匿名函数传递i后立即执行返回一个内部的匿名函数,因为参数是按值传递的,所以此时形参num保存的就是当前i的值,然后赋值给局部变量 a,然后这个内部的匿名函数一直保存着a的引用,也就是一直保存着当前i的值。 所以循环执行完毕后点击每个li,返回的匿名函数执行弹出各自保存的 a 的引用的值。
我们来看看闭包的用途。事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。
1. 匿名自执行函数
我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,比如UI的初始化,那么我们可以使用闭包:
//将全部li字体变为红色 (function(){ var els = document.getElementsByTagName('li');for(var i = 0,lng = els.length;i < lng;i++){ els[i].style.color = 'red'; } })();
我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,
因此els,i,lng这些局部变量在执行完后很快就会被释放,节省内存!
关键是这种机制不会污染全局对象。
2. 实现封装/模块化代码
var person= function(){ //变量作用域为函数内部,外部无法访问 var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }();console.log(person.name);//直接访问,结果为undefined console.log(person.getName()); //default person.setName("jozo"); console.log(person.getName()); //jozo
3. 实现面向对象中的对象
这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,
我们可以模拟出这样的机制。还是以上边的例子来讲:
function Person(){ var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }; var person1= Person(); print(person1.getName()); john.setName("person1"); print(person1.getName()); // person1 var person2= Person(); print(person2.getName()); jack.setName("erson2"); print(erson2.getName()); //person2
Person的两个实例person1 和 person2 互不干扰!因为这两个实例对name这个成员的访问是独立的 。
垃圾回收机制
说到内存管理,自然离不开JS中的垃圾回收机制,有两种策略来实现垃圾回收:标记清除 和 引用计数;
标记清除:
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后,它会去掉环境中的变量的标记和被环境中的变量引用的变量的标记,此后,如果变量再被标记则表示此变量准备被删除。 2008年为止,IE,Firefox,opera,chrome,Safari的javascript都用使用了该方式;
引用计数:
跟踪记录每个值被引用的次数,当声明一个变量并将一个引用类型的值赋给该变量时,这个值的引用次数就是1,如果这个值再被赋值给另一个变量,则引用次数加1。相反,如果一个变量脱离了该值的引用,则该值引用次数减1,当次数为0时,就会等待垃圾收集器的回收。
这个方式存在一个比较大的问题就是循环引用,就是说A对象包含一个指向B的指针,对象B也包含一个指向A的引用。 这就可能造成大量内存得不到回收(内存泄露),因为它们的引用次数永远不可能是 0 。早期的IE版本里(ie4-ie6)采用是计数的垃圾回收机制,闭包导致内存泄露的一个原因就是这个算法的一个缺陷。
我们知道,IE中有一部分对象并不是原生额javascript对象,例如,BOM和DOM中的对象就是以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数。因此,虽然IE的javascript引擎采用的是标记清除策略,但是访问COM对象依然是基于引用计数的,因此只要在IE中设计COM对象就会存在循环引用的问题!
举个栗子:
window.onload = function(){var el = document.getElementById("id"); el.onclick = function(){ alert(el.id); } }
这段代码为什么会造成内存泄露?
el.onclick= function () { alert(el.id); };
执行这段代码的时候,将匿名函数对象赋值给el的onclick属性;然后匿名函数内部又引用了el对象,存在循环引用,所以不能被回收;
解决方法:
window.onload = function(){var el = document.getElementById("id");var id = el.id; //解除循环引用 el.onclick = function(){ alert(id); } el = null; // 将闭包引用的外部函数中活动对象清除 }
优点:
可以让一个变量常驻内存 (如果用的多了就成了缺点
避免全局变量的污染
私有化变量
缺点
Because the closure will carry the scope of the function that contains it, it will occupy more memory than other functions
Cause memory leaks
The above is the detailed content of Detailed introduction to closures in js. For more information, please follow other related articles on the PHP Chinese website!