牛客JS24_使用闭包

在这里插入图片描述

解题

法1 - 数组遍历之map()方法

//解法1:
function makeClosures(arr, fn) {
 return arr.map(function(item){
        return function(){
            return fn(item);
               
        }
    })
}

在这里插入图片描述

法2 - 数组遍历之forEach()方法

//解法2:
function makeClosures(arr, fn) {
  var result = [];
     arr.forEach(function(e){
         result.push(function(num){
             return function(){
                 return fn(num);
             };
         }(e));
     });
     return result;
 }

在这里插入图片描述


一、知识点1 - js作用域(三大作用域)

  • 全局作用域
  • 函数作用域
  • 块级作用域
    • ES6新增,需配合const、let在 { } 中的作用域
    • var 变量会挂载到window对象上,let、const不会
  • 对象没有作用域!!!(但是有自己的this)
// 函数作用域
  var a = "outter";
  function fn(){
    var a = "inner";
    console.log(a);
  }
  fn(); //inner
 
 // 对象没有作用域
  var a = "outter";
  var obj = {
    a: "inner",
    getA(){
      console.log(a);
      console.log(this.a)
    }
  }
  obj.getA(); 
  // outter(因为在同一作用域,先找到outter就停止了)
  // inner(因为this绑定的是这个对象)

二、知识点2 - 闭包

闭包理解1:父函数中返回的子函数;
闭包理解2:一句话说,就是内层函数对外层函数变量的操作
闭包理解3:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
闭包理解4:在JS中,只有函数内部的子函数才能读取局部变量,闭包就是能够读取其他函数内部变量的函数。所以,闭包是将函数内部和函数外部连接起来的一座桥梁。

实例1

  var a = "outer";
  function fn(){
    var a = "inner";
    return function(){
      console.log(a);
      console.log(this);
      console.log(this.a);
    }
  }
  fn()();
  //inner  此时在函数作用域中,a = inner
  //window 匿名函数作为fn函数的返回值在全局作用域下执行,this是window
  //outter
闭包使用场景

任何闭包的使用场景都离不开这两点

  • 创建私有变量
  • 延长变量的生命周期

一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的。

场景一 (函数复用 - 传参)
设计三个按钮,点击后分别对文字设置大小为10px、16px、20px

  • 做法一:为三个按钮分别绑定一个onclick函数,三个函数里分别设置为10px、16px、20px
  • 缺点:代码冗余,可以提炼一个函数,向内部传参
//省去部分html代码
  var btn01 = document.getElementById("btn01");
  var btn02 = document.getElementById("btn02");
  var btn03 = document.getElementById("btn03");
  btn01.onclick = function(){
    var p = document.getElementById("content");
    p.style.fontSize = 10 + "px";
  }
  btn02.onclick = function(){
    var p = document.getElementById("content");
    p.style.fontSize = 16 + "px";
  }
  btn03.onclick = function(){
    var p = document.getElementById("content");
    p.style.fontSize = 20 + "px";
  }
  • 做法二:对一进行修改,向同一个函数传参
  • 问题:函数名加() 会立即执行,而传参必须带();
  var btn01 = document.getElementById("btn01");
  var btn02 = document.getElementById("btn02");
  var btn03 = document.getElementById("btn03");

  function sizeUp(fz) {
    var p = document.getElementById("content");
    p.style.fontSize = fz + "px";
    console.log(fz);
  }

  btn01.onclick = sizeUp(10); //会立即执行
  btn02.onclick = sizeUp(16); //会立即执行
  btn03.onclick = sizeUp(20); //会立即执行

做法3:使用闭包

  var btn01 = document.getElementById("btn01");
  var btn02 = document.getElementById("btn02");
  var btn03 = document.getElementById("btn03");
  
  function sizeUp(fz) {
    return function(){ //闭包
      var p = document.getElementById("content");
      p.style.fontSize = fz + "px";
    }
  }
  
  btn01.onclick = sizeUp(10);
  btn02.onclick = sizeUp(16);
  btn03.onclick = sizeUp(20);

场景二 (参数复用 - 柯里化函数 )
柯里化的目的在于避免频繁调用具有相同参数,使参数复用

// 假设我们有一个求长方形面积的函数
function getArea(width, height) {
    return width * height
}
// 如果我们碰到的长方形的宽老是10
const area1 = getArea(10, 20)
const area2 = getArea(10, 30)

// 我们可以使用闭包柯里化这个计算面积的函数
function getArea(width) {
    return height => {
        return width * height
    }
}
 
const getTenWidthArea = getArea(10)
// 碰到宽度为10的长方形就可以这样计算面积
const area1 = getTenWidthArea(20)
const area2 = getTenWidthArea(30)
 
// 遇到宽度为20也可以轻松复用
const getTwentyWidthArea = getArea(20)
const area3 = getTwentyWidthArea(xx)

场景三(用闭包模拟私有方法)
在JS中,没有支持声明私有变量,但我们可以使用闭包来模拟私有方法

  • 私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
  • 下面的示例展现了如何使用闭包来定义公共函数,且其可以访问私有函数和变量。这个方式也称为 模块模式(module pattern):
var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    function increment() {
      changeBy(1);
    },
    function decrement() {
      changeBy(-1);
    },
    function value() {
      return privateCounter;
    }
  }   
})();
 
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

这里有很多细节

  • 在以往的示例中,每个闭包都有它自己的环境;而这次我们只创建了一个环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。
  • 该共享环境创建于一个匿名函数体内,该函数一经定义立刻执行。环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。 这两项都无法在匿名函数外部直接访问。必须通过匿名包装器返回的三个公共函数访问。
  • 这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法范围的作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。

您应该注意到了,我们定义了一个匿名函数用于创建计数器,然后直接调用该函数,并将返回值赋给 Counter 变量。也可以将这个函数保存到另一个变量中,以便创建多个计数器。

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();

console.log(Counter1.value()); /* logs 0 */

Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */

Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
  • 上述通过使用闭包来定义公共函数,并令其可以访问私有函数和变量,这种方式也叫模块方式
  • 请注意两个计数器是如何维护它们各自的独立性的。每次调用 makeCounter() 函数期间,其环境是不同的,通过改变变量值,会改变这个闭包的词法环境,不会影响另一个闭包中的变量。每次调用中, privateCounter 中含有不同的实例。
  • 这种形式的闭包提供了许多通常由面向对象编程所享有的益处,尤其是数据隐藏和封装。

其他场景(待补充)
例如计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期

:如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响


参考链接:
CSDN - 面试官:说说你对闭包的理解?闭包使用场景?
CSDN - JavaScript—用闭包模拟私有方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值