温故js系列(14)-闭包&垃圾回收&内存泄露&闭包应用&做用域链&再析闭包

前端学习:教程&开发模块化/规范化/工程化/优化&工具/调试&值得关注的博客/Git&面试-前端资源汇总前端

欢迎提issues斧正:闭包git

JavaScript-闭包

闭包(closure)是一个让人又爱又恨的something,它能够实现不少高级功能和应用,同时在理解和应用上有不少难点和须要当心注意的地方。github

闭包的定义

闭包,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(一般是一个函数),于是这些变量也是该表达式的一部分。
简单来讲,闭包就是可以读取其余函数内部变量的函数。在Javascript中,只有函数内部的子函数才能读取函数的局部变量,因此,能够把闭包理解成:定义在一个函数内部的函数,也就是函数嵌套函数,给函数内部和函数外部搭建起一座桥梁。面试

闭包的特色

  1. 定义在一个函数内部的函数。
  2. 函数内部能够引用函数外部的参数和变量。
  3. 做为一个函数变量的一个引用,当函数返回时,其处于激活状态。
  4. 当一个函数返回时,一个闭包就是一个没有释放资源的栈区。函数的参数和变量不会被垃圾回收机制回收。

闭包的造成

Javascript容许使用内部函数,能够将函数定义和函数表达式放在另外一个函数的函数体内。并且,内部函数能够访问它所在的外部函数声明的局部变量、参数以及声明的其余内部函数。当其中一个这样的内部函数在包含它们的外部函数以外被调用时,就会造成闭包。浏览器

function a() {  
    var i = 0;  
    function b() { 
        console.log(i++); 
    }  
    return b; 
}
var c = a(); 
c();

闭包的缺点

1.因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大。因此在闭包不用以后,将不使用的局部变量删除,使其被回收。在IE中可能致使内存泄露,即没法回收驻留在内存中的元素,这时候须要手动释放。安全

function a() {  
    var i = 1;  
    function b() { 
        console.log(i++); 
    }  
    return b; 
}
var c = a(); 
c(); //1
c(); //2
c(); //3   i不被回收
c = null;  //i被回收

2.闭包会在父函数外部,改变父函数内部变量的值。若是你把父函数看成对象使用,把闭包看成它的公用方法,把内部变量看成它的私有属性,要当心,不要随便改变父函数内部变量的值。闭包

var Xzavier = { 
    ten:10,  
    addTen: function(num) {  
       return this.ten + num;   //给一个数加10 
   }    
}
 
console.log(Xzavier.addTen(15));  //25
Xzavier.ten = 20; 
console.log(Xzavier.addTen(15));  //35

内存泄露

内存泄漏指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。模块化

出现缘由:函数

1.循环引用:含有DOM对象的循环引用将致使大部分当前主流浏览器内存泄露。循环 引用,简单来讲假如a引用了b,b又引用了a,a和b就构成了循环引用。
2.JS闭包:闭包,函数返回了内部函数还能够继续访问外部方法中定义的私有变量。
3.Dom泄露,当原有的DOM被移除时,子结点引用没有被移除则没法回收。

JavaScript垃圾回收机制

Javascript中,若是一个对象再也不被引用,那么这个对象就会被GC(garbage collection)回收。若是两个对象互相引用,而再也不被第3者所引用,那么这两个互相引用的对象也会被回收。垃圾回收不是时时的,由于其开销比较大,因此垃圾回收器会按照固定的时间间隔周期性的执行。工具

函数a被b引用,b又被a外的c引用,这就是为何函数a执行后不会被回收的缘由。

垃圾回收的两个方法:

标记清除法:

1.垃圾回收机制给存储在内存中的全部变量加上标记,而后去掉环境中的变量以及被环境中变量所引用的变量(闭包)。
2.操做1以后内存中仍存在标记的变量就是要删除的变量,垃圾回收机制将这些带有标记的变量回收。

引用计数法:

1.垃圾回收机制给一个变量一个引用次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1。
2.当该变量的值变成了另一个值,则这个值得引用次数减1。
3.当这个值的引用次数变为0的时候,说明没有变量在使用,垃圾回收机制会在运行的时候清理掉引用次数为0的值占用的空间。

闭包的应用

1.维护函数内的变量安全,避免全局变量的污染。

函数a中i只有函数b才能访问,而没法经过其余途径访问到。

function xzavier(){
    var i = 1;
    i++;
    console.log(i);
}
xzavier();   //2 
console.log(x);   // x is not defined                 
xzavier();   //2

2.维持一个变量不被回收。

因为闭包,函数a中i的一直存在于内存中,所以每次执行c(),都会给i自加1,且i不被垃圾回收机制回收。

function a() {  
    var i = 1;  
    function b() { 
        console.log(i++); 
    }  
    return b; 
}
var c = a(); 
c();  //1
c();  //2
c();  //3

3.经过第1点的特性设计私有的方法和属性。

