【重学】函数柯里化,偏函数

大纲:

  1. 函数柯里化
  2. 偏函数
  3. 正则截取query部分
  4. ts

前置知识:

  1. 函数的参数
  • 函数的 length 属性,返回函数预期的参数个数 ---------------------- (形参)
  • 函数内的 arguments对象 包含了函数运行时的所有参数 ---------(实参)
  1. 类数组对象转换成数组
  • [].slice.call(类似数组的对象)
  • [].slice.apply(类似数组的对象)
  • Array.prototype.slice.call(类似数组的对象)
  • Array.from()
  1. Function.prototype.apply()
  • apply的第一个参数是要绑定的对象,当是null和undefined时,指的是全局对象window
  1. concat ------------------- concat不会改变原数组
  • concat用于多个数组的拼接,将新数组的成员拼接到旧数组的尾部,返回一个新数组
  • 注意:concat不会改变原数组
  1. 偏函数和函数柯里化概念
    偏函数:是固定一个或多个参数,产生另一个较小元的函数 n元函数 => 转换成n-x元函数
    柯里化:将多个参数函数转化成单一参数函数------------------n元函数 => 转换成n个一元函数
  • 偏函数(partial application)partial:是部分的意思
  • 柯里化 currying
  1. rest参数
  • rest参数: ...变量名 用来获取函数的多余参数
  • ...变量名 变量名是一个数组

(1)函数柯里化

  • 函数柯里化:就是将一个多个参数的函数,转化成一系列一个参数的函数的过程
  • 柯里化, 即 Currying 的音译。
function x(a,b,c) {
    console.log([].slice.call(arguments))
    console.log([].slice.apply(arguments))
    console.log(Array.prototype.slice.apply(arguments))
        console.log(Array.from(arguments))
}
x(1,2,3,4,5)
x.length --------------------------- 函数形参的个数 3
x内部的arguments.length -------------------- 函数的实参的集合 5

柯里化阶段一

需求:将add(a,b,c) 转化成 curryAdd(a)(b)(c)
缺点:只能处理三个参数的情况,不能处理任意多个参数的情况

function add(a,b,c) {
  return a+b+c
}

function curryAdd(a) {
  return function(b) {
    return function(c) {
      return a+b+c
    }
  }
}

柯里化阶段二

需求:处理任意多个参数相加
缺点: 
  1. 处理相加逻辑的代码,只是在没有参数时才会执行,其他部分都在处理怎么收集所有参数
  2. 所以出来相加逻辑的代码,可以通过传参的形式来调用,执行相加逻辑

function curryAdd() {
  let params_arr = [] //-------------------- 定义一个用于收集参数的数组
  const closure = function() { // ------------ 定义一个闭包
    const args = Array.prototype.slice.apply(arguments) //------------ 收集每次调用的参数,一次可能有多个
    if (args.length) {
       params_arr = params_arr.concat(args)
       // 用concat是因为可以一次调用,一次传了多个参数
       // 注意concat不会改变原数组,返回值是拼接过后的新数组
       return closure
     } // 如果参数存在,就收集到数组,并且返回闭包,继续判断存不存在参数
     params_arr.reduce((total, current) => total + current) // 如果参数已经不存在了,就相加所有尺寸参数
  }
  return closure // -------------------------- 返回一个闭包
}

const fn = curryAdd()
const res = fn(1)(2)(3)(4)() // 10-------------注意需要最后一个调用一个空参数的闭包
const res2 = fn(1)(2)(3)(4, 10)() // 20

柯里化阶段三

缺点:
  1. 函数中在判断是否传入参数,没有了再执行相加
  2. 也就是说,最后一次一定要执行一次空参数的回调
  3. 所以更合理的方式:就是通过函数可以接收参数的总数来判断


function add(arr) {
  return arr.reduce((total, current) => total + current)
}

    function curryAdd(fnAdd) {
      let params_arr = [] //-------------------- 定义一个用于收集参数的数组
      const closure = function(...args) { // args是一个数组
        if (args.length) {
          params_arr = params_arr.concat(args) // 用concat是因为每次调用,都可以传多个参数
          return closure
        } // 如果参数存在,就收集到数组,并且返回闭包,继续判断存不存在参数

        // 如果参数不存在,执行传入的函数
        return fnAdd(params_arr)
      }
      return closure // -------------------------- 返回一个闭包
    }

const go = curryAdd(add)
const res = go(1)(2,3)(4)()
console.log(res, 'res')

柯里化阶段四

    function add(x,y,z,m) {
      return x+y+z+m
    }

    function curryAdd (fnAdd) {
      let params_arr = []
      const maxLength = fnAdd.length // 形参的长度
      console.log(maxLength, 'maxLength')

      const closure = function () {
        params_arr = params_arr.concat([].slice.apply(arguments))
        // 执行一次闭包就收集一个参数,而不是判断完再收集
        if (params_arr.length < maxLength) {
         // 如果参数数组中收集到的参数长度 小于 函数形参的参数长度,执行闭包
          return closure
        }
        return fnAdd(...params_arr)
      }

      return closure
    }

    const go = curryAdd(add)
    const res = go(1,2)(3,4)
    console.log(res) //10

https://juejin.im/post/5c677041f265da2de25b7707

https://www.jianshu.com/p/fe0d80b04129

https://juejin.im/post/5c932a556fb9a070cd56998e

