前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【前端】闭包(closure)

【前端】闭包(closure)

作者头像
WEBJ2EE
发布2020-02-19 11:36:55
8120
发布2020-02-19 11:36:55
举报
文章被收录于专栏:WebJ2EEWebJ2EE
代码语言:javascript
复制
目录
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 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

代码语言: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:常规思路

代码语言:javascript
复制
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:柯里化

代码语言:javascript
复制
// 将这个函数柯里化,然后我们就不用每次都写那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; //只在执行的时候才进行计算
代码语言:javascript
复制
let x = 1;

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

fn(x + 1);

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

代码语言:javascript
复制
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 函数

代码语言:javascript
复制
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

代码语言:javascript
复制
// ====================
// 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:

代码语言:javascript
复制
// 【简答题】:
//
// 要求写一个函数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 《自制编程语言》(郑刚著)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档