目录 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)之争。
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
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句