var xzavier = (function(){
    var i = 1;
    var s = 'xzavier';
    function f(){
        i++;
        console.log(i);
    }
    return {
        i:i,
        s:s,             
        f:f
    }
})();
xzavier.s;     //'xzavier'
xzavier.s;     //1
xzavier.f()    //2

4.操做DOM获取目标元素

方法2即便用了闭包的方法,固然操做DOM仍是有别的方法的,好比事件委托就比较好用。

ul id="test">
    <li>first</li>
    <li>second</li>
    <li>third</li>
</ul>
// 方法一:this方法
var lis = document.getElementById('test').getElementsByTagName('li');
for(var i = 0;i < 3;i++){
    lis[i].index = i;
    lis[i].onclick = function(){
        console.log(this.index);
    };
} 
// 方法二:闭包方法
var lis = document.getElementById('test').getElementsByTagName('li');
for(var i = 0;i < 3;i++){
    lis[i].index = i;
    lis[i].onclick = (function(val){
        return function() {
            console.log(val);
        }
    })(i);
}
// 方法三 事件委托方法
var oUl = document.getElementById('test');
oUl.addEventListener('click',function(e){
    var lis = e.target;
    console.log(lis); 
});

5.封装模块

逻辑随业务复杂而复杂O(∩_∩)O~

var Xzavier = function(){       
    var name = "xzavier";       
    return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
}();    

console.log(person.name); //undefined,变量做用域为函数内部,外部没法访问    
console.log(person.getName()); // "xzavier" 
person.setName("xz");    
console.log(person.getName());  //"xz"

6.实现类和继承

function Xzavier(){       
    var name = "xzavier";       
    return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
}

var xz = new Xzavier();  //Xzavier就是一个类,能够实例化
console.log(xz.getName());  // "xzavier"

这里是原型继承,我会在下一篇文章讲一讲原型继承。

var X = function(){};
X.prototype = new Xzavier(); 
X.prototype.sports = function(){
    console.log("basketball");
};
var x = new X();
x.setName("xz");
x.sports();  //"basketball"
console.log(x.getName());  //"xz"

JavaScript做用域链

JavaScript做用域

做用域就是变量与函数的可访问范围,即做用域控制着变量与函数的可见性和生命周期。
在JavaScript中,变量的做用域有全局做用域和局部做用域两种。

JavaScript做用域链

JavaScript函数对象拥有能够经过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。
其中一个内部属性是[[Scope]],该内部属性包含了函数被建立的做用域中对象的集合。
这个集合被称为函数的做用域链。

执行上下文

当函数执行时,会建立一个执行上下文(execution context),执行上下文是一个内部对象,定义了函数执行时的环境。
每一个执行上下文都有本身的做用域链,用于标识符解析。
当执行上下文被建立时,而它的做用域链初始化为当前运行函数的[[Scope]]包含的对象。

活动对象

这些值按照它们出如今函数中的顺序被复制到执行上下文的做用域链中。
它们共同组成了一个新的对象,活动对象(activation object)。
该对象包含了函数的全部局部变量、命名参数、参数集合以及this,而后此对象会被推入做用域链的前端。
当执行上下文被销毁,活动对象也随之销毁。
活动对象是一个拥有属性的对象,但它不具备原型并且不能经过JavaScript代码直接访问。

查找机制:

1.当函数访问一个变量时,先搜索自身的活动对象,若是存在则返回,若是不存在将继续搜索函数父函数的活动对象,依次查找,直到找到为止。
2.若是函数存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。
3.若是整个做用域链上都没法找到,则返回undefined。

在执行上下文的做用域链中,标识符所在的位置越深,读写速度就会越慢。全局变量老是存在于执行上下文做用域链的最末端,所以在标识符解析的时候,查找全局变量是最慢的。

so

在编写代码的时候应尽可能少使用全局变量,尽量使用局部变量。
咱们常用局部变量先保存一个屡次使用的须要跨做用取的值再使用。

再析闭包

function a() {  
    var i = 1;  
    function b() { 
        console.log(i++); 
    }  
    return b; 
}
var c = a(); 
c();

1.当定义函数a,js解释器将函数a的做用域链设置为定义a时a所在的环境。
2.执行函数a的时候,a会进入相应的执行上下文。
3.在建立执行上下文的过程当中,首先会为a添加一个scope属性,即a的做用域,其值就为a的做用域链。
4.而后执行上下文会建立一个活动对象。
5.建立完活动对象后,把活动对象添加到a的做用域链的最顶端。此时a的做用域链包含了两个对象:a的活动对象和window对象。
6.接着在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
7.最后把全部函数a的形参和内部的函数b的引用也添加到a的活动对象上。    
  在这一步中,完成了函数b的的定义(如同a),函数b的做用域链被设置为b所被定义的环境,即a的做用域。
8.整个函数a从定义到执行的步骤完成。

a返回函数b的引用给c,由于函数b的做用域链包含了对函数a的活动对象的引用,也就是说b能够访问到a中定义的全部变量和函数。函数b被c引用,函数b又依赖函数a,所以函数a在返回后不会被GC回收,因此造成了闭包。

相关文章
相关标签/搜索