百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

数组原生api以及es6+函数式编程(curry)实现lodash函数

toyiye 2024-06-21 11:58 13 浏览 0 评论

本文实现方法都是看效果倒推实现方法,并进行一些拓展和思考,和源码无关。lodash这个库的文档更像一个题库,给出了题目让我们刷题的

能收获什么

1、修炼代码基本功,了解常见的套路

2、了解到一些操作的英文命名和规范

3、积累经验,面对复杂逻辑问题可以迅速解决

4、也许可以查到自己的js基础知识的漏洞

我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。


??注意:

  • 三星难度以上的会具体拓展和讲解
  • 文中使用的基本都是数组原生api以及es6+函数式编程,代码简洁且过程清晰
  • 如果说性能当然是命令式好,实现起来稍微麻烦一些而且比较枯燥无味
  • 时代在进步,人生苦短,我选择语法糖和api。面临大数据的性能瓶颈,才是考虑命令式编程的时候


函数系列的总体难度比之前的数组、集合系列都要大一些。恰好,lodash函数系列的方法是面试中经常会问到的

bind


  • _.bind(func, thisArg, [partials])创建一个函数 func,这个函数的 this 会被绑定在 thisArg。并且任何附加在 _.bind 的参数会被传入到这个绑定函数上。这个 _.bind.placeholder 的值,默认是以 _ 作为附加部分参数的占位符。
  • 注意: 不同于原生的 Function#bind,这个方法不会设置绑定函数的 length 属性。
  • 参数:func (Function)是要绑定的函数。thisArg ()的这个 this 会被绑定给 func。[partials] (...)指附加的部分参数
  • 返回值 (Function):新的绑定函数
  • 难度系数:★★★★
  • 建议最长用时:12min
var greet = function(greeting, punctuation) {
 return greeting + ' ' + this.user + punctuation;
};
var object = { 'user': 'fred' };
var bound = _.bind(greet, object, 'hi');
bound('!');
// => 'hi fred!'
// 使用了占位符
var bound = _.bind(greet, object, _, '!');// 填了第二个参数
bound('hi'); // 再传第一个参数
// => 'hi fred!'
var bound = _.bind(greet, object, _, "!");// 填了第二个参数
bound(_, "hi")// 填了第2个参数,第一个参数`_`补上初始参数第一个空格,第二个参数hi接在后面
// => 'fred!'

参考代码

const _ = {
 bind(f, thisArg, ...rest) {
 return function(...args) {
// 参数有空格,走新的逻辑
 return f.apply(thisArg, (rest.includes(_) || args.includes(_))? _.mergeArgs(rest, args): [...rest, ...args])
 }
 },
 toString() {
 return '' // 隐式转换
 },
 mergeArgs(init, args) {
 return init.map((arg) => {
// 执行时传入的参数作为填补
 if (arg === _ && args.length) {
 return args.shift()
 }
 return arg
 }).concat(args) // 剩下的参数都接到后面
 }
}

实现一个bind倒是很简单,但是lodash的bind还有填空格的操作。把lodash自己填进去就是一个空格,而且我们还可以从console.log(bound(_, "hi"))发现,它具有隐式转换:_ + '' === ''。实现lodash的bind的时候,除了兼容正常的函数bind之外,还要兼容传入_的时候的空格的case,并merge初始化参数和调用时参数


curry

正向柯里化

  • _.curry(func, [arity=func.length])创建一个函数,该函数接收一个或多个 func 的参数。当该函数被调用时,如果 func 所需要传递的所有参数都被提供,则直接返回 func 所执行的结果。否则继续返回该函数并等待接收剩余的参数。可以使用 func.length 强制需要累积的参数个数。
  • 这个 _.curry.placeholder 的值,默认是以 _ 作为附加部分参数的占位符。
  • 注意: 这个方法不会设置 "length" 到 curried 函数上。
  • 参数: func (Function)是需要 curry 的函数。[arity=func.length] (number)是指需要提供给 func 的参数数量
  • 返回 curry 后的函数
  • 难度系数:★★★★★
  • 建议最长用时:15min
// example
var abc = function(a, b, c) {
 return [a, b, c];
};
var curried = _.curry(abc);
curried(1)(2)(3);
// => [1, 2, 3]
curried(1, 2)(3);
// => [1, 2, 3]
curried(1, 2, 3);
// => [1, 2, 3]
// 使用了占位符
curried(1)(_, 3)(2);
// => [1, 2, 3]

参考代码:

