专栏首页WebJ2EE【前端】闭包(closure)

【前端】闭包(closure)

目录
1. 什么是闭包?
2. 闭包的应用
    2.1. 模拟私有方法
    2.2. 实现函数柯里化
    2.3. Thunk函数与进阶
3. 几道笔试题

1. 什么是闭包?

计算机科学中,闭包(Closure)是引用了自由变量的函数。即使自由变量原来所属的内存空间不存在了,该自由变量也依然对该函数有效。闭包是函数和其相关的“环境”组成的实体。

函数对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

2. 闭包的应用

2.1. 模拟私有方法

编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

var Counter = (function () {
    var privateCounter = 0;

    function changeBy(val) {
        privateCounter += val;
    }

    return {
        increment: function () {
            changeBy(1);
        },
        decrement: function () {
            changeBy(-1);
        },
        value: function () {
            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 */

2.2. 实现函数柯里化

在计算机科学中,柯里化是接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数。并且返回接受余下的参数而且返回结果的新函数的技术。柯里化其实本身是固定一个可以预期的参数,并返回一个特定的函数,处理批特定的需求。这个技术由Christopher Strachey以逻辑学家 Haskell Curry 命名的。

经典示例:打折程序

版本1:常规思路

function discount(price, discount) {
    return price * discount
}

// 普通顾客消费
let normal1 = discount(500, 0.1) // $50
let normal2 = discount(1500, 0.1) // $150
let normal3 = discount(2000, 0.1) // $200
let normal4 = discount(50, 0.1) // $5
let normal5 = discount(300, 0.1) // $30
console.log(normal1, normal2, normal3, normal4, normal5);

// vip 顾客消费
let vip1 = discount(500, 0.2) // $50
let vip2 = discount(1500, 0.2) // $150
console.log(vip1, vip2);

版本2:柯里化

// 将这个函数柯里化,然后我们就不用每次都写那0.1了
function discountFactory(discount) {
    return (price) => {
        return price * discount
    }
}

// 普通顾客消费
const normalDiscount = discountFactory(0.1);
let normal1 = normalDiscount(500) // $50
let normal2 = normalDiscount(1500) // $150
let normal3 = normalDiscount(2000) // $200
let normal4 = normalDiscount(50) // $5
let normal5 = normalDiscount(300) // $30
console.log(normal1, normal2, normal3, normal4, normal5);

// vip 顾客消费
const vipDiscount = discountFactory(0.2);
let vip1 = vipDiscount(500) // $50
let vip2 = vipDiscount(1500) // $150
console.log(vip1, vip2);

2.3. Thunk 函数与进阶

2.3.1. Thunk 的起源

最早的 Thunk 函数起源来自于“传值调用”(call by value)和“传名调用”(call by name)之争。

  • 传值调用的主张:fn(x+1) == fn(2); //执行前就进行计算
  • 传名调用的主张:fn(x+1) == (1+1)*2; //只在执行的时候才进行计算
let x = 1;

function fn(m) {
    return m * 2;
}

fn(x + 1);

广义上thunk函数就是“传名调用”的实际表现,表示

let x = 1;

function fn(thunk) {
    return thunk() * 2;
}

function thunk() {
    return x + 1;
}

fn(thunk);

此时x+1是在fn被执行后才进行计算的,名副其实的传名调用。传名调用的好处是使用时计算,不浪费资源。

个人理解,核心在于

延迟——必要的时候才做必要的事

2.3.2. JS 中的 Thunk

Javascript 本身是就是【传值调用】,在 JS 中 thunk 函数主要用于延迟callback 的执行。即将带有 callback 的函数拆分为两部分,第一部分只包含必要业务参数,第二部分是当需要结果时传入的 callback。

经典示例1:Thunk 函数

var Thunk = function (fn) {
    return function () {
        var args = Array.prototype.slice.call(arguments);
        return function (callback) {
            args.push(callback);
            return fn.apply(this, args);
        }
    };
};

// 原始函数
function test(a, b, cb) {
    cb(a + b);
}

// Thunk函数
let thunker = Thunk(test);

// 1. 先收集参数
let handle = thunker(1, 2);

// 2. 未来再传入回调
handle(function (value) {
    console.log(value);
});

经典示例2

Thunk + Generator 模拟 async await

// ====================
// Tools
// ====================

function Thunk(fn) {
    return function () {
        var args = Array.prototype.slice.call(arguments);
        return function (callback) {
            args.push(callback);
            return fn.apply(this, args);
        }
    };
}

function Worker(generator){
    function doNext(gInst, result, cb){
        var hasNext = !result.done;
        var yieldRet = result.value;

        if(hasNext && typeof yieldRet == "function"){
            yieldRet(function(cbkret){ // thunker+callback => result
                doNext(gInst, gInst.next(cbkret), cb);
            });
        }else{
            cb(yieldRet);
        }
    }

    this.run = function(cb){
        var gInst = generator();
        var result = gInst.next();
        doNext(gInst, result, cb);
    }
}

// ====================
// DEMO
// ====================

// function with callback
function add(a, b, cb) {
    cb(a + b);
}

// make function Thunkable
var thunkAdd = Thunk(add);

// Generator: convert callback to yield
function* myComputer() {
    let sum = 0;

    sum += yield thunkAdd(1, 2);
    console.log(sum);

    sum += yield thunkAdd(3, 4);
    console.log(sum);

    sum += yield thunkAdd(5, 6);
    console.log(sum);

    return sum;
}

// run "myComputer"
new Worker(myComputer).run(function(result){
    console.log("result", result);
});

3. 几道笔试题

题目01:

题目02:

题目03:

题目04:

题目05:

// 【简答题】:
//
// 要求写一个函数add(),分别实现能如下效果:
//
// 1. console.log(add(1,2)()) // 3
// 2. console.log(add(1)(2)()) // 3
// 3. console.log(add(1)(2)(3)()) // 6
// 4. console.log(add(1,2,3)(4)()) // 10
//
// 【答案】:
//

function add(...args) {
    let argsArr = [];

    function __calc(){
        return argsArr.reduce(function (acc, cur) {
            return acc + cur;
        }, 0);
    }

    function __saveArgs(...args){
        argsArr = [...argsArr, ...args];
    }

    function __innerAdd(...args) {
        if (!args.length) {
            return __calc();
        }

        __saveArgs(...args);

        return __innerAdd;
    }

    return __innerAdd(...args);
}

console.log(add(1, 2)());
console.log(add(1)(2)());
console.log(add(1)(2)(3)());
console.log(add(1, 2, 3)(4)());

参考:

Closures: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures 《自制编程语言》(郑刚著)

本文分享自微信公众号 - WebJ2EE(WebJ2EE),作者:WEBJ2EE

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JS:你真的会用 Promise 吗?

    试想一下,有 3 个异步请求,第二个需要依赖第一个请求的返回结果,第三个需要依赖第二个请求的返回结果,一般怎么做?

    WEBJ2EE
  • 算法:编辑距离(Levenshtein Distance)

    “编辑距离”又称 Leveinshtein 距离,是由俄罗斯科学家 Vladimir Levenshtein 在 1965 年提出。

    WEBJ2EE
  • WEB:文件上传 —— 看这篇就够了

    HTML 表单最初只支持 application/x-www-form-urlencoded 形式编码(key=value&key=value...),但它不适...

    WEBJ2EE
  • mysql报错This function has none of DETERMINISTIC解决方案

    创建存储过程时 出错信息: ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQ...

    似水的流年
  • 【前沿】见人识面,TensorFlow实现人脸性别/年龄识别

    【导读】近期,浙江大学学生Boyuan Jiang使用TensorFlow实现了一个人脸年龄和性别识别的工具,首先使用dlib来检测和对齐图片中的人脸,然后使用...

    WZEARW
  • leetcode 周赛速递 | 1048 DAG动态规划

    这几天在看动态规划的题目,看的不多,但是学到了一个很重要的概念,那就是DAG上的动态规划。

    ACM算法日常
  • 【Dev Club分享】React Native项目实战总结。

    “8小时内拼工作,8小时外拼成长”这是大家共同的理想。除了每天忙于工作外,我们都希望能更多地区吸收领域内的新知识与新技能,从而走向人生巅峰。 Dev Club...

    腾讯Bugly
  • SQL语句执行过程详解

    一条sql,plsql的执行到底是怎样执行的呢? 一、SQL语句执行原理: 第一步:客户端把语句发给服务器端执行 当我们在客户端执行 select 语句时,客户...

    wangxl
  • 判断两个Integer值相等为什么不用==

    挑战者
  • 基于注视的人机交互自然抓取意图识别(CS RO)

    眼球运动与肢体动作密切相关,因此可以用来推断运动意图。更重要的是,在某些情况下,眼球运动是患有严重运动障碍的瘫痪和受损患者与环境交流和互动的唯一途径。尽管如此,...

    毛艺漩8078803

扫码关注云+社区

领取腾讯云代金券