• javascript中闭包的理解及应用


    一、引子

    闭包是有权访问另一个函数作用域中的变量的函数。
    闭包是javascript中很难理解的部分,很多高级的应用都依靠闭包来实现的,我们先来看下面的一个例子:

    function outer() {
    var i = 100;
    function inner() {
    console.log(i);
    }
    }
    上面代码,根据变量的作用域,函数outer中所有的局部变量,对函数inner都是可见的;函数inner中的局部变量,在函数inner外是不可见的,所以在函数inner外是无法读取函数inner的局部变量的。

    既然函数inner可以读取函数outer的局部变量,那么只要将inner作为返会值,就可以直接在ouer外部读取inner的局部变量。
    function outer() {
    var i = 100;
    function inner() {
    console.log(i);
    }
    return inner;
    }
    var rs = outer();
    rs();
    这个函数有两个特点:
      1.函数inner嵌套在函数ouer内部;
      2.函数outer返回函数inner。
    这样执行完var rs = outer()后,实际rs指向了函数inner。这段代码其实就是一个闭包。也就是说当函数outer内的函数inner被函数outer外的一个变量引用的时候,就创建了一个闭包。

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

    1.全局作用域
    var num1 = 1;
    function fun1 (){
    num2 = 2;
    }
    以上三个对象num1,num2和fun1均是全局作用域,这里要注意的是末定义直接赋值的变量自动声明为拥有全局作用域;

    2.局部作用域
    function wrap(){
    var obj = "我被wrap包裹起来了,wrap外部无法直接访问到我";
    function innerFun(){
    //外部无法访问我
    }
    }
    3.作用域链
    Javascript中一切皆对象,这些对象有一个[[Scope]]属性,该属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链(Scope Chain),它决定了哪些数据能被函数访问。

    function add(a,b){
    return a+b;
    }
    当函数创建的时候,它的[[scope]]属性自动添加好全局作用域
    var sum = add(3,4);
    当函数调用的时候,会创建一个称为运行期上下文(execution context)的内部对象,z这个对象定义了函数执行时的环境。它也有自己的作用域链,用于标识符解析,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。

    在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象(最后一个为全局对象)都未找到,则认为该标识符未定义。

    三、闭包
    1.闭包简单来说就是一个函数访问了它的外部变量。
    百度百科对于闭包的解释是:闭包是指可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)

    var quo = function(status){
    return {
    getStatus: function(){
    return status;
    }
    }
    }
    status保存在quo中,它返回了一个对象,这个对象里的方法getStatus引用了这个status变量,即getStatus函数访问它的外部变量status;

    var newValue = quo('string');//返回了一个匿名对象,被newValue引用着
    newValue.getStatus();//访问到了quo的内部变量status
    假如并没有getStatus这个方法,那么quo('sting')结束后,status自动被回收,正是因为返回的匿名对象被一个全局对象引用,那么这个匿名对象又依赖于status,所以会阻止status的释放。

    例子:
    //错误方案
    var test = function(nodes){
    var i ;
    for(i = 0;i<nodes.length;i++){
    nodes[i].onclick = function(e){
    alert(i);
    }
    }
    }
    匿名函数创建了一个闭包,那么其访问的i是外部test函数中的i,所以每一个节点实际上引用的是同一个i。

    //改进方案
    var test = function(nodes){
    var i ;
    for(i = 0;i<nodes.length;i++){
    nodes[i].onclick = function(i){
    return function(){
    alert(i);
    };
    }(i);
    }
    }
    每一个节点绑定了一个事件,这个事件接收一个参数,并且立即运行,传入i,因为是按值传递的,所以每一次循环都会为当前i产生一个新的备份。

    2.闭包的作用
    function outer() {
    var i = 100;
    function inner() {
    console.log(i++);
    }
    return inner;
    }
    var rs = outer();
    rs(); //100
    rs(); //101
    rs(); //102
    上面的代码中,rs是闭包inner函数。rs共运行了三次,第一次100,第二次101,第三次102,这说明在函数outer中的局部变量i一直保存在内存中,并没有在调用自动清除。

    闭包的作用就是在outer执行完毕并返回后,闭包使javascript的垃圾回收机制(grabage collection)不会回收outer所占的内存,因为outer的内部函数inner的执行要依赖outer中的变量。(另一种解释:outer是inner的父函数,inner被赋给了一个全局变量,导致inner会一直在内存中,而inner的存在依赖于outer,因些outer也始终于在内存中,不会在调用结束后被垃圾收集回收)。

    闭包有权访问函数内部的所有变量。
    当函数返回一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。

    3.闭包与变量

    由于作用域链的机制,闭包只能取得包含函数中任何变量的最后一个值。看下面例子:

    function f() {
    var rs = [];

    for (var i=0; i <10; i++) {
    rs[i] = function() {
    return i;
    };
    }

    return rs;
    }

    var fn = f();

    for (var i = 0; i < fn.length; i++) {
    console.log('函数fn[' + i + ']()返回值:' + fn[i]());
    }
    运行结果:
    //函数fn[0]()返回值:10
    //函数fn[1]()返回值:10
    //函数fn[2]()返回值:10
    //函数fn[3]()返回值:10
    //函数fn[4]()返回值:10
    //函数fn[5]()返回值:10
    //函数fn[6]()返回值:10
    //函数fn[7]()返回值:10
    //函数fn[8]()返回值:10
    //函数fn[9]()返回值:10

    函数会返回一个数组,表面上看,似乎每个函数都应该返回自己的索引值,实际上,每个函数都返回10,这是因为第个函数的作用域链上都保存着函数f的活动对象,它们引用的都是同一变量i。当函数f返回后,变量i的值为10,此时每个函数都保存着变量i的同一个变量对象。我们可以通过创建另一个匿名函数来强制让闭包的行为符合预期。

    function f() {
    var rs = [];

    for (var i=0; i <10; i++) {
    rs[i] = function(num) {
    return function() {
    return num;
    };
    }(i);
    }

    return rs;
    }

    var fn = f();

    for (var i = 0; i < fn.length; i++) {
    console.log('函数fn[' + i + ']()返回值:' + fn[i]());
    }
    运行结果:
    //函数fn[0]()返回值:0
    //函数fn[1]()返回值:1
    //函数fn[2]()返回值:2
    //函数fn[3]()返回值:3
    //函数fn[4]()返回值:4
    //函数fn[5]()返回值:5
    //函数fn[6]()返回值:6
    //函数fn[7]()返回值:7
    //函数fn[8]()返回值:8
    //函数fn[9]()返回值:9

    这个版本中,我们没有直接将闭包赋值给数组,而是定义了一个匿名函数,并将立即执行匿名函数的结果赋值给数组。这里匿名函数有一个参数num,在调用每个函数时,我们传入变量i,由于参数是按值传递的,所以就会将变量i复制给参数num。而在这个匿名函数内部,又创建了并返回了一个访问num的闭包,这样,rs数组中每个函数都有自己num变量的一个副本,因此就可以返回不同的数值了。

    4.闭包中的this对象
    var name = 'Jack';

    var o = {
    name : 'bingdian',

    getName : function() {
    return function() {
    return this.name;
    };
    }
    }
    console.log(o.getName()()); //Jack

    var name = 'Jack';
    var o = {
    name : 'bingdian',

    getName : function() {
    var self = this;
    return function() {
    return self.name;
    };
    }
    }

    console.log(o.getName()()); //bingdian
    5.内存泄露
    function assignHandler() {
    var el = document.getElementById('demo');
    el.onclick = function() {
    console.log(el.id);
    }
    }
    assignHandler();
    以上代码创建了作为el元素事件处理程序的闭包,而这个闭包又创建了一个循环引用,只要匿名函数存在,el的引用数至少为1,因些它所占用的内存就永完不会被回收。

    function assignHandler() {
    var el = document.getElementById('demo');
    var id = el.id;

    el.onclick = function() {
    console.log(id);
    }

    el = null;
    }
    assignHandler();
    把变量el设置null能够解除DOM对象的引用,确保正常回收其占用内存。

    6.模仿块级作用域

    任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。
    (function(){
    //块级作用域
    })();

    四、闭包的应用场景

    1.保护函数内的变量安全。如前面的例子,函数outer中i只有函数inner才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
    2.在内存中维持一个变量。如前面的例子,由于闭包,函数outer中i的一直存在于内存中,因此每次执行rs(),都会给i加1。

    原文链接:1.http://www.jb51.net/article/84100.htm
         2.https://www.cnblogs.com/duzheqiang/p/5777954.html
  • 相关阅读:
    Extjs Ext.extend函数的使用
    Silverlight 4 用户名密码验证提示
    Balsamiq Mockups
    自定义RDLC报表的数据集(手工编辑rdlc文件,配置数据集)
    VS2010 帮助文档离线安装
    为 Silverlight 客户端生成服务
    将Win7电脑改造成无线路由
    RDLC报表:每页显示N条记录
    C# 禁止ALT+F4
    让C#程序run anywhere 脱离.net Framework框架环境
  • 原文地址:https://www.cnblogs.com/lingdu87/p/9105014.html
Copyright © 2020-2023  润新知