const _ = {
 curry(f, arity = f.length) {
 return function(...initValues) {
// 每次执行,都是一个新的闭包,executes的位置要放这里
 let executes = initValues
 function curried(...args) {
// 兼容空格
 const newArgs = _.mergeArgs(executes, args)
 executes = newArgs
// 过滤空格的真实长度
 if (_.getReallLength(newArgs) < arity) {
 return curried
 }
 const ret = f.apply(null, newArgs)
 return ret
 }
 return curried
 }
 },
 toString() {
 return ''
 },
 mergeArgs(init, args) {
// 有没有空格
 if (!init.includes(_)) {
 return [...init, ...args]
 }
 return init.map((arg) => {
 if (arg === _ && args.length) {
 return args.shift()
 }
 return arg
 }).concat(args)
 },
 getReallLength(args) {
// 获取真实长度
 return args.filter(arg => arg !== _).length
 }
}
function curry(f, arity = f.length) {
 const executes = []
 function curried(...args) {
 executes.push(...args)
 if (executes.length < arity) {
 return curried
 }
 const ret = f.apply(null, executes)
 executes.length = 0
 return ret
 }
 return curried
}

家喻户晓的柯里化,可能很多人都会写。但是在这里还要考虑到lodash的空格以及柯里化函数多次复用

反向柯里化

原理一样,只是取参数的时候从右边往左边取

  • 难度系数:★★★★★★(如果已经实现了正向柯里化curry,难度降为1星)
  • 建议最长用时:18min

example

var abc = function(a, b, c) {
 return [a, b, c];
};
var curried = _.curryRight(abc);
curried(3)(2)(1);
// => [1, 2, 3]
curried(2, 3)(1);
// => [1, 2, 3]
curried(1, 2, 3);
// => [1, 2, 3]
// 使用了占位符
curried(3)(1, _)(2);
// => [1, 2, 3]

参考代码:

// 只需要把上文的mergeArgs方法改一下即可
 _.mergeArgs = function(init, args) {
 if (!init.includes(_)) {
// 就改这里,换个位置
 return [...init, ...args]
 }
 return init.map((arg) => {
 if (arg === _ && args.length) {
 return args.shift()
 }
 return arg
 }).concat(args)
 },


debounce


  • _.debounce(func, [wait=0] debounce, [options])创建一个防抖动函数。该函数会在 wait 毫秒后调用 func 方法。该函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。可以提供一个 options 对象决定如何调用 func 方法, options.leading 与|或 options.trailing 决定延迟前后如何触发。func 会传入最后一次传入的参数给防抖动函数。随后调用的防抖动函数返回是最后一次 func 调用的结果。
  • 注意: 如果 leading 和 trailing 都设定为 true。则 func 允许 trailing 方式调用的条件为: 在 wait 期间多次调用防抖方法。
  • 参数
  • func (Function)
  • 要防抖动的函数
  • [wait=0] (number)
  • 需要延迟的毫秒数
  • [options] (Object)
  • 选项对象
  • [options.leading=false] (boolean)
  • 指定调用在延迟开始前
  • [options.maxWait] (number)
  • 设置 func 允许被延迟的最大值
  • [options.trailing=true] (boolean)
  • 指定调用在延迟结束后
  • 返回值 (Function)
  • 返回具有防抖动功能的函数
  • 难度系数:★★★★★★
  • 建议最长用时:20min

我相信,80%的人可以1分钟内写出trailing模式的debounce方法(定时器到了就执行函数,在定时器还没到期间重复执行函数,定时器重置),但是同时支持options配置和leading模式的话,难度大大增加了

参考代码:

// 执行方式:delay前、delay后、delay前后
function execute(f, timeout, ref, { isDelay, isDirectly }) {
 if (!ref.last && isDirectly) {
// 调用上一次保存下来的方法
 ref.isExecute = true
 ref.last = f
 f()
 }
 return setTimeout(() => {
 if (isDirectly) {
// 调用了就清掉
 ref.last = null
 }
 ref.isExecute = true
 if (isDelay) {
 f()
 }
 }, timeout);
}
function debounce(func, wait = 0, options = {}) {
 const { leading, maxWait, trailing = true } = options
 const ref = {
 t: undefined,
 isExecute: false, // 给maxWait用的标记
 maxWaitTimer: undefined,
 last: undefined, // leading模式用的
 }
 return function(...args) {
 const main = () => func.apply(null, args)
// 最大超时时间设置
 if ('maxWait' in options && !ref.maxWaitTimer) {
 ref.maxWaitTimer = setTimeout(() => {
 if (!ref.isExecute) {
 ref.maxWaitTimer = undefined
 return main()
 }
 }, maxWait);
 }
 clearTimeout(ref.t)
// 支持trailing、leading模式选择
 ref.t = execute(main, wait, ref, { isDelay: trailing, isDirectly: leading })
 }
}


