From 5ba27c3ae583020e9e3e3c4496bb5e59258135c5 Mon Sep 17 00:00:00 2001 From: foxhsx <932163475@qq.com> Date: Sat, 31 Jul 2021 17:46:01 +0800 Subject: [PATCH 1/4] functional javascript --- .gitignore | 2 ++ README.md | 3 +++ functional-playground/README.md | 1 + functional-playground/play.js | 36 ++++++++++++++++++++++++++++ lib/README.md | 1 + lib/es6-functional.js | 42 +++++++++++++++++++++++++++++++++ package.json | 16 +++++++++++++ 7 files changed, 101 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 functional-playground/README.md create mode 100644 functional-playground/play.js create mode 100644 lib/README.md create mode 100644 lib/es6-functional.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ccb2c80 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a950a3a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +## 说明 + +本项目是基于《JavaScript ES6 函数式编程入门经典》一书进行编写的代码样例。 \ No newline at end of file diff --git a/functional-playground/README.md b/functional-playground/README.md new file mode 100644 index 0000000..92d274f --- /dev/null +++ b/functional-playground/README.md @@ -0,0 +1 @@ +运行和理解函数式技术 \ No newline at end of file diff --git a/functional-playground/play.js b/functional-playground/play.js new file mode 100644 index 0000000..d18ac05 --- /dev/null +++ b/functional-playground/play.js @@ -0,0 +1,36 @@ +import { + forEach, + forEachObject, + unless, + times, + every +} from "../lib/es6-functional"; + +// var array = [1, 2, 3] +// forEach(array, (data) => { +// console.log(data); +// }) + +// forEach(array, (data) => { +// console.log(2 * data); +// }) + +// let object = { a: 1, b: 2 }; +// forEachObject(object, (k, v) => { +// console.log(`${k} : ${v}`); +// }) + +// forEach([1,2,3,4,5,6,7], (num) => { +// unless((num % 2), () => { +// console.log(num, ' is even'); +// }) +// }) + +// times(100, (n) => { +// unless((n % 2), () => { +// console.log(n, ' is even'); +// }) +// }) + +console.log(every([NaN, NaN, NaN], isNaN)) +console.log(every([NaN, NaN, 4], isNaN)) \ No newline at end of file diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..a089020 --- /dev/null +++ b/lib/README.md @@ -0,0 +1 @@ +存放所有的函数式类库代码 \ No newline at end of file diff --git a/lib/es6-functional.js b/lib/es6-functional.js new file mode 100644 index 0000000..c466de3 --- /dev/null +++ b/lib/es6-functional.js @@ -0,0 +1,42 @@ +// 遍历数组 +const forEach = (array, fn) => { + let i; + for (i = 0; i < array.length; i++) { + fn(array[i]) + } +} + +// 遍历 JavaScript 对象 +const forEachObject = (obj, fn) => { + for (var property in obj) { + if (obj.hasOwnProperty(property)) { + fn(property, obj[property]) + } + } +} + +const unless = (predicate, fn) => { + if (!predicate) fn() +} + +const times = (times, fn) => { + for (let i = 0; i < times; i++) { + fn(i) + } +} + +const every = (arr, fn) => { + let result = true; + for (let i = 0; i < arr.length; i++) { + result = result && fn(arr[i]) + } + return result +} + +export { + forEach, + forEachObject, + unless, + times, + every +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..8b026b0 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "functional", + "version": "1.0.0", + "description": "a project with functional javascript book", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "play": "npx babel-node functional-playground/play.js --presets es2015-node5" + }, + "author": "cecil", + "license": "ISC", + "devDependencies": { + "babel-preset-es2015-node5": "^1.2.0", + "babel-cli": "^6.23.0" + } +} From 8048ece742933579d980debd3d98acda5572385b Mon Sep 17 00:00:00 2001 From: foxhsx <932163475@qq.com> Date: Tue, 10 Aug 2021 23:23:32 +0800 Subject: [PATCH 2/4] add array functional --- functional-playground/play.js | 39 ++++++++- lib/es6-functional.js | 154 +++++++++++++++++++++++++++++++++- 2 files changed, 187 insertions(+), 6 deletions(-) diff --git a/functional-playground/play.js b/functional-playground/play.js index d18ac05..aebe8e7 100644 --- a/functional-playground/play.js +++ b/functional-playground/play.js @@ -3,7 +3,13 @@ import { forEachObject, unless, times, - every + every, + some, + tap, + unary, + once, + memoized, + arrayUtils } from "../lib/es6-functional"; // var array = [1, 2, 3] @@ -32,5 +38,32 @@ import { // }) // }) -console.log(every([NaN, NaN, NaN], isNaN)) -console.log(every([NaN, NaN, 4], isNaN)) \ No newline at end of file +// console.log(some([NaN, NaN, 4], isNaN)) +// console.log(some([2, 3, 4], isNaN)) + +// const arr = ['1', '2', '3'].map(unary(parseInt)) +// console.log(arr, 'arr'); + +// var doPayment = once(() => { +// console.log('Payment is done'); +// }) + +// doPayment(); +// doPayment(); + +// let fastFactorial = memoized((n) => { +// if (n === 0) { +// return 1; +// } + +// // 递归计算阶乘 +// return n * fastFactorial(n - 1) +// }) + +// console.log(fastFactorial(5)) + +// const arr = arrayUtils.map([1,2,3], (x) => x * x) +// console.log(arr); + +const arr = arrayUtils.concatAll([[1,2,3], [2,3,4]]) +console.log(arr); \ No newline at end of file diff --git a/lib/es6-functional.js b/lib/es6-functional.js index c466de3..f7cda51 100644 --- a/lib/es6-functional.js +++ b/lib/es6-functional.js @@ -27,16 +27,164 @@ const times = (times, fn) => { const every = (arr, fn) => { let result = true; - for (let i = 0; i < arr.length; i++) { - result = result && fn(arr[i]) + // 使用 for 循环 + // for (let i = 0; i < arr.length; i++) { + // result = result && fn(arr[i]) + // } + + // 使用 for...of + for(const value of arr) { + result = result && fn(value) + } + + return result +} + +const some = (arr, fn) => { + let result = false; + for (const value of arr) { + result = result || fn(value) } return result } +const tap = (value) => (fn) => ( + typeof(fn) === 'function' && fn(value), + console.log(value) +) + +/** + * 接受一个给定的多参数函数,并把它转换为一个只接受一个参数的函数 + * 检查传入的 fn 是否有一个长度为 1 的参数列表(可以通过 length 属性查看) + * @param {Function} fn 传入的函数 + * @returns function 只接受一个参数 + */ +const unary = (fn) => fn.length === 1 ? fn : (arg) => fn(arg); + +/** + * 只允许一次给定的函数 + * @param {Function} fn 传入只执行一次的函数 + * @returns 返回只能执行一次的高阶函数体 + */ +const once = (fn) => { + let done = false; + + // 返回的函数会形成一个覆盖它的闭包作用域 + return function () { + return done ? undefined : ((done = true), fn.apply(this, arguments)) + } +} + +/** + * + * @param {Function} fn + * @returns 记录了结果的函数体 + */ +const memoized = (fn) => { + const lookupTable = {} + + return (arg) => lookupTable[arg] || (lookupTable[arg] = fn(arg)) +} + +/** + * 循环数组,并返回一个处理后的新数组 + * @param {要做处理的原数组} array + * @param {处理数组每一项的方法} fn + * @returns 返回处理过后的新数组 + */ +const map = (array, fn) => { + let results = []; + for (const value of array) { + results.push(fn(value)) + } + return results; +} + +/** + * 对当前数组进行过滤,筛选出符合条件的数据,并返回一个新数组 + * @param {要过滤的数组} array + * @param {过滤的条件} fn + * @returns 返回符合预期的一组集合 + */ +const filter = (array, fn) => { + let results = [] + for (const value of array) { + (fn(value) ? results.push(value) : undefined) + } + return results; +} + +/** + * 数组扁平化方法 + * @param {要处理的二维数组} array + * @param {额外的函数方法} fn + * @returns 返回扁平化数组 + */ +const concatAll = (array, fn) => { + let results = []; + for (const value of array) { + // apply 会把第二个参数数组的每一项传到 results 数组中去 + results.push.apply(results, value) + } + return results; +} + +/** + * 利用闭包进行累计计算 + * @param {要处理的数组} array + * @param {处理函数} fn + * @param {初始值} initialValue + * @returns 返回最后累计计算的值 + */ +const reduce = (array, fn, initialValue) => { + let accumlator; + if (initialValue != undefined) { + accumlator = initialValue + } else { + accumlator = array[0] + } + + if (initialValue === undefined) { + for (let i = 1; i < array.length; i++) { + accumlator = fn(accumlator, array[i]) + } + } else { + for (const value of array) { + accumlator = fn(accumlator, value) + } + } + + return [accumlator] +} + +const zip = (leftArr, rightArr, fn) => { + let index, results = []; + + for (index = 0; index < Math.min(leftArr.length, rightArr.length); index++) { + results.push(fn(leftArr[index], rightArr[index])) + } + return results; +} + +// 数组方法常量 +const arrayUtils = { + map: map, + filter: filter, + concatAll: concatAll, + reduce: reduce, + zip: zip +} + export { forEach, forEachObject, unless, times, - every + every, + some, + tap, + unary, + once, + memoized, + arrayUtils }; \ No newline at end of file From c2cd03037c1c8786dbb283bc834a6fcf15cb655f Mon Sep 17 00:00:00 2001 From: foxhsx <932163475@qq.com> Date: Sat, 28 Aug 2021 22:21:32 +0800 Subject: [PATCH 3/4] add funtor --- lib/es6-functional.js | 180 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 1 deletion(-) diff --git a/lib/es6-functional.js b/lib/es6-functional.js index f7cda51..1b04f7a 100644 --- a/lib/es6-functional.js +++ b/lib/es6-functional.js @@ -175,6 +175,176 @@ const arrayUtils = { zip: zip } +/* ---------------------------- 柯里化 --------------------------------- */ + +/** + * 日志函数 + * @param {日志类型} mode + * @param {初始信息} initialMessage + * @param {错误信息} errorMessage + * @param {函数} lineNo + */ +const loggerHelper = (mode, initialMessage, errorMessage, lineNo) => { + switch (mode) { + case 'DEBUG': + console.debug(initialMessage, errorMessage + 'at line:' + lineNo); + break; + case 'ERROR': + console.error(initialMessage, errorMessage + 'at line:' + lineNo); + break; + case 'WARN': + console.warn(initialMessage, errorMessage + 'at line:' + lineNo); + break; + default: + throw 'Wrong mode'; + } +} + +/** + * 柯里化转换函数 + * @param {需要柯里化的函数} fn + * @returns 处理过后的柯里化函数 + */ +const curry = (fn) => { + if (typeof fn !== 'function') { + throw Error('No function provided'); + } + + return function curriedFn(...args) { + if (args.length < fn.length) { + return function() { + return curriedFn.apply(null, args.concat( + [].slice.call(arguments) + )) + } + } + return fn.apply(null, args) + } +} + +const loggerUtils = { + errorLogger: curry(loggerHelper)('ERROR')('Error At State.js'), + debugLogger: curry(loggerHelper)('DEBUG')('Debug At State.js'), + warnLogger: curry(loggerHelper)('WARN')('Warn At State.js'), +} + +/** + * 判断传入内容是否符合预期条件 + */ +const match = curry(function (expr, str) { + return str.match(expr) +}) + +/** + * 过滤数组中符合逻辑的一项 + */ +const filter = curry(function (f, ary) { + return ary.filter(f) +}) + +const partial = function (fn, ...partialArgs) { + let args = partialArgs; + return function (...fullArguments) { + let arg = 0; + for (let i = 0; i < args.length && arg < fullArguments.length; i++) { + if (args[i] === undefined) { + args[i] = fullArguments[arg++]; + } + } + return fn.apply(null, args) + } +} + +/** + * 函数式组合,数据流方向从右向左 + * @param {需要组合的函数集合 fns} fns + * @returns 返回一个接受参数 c 的函数,此函数返回 a 调用 b 调用 c + */ +const compose = (...fns) => value => reduce(fns.reverse(), (acc, fn) => fn(acc), value) + +/** + * 管道,数据流方向从左向右 + * @param {需要组合的函数集合} fns + * @returns 返回一个接受参数 value 的函数,此函数使用 reduce 函数递归调用 fns + */ +const pipe = (...fns) => value => reduce(fns, (acc, fn) => fn(acc), value) + +/** + * 用于管道和组合两种方法的中间调试工作 + * @param {接收到的计算值} it + * @returns 把接收到的值传出去 + */ +const identity = (it) => { + console.log(it); + return it; +} + +/** + * 函子,用来处理错误的纯函数 + * @param {需要存储起来的值} value + */ +function Container(value) { + this.value = value +} + +/** + * 函子的静态方法,在创建新的 Container 时省略 new 关键字 + * @param {需要存储起来的值} value + * @returns new 一个函子的实例 + */ +Container.of = function (value) { + return new Container(value) +} + +Container.prototype.map = function (fn) { + return Container.of(fn(this.value)) +} + +const MayBe = function (value) { + this.value = value +} + +MayBe.of = function (value) { + return new MayBe(value) +} + +MayBe.prototype.isNothing = function () { + return (this.value === null || this.value === undefined); +} + +MayBe.prototype.map = function (fn) { + return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value)) +} + +const Nothing = function (value) { + this.value = value +} + +Nothing.of = function (value) { + return new Nothing(value) +} + +Nothing.prototype.map = function (fn) { + return this; +} + +const Some = function (value) { + this.value = value +} + +Some.of = function (value) { + return new Some(value) +} + +Some.prototype.map = function (fn) { + return Some.of(fn(this.value)) +} + +const Either = { + Some: Some, + Nothing: Nothing +} + export { forEach, forEachObject, @@ -186,5 +356,13 @@ export { unary, once, memoized, - arrayUtils + arrayUtils, + loggerHelper, + curry, + loggerUtils, + match, + compose, + partial, + pipe, + identity }; \ No newline at end of file From deca01abcd6008d7bb33d5372b7800f7a5358189 Mon Sep 17 00:00:00 2001 From: foxhsx <932163475@qq.com> Date: Sun, 29 Aug 2021 13:45:23 +0800 Subject: [PATCH 4/4] Chain and Generator --- lib/es6-functional.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/es6-functional.js b/lib/es6-functional.js index 1b04f7a..d25e29c 100644 --- a/lib/es6-functional.js +++ b/lib/es6-functional.js @@ -304,18 +304,49 @@ const MayBe = function (value) { this.value = value } +/** + * 隐藏 new 关键字的用法 + * @param {要存储的值} value + * @returns MayBe 类型的实例 + */ MayBe.of = function (value) { return new MayBe(value) } +/** + * 判断传入的值是否是 null 或者 undefined + * @returns ture 或 false + */ MayBe.prototype.isNothing = function () { return (this.value === null || this.value === undefined); } +/** + * 递归地将 fn 处理后的值存储到 MayBe 中 + * @param {传入的处理函数} fn + * @returns 如果传入的值为 Null 或者 undefined 就返回一个 MayBe 的空对象,反之返回 MayBe 的处理对象 + */ MayBe.prototype.map = function (fn) { return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value)) } +/** + * 返回存储的值,而不是 MayBe 实例 + * @returns 如果有值,就返回里面的值 + */ +MayBe.prototype.join = function () { + return this.isNothing() ? MayBe.of(null) : this.value +} + +/** + * 封装用来扁平化数据的方法 + * @param {内部要处理数据的函数} f + * @returns 返回扁平化数据 + */ +MayBe.prototype.chain = function (f) { + return this.map(f).join() +} + const Nothing = function (value) { this.value = value }