美文网首页
闭包和高阶函数学习笔记

闭包和高阶函数学习笔记

作者: 回调的幸福时光 | 来源:发表于2019-06-08 20:58 被阅读0次

一、闭包

闭包的形成与变量的作用域以及变量的生存周期密切相关。

1.1 变量的作用域

变量的作用域:指变量的有效范围。

变量的作用域

这里还涉及到“变量声明方式”、“变量提升”的影响。

1.2 变量的生存周期
  • 全局变量:生存周期是永久的,除非主动销毁这个全局变量。
  • 函数作用域内的局部变量:当退出函数时,如果这些局部变量没有被其它函数引用,那这些局部变量会跟随函数调用的结束而被销毁。
  • 块状作用域中的变量:变量只在代码块内起作用。

二、闭包的应用场景

2.1 封装变量

把不需要暴露在全局的变量封装成“私有变量”。

用闭包实现累乘运算,并且具有缓存机制。

var mult = (function (){
  var cache = {}; // 缓存结果
  // 提炼小函数
  var calculate = function () {
    var sum = 1;
    for (var i = 0, l = arguments.length; i < l; i++) {
      sum *= arguments[i];
    }
    return sum;
  }

  return function () {
    var args = Array.prototype.join.call(arguments, ',');
    if (cache[args]) {
      return cache[args];
    }
    return cache[args] = calculate.apply(null, arguments);
  }
})();
2.2 面向对象设计

过程与数据的结合是形容面向对象中的“对象”时经常使用的表达。对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据。

var extent = function () {
  var value = 0;
  return {
    call: function () {
      value++;
      console.log(value);
    }
  }
}
2.3 闭包和内存管理
  • 选择闭包的一部分原因是我们主动把一些变量封闭在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的。如果将来需要回收这些变量,可以手动把这些变量设为 null。

  • 闭包和内存泄露有关系的地方是:使用闭包不当,容易形成循环引用。

三、高阶函数

高阶函数是指至少满足下列条件之一的函数:

  • 函数可以作为参数传递
  • 函数可以作为返回值输出

3.1 函数作为参数传递

把函数当作参数传递,代表我们可以抽离出一部分容易变化的业务场景,把这部分业务逻辑放在函数参数中,分离业务代码中变化与不变的部分。

场景举例:回调函数、Array.prototype.sort

3.2 函数作为返回值

判断数据类型

var isType = function (type) {
  return function (obj) {
    return Object.prototype.toString.call(obj) === `[object ${type}]`;
  }
}
var isString = isType('String');
var isArray = isType('Array');

3.3 实现 AOP

AOP (面向切面编程) :把一些跟核心业务逻辑模块无关的功能抽离出来,再通过“动态织入”的方式掺入业务逻辑模块中。
通常在 JavaScript 中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中。