throttle


  • _.throttle(func, [wait=0], [options])创建一个节流函数,在 wait 秒内最多执行 func 一次的函数。该函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。可以提供一个 options 对象决定如何调用 func 方法, options.leading 与|或 options.trailing 决定 wait 前后如何触发。func 会传入最后一次传入的参数给这个函数。随后调用的函数返回是最后一次 func 调用的结果。
  • 注意: 如果 leading 和 trailing 都设定为 true。则 func 允许 trailing 方式调用的条件为: 在 wait 期间多次调用。
  • 参数:
  • func (Function)
  • 要节流的函数
  • [wait=0] (number)
  • 需要节流的毫秒
  • [options] (Object)
  • 选项对象
  • [options.leading=true] (boolean)
  • 指定调用在节流开始前
  • [options.trailing=true] (boolean)
  • 指定调用在节流结束后
  • 返回值 (Function)
  • 返回节流的函数
  • 难度系数:★★★★★
  • 建议最长用时:15min

参考代码:

function throttle(func, wait = 0, options = {}) {
 const { leading = true, maxWait, trailing } = options
 const ref = {
 t: undefined,
 isExecute: false,
 maxWaitTimer: undefined,
 last: undefined,
 }
 return function(...args) {
 const main = () => func.apply(null, args)
 if ('maxWait' in options && !ref.maxWaitTimer) {
 ref.maxWaitTimer = setTimeout(() => {
 if (!ref.isExecute) {
 ref.maxWaitTimer = undefined
 return main()
 }
 }, maxWait);
 }
 if (!ref.isExecute) {
 if (leading) {
 ref.isExecute = true
 main()
 }
 if (!ref.last && trailing) {
// 先记录下等下trailing模式要执行的函数
 ref.last = main
 }
 }
 if (ref.t === undefined) {
 ref.t = setTimeout(() => {
// wait时间内只能执行一次
 ref.isExecute = false
 ref.t = undefined
 if (ref.last && trailing) {
// 执行记录下来的函数
 ref.isExecute = true
 ref.last()
 ref.last = undefined
 }
 }, wait);
 }
 }
}


memorize


  • _.memoize(func, [resolver])创建一个会缓存 func 结果的函数。如果提供了 resolver,就用 resolver 的返回值作为 key 缓存函数的结果。默认情况下用第一个参数作为缓存的 key。func 在调用时 this 会绑定在缓存函数上。
  • 注意: 缓存会暴露在缓存函数的 cache 上。它是可以定制的,只要替换了 _.memoize.Cache 构造函数,或实现了 Map 的 delete, get, has, 以及 set方法。
  • 参数
  • func (Function)
  • 需要缓存化的函数
  • [resolver] (Function)
  • 这个函数的返回值作为缓存的 key
  • 返回值 (Function)
  • 返回缓存化后的函数
  • 难度系数:★★
  • 建议最长用时:6min
// example
var object = { 'a': 1, 'b': 2 };
var other = { 'c': 3, 'd': 4 };
var values = _.memoize(_.values);
values(object);
// => [1, 2]
values(other);
// => [3, 4]
object.a = 2;
values(object);
// => [1, 2]
// 修改结果缓存
values.cache.set(object, ['a', 'b']);
values(object);
// => ['a', 'b']
// 替换 `_.memoize.Cache`
_.memoize.Cache = WeakMap;

参考代码:

function memoize(func, resolver) {
 const cache = new Map()
 function f(...args) {
 const key = typeof resolver === 'function' ? resolver.apply(null, args) : args[0]
 if (!cache.get(key)) {
 const ret = func.apply(null, args)
 cache.set(key, ret)
 return ret
 } else {
 return cache.get(key)
 }
 }
 f.cache = cache
 return f
}


其他


其他方法都比较简单,不需要20行代码即可实现。需要注意的点是,执行传入的函数的时候,要call、apply一下null,默认没有this,这是基本操作。为什么呢?如果执行的那个函数内部依赖this,那传入的必须是箭头函数或者bind过this的函数。如果开发者传入的不是箭头函数或者bind过this的函数,框架代码里面执行传入的函数的时候又没有call、apply一下null的话,那框架本身就对业务代码造成了污染了。另外,如果不依赖this,那为何改他的this呢。我们可以看看丢失的this的例子:

// 内部依赖this的函数,不bind的话,this指向改变了导致报错
const { getElementById } = document
getElementById('id')
// Uncaught TypeError: Illegal invocation
// 正确的做法
const getElementById = document.getElementById.bind(document)
getElementById('id')


原文链接:https://mp.weixin.qq.com/s/Dar9X82PGe2zCIeqJxP7NQ作者:不一样的前端


相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码