乐趣区

关于javascript:javascript中的闭包closure详解

简介

闭包 closure 是 javascript 中一个十分弱小的性能。所谓闭包就是函数中的函数,外部函数能够拜访内部函数的作用域范畴,从而能够应用闭包来做一些比拟弱小的工作。

明天将会给大家具体介绍一下闭包。

函数中的函数

咱们提到了函数中的函数能够拜访父函数作用域范畴的变量,咱们看一个例子:

function parentFunction() {
  var address = 'flydean.com'; 
  function alertAddress() {alert(address); 
  }
  alertAddress();}
parentFunction();

下面的例子中,咱们在 parentFunction 中定义了一个变量 address,在 parentFunction 外部定义了一个 alertAddress 办法,在该办法外部拜访内部函数中定义的 address 变量。

下面代码运行是没问题的,能够正确的拜访到数据。

Closure 闭包

函数中的函数有了,那么什么是闭包呢?

咱们看上面的例子:

function parentFunction() {
  var address = 'flydean.com'; 
  function alertAddress() {alert(address); 
  }
  return alertAddress;
}
var myFunc = parentFunction();
myFunc();

这个例子和第一个例子很相似,不同之处就是咱们将外部函数返回了,并且赋值给了 myFunc。

接下来咱们间接调用了 myFunc。

myFunc 中拜访了 parentFunction 中的 address 变量,尽管 parentFunction 曾经执行结束返回。

然而咱们在调用 myFunc 的时候,任然能够拜访到 address 变量。这就是闭包。

闭包的这个个性十分领有,咱们能够应用闭包来生成 function factory, 如下所示:

function makeAdder(x) {return function(y) {return x + y;};
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

其中 add5 和 add10 都是闭包,他们是由 makeAdder 这个 function factory 创立进去的。通过传递不同的 x 参数,咱们失去了不同的基数的 add 办法。

最终生成了两个不同的 add 办法。

应用 function factory 的概念,咱们能够思考一个闭包的理论利用,比方咱们在页面上有三个 button,通过点击这些 button 可实现批改字体的性能。

咱们能够先通过 function factory 来生成三个办法:

function makeSizer(size) {return function() {document.body.style.fontSize = size + 'px';};
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

有了这三个办法,咱们把 DOM 元素和 callback 办法绑定起来:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

应用闭包实现 private 办法

比照 java 来说,java 中有 private 拜访描述符,通过 private,咱们能够指定办法只在 class 外部拜访。

当然,在 JS 中并没有这个货色,然而咱们能够应用闭包来达到同样的成果。

var counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {privateCounter += val;}

  return {increment: function() {changeBy(1);
    },

    decrement: function() {changeBy(-1);
    },

    value: function() {return privateCounter;}
  };
})();

console.log(counter.value());  // 0.

counter.increment();
counter.increment();
console.log(counter.value());  // 2.

counter.decrement();
console.log(counter.value());  // 1.

咱们在父 function 中定义了 privateCounter 属性和 changeBy 办法,然而这些办法只可能在外部 function 中拜访。

咱们通过闭包的概念,将这些属性和办法封装起来,裸露给内部应用,最终达到了公有变量和办法封装的成果。

闭包的 Scope Chain

对于每个闭包来说,都有一个作用域范畴,包含函数自身的作用域,父函数的作用域和全局的作用域。

如果咱们在函数外部嵌入了新的函数,那么就会造成一个作用域链,咱们叫做 scope chain。

看上面的一个例子:

// global scope
var e = 10;
function sum(a){return function(b){return function(c){
      // outer functions scope
      return function(d){
        // local scope
        return a + b + c + d + e;
      }
    }
  }
}

console.log(sum(1)(2)(3)(4)); // log 20

闭包常见的问题

第一个常见的问题就是在循环遍历中应用闭包,咱们看一个例子:

function showHelp(help) {document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [{'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {showHelp(item.help);
    }
  }
}

setupHelp();

下面的例子中,咱们创立了一个 setupHelp 函数,setupHelp 中,onfocus 办法被赋予了一个闭包,所以闭包中的 item 能够拜访到内部 function 中定义的 item 变量。

因为在循环外面赋值,所以咱们实际上创立了 3 个闭包,然而这 3 个闭包共享的是同一个内部函数的作用域范畴。

咱们的本意是,不同的 id 触发不同的 help 音讯。然而如果咱们真正执行就会发现,不论是哪一个 id,最终的音讯都是最初一个。

因为 onfocus 是在闭包创立结束之后才会触发,这个时候 item 的值实际上是变动的,在循环完结之后,item 的值曾经指向了最初一个元素,所以全副显示的是最初一条数据的 help 音讯。

怎么解决这个问题呢?

最简略的方法应用 ES6 中引入的 let 描述符,从而将 item 定义为 block 的作用域范畴,每次循环都会创立一个新的 item,从而放弃闭包中的 item 的值不变。

  for (let i = 0; i < helpText.length; i++) {let item = helpText[i];
    document.getElementById(item.id).onfocus = function() {showHelp(item.help);
    }
  }

还有一种办法,就是再创立一个闭包:


function makeHelpCallback(help) {return function() {showHelp(help);
  };
}

  for (var i = 0; i < helpText.length; i++) {var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }

这里用到了之前咱们提到的 function factory 的概念,咱们为不同的闭包创立了不同的作用域环境。

还有一种办法就是将 item 蕴含在一个新的 function 作用域范畴之内,从而每次创立都是新的 item,这个和 let 的原理是类似的:

  for (var i = 0; i < helpText.length; i++) {(function() {var item = helpText[i];
       document.getElementById(item.id).onfocus = function() {showHelp(item.help);
       }
    })();}

第二个常见的问题就是内存泄露。

 function parentFunction(paramA)
 {
 var a = paramA;
 function childFunction()
 {return a + 2;}
 return childFunction();}

下面的例子中,childFunction 援用了 parentFunction 的变量 a。只有 childFunction 还在被应用,a 就无奈被开释,从而导致 parentFunction 无奈被垃圾回收。

闭包性能的问题

咱们定义了一个对象,并且通过闭包来拜访其公有属性:

function MyObject(name, message) {this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {return this.name;};

  this.getMessage = function() {return this.message;};
}

下面的对象会有什么问题呢?

下面对象的问题就在于,对于每一个 new 进去的对象,getName 和 getMessage 办法都会被复制一份,一方面是内容的冗余,另一方面是性能的影响。

通常来说,咱们将对象的办法定义在 prototype 下面:

function MyObject(name, message) {this.name = name.toString();
  this.message = message.toString();}
MyObject.prototype.getName = function() {return this.name;};
MyObject.prototype.getMessage = function() {return this.message;};

留神,咱们不要间接重写整个 prototype,这样会导致未知的谬误,咱们只须要依据须要重写特定的办法即可。

总结

闭包是 JS 中十分弱小和有用的概念,心愿大家可能喜爱。

本文作者:flydean 程序那些事

本文链接:http://www.flydean.com/js-closure/

本文起源:flydean 的博客

欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

退出移动版