2

聊一聊函数之美

 3 years ago
source link: http://developer.51cto.com/art/202102/644970.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

uUNbyi.jpg!mobile

函数在任何编程语言中都占据着主导地位。

而在js中,函数是另类的存在,本质上是特殊的Object,它可以设置属性:

const fn = () => { }; 
fn.foo = "foo"; 
console.log(fn.foo); // 'foo' 

今天分享的是函数的一些操作:

  • 函数的缓冲功能memoize
  • 函数柯里化curry
  • 截取参数处理arg
  • 防抖节流
  • 延迟函数执行delay
  • 延迟函数调用defer
  • 异步函数调用compose
  • 函数只被调用一次once
  • 判断函数是否可以执行
  • 检查对象属性checkProp
  • 链式调用函数

函数的缓冲功能memoize

关于memoize的思考来源于reack的Hook文档中,memoize的特性就是「 利用函数的特性做缓存 」。

不知道你做算法的时候,是否考虑过递归是怎么缓存结果,层层储存的。

如下的斐波那契,每一次计算的结果缓存在哪里呢?

const fibonacci = (n) => { 
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); 
}; 

我们可以简单模拟一下memoize的实现:

const memoize = function (fn) { 
  const cache = {}; 
  return function () { 
    const key = JSON.stringify(arguments); 
    var value = cache[key]; 
    if (!value) { 
      // 为了了解过程加入的log,正式场合应该去掉 
      console.log('新值,执行中...');  
      // 放在一个数组中,方便应对undefined,null等异常情况 
      value = [fn.apply(this, arguments)];   
      cache[key] = value; 
    } else { 
      console.log('来自缓存');   
    } 
    return value[0]; 
  } 
} 

测试一下:

const memoizeFibonacci = memoize(fibonacci); 
 
const log = console.log; 
log(memoizeFibonacci(45)); 
// 新值,执行中...;    1134903170  // 等待时间比较长 
log(memoizeFibonacci(45)); 
// 来自缓存;    1134903170 
log(memoizeFibonacci(45)); 
// 来自缓存;    1134903170 
log(memoizeFibonacci(45)); 
// 来自缓存;    1134903170 
log(memoizeFibonacci(45)); 

函数柯里化curry

柯里化的概念就是「 把接受多个参数的函数变换成接受一个单一参数的函数 」。

const curry = (fn, arity = fn.length, ...args) => 
  arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args); 
 
curry(Math.pow)(2)(10); // 1024 
curry(Math.min, 3)(10)(50)(2); // 2 

这个bind用得非常好,借助它积累每次传进来的参数,等到参数足够时,再调用。

有了柯里化,还有反柯里化,它的概念是「 把多个接受多个参数的函数层层铺平 」。

const uncurry = (fn, n = 1) => (...args) => { 
  const next = acc => args => args.reduce((x, y) => x(y), acc); 
  if (n > args.length) throw new RangeError('Arguments too few!'); 
  return next(fn)(args.slice(0, n)); 
}; 
 
const add = x => y => z => x + y + z; 
const uncurriedAdd = uncurry(add, 3); 
uncurriedAdd(1, 2, 3); // 6 

截取函数参数ary

「 截取指定函数参数做操作 」;ary的第二个参数接收一个索引参数,表示只截取得到n的位置。

// ary 截取指定参数处理 
const ary = (fn, n) => (args) => fn(args.slice(0, n)); 
 
// 如果处理的数据是字符串 
const checkPe = (arg) => { 
  if (arg && arg.indexOf('pe') > -1) { 
    return arg.indexOf('pe') 
  } 
  return -1 
} 
const getPe = ary(checkPe, 5); 
const numsPe = ['wpe', 'wwperr', 'wwepe'].map(x => getPe(x)); 
 
console.log(numsPe, 'numsPe') 
// [1, 2, 3] 

如果是数组的话,需要使用扩展运算符。

// 如果处理的数据是数组 
const ary = (fn, n) => (...args) => fn(...args.slice(0, n)); 
 
const firstTwoMax = ary(Math.max, 3); 
const nums = [[2, 6, 9, 'a'], [6, 4, 8], [10]].map(x => firstTwoMax(...x)); 
 
console.log(nums, 'nums') 
// [9, 8, 10] 

防抖节流

关于防抖和节流的区别可以参考我之前的文章《电梯与地铁之说》。

const debounce = (fn, ms = 0) => { 
  let timeoutId; 
  return function(...args) { 
    clearTimeout(timeoutId); 
    timeoutId = setTimeout(() => fn.apply(this, args), ms); 
  }; 
}; 
 
