对JS闭包的理解

对JS闭包的理解

1.什么是闭包

闭包,简单来说就是外部函数可以访问内部函数的变量,我们知道由于JS作用域的原因,内部函数通过作用域链依次来访问外层作用域所定义的变量,但是内部函数定义的变量,可以说是函数私有的变量,不允许外层函数访问,那要访问内部函数变量,就要通过闭包了
例如:

let a = "123";
  function fun(){
    let b = "234"
    console.log(a,b) //123,234
  }
  console.log(a)    //123
  console.log(b)    //b is not defined

上述代码中,变量a定义在fun函数外部,它的作用域为全局作用域,变量b定义在函数内部,作用域为fun,在fun函数中访问a,b都可以访问到,但在fun函数外部来访问b变量,控制台就会报错,因为b并非定义在全局环境中。

在某些情况下,我们需要去访问内部函数的变量,我们将上述函数做个变形:

 let a = "123"
  function fun(){
    let b = "234";
    function fn(){
       console.log(a,b)
    }
    return fn
  }
  let result = fun();
  result()  // 123,234

在上述函数中,我们在函数fun内部定义了一个函数fn,对于fn来说,他的作用域就是fnn函数,所以它可以获取到fnn函数内部所有的变量,既然fn可以获取到函数内部的变量,那将fn函数作为返回值,那函数外部就可以获取到函数内部的变量了。所以说fn函数可以看作是一个闭包,它作为了连接fnn函数内部变量和外层函数的一个桥梁。所以,本质上闭包就是一个函数

2.关于闭包的使用

2.1.计数器

一般我们要实现一个计数可能会通过以下方式来实现:

let count = 0;
  function add(){
    return ++count;
  }
  console.log(add())  //1
  console.log(add())  //2

通过以上方式我们是可以实现计数,但是count变量是定义在全局环境中的,这就意味着它随时可能被修改,在开发中我们往往不希望定义的变量修改。因此count应该是一个局部变量,定义在函数内部。
可通过以下方式来实现:

  function add(){
    let count = 0;
    function fn(){
      return ++count;
    }
    return fn;
  }
  var sum = add();
  sum();
  sum();

将计数的功能放在函数内部,将它作为add的返回值,外层函数在访问add函数时,也就可以获取到计数的变量,那么,fn函数就是一个闭包。

2.2.私有属性和方法

我们可以通过闭包来封装一个函数的私有属性和方法,例如:对于一个产品,我设置他的单价和数量,想知道这个产品的总价:

function Pruduct(name){
    var price;
    var total;
    function setPrice(p){
       price = p;
    }
    function setTotal(t){
      total = t;
    }
    function getTotalPrice(){
      return price*total;
    }
    return {
      name:name,
      setPrice:setPrice,
      setTotal:setTotal,
      getTotalPrice:getTotalPrice
    }
  }
  let p = Pruduct();
  p.setPrice(10);
  p.setTotal(10);
  console.log(p.getTotalPrice());  //100
  p=null;

函数Product的变量price和total,通过setPrice,setTotal,getTotalPrice变成p对象的私有变量

2.3.循环调用

在开发中我们常见的一个问题就是循环调用的问题
例如:

function foo(){
      var arr = [];
      for(var i=0;i<10;i++){
        arr[i] = function(){
          return i
        };
      }
      return arr
    }
    var result = foo();
    console.log(result[0]());  //10
    console.log(result[1]());  //10
    console.log(result[2]());  //10

上述结果其实并不是我们想要的结果,由于for没有作用域,所以,相当于i变量在foo函数中定义,每次在执行完循环后,i会被改变,在调用函数的时候最终是获得的循环执行完成之后的变量
使用闭包实现循环调用:

function foo(){
     var arr = [];
     for(var i=0;i<10;i++){
       arr[i] = (function(n){
         return function(){
           return n
         }
       })(i)
     }
     return arr
   }
   var result = foo();
   console.log(result[0]());  //0
   console.log(result[1]());  //1
   console.log(result[2]());  //2
   result = null

arr[i]赋值时形成独立的函数作用域,外部的i变化,对于函数内部的变量不影响

3.闭包的几种表现形式

3.1.返回值 (返回内部函数的一个变量)
function foo(){
  let name = 'crystal';
  return function(){
    return name
  }
}
var result = foo()
console.log(result())
3.2.函数赋值,将内部函数赋值给一个外部变量
function foo1(){
  let name = 'crystal';
  let a = function(){
    console.log(name)
  }
  return a
}

let result1 = foo1();
console.log(result1)
3.3.函数参数,将一个函数作为另一个函数的参数
function foo2(){
  let name = 'crystal';
  let a = function(){
    console.log(name)
  }
  fn(a)
}
function fn(f){
  console.log(f)
}
let result2 = foo2();
console.log(result2)
3.4 IIEF(立即执行函数)

ps:下篇文章单独来记录立即执行函数

3.5 循环/迭代
3.6 私有属性(getter/setter)
3.7 区分首次调用
var firstLoad = (function(){
  var list = []
  return function(id){
    if(list.indexOf(id) >= 0){
      return false;
    }else{
      list.push(id);
      return true;
    }
  }
})()
console.log(firstLoad())    //true
console.log(firstLoad())    //false
3.8 缓存机制
3.8.1.未使用缓存求和
function multi(){
 var sum = 0;
 for(var i = 0;i<arguments.length;i++){
   sum+=arguments[i]
 }
 return sum;
}

console.log(multi(10,20,30,40))
console.log(multi(10,20,30,40))

在多次调用时,每次都会重新初始化sum的值,通过for循环来计算sum的和

3.8.1.使用缓存求和
var result =  (function multi(){
 var cache = {}
 var calculate = function(){
   console.log(1)     //只输出一次
   var sum = 0;
   for(var i=0;i<arguments.length;i++){
      sum+=arguments[i];
   }
   return sum;
 }
 return function(){
   var args = Array.prototype.join.call(arguments,',');
   if(args in cache){
     return cache[args];
   }else{
     cache[args] = calculate.apply(null,arguments);
     return cache[args];
   }
 }
}());
 console.log(result(10,20,30,40))
 console.log(result(10,20,30,40))

一般情况下,在多次调用函数时,如果函数参数不变,可以将结果缓存起来,在下次调用时,函数参数未发生变化,应该直接返回上次调用结果,而不是重新初始化在计算一次 。
实现思路:可以将上次计算结果存储在对象当中,将函数参数作为Key值,求和结果作为Value,当再次调用的时候,若对象中含有该Key,直接返回Value对象

3.9 img对象上传
var report = (function(src){
 var imgs = [];
 return function(){
   var img = new Image();
   imgs.push(img);
   img.src = src;
 }
}())

new Image() 低版本浏览器在进行数据上传时会丢失30%左右的数据,Image对象在使用完以后会立即被销毁,导致在图片上传的时候数据容易丢失,可以使用闭包将img 换存起来,始终存在内存当中

以上就是今天总结内容~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值