什么是闭包:
是一个函数在创建时允许该自身函数访问并操作该自身函数以外的变量时所创建的作用域
闭包可以让函数访问该函数声明时作用域的所有变量和函数
书中例子我看的莫名其妙….甚至觉得理所当然….
还是写出来吧
var outValue='ninja';
var later;
function outFunction() {
var innerValue='samurai'
function innerFunction(paramValue) {
console.log(outValue)//全局作用域中的肯定能拿到
console.log(innerValue)//因为闭包的形成也就是innerFunction函数一直存在会保护作用域内不会被垃圾回收
console.log(paramValue)//传入参数也是能到的
console.log(tooLate)//在调用方法是tooLate已经被声明了
}
later=innerFunction;//这就形成闭包了?
}
console.log(tooLate)
var tooLate='hello'
outFunction();
later('wakizashi')
使用闭包
起码在上面我们知道了,闭包的用处,现在看看具体有什么用途
使用闭包模拟私有变量
function Ninja() {
var feinit=0;
this.getFrinit=function () {
return feinit;
}
this.feinits=function () {
feinit++;
}
}
var ninja=new Ninja();
ninja.feinits();
console.log(ninja.getFrinit())//1
其实吧,这个用法,在ES6中已经有个替代,其中 Ninja中的feinit变量如果你不通过方法你是无法修改它的
回调和计时器
这里基本不用说了,匿名函数大多数是出现在回调中…..
绑定函数上下文
先看个例子:
var button={
clicked:false,
click:function () {
this.clicked=true;
console.log(button.clicked);
}
}
var elem=document.getElementById("test");
elem.addEventListener("click",button.click,false);
你是不是会认识button里面的clicked会是true,结果他还是false
这是为什么呢
因为在例子中,浏览器的事件处理系统认为函数调用的上下文是事件的目标元素
也就是dom元素,而不是button函数
所以其中的赋值并没有成功..
继续修改这个例子 你没猜错还是用apply
function bind(context, name) {
return function () {
return context[name].apply(context,arguments);
}
}
var button={
clicked:false,
click:function () {
this.clicked=true;
console.log(button.clicked);
}
}
var elem=document.getElementById("test");
elem.addEventListener("click",bind(button,"click"),false);
而这里使用闭包也是把bind的参数传递给了button
函数重载
缓存记忆:
实现方式有两种,第一种修改现有函数,或基于现有函数创建一个自更新的新函数
第一种 修改现有函数添加属性记忆字段
Function.prototype.memoized=function (key) {
this._value=this._value||{};
return this._value[key]!==undefined?
this._value[key]:
this._value[key]=this.apply(this,arguments);
};
function isPrime(num) {
var prime=num!=1;
for(var i=2;i<num;i++){
if(num%i==0){
prime=false;
break;
}
}
return prime;
}
console.log(isPrime.memoized(5))
console.log(isPrime._value[5])
其实这和我之前写的自记忆函数是一样的结果,如果对这种写法不清楚可以看看之前的
第二种 使用闭包实现
Function.prototype.memoized=function (key) {
this._value=this._value||{};
return this._value[key]!==undefined?
this._value[key]:
this._value[key]=this.apply(this,arguments);
};
Function.prototype.memoize=function (key) {
var fn=this;
return function () {
return fn.memoized.apply(fn,arguments);
}
}
var isPrime=(function (num) {
var prime=num!=1;
for(var i=2;i<num;i++){
if(num%i==0){
prime=false;
break;
}
}
}).memoize();
console.log(isPrime(5))
可以看到省略了一个步骤,可以看到代码调用isPrime()的时候,就调用memoize了,后面才传值进行闭包中的逻辑
但是这么写复杂了点,后面优化
函数封装
函数包装是一种封装函数逻辑的技巧,用于在单个步骤内重载创建新函数或继承函数。最有价值的场景是,在重载一些已经存在的函数时,同时保持原始函数在被包装后仍然能够有效使用。
function wrap(object,method,wrapper){
var fn = object[method];
return object[method] = function(){
return wrapper.apply(this,[fn.bind(this)].concat(
Array.prototype.slice.call(arguments)));
};
}
if (Prototype.Browser.Opera) {
wrap(Element.Methods,"readAttribute",function(original,elem,attr){
return attr == "title" ? elem.title : original(elem,attr);
});
}
以上代码首先将原有方法保存在变量fn中,稍后我们会在后面会通过匿名函数的闭包来访问它。然后,我们使用一个新的匿名函数来重载该方法。新函数执行了之前传进来的包装器函数wrapper,并传递一个重新构造过的参数列表。在构建这个参数列表时,我们希望第一个参数是我们要重载的原有函数,所以创建了一个数组,其中包含原始函数的引用并将原始参数也追加到该数组中。
即时函数(立即函数)
(function(){})();
大家在写框架的时候经常用到,我们来剖析一下这段代码
先忽略第一括号内的(…)();
我们知道()更多的是用来调用函数,那第一个()是干嘛的呢,是用来划定表达式范围的
如(3+4)*5这里面的()就是划定表达式方位的
而第一个括号里面呢,你可以写一个函数,或者匿名函数比如
(function(){})();
而后面括号就是执行该函数,执行完,里面就被销毁掉了
接下来我们看看即时函数的作用
临时作用域和私有变量:
(function () {
var numClick=0;
document.addEventListener("click",function () {
alert(++numClick);
},false)
})()
可以看到numClick属性 只有闭包中的才能访问该属性,同时函数运行click事件就会被绑定,外部是不可访问的
这也是一种最常见的即时函数使用方式:简单,自包装
解决循环变量问题
<body>
<div style="width:500px;height: 500px;background-color: red"></div>
<div style="width:500px;height: 500px;background-color: green"></div>
<div style="width:500px;height: 500px;background-color: black"></div>
</body>
<script type="text/javascript">
var divs=document.getElementsByTagName("div");
for (var i=0;i<divs.length;i++){
divs[i].addEventListener("click",function () {
alert("divs"+i+"was click")
},false)
}
</script>
这里导致i永远是最后一位,这是为什么呢,
闭包记住的是变量的引用,而不是闭包创建时刻该变量的值
该怎么解决呢?使用立即函数
var divs=document.getElementsByTagName("div");
for (var i=0;i<divs.length;i++)(function(n){
divs[n].addEventListener("click",function () {
alert("divs"+n+"was click")
},false)
})(i)
类库封装
关于闭包和即时函数细粒度应用的另一个重要用途,将其用于javascript类库的开发,当我们开发一个类库时,很重要的一点不希望一些不必要的变量去污染全局命名空间,尤其是那些临时变量
如:jquery
(function(){
var jquery=window.jquery=function(){
...
};
//...
})()
为了保证有时候window中的jquery对象也会被销毁,在闭包中也赋予了一个变量