jsliang 求职系列 - 03 - 闭包与柯里化

4,120 阅读5分钟

本文讲述闭包及柯里化知识点。

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 闭包
三 闭包好处和坏处
四 柯里化
4.1 柯里化举例
4.2 柯里化好处
4.3 题目:实现 add(1)(2)(3)
4.4 题目:实现 compose(foo, bar, baz)('start')
五 题目列表
5.1 求打印结果 1
5.2 求打印结果 2
六 参考文献

二 闭包

返回目录

在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量。

当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。

简单来说:

  • 在函数 A 中还有函数 B,函数 B 调用了函数 A 中的变量,那么函数 B 就称为函数 A 的闭包。

举个例子:

function foo() {
  let num = 0;
  return function() {
    num++;
    console.log(num);
  };
}
const f = foo();
f(); // 1
f(); // 2

这个 num 在返回的函数中存在了,所以每次调用都会 + 1。

三 闭包好处和坏处

返回目录

好处:

  1. 缓存。将变量隐藏起来不被 GC 回收。
  2. 实现柯里化。利用闭包特性完成柯里化。

坏处:

  1. 内存消耗。闭包产生的变量无法被销毁。
  2. 性能问题。由于闭包内部变量优先级高于外部变量,所以需要多查找作用域链的一个层次,一定程度影响查找速度。

四 柯里化

返回目录

柯里化(Currying)是把接受多个参数的函数转变为单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。

简单来说

  1. 通过闭包管理
  2. 支持链式调用
  3. 每次运行返回一个 function

即:通过将多个参数换成一个参数,每次运行返回新函数的技术

4.1 柯里化举例

返回目录

普通的 add 函数

function add (a, b) {
  return a + b;
}
add(1, 2);

柯里化函数

function curryingAdd (x) {
  return function(y) {
    return x + y;
  }
}
curryingAdd(x)(y);

哎,这样不是浪费程序员时间么?

不急,我们继续往下面看。

4.2 柯里化好处

返回目录

  1. 参数复用
  2. 提前确认
  3. 延迟运行

参数复用

// 正则表达式

// 校验数字
let numberReg = /[0-9]+/g;

// 校验小写字母
let stringReg = /[a-z]+/g;

// currying 后
function curryingCheck(reg) {
  return function(txt) {
    return reg.test(txt);
  }
}

// 校验数字
let checkNumber = curryingCheck(numberReg);
let checkString = curryingCheck(stringReg);

// 使用
console.log(checkNumber('13888888888')); // true
console.log(checkString('jsliang')); // true

利用闭包实现柯里化。

4.3 题目:实现 add(1)(2)(3)

返回目录

// 实现一个 add 方法,使计算结果能够满足以下预期
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

答:

function add () {
  const numberList = Array.from(arguments);

  // 进一步收集剩余参数
  const calculate = function() {
    numberList.push(...arguments);
    return calculate;
  }

  // 利用 toString 隐式转换,最后执行时进行转换
  calculate.toString = function() {
    return numberList.reduce((a, b) => a + b, 0);
  }

  return calculate;
}

// 实现一个 add 方法,使计算结果能够满足以下预期
console.log(add(1)(2)(3)); // 6
console.log(add(1, 2, 3)(4)); // 10;
console.log(add(1)(2)(3)(4)(5)); // 15;

4.4 题目:实现 compose(foo, bar, baz)('start')

返回目录

function foo(...args) {
  console.log(args[0]);
  return 'foo';
}
function bar(...args) {
  console.log(args[0]);
  return 'bar';
}
function baz(...args) {
  console.log(args[0]);
  return 'baz';
}

function compose() {
  // 闭包元素 - 函数列表
  const list = Array.from(arguments);

  // 闭包元素 - 函数列表执行位置
  let index = -1;

  // 闭包元素 - 上一个函数的返回
  let prev = '';

  // 返回闭包函数
  const doNext = function() {
    index++; // 索引值累加
    // 一开始没有上一个元素时,获取第二个括号的值
    if (!prev) {
      prev = arguments[0];
    }
    // 设置前一个结果为当前函数返回
    prev = list[index](prev);
    // 递归调用
    if (index < list.length - 1) {
      doNext(index + 1);
    }
  };

  // 第一次返回闭包函数
  return doNext;
}

compose(foo, bar, baz)('start');

五 题目列表

返回目录

5.1 求打印结果 1

返回目录

function test() {
  var n = 4399;

  function add() {
    n++;
    console.log(n);
  }

  return {
    n,
    add
  };
};

var result = test();
var result2 = test();

result.add(); // 输出啥
result.add(); // 输出啥

console.log(result.n); // 输出啥

result2.add(); // 输出啥

选择:

  • A:4400 4401 4399 4400
  • B:4400 4401 4401 4402
  • C:4400 4400 4399 4400
  • D:4400 4401 4399 4402
  • E:4400 4401 4401 4400

答案:A

5.2 求打印结果 2

返回目录

function Foo() {
  var i = 0;
  return function() {
    console.log(i++);
  }
}

var f1 = Foo();
var f2 = Foo();

f1();
f1();
f2();

请选择:

  • A:0 1 0
  • B:0 1 2
  • C:0 0 0
  • D:0 0 2

答案:A

原因:f1 每次执行会产生闭包,然后 i++ 是先赋值后展示。

六 参考文献

返回目录


license 图
jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。
基于 github.com/LiangJunron… 上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。