Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript ES6函数式编程入门经典【摘记】 #35

Open
qingfengmy opened this issue Jun 26, 2018 · 10 comments
Open

JavaScript ES6函数式编程入门经典【摘记】 #35

qingfengmy opened this issue Jun 26, 2018 · 10 comments

Comments

@qingfengmy
Copy link
Owner

qingfengmy commented Jun 26, 2018

image

随书代码

函数的第一条原则要小,函数的第二条原则是要更小

函数和方法的区别

我的看法:函数可以作为一个变量存在,而方法只能依赖于对象,许多语言没有函数的支持,如java。而js,C,C++里函数都可以作为变量存在。
书中看法:函数是一段可以通过其名称被调用的代码,方法是必须通过其名称和其关联对象名称被调用的代码。

@qingfengmy
Copy link
Owner Author

命令式编程和声明式编程

需求:打印数组的内容
命令式编程是:写命令告诉计算机怎么做

// 1. 取数组长度
// 2. 循环数组
// 3. 打印

const arr = [1,2,3,4,5]
const len = arr.length
for(let i=0;i<len;i++){
  console.log(arr[i])
}

声明式编程:忽略其他,只关注做什么

const arr = [1,2,3,4,5]
arr.forEach(i=>console.log(i))

声明式编程依赖forEach这些函数

@qingfengmy
Copy link
Owner Author

纯函数的优点

1. 可测试

2. 可缓存,一个参数对应确定结果,所以结果可缓存

3. 可并发,并发主要对全局变量难处理,纯函数不修改全局变量

4. 可组合,functional composition

5. 纯函数就是数学函数

@qingfengmy
Copy link
Owner Author

高阶函数

高阶函数就是接受参数为函数且返回值也为函数的函数
高阶函数通常用于抽象通用的问题,换句话讲,高阶函数就是定义抽象。

它功过建立一个人与系统进行交互的复杂程度,把更复杂的细节抑制在当前水平之下。程序员应该使用理想界面,并且添加额外功能,否则处理起来会很复杂。

@qingfengmy
Copy link
Owner Author

抽象的理解: 屏蔽一些东西,对外提供入口参数和返回值

/**
 * 如果predicate为true,就执行fn (专注于fn,封装/抽象/屏蔽了断言如何实现)
 * @param predicate
 * @param fn
 */
const unless = (predicate, fn) => {
  predicate && fn();
};

/**
 * fn执行times次 (专注于fn,封装/抽象/屏蔽了次数判断如何实现)
 * @param times
 * @param fn
 */
const times = (times, fn) => {
  for (let i = 0; i < times; i++) {
    fn(i);
  }
};

/**
 * 需求:找到数组中的偶数
 */
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

times(arr.length, i => {
  unless(arr[i] % 2 == 0, () => console.log(arr[i]));
});

@qingfengmy
Copy link
Owner Author

闭包

闭包:内部函数,也就是函数中的函数,上下文有三层变量,全局\外层函数\内层函数变量
闭包实现sortBy函数

/**
 * 排序
 */
const fruit = ["cherries", "apples", "bananas"];
console.log(fruit.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)));

/**
 * sortBy
 */
const sortBy = property => {
  return (a, b) => {
    const result =
      a[property] < b[property] ? -1 : a[property] > b[property] ? 1 : 0;
    return result;
  };
};
const students = [
  { firstName: "kobe", lastName: "green" },
  { firstName: "allen", lastName: "len" },
  { firstName: "time", lastName: "duncan" }
];
console.log(students.sort(sortBy("firstName")));

@qingfengmy
Copy link
Owner Author

qingfengmy commented Jun 26, 2018

tab unary once memoized函数

/**
 * tap函数
 * @param value
 * @returns {Function}
 */
const tap = value => {
  return fn => {
    typeof fn === "function" && fn(value);
  };
};

// tap("fun")(it => console.log("it is " + it));

/**
 * 场景:遍历数组并打印
 */
[1, 2, 3, 4, 5].forEach(i => tap(i)(() => console.log(i)));

/**
 * 问题
 */
console.log(["1", "2", "3"].map(parseInt)); // [1, NaN, NaN]

/**
 * 分析parseInt(value, radix)两个参数,而map给他的两个参数是(item, index),index为0时,radix默认为10;
 * 而index小于2或者大于36,则返回NaN
 * radix为2时,其支持的value只能有0和1,其他数字都会是NaN
 * 解决方式是对parseInt进行unary
 */

/**
 * 一元化:多参数变为单参数,其他参数不要了.和curry不同,和unary相对应的是binary,单参数变两参数
 * @param fn
 */
const unary = fn => (fn.length === 1 ? fn : arg => fn(arg));
console.log(["1", "2", "3"].map(unary(parseInt))); // [1,2,3]

/**
 * 执行一次,如加载lib,支付操作等
 * @param fn
 * @returns {function(): boolean}
 */
const once = fn => {
  let done = false;
  return () => !done && ((done = true), fn.apply(this, arguments));
};

const doPay = once(() => console.log("pay..."));

doPay();
doPay();

/**
 * 记忆,记录
 */
const memoized = fn => {
  const lookupTable = {};
  return arg => {
    console.log(lookupTable);
    return lookupTable[arg] || (lookupTable[arg] = fn(arg));
  };
};

/**
 * 阶乘
 */
const factorial = n => {
  if (n == 1) {
    return 1;
  }
  return n * factorial(n - 1);
};
const newFactorial = memoized(factorial)
console.log(factorial(10));
console.log(newFactorial(10));
console.log(newFactorial(11));
console.log(newFactorial(12));

@qingfengmy
Copy link
Owner Author

Array的函数实现

/**
 * 实现array的forEach,map,filter,concat/flatten,reduce和zip等高阶函数
 */
// 1. forEach
const forEach = (arr, fn) => {
  for (let i = 0; i < arr.length; i++) {
    fn(arr[i]);
  }
};
forEach([1, 2, 3], item => console.log(item));

// 2. map
const map = (arr, fn) => {
  const results = [];
  forEach(arr, item => {
    results.push(fn(item));
  });
  return results;
};
console.log(map([1, 2, 3], item => item * item));

// 3. filter
const filter = (arr, fn) => {
  const results = [];
  forEach(arr, item => {
    fn(item) && results.push(item);
  });
  return results;
};

console.log(filter([1, 2, 3], item => item > 1));

let apressBooks = [
  {
    name: "beginners",
    bookDetails: [
      {
        id: 111,
        title: "C# 6.0",
        author: "ANDREW TROELSEN",
        rating: [4.7]
      },
      {
        id: 222,
        title: "Efficient Learning Machines",
        author: "Rahul Khanna",
        rating: [4.5]
      }
    ]
  },
  {
    name: "pro",
    bookDetails: [
      {
        id: 333,
        title: "Pro AngularJS",
        author: "Adam Freeman",
        rating: [4.0]
      },
      {
        id: 444,
        title: "Pro ASP.NET",
        author: "Adam Freeman",
        rating: [4.2]
      }
    ]
  }
];
let reviewDetails = [
  {
    id: 111,
    reviews: [{ good: 4, excellent: 12 }]
  },
  {
    id: 222,
    reviews: []
  },
  {
    id: 333,
    reviews: []
  },
  {
    id: 444,
    reviews: [{ good: 14, excellent: 12 }]
  }
];
/**
 * 4. flatten/concat
 * 将嵌套数组转为非嵌套数组 [[{},{}],[{}],[{}]]=>[{},{},{},{}]
 * @param array
 * @param fn
 */
const flatten = array => {
  let results = [];
  for (const value of array) {
    // value是个数组,apply接受的参数是个数组,相当于results.push(...value)
    results.push.apply(results, value);
    // results = [...results, ...value];
  }
  return results;
};

console.log(
  flatten(map(apressBooks, item => item.bookDetails)).map(item => ({
    title: item.title,
    author: item.author
  }))
);