https://juejin.im/post/5c619de8f265da2d8a559131







(2) 偏函数

  • 将一个或者多个参数固定到一个函数,产生一个更小元的函数
function add (a, b) {
  return a + b
}
function partial (fn) {...}

const addPartial = partial(add, 1)  // ------------------ 实现固定一部分参数1
const res = addPartial(2) // 3 -------------------------- 只传一部分参数 2

偏函数实现方式一

  • bind方法
    function add (a, b) {
      return a + b
    }
    function partial () {
      let args = Array.prototype.slice.call(arguments)
      const fn = args.shift()
      return fn.bind(this, ...args)  
     // ----------- bind方法,返回新的函数
     // ----------- 除了第一个参数,后面的参数,是传给fn的参数
    }
    const addPartial = partial(add, 1)
    const res = addPartial(2) // 除了固定的参数,剩下的参数在这里传入
    console.log(res, 'res') // 3

偏函数实现方法二

    function add (a, b, c) {
      return a + b + c
    }

    function partial(fn) {
      let params = Array.prototype.slice.call(arguments, 1) // 拿到除取函数外的其他所有参数,是一个数组

      const closure = function() {
        const currentParams =  Array.prototype.slice.call(arguments) // 每次闭包接收的参数
        params = params.concat(currentParams) // 传入收集参数的数组中
        console.log(params)
        if ( params.length < fn.length ) { // 参数收集数组长度 如果 小于add函数预期参数个数时,继续收集
          return closure
        }
        return fn.apply(null, params) // 如果等于add预期参数时,停止收集参数,执行add函数
      }
      return closure
    }
    const addPratial = partial(add, 2)
    const res = addPratial(3)(4)
    console.log(res, 'res') // 9

https://juejin.im/post/5993a7ea6fb9a0247f4f2d08

正则截取query部分

const str = 'https://www.jianshu.com/u/70c8a3b8bb44?name=wu&age=20'

const slice = str.match(/([^?&=]+)=([^?&=]+)/g)
// 全局匹配
// ([^?&=]+) 除去? & = 外的一个或者多个字符

console.log(slice) // ["name=wang", "age=20"]
const str = 'http://www.baidu.com/?name=wang&age=20&sex=man#'
const res = str.match(/\?(.+)#/)[1].match(/[^&#]+=[^&#]+/g)
console.log(res)






TS

数组

数组的两种表示方法

let list: number[] = [1, 2, 3];

let list: Array = [1, 2, 3]; -------------- 数组泛型 Array<类型变量>

null 和 undefined

  • null 和 undefined 是所有类型的子类型
  • 当指定了 --strictNullChecks标记,null和undefined就只能赋值给void和他们各自

never

  • never类型是任何类型的子类型

类型断言 ----------------------------- 相当于类型转换

  • 类型断言有两种形式
    • 尖括号语法
    • as语法
1. as语法 ---- 在jsx中只能使用 as

let someValue: any = "this is a string"; -------------------------- 本来 someValue 是 any类型的 变量 
let strLength: number = (someValue as string).length; ------------- 被断言成了字符串类型

接口

  • ReadonlyArray ------------ 此数组不能被修改,且类型是T类型的数组
  • Array ------------------------- 数组泛型
  • readonly和const的区别
    • const 用于变量
    • readonly 用于属性

函数类型

interface SearchFunc {
  (source: string, subString: string): boolean;   --------------- 函数类型,包含参数列表和返回值
}

  • private 不能在声明他的类的外部访问
  • 参数属性:通过给构造函数参数前面添加一个访问限制符来声明
    • 比如:使用 private 限制一个参数属性,会声明并初始化一个私有成员
  • abstract:是抽象的意思

类型谓词

  • parameterName is Type,parameterName必须来自当函数签名里的一个参数名

typeof类型保护

类型别名

  • 类型别名会给类型取一个新名字
  • 类型别名和接口很像,当可以作用域原始值,联合类型,元组,和任何其他任何你需要手写的类型
  • 类型别名不会新建一个类型,而是创建了一个名字,来引用那个类型
  • 同接口一个,类型别名也可以是泛型
  • 可以在类型别名的属性中引用自己
type ------------------------------------------- 声明一个类型别名


type Name = string;  ---------------------- 类型别名可以作用于原始类型,联合类型,元组,或者其他任何需要手写的类型
type NameResolver = () => string;  -------- 函数类型
type NameOrResolver = Name | NameResolver; ------- 联合类型
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') { ----------------- typeof类型保护
        return n;
    }
    else {
        return n();
    }
}

type Container = { value: T }; ----------- 类型别名也可以是泛型


接口 vs 类型别名

  • 接口创建了一个姓名字,在任何地方都可以使用,类型别名不创建新名字(错误信息就不使用别名)
  • 类型别名不能被 extends 和 implements
  • 如果你无法通过接口来描述一个类型,并且你需要使用联合类型或者元组类型,通常会使用类型别名

索引类型

  • 索引类型查询操作符,索引访问操作符
    • 索引类型查询操作符:keyof T 的结果表示:T上已知公共属性名的联合
    • 索引访问操作符:T[K] ------- 就是T中K属性对应的类型

映射类型

  • 从就类型中创建新类型的方式
    • Keys:是已经存在的联合类型
    • k内部会循环keys中的属性
    • 最后是:属性的结果类型

你可能感兴趣的:(【重学】函数柯里化,偏函数)