黑马程序员技术交流社区

标题: 【广州前端】前端之路之闭包的理解 [打印本页]

作者: AMay    时间: 2018-8-23 15:40
标题: 【广州前端】前端之路之闭包的理解
本帖最后由 AMay 于 2018-8-23 15:53 编辑

查看更多精彩前端资源
【前言】
    闭包(closure)是javascript的一大难点,也是它的特色。很多高级应用都要依靠闭包来实现。理解闭包,首先必须理解变量作用域。

【关于作用域的问题】
[JavaScript] 纯文本查看 复制代码
var n = 999;
function f1() {
    console.log(n);
}
f1() // 999
函数内部可以直接读取全局变量,函数 f1 可以读取全局变量 n。
但是,在函数外部无法读取函数内部声明的变量
[JavaScript] 纯文本查看 复制代码
function f1() {
  var n = 99;
}
f1()
console.log(n);
有时我们却需要在函数外部访问函数内部的变量;正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
[JavaScript] 纯文本查看 复制代码
function f1() {
    var n = 999;
    var f2 = function() {
       console.log(n);
    }
    return f2;
}
var f = f1();
f();
上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。
这就是JavaScript语言特有的”链式作用域”结构(chain scope),子级会一层一层地向上寻找所有父级的变量。所以,父级的所有变量,对子级都是可见的,反之则不成立。
既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。
在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

【闭包的作用】
1.  匿名自执行函数
我们知道所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,
比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。
除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,
比如UI的初始化,那么我们可以使用闭包:
[JavaScript] 纯文本查看 复制代码
var data= {   
    table : [],   
    tree : {}   
};   
(function(dm){   
    for(var i = 0; i <dm.table.rows; i++){   
       var row =dm.table.rows;   
       for(var j = 0; j< row.cells; i++){   
           drawCell(i,j);   
       }   
    }   
})(data);

我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在函数执行完后会立刻释放资源,关键是不污染全局对象。
2. 结果缓存
我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,
那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
[JavaScript] 纯文本查看 复制代码
var CachedSearchBox = (function(){   
    var cache = {},   
       count = [];   
    return {   
       attachSearchBox :function(dsid){   
           if(dsid incache){//如果结果在缓存中   
              returncache[dsid];//直接返回缓存中的对象   
           }   
           var fsb = newuikit.webctrl.SearchBox(dsid);//新建   
           cache[dsid] =fsb;//更新缓存   
           if(count.length> 100){//保正缓存的大小<=100   
              deletecache[count.shift()];   
           }   
           returnfsb;         
       },   
       clearSearchBox :function(dsid){   
           if(dsid incache){   
              cache[dsid].clearSelection();      
           }   
       }   
    };   
})();   
CachedSearchBox.attachSearchBox("input");
这样我们在第二次调用的时候,就会从缓存中读取到该对象。
3. 封装
[JavaScript] 纯文本查看 复制代码
var person = function(){   
    //变量作用域为函数内部,外部无法访问   
    var name ="default";      
    return {   
       getName :function(){   
           returnname;   
       },   
       setName :function(newName){   
           name =newName;   
       }   
    }   
}();   
print(person.name);//直接访问,结果为undefined   
print(person.getName());   
person.setName("abruzzi");   
print(person.getName());   

得到结果如下:  

undefined  
default  
abruzzi





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2