window.addEventListener( 
  'resize', 
  debounce(() => { 
    console.log(window.innerWidth); 
    console.log(window.innerHeight); 
  }, 250) 

传入高频次调用的函数和时间间隔,返回一个已防抖的函数。

节流会稀释函数的执行频率。在wait秒内只执行一次。

const throttle = (fn, wait) => { 
  let inThrottle, lastFn, lastTime; 
  return function() { 
    const context = this, 
      args = arguments; 
    if (!inThrottle) { 
      fn.apply(context, args); 
      lastTime = Date.now(); 
      inThrottle = true; 
    } else { 
      clearTimeout(lastFn); 
      lastFn = setTimeout(function() { 
        if (Date.now() - lastTime >= wait) { 
          fn.apply(context, args); 
          lastTime = Date.now(); 
        } 
      }, Math.max(wait - (Date.now() - lastTime), 0)); 
    } 
  }; 
}; 
 
window.addEventListener( 
  'resize', 
  throttle(function(evt) { 
    console.log(window.innerWidth); 
    console.log(window.innerHeight); 
  }, 250) 
); // Will log the window dimensions at most every 250ms 

延迟函数执行delay

delay字面意思:「 延迟执行 」。

const delay = (fn, wait, ...args) => setTimeout(fn, wait, ...args); 
 
delay( 
  function (text) { 
    console.log(text); 
  }, 
  1000, 
  'later' 
); // Logs 'later' after one second. 

延迟函数调用defer

defer字面意思:「 延迟调用 」。

可适用于推迟 cpu 密集型计算,以免阻塞渲染引擎工作。使用setTimeout(超时时间为1ms)将函数参数添加到浏览器事件队列末尾。

const defer = (fn, ...args) => setTimeout(fn, 1, ...args); 
 
// Example A: 
defer(console.log, 'a'), console.log('b'); // logs 'b' then 'a' 

异步函数compose

compose函数是「 从右向左去实现的数据执行流 」。它的真正意义在于逻辑分层。利用reduce方法实现函数的“洋葱”包裹。

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args))); 
 
const substract3 = x => x - 3; 
const add5 = x => x + 5; 
const multiply = (x, y) => x * y; 
const multiplyAndAdd5AndSubstract3 = compose( 
  substract3, 
  add5, 
  multiply 
); 
multiplyAndAdd5AndSubstract3(5, 2); // 12 

要想实现从左向右执行也非常简单,把f和g的位置互调一下。

函数只被调用一次once

因为 JavaScript 是单线程执行环境,不需要考虑并发环境,直接一个内部变量存到闭包中,每次调用前判断,并在第一次调用时,修改其值,让后续调用全部失效。

const once = (fn) => { 
  let called = false; 
  return function (...args) { 
    if (called) return; 
    called = true; 
    return fn.apply(this, args); 
  }; 
}; 
 
const startApp = function (event) { 
  console.log(this, event); // document.body, MouseEvent 
}; 
document.body.addEventListener("click", once(startApp)); 

判断函数是否可以执行

第一个参数为函数是否可以执行的判断条件,第二个参数为执行的函数。

const when = (pred, whenTrue) => (x) => (pred(x) ? whenTrue(x) : x); 
 
const doubleEvenNumbers = when( 
  (x) => x % 2 === 0, 
  (x) => x * 2 
); 
doubleEvenNumbers(2); // 4 
doubleEvenNumbers(1); // 1 

检查对象属性

「 判断某个对象是否具备要求 」。用!!强制转化为布尔类型。

const checkProp = (predicate, prop) => (obj) => !!predicate(obj[prop]); 
 
const lengthIs4 = checkProp((l) => l === 4, "length"); 
lengthIs4([]); // false 
lengthIs4([1, 2, 3, 4]); // true 
 
const sizeIs4 = checkProp((l) => l === 4, "size"); 
sizeIs4(new Set([1, 2, 3, 4])); // true 
 
const session = { obj: { active: true, disabled: false } }; 
const validUserSession = checkProp((u) => u.active && !u.disabled, "obj"); 
 
validUserSession(session); // true 

链式调用

将函数数组转换为有决策权的链式函数调用。

const chainAsync = (fns) => { 
  let curr = 0; 
  const last = fns[fns.length - 1]; 
  const next = () => { 
    const fn = fns[curr++]; 
    fn === last ? fn() : fn(next); 
  }; 
  next(); 
}; 
 
chainAsync([ 
  (next) => { 
    console.log("0 seconds"); 
    setTimeout(next, 1000); 
  }, 
  (next) => { 
    console.log("1 second"); 
    setTimeout(next, 1000); 
  }, 
  () => { 
    console.log("2 second"); 
  }, 
]); 

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK