js基础

闭包1(基础)

2018-02-05  本文已影响4人  blueblue_c41a

(什么是闭包?闭包的作用?闭包的缺陷?)

(闭包的几种可能的应用场景)

(闭包与内存泄漏,有关闭包的面试题)

推荐学习网站:


一、什么是闭包?

二、产生一个闭包

创建闭包最常见方式,就是在一个函数内部创建另一个函数

// closure 就是一个闭包
function func(){
  var a = 1,b = 2;  
  function closure(){
    return a+b;
  }
  return closure;
}

闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。


三、闭包的注意事项

1、函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。

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 = null;
add10 = null;

add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。最后通过 null 释放了 add5 和 add10 对闭包的引用

在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

2、 闭包只能取得包含函数中任何变量的最后一个值,这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量。

function test(){
  var arr = [];
  for(var i = 0;i < 10;i++){
    arr[i] = function(){
      return i;
    };
  }
  for(var a = 0;a < 10;a++){
    console.log(arr[a]());
  }
}
test(); // 连续打印 10 个 10
// 函数1作用域——for作用域
// 执行完for之后,在for作用域中i的值为10 
for(var i = 0; i < 10; i++) { // 函数1作用域
        // 我在函数1作用域中
        arr[i] = function() {// 函数2作用域
          // 我在函数2作用域中
          return i;
        };
}
for(var a = 0;a < 10;a++){
//  程序执行for的时候访问闭包function(){return i}词法作用域向上查找,
//  找到for(var i=0;i<10;i++)的词法作用域i=10,因此每次都是10
    console.log(arr[a]());
}
// 毫无疑问,执行到这里的时候,i是10
// 函数2作用域中没有,向上去函数1作用域中找
console.log(i); 

// 函数1作用域

对于上面的情况,如果我们改变代码如下:

function test(){
  var arr = [];
  for(let i = 0;i < 10;i++){  // 仅在这里作出了改动
    arr[i] = function(){
      return i;
    };
  }
  for(var a = 0;a < 10;a++){
    console.log(arr[a]());
  }
}
test(); // 打印 0 到 9
//当你换成let的时候,读取i的时候,在当前作用域(块3)中没有找到
// 向上一个作用域(块2)寻找,在块2中发现i,于是拿到值
// 块1作用域
for(let i = 0; i < 10; i++) { // 块2作用域
    // 我在块2作用域中
    // 在这里每次循环的块作用域都被劫持了,并且每次迭代i都被重新声明
    // 即在此时,每层迭代会生成一个块作用域,并且变量i的值被定义为上次结算的值,
    console.log(i); // 毫无疑问,这里的i从0依次增加到10  
    arr[i] = function() { // 块3作用域
      // 我在块3作用域中
      return i;
    };
}
for(var a = 0;a < 10;a++){
    //当再次访问闭包的时候访问的是劫持的块作用域,因此 i正确显示
    console.log(arr[a]());
}
// 块1作用域

3、函数名与函数功能(或者称函数值)是分割开的,不要认为函数在哪里,其内部的this就指向哪里。匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。

var name = "The Window";

