JavaScript:闭包学习

基本概念

  • 闭包是指有权访问另一个函数作用域变量的函数,创建闭包的通常方式,是在一个函数内部创建另一个函数
  • 如果一个函数访问了它的外部变量,那么它就是一个闭包。
  • 闭包的本质是函数
  • 闭包能访问其他函数的变量
  • 闭包通常作为其他函数的返回值,也可能是函数的参数
  • 外部函数不能访问内部函数的变量,但是内部函数可以访问外部函数的变量。所以闭包通常是被返回的(参数中的)内部函数
  • "链式作用域"结构(chain scope):子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
  • 自由变量跨作用域取值时:要去创建这个函数的作用域取值,而不是“父作用域”。
  • 函数的特别之处在于可以创建一个独立的作用域。
  • 闭包引用的变量,保存在创建闭包的函数的作用域中,可以一直延伸到全局作用域,形成作用域链(chain scope)。
  • this的关键是确定调用函数的对象;闭包变量的关键确定创建闭包的作用域链。这两者有本质的区别。
var a = 10;
var b = 200;

function fn() {
    var b = 20;

    function bar() {
        console.log("a + b = " + (a + b));
        console.log("this.b = " + this.b);
    }

    return bar;
}

var foo = fn();
foo();  
// a + b = 30
// this.b = 200
  • 闭包foo()的创建环境是作用域链bar() => fn() => 全局
    abar()fn()中都没有,所以取了全局的a = 10
    bbar()中没有,取了fn()b = 20;;全局中的 b = 200;被屏蔽了。
  • 闭包foo()的调用者(执行环境)是全局,所以this.b指的是全局中的 b = 200;

使用场景

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

实现getset

function Person(){  
    var name = "default";     
     
    return {  
       getName : function(){  
           return name;  
       },  
       setName : function(newName){  
           name = newName;  
       }  
    }  
};  
   
var john = Person();  
john.getName();          // "default"
john.setName("john");  
john.getName();          // "john"
console.log(john.name);  // undefined
   
var jack = Person();  
jack.getName();          // "default"
jack.setName("jack");    
jack.getName();          // "jack"
console.log(jack.name);  // undefined 

这个看上去就很面向对象了。封装了数据name,同时提供了getName()setName(newName)两个闭包(返回的内部函数)来作为这个变量的访问接口。

缓存

var CachedSearchBox = (function(){  
    var cache = {},  
       count = [];  
    return {  
       attachSearchBox : function(dsid){  
           if(dsid in cache){//如果结果在缓存中  
              return cache[dsid];//直接返回缓存中的对象  
           }  
           var fsb = new uikit.webctrl.SearchBox(dsid);//新建  
           cache[dsid] = fsb;//更新缓存  
           if(count.length > 100){//保正缓存的大小<=100  
              delete cache[count.shift()];  
           }  
           return fsb;        
       },  
   
       clearSearchBox : function(dsid){  
           if(dsid in cache){  
              cache[dsid].clearSelection();    
           }  
       }  
    };  
})();  
   
CachedSearchBox.attachSearchBox("input1"); 
  • 闭包attachSearchBox(dsid)clearSearchBox(dsid)访问了对象cache(也可以看做是字典),所以cache会一直保存在内存中。
  • 这是一个缓存,如果命中了,直接就从缓存中取,不需要再查找,可以提升查找速度。
  • 这是一个利用闭包占内存的例子,大多数情况下,要注意闭包导致内存占用过多的问题

解决this问题

var name = "The Window";
var object = {
    name : "My Object",
   getNameFunc : function(){
       return function(){
        return this.name;
     };
   }
};
object.getNameFunc()();  // "The Window"
  • object.getNameFunc()返回的是一个匿名函数,可以给个名字,比如var foo = object.getNameFunc();
  • 然后执行foo();这里的执行环境是全局,所以此时的this.name就返回全局的var name = "The Window";

that大法:

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    var that = this;
    return function(){
      return that.name;
    };
  }
};
object.getNameFunc()(); // "My Object"
  • 同样给匿名函数一个名字,方便分析var foo = object.getNameFunc();这时的执行环境是object,所以函数getNameFunc()中的this指的是object;
  • var that = this; 那么 that = object; 这样object中的信息(比如name : "My Object")都保存在that中了。that处于函数getNameFunc()的作用域中
  • 函数foo()执行环境是全局,但是创建环境是函数getNameFunc()
  • 函数foo()访问了不在自己作用域中的变量that,所以他是一个闭包。
  • 闭包foo()沿着作用域链往上找,在函数getNameFunc()的作用域中找到了变量that,就拿出来用了that.name = "My Object"

匿名包装器

其实就是我们通常说的自执行匿名函数
匿名函数的作用是减少临时的中间变量,解决取名困难问题。
自执行就是定义后马上执行,并且只执行一次。如果没有闭包,函数马上就垃圾回收了,如果有闭包引用变量,那么作用域会继续替闭包保存变量,相当于一个缓存。

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
} // 输出10个10,与预期有差距
  • 函数setTimeout(fn, time);的参数fn是一个函数;这里的fn是一个匿名函数,并且使用了外部的变量i,所以这个匿名函数是一个闭包。
  • fn的执行环境是全局的(异步执行函数),创建的作用域是for循环所在的作用域。
  • fn执行的时候,for循环所在的作用域保存了变量i,闭包可以访问,由于1秒钟之后,for循环早就执行完了(对电脑来说1秒钟是很长的时间),这时变量i的值已经变成了10
  • 1秒钟之后,fn执行了10次,变量i只有1个,值是10,所以输出了1010
for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}
  • (funtion(e))(i)是一个自执行匿名函数,定义完后马上执行。变量i属于for循环所在的作用域,通过参数e传递给函数setTimeout(fn, time);
  • 每次for循环都会创建一个自执行匿名函数(funtion(e))(i),定义完后马上执行。所以有10个不同的e
  • 自执行匿名函数(funtion(e))(i)虽然执行完了,但是函数作用域还在内存中,没有被回收,因为变量esetTimeout(fn, time);中的闭包使用
  • fn的执行环境是全局的(异步执行函数),执行时,取10个不同的自执行匿名函数(funtion(e))(i)中的e来执行
  • 10个不同的e中保存了0~9,输出符合预期
  • 10fn执行完后,自执行匿名函数(funtion(e))(i)中的e没有闭包使用,这些执行匿名函数被系统回收,内存占用下降。

参考文章

深入理解javascript原型和闭包(14)——从【自由变量】到【作用域链】
深入理解javascript原型和闭包(15)——闭包
作用域的图画得不错

学习Javascript闭包(Closure)
两个作用总结得不错
最后的例子给的挺好,thisthat(或者self)大法

详解js中的闭包
这里的图画得挺不错的

js闭包的用途
使用的例子不错

闭包和引用
循环的例子给得不错

Javascript闭包——懂不懂由你,反正我是懂了
既然是翻译stackflow的文章,可以看看

你可能感兴趣的:(JavaScript:闭包学习)