Function.prototype.before = function(beforefn) {
  var __self = this; // __self 表示原函数
  return function() {
    beforefn.apply(this, arguments);
    return __self.apply(this, arguments);
  }
}
Function.prototype.after = function(afterfn) {
  var __self = this;
  return function () {
    var ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
}
var func = function() {
  console.log(‘something’);
}
func.before(function(){
  console.log('before');
}).after(function(){
  console.log('after');
})

3.4 柯里化(currying)

一个 currying 的函数首先会接收一些参数,但不会立即求值,而是返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来,等到函数真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

以下是一个示例:用于实现灵活地 mult()

mult(1,2)(3)();
mult(1)(2,3)();
mult(1)(2)(3)();
var currying = function (fn) {
  var args = [];
   function next() {
    if (arguments.length === 0) {
      const res = fn.apply(this, args);
      args = []; // 计算完毕后,清空缓存的所有参数。
      return res;
    } else {
      [].push.apply(args, arguments);
      return next;
    }
   }
    return next;
}
var calculate = function () {
    var sum = 1;
    for (var i = 0, l = arguments.length; i < l; i++) {
      sum *= arguments[i];
    }
    return sum;
  }

var mult = currying(calculate);

mult(1,2)(3)();
mult(1)(2,3)();
mult(1)(2)(3)();

有的文章是用以下方式,可以使 mult(1)(2)(3)() -> mult(1)(2)(3)
但有个弊端是,toString() 的调用时机最晚,如果连续多次调用 mult() 方法,将导致参数是共享的。

next.toString = function () {
  const res = fn.apply(this, args);
  return res;
}
toString的弊端
3.5 uncurrying
Function.prototype.uncurrying = function () {
  var self = this;
  return function() {
    var obj = Array.prototype.shift.call(arguments);
    return self.apply(obj, arguments);
  }
}

另外的版本:

Function.prototype.uncurrying = function () {
  var self = this;
  return function() {
    return Function.prototype.call.apply(self, arguments);
   }
}

3.6 节流函数

var throttle = function (fn, interval) {
  var __self = fn,
      timer,
      firstTime = true;
  return function () {
    var args = arguments,
        __me = this;
    
    if (firstTime) {
      __self.apply(__me, args);
      return firstTime = false;
    }
    if (timer) {
      return false;
    }
    timer = setTimeout(function (){
      clearTimeout(timer);
      timer = null;
      __self.apply(__me, args);
    }, interval || 500);
  }
}

3.7 分时函数

var timeChunk = function (arr, fn, count) {
  var obj,
      t;
  var len = ary.length;
  var start = function (){
    for (var i = 0; i < Math.min(count || 1, ary.length); i++) {
      var obj = ary.shift();
      fn(obj);
    }
  }
  return function () {
    t = setInterval(function() {
      if (arr.length === 0) {
        return clearInterval(t);
      }
      start();
    })
  }
}

3.8 惰性加载函数

重写函数。

参考

js 闭包、常见认识误区与主要应用
十年踪迹:征服 JavaScript 面试:什么是闭包?

相关文章

  • Kotlin学习之高阶函数和Lambda表达式:闭包

    Kotlin学习之高阶函数和Lambda表达式:闭包 一、闭包 闭包指Lambda表达式和匿名函数外部定义的局部变...

  • Python笔记四 装饰器

    装饰器 = 高阶函数+函数嵌套+闭包 高阶函数 = 参数 or 返回值为函数 函数嵌套 = 在函数中定义函数 闭包...

  • Clojure 学习笔记 :11 函数组合

    Clojure 零基础 学习笔记 偏函数 串行宏 高阶函数 闭包 函数组合 --- 简单而又有力的武器 在函数式编...

  • Python学习笔记

    参考文章 Python学习笔记[2] 一步一步教你认识Python闭包 高阶函数 特殊的函数,特殊在能将函数作为参...

  • 闭包和高阶函数学习笔记

    一、闭包 闭包的形成与变量的作用域以及变量的生存周期密切相关。 1.1 变量的作用域 变量的作用域:指变量的有效范...

  • Python 进阶语法

    高阶函数 python 的高阶函数非常 Nice,高级函数的核心就是闭包{},闭包就可以把一段{}包裹的代码当成对...

  • <>

    使用函数的正确姿势# 什么是高阶函数? 满足任意一点都可以称为高阶函数!! 闭包的概念 闭包个人了解就是函数和局部...

  • 闭包详解二:JavaScript中的高阶函数

    本文讲解的高阶函数是之前讲解的闭包的续集,所以在学习高阶函数之前,一定要确保对闭包以及作用域的概念已经有了解: 深...

  • Swift高阶函数map、flatMap、CompactMap

    高阶函数 高阶函数的本质也是函数,有两个特点 接受函数或者是闭包作为参数 返回值是一个函数或者是闭包 Map函数 ...

  • python装饰器

    装饰器简述 要理解装饰器需要知道Python高阶函数和python闭包,Python高阶函数可以接受函数作为参数,...

网友评论

      本文标题:闭包和高阶函数学习笔记

      本文链接:https://www.haomeiwen.com/subject/cxrdtctx.html