var obj = {
  name: "My Object",

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

console.log(obj.getName()());  // The Window

obj.getName()()实际上是在全局作用域中调用了匿名函数,this指向了window。

var name = "The Window";

var obj = {
  name: "My Object",

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

console.log(obj.getName()());  // My Object

四、闭包的应用
1、面向对象编程,实现对象数据的私有化,实例独立访问成员变量之间互不影响
function Animal(){
  
  // 私有变量
  var series = "哺乳动物";
  function run(){
    console.log("Run!!!");
  }
  
  // 特权方法
  this.getSeries = function(){
    return series;
  };
}
var objEvent = objEvent || {};
(function(){ 
    var addEvent = function(){ 
      // some code
    };
    function removeEvent(){
      // some code
    }

    objEvent.addEvent = addEvent;
    objEvent.removeEvent = removeEvent;
})();

在这段代码中函数 addEvent 和 removeEvent 都是局部变量,但我们可以通过全局变量 objEvent 使用它,这就大大减少了全局变量的使用,增强了网页的安全性。

var countNumber = (function(){
  var num = 0;
  return function(){
    return ++num;
  };
})();
2、 匿名自执行函数

创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。

// 将匿名函数赋值给一个变量,通过变量引用进行函数调用
(function(a,b)
    {
       console.log('匿名函数加圆括号:'+(a+b));
    }(1,2));

var noName= function(a,b){
    console.log('匿名函数赋值变量:'+(a+b));
}(1,4);
3、 缓存(未整理验证)

:设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。
闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
(1)

 /*
    * 闭包实现缓存
    * 属性:有个键--值   --->所以可以将缓存数据存放在一个对象中
    * 缓存存储方法   setCache
    * 缓存的获取方法  getCache
*/
    function  configCache(){
        var  obj={};//设置一个内部的对象 用来存储缓存数据;这个属性是私有的
        //对外暴露两个公共的方法
        return {
            setCache:function(k,v){//设置缓存
                obj[k]=v;
            },
            getCache:function(k){//获取缓存
                return obj[k];
            }
        };
    }
    var conf = configCache();
    console.log(conf);
    conf.setCache(1,'sdwqecwqv');
    console.log(conf.getCache(1));//sdwqecwqv
   // 注意下面这种情况,两次configCache()会产生不同的执行环境
    configCache().setCache(1,'sdwqecwqv');
    console.log(configCache().getCache(1));//undefined

(2)

function doSometingToGetData () {
    //可以在这里向后台取数据,也可以是其他操作,目的就是获取你需要缓存的数据并返回
    return data;
}

var a = (function b () { 
    var _cache = {};
    return {
        setCache: function (name) {
            _cache[name] = doSometingToGetData(name);
        },
        getCache: function (name) {
            return _cache[name];
        }
    }
});
 
var c = function () {
    a.setCache('daihere');
    var data = a.getCache('daihere');
}

(3)

var a = (function b () {
    var _cache = {};
    return {
        doSometing: function (name) {
            if (name in cache) {
                var data = _cache[name];//获取缓存数据
                doSometingByData(data);
            }else {
                _cache[name] = doSometingToGetData(name);
            }
        },
         
    };
})();
 
function doSometingToGetData () {
    //可以在这里向后台取数据,也可以是其他操作,目的就是获取你需要缓存的数据并返回
    return data;
}
4、实现封装

闭包可以封装形成‘私有变量‘,如:实现计算乘积:

const mult = function() {
   var a = 1;
   for (let i = 0; i < arguments.length; i++) {
      a = a * arguments[i];
   }
   return a;
}
mult(1,2,3,4) // 24

五、闭包的缺陷
六、面试题
例1:
function fun(n,o){
  console.log(o);
  return {
    fun: function(m){
      return fun(m,n);
    }
  };
}

var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);    // ?,?,?,?

var b = fun(0).fun(1).fun(2).fun(3);  // ?,?,?,?

var c = fun(0).fun(1);  c.fun(2);  c.fun(3);  // ?,?,?,?

a: undefined,0,0,0
b: undefined, 0, 1, 2
c: undefined, 0,1,1
例2:
for (var i = 1; i <= 5; i++) {

  setTimeout( function timer() {

      console.log(i);

  }, 1000 );

}

上面代码输出什么?如何改动使它依次输出1、2、3、4、5

// 上面代码返回6、6、6、6、6
// 方法一
for (let i = 1; i <= 5; i++) {

  setTimeout( function timer() {

      console.log(i);

  }, 1000 );

}
// 方法二
for (var i = 1; i <= 5; i++) {

    (function(i){

        setTimeout( function timer() {

              console.log(i);

          },  1000 );

    })(i);

}
上一篇下一篇

猜你喜欢

热点阅读