// 5. reduce: 归约操作
const reduce = (arr, fn, initValue) => {
  let accumlator;
  if (initValue != undefined) {
    accumlator = initValue;
  } else {
    accumlator = arr[0];
  }
  if (initValue != undefined) {
    for (const value of arr) {
      accumlator = fn(accumlator, value);
    }
  } else {
    for (let i = 1; i < arr.length; i++) {
      accumlator = fn(accumlator, arr[i]);
    }
  }
  return accumlator;
};

console.log(reduce([1, 2, 3], (accumlator, item) => accumlator + item));
console.log(reduce([1, 2, 3], (accumlator, item) => accumlator + item, 10));

// 6. zip: 合并两个不同的数组 根据bookId合并apressBooks和reviewDetails
const zip = (firstArr, secondArr, fn) => {
  const result = [];
  const len = Math.min(firstArr.length, secondArr.length);
  for (let i = 0; i < len; i++) {
    result.push(fn(firstArr[i], secondArr[i]));
  }
  return result;
};
console.log(
  zip(
    flatten(map(apressBooks, item => item.bookDetails)),
    reviewDetails,
    (first, second) => ({ ...first, ...second })
  )
);
// 优化zip:真实业务中很难两块数据是顺序一致的,那也用不到zip了,zip就是按顺序合并的

@qingfengmy
Copy link
Owner Author

/**

  • 一元函数:只接受一个参数的函数
  • 二元函数:接受两个参数的函数
  • 变参函数:接受多个参数的函数
    */
    // es5处理多参
    const es5Fun = function() {
    console.log(Array.prototype.slice.apply(arguments));
    };
    // es6处理多参
    const es6Fun = (...args) => {
    console.log(args);
    };

es5Fun(1, 2, 3, 4, 5, "6");
es6Fun(1, 2, 3, 4, 5, "6");

@qingfengmy
Copy link
Owner Author

偏应用

/**
 * 偏函数
 * 它允许开发者部分的应用函数参数
 * 有时填充函数的前两个参数和最后一个参数会使中间的参数处于一种未知状态,这正是偏应用发挥作用的地方
 * 固定几个参数,留出几个参数位置给后面的用,这里用undefined占位
 * @param fn
 * @param partialArgs
 * @returns {Function}
 */
const partial = (fn, ...partialArgs) => {
  let args = partialArgs;
  return function(...fullArgs) {
    let arg = 0;
    for (let i = 0; i < args.length && arg < fullArgs.length; i++) {
      if (args[i] === undefined) {
        args[i] = fullArgs[arg++];
      }
      return fn.apply(null, args);
    }
  };
};

// 需求,下面两行代码,100重复了,需要抽象
setTimeout(() => console.log("just do it"), 100);
setTimeout(() => console.log("just write it"), 100);

// 方式1: 封装开销
const setTimeoutWrapper = fn => setTimeout(fn, 100);
// 方式2:偏应用
let delay = partial(setTimeout, undefined, 10);
delay(() => console.log("just do it"));
delay(() => console.log("just write it"));

// 需求2:json美化输出
let obj = { foo: "bar", bar: "foo" };
console.log(JSON.stringify(obj));
console.log(JSON.stringify(obj, null, 2)); // 2个空格

const prettyJson = partial(JSON.stringify, undefined, null, 2);
console.log(prettyJson(obj));

@uldaman
Copy link

uldaman commented Jan 18, 2019

const partial = (fn, ...partialArgs) => {
  let args = partialArgs;
  return function(...fullArgs) {
    let arg = 0;
    for (let i = 0; i < args.length && arg < fullArgs.length; i++) {
      if (args[i] === undefined) {
        args[i] = fullArgs[arg++];
      }
      return fn.apply(null, args);
    }
  };
};
  1. 为什么使用 apply, 用解构语法不是更好吗
  2. 返回的函数为什么不用箭头定义
const partial = (fn, ...partialArgs) => {
  let args = partialArgs;
  return (...fullArgs) => {
    let arg = 0;
    for (let i = 0; i < args.length && arg < fullArgs.length; i++) {
      if (args[i] === undefined) {
        args[i] = fullArgs[arg++];
      }
      return fn(...args);
    }
  };
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants