├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── __tests__ └── lodash.test.js ├── babel.config.js ├── jest.config.js ├── package.json ├── src ├── index.ts └── lodash.js ├── tsconfig.json ├── types └── index.d.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | end_of_line = lf 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # __tests__ 2 | node_modules 3 | dist 4 | priv 5 | packages/cna-template 6 | *.md 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: ['airbnb-base', 'prettier'], 7 | plugins: ['prettier'], 8 | overrides: [ 9 | // { 10 | // files: ['**/*.md'], 11 | // processor: 'markdown/markdown', 12 | // }, 13 | ], 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | sourceType: 'module', 17 | }, 18 | rules: { 19 | semi: ['error', 'always'], 20 | 'comma-dangle': ['error', 'always-multiline'], 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | .DS_Store 107 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules/ 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "semi": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 niexq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔥 coding-interview-questions 2 | 3 | 前端常用工具库及高频面试题 4 | 5 | ### 目录 6 | 7 | | 序号 | 系列 | 测试 | 8 | | ---- | ------------------------------------------------------------------------------------ | ------- | 9 | | 1 | [✅ Lodash 函数列表](#lodash-函数列表) | ✅ 通过 | 10 | | 2 | [💻 Ramda 函数列表](#ramda-函数列表) | 💻 TODO | 11 | | 3 | [💻 JavaScript 高级程序设计第 4 版学习笔记](#javascript-高级程序设计第-4-版学习笔记) | 💻 TODO | 12 | | 4 | [💻 React 面试题](#react-面试题) | 💻 TODO | 13 | | n | [💻 TODO](#TODO) | 💻 TODO | 14 | 15 | ## Lodash 16 | 17 | > Lodash 是一个 JavaScript 工具库 18 | 19 | ### Lodash 函数列表 20 | 21 | **[数组](#数组)** 22 | 23 | 1. [chunk:将一个数组分成多个小数组](#chunk) 24 | 1. [compact:去除数组中的假值(false、null、0、""、undefined、NaN)](#compact) 25 | 1. [concat:合并多个数组](#concat) 26 | 1. [difference:返回一个数组,包含在第一个数组中但不在其他数组中的元素](#difference) 27 | 1. [differenceBy:与 difference 类似,但是可以指定一个函数对比数组中的元素](#differenceby) 28 | 1. [differenceWith: 从第一个数组中过滤出第二个数组中没有的元素,使用一个自定义比较函数进行比较。](#differencewith) 29 | 1. [drop:返回一个新数组,去掉原数组中的前 n 个元素](#drop) 30 | 1. [dropRight:返回一个新数组,去掉原数组中的后 n 个元素](#dropright) 31 | 1. [dropRightWhile:返回一个新数组,去掉原数组中从最后一个符合条件的元素到结尾之间的元素](#droprightwhile) 32 | 1. [fill:用指定的值填充数组](#fill) 33 | 1. [findIndex:返回第一个符合条件的元素的下标](#findindex) 34 | 1. [findLastIndex:返回最后一个符合条件的元素的下标](#findlastindex) 35 | 1. [head:返回数组中的第一个元素](#head) 36 | 1. [flatten:将多维数组转化为一维数组](#flatten) 37 | 1. [flattenDeep:将多维数组转化为一维数组,递归进行](#flattendeep) 38 | 1. [fromPairs:将一个二维数组转化为一个对象](#frompairs) 39 | 1. [indexOf:返回一个元素在数组中的下标,从前往后找](#indexof) 40 | 1. [initial:返回一个新数组,去掉原数组中的最后一个元素](#initial) 41 | 1. [intersection:返回一个数组,包含在所有数组中都存在的元素](#intersection) 42 | 1. [join:将数组转化为字符串,并用指定的分隔符分隔](#join) 43 | 1. [last:返回数组中的最后一个元素](#last) 44 | 1. [lastIndexOf:返回一个元素在数组中的下标,从后往前找](#lastindexof) 45 | 1. [pull:从数组中去掉指定的元素](#pull) 46 | 1. [pullAt:从数组中取出指定下标的元素,并返回一个新数组](#pullat) 47 | 1. [reverse:反转数组](#reverse) 48 | 1. [slice:返回一个新数组,从原数组中截取指定范围的元素](#slice) 49 | 1. [sortedIndex:返回一个元素应该插入到数组中的下标](#sortedindex) 50 | 1. [tail:返回一个新数组,去掉原数组中的第一个元素](#tail) 51 | 1. [take:返回一个新数组,包含原数组中前 n 个元素](#take) 52 | 1. [takeRight:返回一个新数组,包含原数组中后 n 个元素](#takeright) 53 | 1. [union:返回一个新数组,包含所有数组中的不重复元素](#frompairs) 54 | 1. [uniq:返回一个新数组,包含所有数组中的不重复元素](#uniq) 55 | 1. [without:返回一个新数组,去掉原数组中指定的元素](#without) 56 | 1. [xor:返回一个新数组,包含只在其中一个数组中出现过的元素](#xor) 57 | 1. [zip:将多个数组的同一位置的元素合并为一个数组](#zip) 58 | 1. [unzip:将 zip 函数生成的数组还原成原始的数组](#unzip) 59 | 1. [dropWhile:返回一个新数组,去掉原数组中从开始到第一个符合条件的元素之间的元素](#dropwhile) 60 | 1. [intersectionBy:与 intersection 类似,但是可以指定一个函数对比数组中的元素](#intersectionby) 61 | 1. [pullAll:与 pull 类似,但是接收一个数组作为参数](#pullall) 62 | 1. [pullAllBy:与 pullBy 类似,但是接收一个数组作为参数](#pullallby) 63 | 1. [pullAllWith:与 pullWith 类似,但是接收一个数组作为参数](#pullallwith) 64 | 1. [sortedIndexOf:与 indexOf 类似,但是可以在已排序的数组中使用](#sortedindexof) 65 | 1. [sortedLastIndexOf:与 lastIndexOf 类似,但是可以在已排序的数组中使用](#sortedlastindexof) 66 | 1. [sortedUniq:与 uniq 类似,但是可以在已排序的数组中使用](#sorteduniq) 67 | 1. [sortedUniqBy:与 uniqBy 类似,但是可以在已排序的数组中使用](#sorteduniqby) 68 | 1. [takeWhile:返回一个新数组,包含原数组中从开始到第一个不符合条件的元素之间的元素](#takewhile) 69 | 1. [takeRightWhile:返回一个新数组,包含原数组中从最后一个不符合条件的元素到结尾之间的元素](#frompairs) 70 | 1. [unionBy:与 union 类似,但是可以指定一个函数对比数组中的元素](#unionby) 71 | 1. [uniqBy:与 uniq 类似,但是可以指定一个函数对比数组中的元素](#uniqby) 72 | 1. [unzipWith:与 unzip 类似,但是可以指定一个函数来处理 zip 函数生成的数组](#unzipwith) 73 | 1. [xorBy:与 xor 类似,但是可以指定一个函数对比数组中的元素](#xorby) 74 | 1. [zipObject:将两个数组转化为一个对象](#zipobject) 75 | 1. [zipObjectDeep:将两个数组转化为一个嵌套对象](#zipobjectdeep) 76 | 77 | **[集合](#集合)** 78 | 79 | 1. [countBy:统计数组中每个元素出现的次数](#countby) 80 | 1. [each->forEach:遍历数组或对象,并对每个元素执行指定的函数](#each) 81 | 1. [filter:遍历数组或对象,返回符合条件的元素](#filter) 82 | 1. [find:遍历数组或对象,返回第一个符合条件的元素](#find) 83 | 1. [flatMap:遍历数组,将每个元素映射成一个新的数组,再将多个数组合并成一个新数组](#flatmap) 84 | 1. [flatMapDeep:遍历数组,将每个元素映射成一个新的数组,递归进行,再将多个数组合并成一个新数组](#flatmapdeep) 85 | 1. [flatMapDepth:遍历数组,将每个元素映射成一个新的数组,指定递归的深度,再将多个数组合并成一个新数组](#flatmapdepth) 86 | 1. [forEach:遍历数组或对象,并对每个元素执行指定的函数,与 each 函数类似](#foreach) 87 | 1. [includes:判断一个元素是否在数组或对象中](#includes) 88 | 1. [invokeMap:对数组中的每个元素调用指定的方法,并返回结果](#invokemap) 89 | 1. [keyBy:将数组转化为对象,对象的键值是指定属性的值,值是该元素](#keyby) 90 | 1. [map:遍历数组或对象,将每个元素映射成一个新的元素](#map) 91 | 1. [orderBy:按照指定的方式对数组进行排序](#orderby) 92 | 1. [partition:按照指定的条件对数组进行分割](#partition) 93 | 1. [reduce:遍历数组或对象,累加每个元素到累加器中](#reduce) 94 | 1. [reduceRight:与 reduce 类似,但是从数组的末尾开始遍历](#reduceright) 95 | 1. [reject:遍历数组或对象,返回不符合条件的元素](#reject) 96 | 1. [sample:随机返回数组或对象中的一个元素](#sample) 97 | 1. [sampleSize:随机返回数组或对象中的多个元素](#samplesize) 98 | 1. [shuffle:随机打乱数组或对象中的元素](#shuffle) 99 | 1. [size:返回数组或对象的长度或元素个数](#size) 100 | 1. [some:遍历数组或对象,判断是否至少有一个元素符合条件](#some) 101 | 1. [sortBy:按照指定的方式对数组进行排序](#sortby) 102 | 103 | **[函数](#函数)** 104 | 105 | 1. [after:指定一个函数在被调用多少次后执行](#after) 106 | 1. [ary:对指定函数进行封装,指定最多接收多少个参数](#ary) 107 | 1. [before:指定一个函数在被调用多少次前执行](#before) 108 | 1. [bind:绑定函数的 this 值和指定的参数,并返回一个新的函数](#bind) 109 | 1. [bindKey:与 bind 类似,但是绑定的是对象上的指定方法](#bindkey) 110 | 1. [curry:对指定函数进行柯里化](#curry) 111 | 1. [curryRight:与 curry 类似,但是从右到左处理参数](#curryright) 112 | 1. [debounce:对指定函数进行防抖处理](#debounce) 113 | 1. [defer:将指定函数延迟执行](#defer) 114 | 1. [delay:将指定函数延迟一段时间后执行](#delay) 115 | 1. [flip:对指定函数的参数进行反转](#flip) 116 | 1. [memoize:对指定函数进行记忆化处理,缓存函数的计算结果](#memoize) 117 | 1. [negate:对指定函数进行封装,返回原函数的否定值](#negate) 118 | 1. [once:指定一个函数只能被调用一次](#once) 119 | 1. [overArgs:对指定函数进行封装,转换参数的形式](#overargs) 120 | 1. [partial:对指定函数进行部分应用,指定部分参数](#partial) 121 | 1. [partialRight:与 partial 类似,但是从右到左指定部分参数](#partialright) 122 | 1. [rearg:对指定函数进行封装,调整参数的位置](#rearg) 123 | 1. [rest:对指定函数进行封装,将参数集合成一个数组传入原函数](#rest) 124 | 1. [spread:对指定函数进行封装,将参数数组展开作为多个参数传入原函数](#spread) 125 | 1. [throttle:对指定函数进行节流处理](#throttle) 126 | 127 | **[对象](#对象)** 128 | 129 | 1. [assign:合并对象的属性,后面的对象的属性会覆盖前面的对象](#assign) 130 | 1. [defaults:对指定对象进行封装,将默认值合并进去](#defaults) 131 | 1. [defaultsDeep:与 defaults 类似,但是支持嵌套对象](#defaultsdeep) 132 | 1. [findKey:遍历对象,返回第一个符合条件的键名](#findkey) 133 | 1. [findLastKey:与 findKey 类似,但是从对象的末尾开始](#findlastkey) 134 | 1. [forIn:遍历对象,对每个属性调用指定的函数](#forin) 135 | 1. [forInRight:与 forIn 类似,但是从对象的末尾开始遍历](#forinright) 136 | 1. [forOwn:遍历对象自身的可枚举属性,对每个属性调用指定的函数](#forown) 137 | 1. [forOwnRight:与 forOwn 类似,但是从对象的末尾开始遍历](#forownright) 138 | 1. [functions:返回指定对象上的所有函数名](#functions) 139 | 1. [get:获取对象上的属性,支持使用点和方括号的方式指定属性路径](#get) 140 | 1. [has:判断对象上是否有指定属性](#has) 141 | 1. [hasIn:判断对象上是否有指定路径的属性](#hasin) 142 | 1. [invert:对指定对象的属性和值进行反转](#invert) 143 | 1. [invertBy:与 invert 类似,但是支持指定反转后值的集合](#invertby) 144 | 1. [invoke:对指定对象上的方法进行调用](#invoke) 145 | 1. [keys:返回对象上的所有可枚举属性名](#keys) 146 | 1. [keysIn:返回对象上的所有属性名,包括不可枚举属性](#keysin) 147 | 1. [mapKeys:遍历对象上的每个属性,返回一个新对象,其中每个属性的名称由指定的函数计算得出](#mapkeys) 148 | 1. [mapValues:遍历对象上的每个属性,返回一个新对象,其中每个属性的值由指定的函数计算得出](#mapvalues) 149 | 1. [merge:合并对象和源对象的属性,并返回合并后的对象](#merge) 150 | 1. [mergeWith:与 merge 类似,但是指定合并函数,用于处理冲突的属性值](#mergewith) 151 | 1. [omit:返回一个新对象,其中省略了指定属性的属性值](#omit) 152 | 1. [omitBy:与 omit 类似,但是根据指定函数判断是否省略属性](#omitby) 153 | 1. [pick:返回一个新对象,其中只包含指定属性的属性值](#pick) 154 | 1. [pickBy:与 pick 类似,但是根据指定函数判断是否保留属性](#pickby) 155 | 1. [result:获取对象上指定路径的值,并根据情况进行函数调用](#result) 156 | 1. [set:设置对象上指定路径的属性值](#set) 157 | 1. [setWith:与 set 类似,但是指定自定义函数用于设置属性值](#setwith) 158 | 1. [toPairs:将对象转化为键值对数组](#topairs) 159 | 1. [toPairsIn:将对象转化为键值对数组,包括不可枚举属性](#topairsin) 160 | 1. [transform:对指定对象进行封装,指定转换函数,处理对象上的属性](#transform) 161 | 1. [unset:删除对象上指定路径的属性值](#unset) 162 | 1. [countBy:统计数组中每个元素出现的次数](#countby) 163 | 1. [countBy:统计数组中每个元素出现的次数](#countby) 164 | 1. [countBy:统计数组中每个元素出现的次数](#countby) 165 | 1. [countBy:统计数组中每个元素出现的次数](#countby) 166 | 1. [countBy:统计数组中每个元素出现的次数](#countby) 167 | 1. [countBy:统计数组中每个元素出现的次数](#countby) 168 | 1. [countBy:统计数组中每个元素出现的次数](#countby) 169 | 170 | ### 数组 171 | 172 | ### chunk 173 | 174 | 将一个数组分成多个小数组 175 | 176 | ```js 177 | const chunk = (arr, size) => 178 | Array.from({ length: Math.ceil(arr.length / size) }, (v, i) => 179 | arr.slice(i * size, i * size + size) 180 | ); 181 | 182 | chunk(['a', 'b', 'c', 'd'], 2); 183 | // => [['a', 'b'], ['c', 'd']] 184 | 185 | chunk(['a', 'b', 'c', 'd'], 3); 186 | // => [['a', 'b', 'c'], ['d']] 187 | ``` 188 | 189 | 思路:根据指定的大小将数组切割成多个小数组,使用 ES6 的 Array.from()方法,创建一个长度为切割后数组个数的新数组,使用 slice()方法将原数组按照切割后的大小进行分割,然后将每个小数组存储在新数组中返回。 190 | 191 | **[⬆ 返回顶部](#lodash-函数列表)** 192 | 193 | ### compact 194 | 195 | 去除数组中的假值(false、null、0、""、undefined、NaN) 196 | 197 | ```js 198 | const compact = arr => arr.filter(Boolean); 199 | 200 | compact([0, 1, false, 2, '', 3]); 201 | // => [1, 2, 3] 202 | ``` 203 | 204 | 思路:使用 filter()方法,过滤出数组中的真值,Boolean()函数将所有假值转化为 false,所有真值转化为 true。 205 | 206 | **[⬆ 返回顶部](#lodash-函数列表)** 207 | 208 | ### concat 209 | 210 | 合并多个数组 211 | 212 | ```js 213 | const concat = (...args) => [].concat(...args); 214 | 215 | const array = [1]; 216 | const other = concat(array, 2, [3], [[4]]); 217 | 218 | console.log(other); 219 | // => [1, 2, 3, [4]] 220 | 221 | console.log(array); 222 | // => [1] 223 | ``` 224 | 225 | 思路:使用 ES6 的扩展运算符(...)将传入的参数转化为数组,然后使用 concat()方法将所有数组合并成一个新数组并返回。 226 | 227 | **[⬆ 返回顶部](#lodash-函数列表)** 228 | 229 | ### difference 230 | 231 | 返回一个数组,包含在第一个数组中但不在其他数组中的元素 232 | 233 | ```js 234 | // // 第1种实现 235 | // const difference = (arr, ...args) => arr.filter((item) => !args.flat().includes(item)); 236 | // 第2种实现 237 | const difference = (arr, ...args) => 238 | arr.filter(item => args.every(arg => !arg.includes(item))); 239 | 240 | difference([3, 2, 1], [4, 2]); 241 | // => [3, 1] 242 | ``` 243 | 244 | 思路:使用 filter()方法遍历第一个数组,使用 every()方法遍历其他数组,将第一个数组中不包含在其他数组中的元素过滤出来,返回一个新数组。 245 | 246 | **[⬆ 返回顶部](#lodash-函数列表)** 247 | 248 | ### differenceBy 249 | 250 | 与 difference 类似,但是可以指定一个函数对比数组中的元素 251 | 252 | ```js 253 | const differenceBy = (array, values, iteratee) => { 254 | const fn = typeof iteratee === 'function' ? iteratee : item => item[iteratee]; 255 | const valuesSet = new Set(values.map(fn)); 256 | return array.filter(item => !valuesSet.has(fn(item))); 257 | }; 258 | 259 | differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor); 260 | // => [3.1, 1.3] 261 | 262 | // The `property` iteratee shorthand. 263 | differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], 'x'); 264 | // => [{ 'x': 2 }] 265 | ``` 266 | 267 | 思路:先判断第三个参数是否为函数,如果是则使用函数对比数组中的元素进行差异筛选,否则直接使用 lodash 的差异筛选函数来实现。 268 | 269 | **[⬆ 返回顶部](#lodash-函数列表)** 270 | 271 | ### differenceWith 272 | 273 | 从第一个数组中过滤出第二个数组中没有的元素,使用一个自定义比较函数进行比较。 274 | 275 | ```js 276 | const differenceWith = (array, values, comparator) => 277 | array.filter(item => !values.some(value => comparator(item, value))); 278 | 279 | const objects = [ 280 | { x: 1, y: 2 }, 281 | { x: 2, y: 1 }, 282 | ]; 283 | 284 | _.differenceWith(objects, [{ x: 1, y: 2 }], _.isEqual); 285 | // => [{ 'x': 2, 'y': 1 }] 286 | ``` 287 | 288 | 思路:利用高阶函数 filter 和 some 对两个数组进行比较,返回第一个数组中不包含在第二个数组中的元素。 289 | 290 | **[⬆ 返回顶部](#lodash-函数列表)** 291 | 292 | ### drop 293 | 294 | 返回一个新数组,去掉原数组中的前 n 个元素 295 | 296 | ```js 297 | const drop = (arr, n = 1) => arr.slice(n); 298 | 299 | drop([1, 2, 3]); 300 | // => [2, 3] 301 | 302 | drop([1, 2, 3], 2); 303 | // => [3] 304 | 305 | drop([1, 2, 3], 5); 306 | // => [] 307 | 308 | drop([1, 2, 3], 0); 309 | // => [1, 2, 3] 310 | ``` 311 | 312 | 思路:使用 slice()方法将原数组中的前 n 个元素删除并返回。 313 | 314 | **[⬆ 返回顶部](#lodash-函数列表)** 315 | 316 | ### dropRight 317 | 318 | 返回一个新数组,去掉原数组中的后 n 个元素 319 | 320 | ```js 321 | const dropRight = (arr, n = 1) => 322 | n >= arr.length ? [] : arr.slice(0, arr.length - n); 323 | 324 | dropRight([1, 2, 3]); 325 | // => [1, 2] 326 | 327 | dropRight([1, 2, 3], 2); 328 | // => [1] 329 | 330 | dropRight([1, 2, 3], 5); 331 | // => [] 332 | 333 | dropRight([1, 2, 3], 0); 334 | // => [1, 2, 3] 335 | ``` 336 | 337 | 思路:根据 n 的值,通过数组的 slice 方法获取新的数组,从而实现删除末尾元素的操作。 338 | 339 | **[⬆ 返回顶部](#lodash-函数列表)** 340 | 341 | ### dropRightWhile 342 | 343 | 返回一个新数组,去掉原数组中从最后一个符合条件的元素到结尾之间的元素 344 | 345 | ```js 346 | const dropRightWhile = (array, iteratee) => { 347 | let right = array.length - 1; 348 | if (typeof iteratee === 'function') { 349 | while (iteratee(array[right])) { 350 | right--; 351 | } 352 | } 353 | if (typeof iteratee === 'object' && !Array.isArray(iteratee)) { 354 | const entries = Object.entries(iteratee); 355 | while (entries.every(([key, value]) => array[right][key] === value)) { 356 | right--; 357 | } 358 | } 359 | if (Array.isArray(iteratee) && iteratee.length === 2) { 360 | const [key, value] = iteratee; 361 | while (array[right][key] === value) { 362 | right--; 363 | } 364 | } 365 | return array.slice(0, right + 1); 366 | }; 367 | 368 | const users = [ 369 | { user: 'barney', active: true }, 370 | { user: 'fred', active: false }, 371 | { user: 'pebbles', active: false }, 372 | ]; 373 | 374 | dropRightWhile(users, o => !o.active); 375 | // => objects for ['barney'] 376 | 377 | // The `matches` iteratee shorthand. 378 | dropRightWhile(users, { user: 'pebbles', active: false }); 379 | // => objects for ['barney', 'fred'] 380 | 381 | // The `matchesProperty` iteratee shorthand. 382 | dropRightWhile(users, ['active', false]); 383 | // => objects for ['barney'] 384 | 385 | // The `property` iteratee shorthand. 386 | dropRightWhile(users, 'active'); 387 | // => objects for ['barney', 'fred', 'pebbles'] 388 | ``` 389 | 390 | 思路:该函数实现了一个从数组末尾开始遍历,当元素满足传入的迭代器条件时,将该元素从数组中删除并返回删除后的新数组的函数。迭代器可以是函数、对象或数组。 391 | 392 | **[⬆ 返回顶部](#lodash-函数列表)** 393 | 394 | ### fill 395 | 396 | 用指定的值填充数组 397 | 398 | ```js 399 | const fill = (arr, value, start = 0, end = arr.length) => 400 | arr.fill(value, start, end); 401 | 402 | const array = [1, 2, 3]; 403 | 404 | fill(array, 'a'); 405 | console.log(array); 406 | // => ['a', 'a', 'a'] 407 | 408 | fill(Array(3), 2); 409 | // => [2, 2, 2] 410 | 411 | fill([4, 6, 8, 10], '*', 1, 3); 412 | // => [4, '*', '*', 10] 413 | ``` 414 | 415 | 思路:使用 fill()方法将数组的指定位置开始到指定位置结束的元素替换成指定的值,并返回修改后的数组。 416 | 417 | **[⬆ 返回顶部](#lodash-函数列表)** 418 | 419 | ### findIndex 420 | 421 | 返回第一个符合条件的元素的下标 422 | 423 | ```js 424 | const findIndex = (arr, fn) => arr.findIndex(fn); 425 | 426 | const users = [ 427 | { user: 'barney', active: false }, 428 | { user: 'fred', active: false }, 429 | { user: 'pebbles', active: true }, 430 | ]; 431 | 432 | findIndex(users, o => o.user === 'barney'); 433 | // => 0 434 | 435 | // The `matches` iteratee shorthand. 436 | findIndex(users, { user: 'fred', active: false }); 437 | // => 1 438 | 439 | // The `matchesProperty` iteratee shorthand. 440 | findIndex(users, ['active', false]); 441 | // => 0 442 | 443 | // The `property` iteratee shorthand. 444 | findIndex(users, 'active'); 445 | // => 2 446 | ``` 447 | 448 | 思路:使用 findIndex()方法查找符合条件的元素在数组中的下标,如果没有找到则返回-1。 449 | 450 | **[⬆ 返回顶部](#lodash-函数列表)** 451 | 452 | ### findLastIndex 453 | 454 | 返回最后一个符合条件的元素的下标 455 | 456 | ```js 457 | const findLastIndex = (arr, predicate) => { 458 | if (typeof predicate === 'function') { 459 | for (let i = arr.length - 1; i >= 0; i--) { 460 | if (predicate(arr[i], i, arr)) { 461 | return i; 462 | } 463 | } 464 | } else if (Array.isArray(predicate)) { 465 | const [key, value] = predicate; 466 | for (let i = arr.length - 1; i >= 0; i--) { 467 | if (arr[i][key] === value) { 468 | return i; 469 | } 470 | } 471 | } else if (typeof predicate === 'object') { 472 | for (let i = arr.length - 1; i >= 0; i--) { 473 | const keys = Object.keys(predicate); 474 | const match = keys.every(key => predicate[key] === arr[i][key]); 475 | if (match) { 476 | return i; 477 | } 478 | } 479 | } else { 480 | for (let i = arr.length - 1; i >= 0; i--) { 481 | if (arr[i] && arr[i][predicate]) { 482 | return i; 483 | } 484 | } 485 | } 486 | return -1; 487 | }; 488 | 489 | const users = [ 490 | { user: 'barney', active: true }, 491 | { user: 'fred', active: false }, 492 | { user: 'pebbles', active: false }, 493 | ]; 494 | 495 | findLastIndex(users, o => o.user === 'pebbles'); 496 | // => 2 497 | 498 | // The `matches` iteratee shorthand. 499 | findLastIndex(users, { user: 'barney', active: true }); 500 | // => 0 501 | 502 | // The `matchesProperty` iteratee shorthand. 503 | findLastIndex(users, ['active', false]); 504 | // => 2 505 | 506 | // The `property` iteratee shorthand. 507 | findLastIndex(users, 'active'); 508 | // => 0 509 | ``` 510 | 511 | 思路:使用 findLastIndex()方法,返回数组中满足提供的测试函数条件的最后一个元素的索引。若没有找到对应元素,则返回 -1 512 | 513 | **[⬆ 返回顶部](#lodash-函数列表)** 514 | 515 | ### head 516 | 517 | 返回数组中的第一个元素 518 | 519 | ```js 520 | const head = arr => arr[0]; 521 | 522 | head([1, 2, 3]); 523 | // => 1 524 | 525 | head([]); 526 | // => undefined 527 | ``` 528 | 529 | 思路:直接返回数组的第一个元素。 530 | 531 | **[⬆ 返回顶部](#lodash-函数列表)** 532 | 533 | ### flatten 534 | 535 | 将多维数组转化为一维数组 536 | 537 | ```js 538 | const flatten = arr => [].concat(...arr); 539 | 540 | flatten([1, [2, [3, [4]], 5]]); 541 | // => [1, 2, [3, [4]], 5] 542 | ``` 543 | 544 | 思路:使用扩展运算符展开多维数组,再使用 concat 方法将展开后的一维数组拼接起来,得到最终的一维数组。 545 | 546 | **[⬆ 返回顶部](#lodash-函数列表)** 547 | 548 | ### flattenDeep 549 | 550 | 将多维数组转化为一维数组,递归进行 551 | 552 | ```js 553 | const flattenDeep = arr => 554 | [].concat(...arr.map(v => (Array.isArray(v) ? flattenDeep(v) : v))); 555 | 556 | flattenDeep([1, [2, [3, [4]], 5]]); 557 | // => [1, 2, 3, 4, 5] 558 | ``` 559 | 560 | 思路:使用 Array.map 遍历数组,对于数组中的每个元素,如果是数组则递归调用 flattenDeep 函数,否则直接返回该元素,最终使用扩展运算符展开数组,并使用 concat 方法拼接起来。 561 | 562 | **[⬆ 返回顶部](#lodash-函数列表)** 563 | 564 | ### fromPairs 565 | 566 | 将一个二维数组转化为一个对象 567 | 568 | ```js 569 | const fromPairs = arr => 570 | arr.reduce((obj, [key, val]) => ({ ...obj, [key]: val }), {}); 571 | 572 | fromPairs([ 573 | ['a', 1], 574 | ['b', 2], 575 | ]); 576 | // => { 'a': 1, 'b': 2 } 577 | ``` 578 | 579 | 思路:使用 Array.reduce 遍历数组,对于数组中的每个元素,将其解构为 key 和 val,并将其添加到一个新对象中,最终得到一个包含所有 key-value 对的对象。 580 | 581 | **[⬆ 返回顶部](#lodash-函数列表)** 582 | 583 | ### indexOf 584 | 585 | 返回一个元素在数组中的下标,从前往后找 586 | 587 | ```js 588 | const indexOf = (arr, val, fromIndex = 0) => 589 | arr.findIndex((item, index) => index >= fromIndex && item === val); 590 | 591 | indexOf([1, 2, 1, 2], 2); 592 | // => 1 593 | 594 | // Search from the `fromIndex`. 595 | indexOf([1, 2, 1, 2], 2, 2); 596 | // => 3 597 | ``` 598 | 599 | 思路:使用数组的 findIndex 方法查找,并且支持从指定索引位置开始查找。 600 | 601 | **[⬆ 返回顶部](#lodash-函数列表)** 602 | 603 | ### initial 604 | 605 | 返回一个新数组,去掉原数组中的最后一个元素 606 | 607 | ```js 608 | const initial = arr => arr.slice(0, -1); 609 | 610 | initial([1, 2, 3]); 611 | // => [1, 2] 612 | ``` 613 | 614 | 思路:使用数组的 slice 方法截取出除了最后一个元素的部分,得到一个新数组。 615 | 616 | **[⬆ 返回顶部](#lodash-函数列表)** 617 | 618 | ### intersection 619 | 620 | 返回一个数组,包含在所有数组中都存在的元素 621 | 622 | ```js 623 | const intersection = (...arr) => [ 624 | ...new Set(arr.reduce((a, b) => a.filter(v => b.includes(v)))), 625 | ]; 626 | 627 | intersection([2, 1], [4, 2], [1, 2]); 628 | // => [2] 629 | ``` 630 | 631 | 思路:使用数组的 reduce 方法遍历所有数组,使用 filter 方法筛选出在当前数组中也存在的元素,最后使用 Set 去重并转换为数组。 632 | 633 | **[⬆ 返回顶部](#lodash-函数列表)** 634 | 635 | ### join 636 | 637 | 将数组转化为字符串,并用指定的分隔符分隔 638 | 639 | ```js 640 | const join = (arr, separator = ',') => 641 | arr.reduce((res, val, i) => `${res}${i ? separator : ''}${val}`, ''); 642 | 643 | join(['a', 'b', 'c'], '~'); 644 | // => 'a~b~c' 645 | ``` 646 | 647 | 思路:使用 reduce 方法遍历数组,将每个元素和分隔符拼接起来,最终得到一个拼接好的字符串。 648 | 649 | **[⬆ 返回顶部](#lodash-函数列表)** 650 | 651 | ### last 652 | 653 | 返回数组中的最后一个元素 654 | 655 | ```js 656 | const last = arr => arr[arr.length - 1]; 657 | 658 | last([1, 2, 3]); 659 | // => 3 660 | ``` 661 | 662 | 思路:返回数组中的最后一个元素。 663 | 664 | **[⬆ 返回顶部](#lodash-函数列表)** 665 | 666 | ### lastIndexOf 667 | 668 | 返回一个元素在数组中的下标,从后往前找 669 | 670 | ```js 671 | const lastIndexOf = (arr, val) => arr.lastIndexOf(val); 672 | 673 | lastIndexOf([1, 2, 1, 2], 2); 674 | // => 3 675 | 676 | // Search from the `fromIndex`. 677 | lastIndexOf([1, 2, 1, 2], 2, 2); 678 | // => 1 679 | ``` 680 | 681 | 思路:使用数组的 lastIndexOf 方法查找元素在数组中的下标。 682 | 683 | **[⬆ 返回顶部](#lodash-函数列表)** 684 | 685 | ### pull 686 | 687 | 从数组中去掉指定的元素 688 | 689 | ```js 690 | const pull = (arr, ...args) => arr.filter(item => !args.includes(item)); 691 | 692 | const array = [1, 2, 3, 1, 2, 3]; 693 | 694 | pull(array, 2, 3); 695 | console.log(array); 696 | // => [1, 1] 697 | ``` 698 | 699 | 思路:通过 filter 方法筛选掉不需要的元素即可。 700 | 701 | **[⬆ 返回顶部](#lodash-函数列表)** 702 | 703 | ### pullAt 704 | 705 | 从数组中取出指定下标的元素,并返回一个新数组 706 | 707 | ```js 708 | const pullAt = (arr, ...args) => args.map(index => arr.splice(index, 1)[0]); 709 | 710 | const array = [5, 10, 15, 20]; 711 | const evens = pullAt(array, 1, 3); 712 | 713 | console.log(array); 714 | // => [5, 15] 715 | 716 | console.log(evens); 717 | // => [10, 20] 718 | ``` 719 | 720 | 思路:map() 方法遍历传入的下标数组,通过 splice() 方法从原数组中删除相应的元素并返回。 721 | 722 | **[⬆ 返回顶部](#lodash-函数列表)** 723 | 724 | ### reverse 725 | 726 | 反转数组 727 | 728 | ```js 729 | const reverse = arr => [...arr].reverse(); 730 | 731 | const array = [1, 2, 3]; 732 | 733 | reverse(array); 734 | // => [3, 2, 1] 735 | 736 | console.log(array); 737 | // => [3, 2, 1] 738 | ``` 739 | 740 | 思路:利用解构赋值和 reverse() 方法即可。 741 | 742 | **[⬆ 返回顶部](#lodash-函数列表)** 743 | 744 | ### slice 745 | 746 | 返回一个新数组,从原数组中截取指定范围的元素 747 | 748 | ```js 749 | const slice = (arr, start, end) => arr.slice(start, end); 750 | ``` 751 | 752 | 思路:直接调用原生的 slice() 方法。 753 | 754 | **[⬆ 返回顶部](#lodash-函数列表)** 755 | 756 | ### sortedIndex 757 | 758 | 返回一个元素应该插入到数组中的下标 759 | 760 | ```js 761 | const sortedIndex = (arr, value) => { 762 | let left = 0; 763 | let right = arr.length; 764 | while (left < right) { 765 | const mid = Math.floor((left + right) / 2); 766 | if (arr[mid] < value) { 767 | left = mid + 1; 768 | } else { 769 | right = mid; 770 | } 771 | } 772 | return right; 773 | }; 774 | 775 | sortedIndex([30, 50], 40); 776 | // => 1 777 | ``` 778 | 779 | 思路:二分查找算法实现,找到元素应该插入的位置。 780 | 781 | **[⬆ 返回顶部](#lodash-函数列表)** 782 | 783 | ### tail 784 | 785 | 返回一个新数组,去掉原数组中的第一个元素 786 | 787 | ```js 788 | const tail = arr => arr.slice(1); 789 | 790 | tail([1, 2, 3]); 791 | // => [2, 3] 792 | ``` 793 | 794 | 思路:利用 slice() 方法去掉第一个元素即可。 795 | 796 | **[⬆ 返回顶部](#lodash-函数列表)** 797 | 798 | ### take 799 | 800 | 返回一个新数组,包含原数组中前 n 个元素 801 | 802 | ```js 803 | const take = (arr, n = 1) => arr.slice(0, n); 804 | 805 | take([1, 2, 3]); 806 | // => [1] 807 | 808 | take([1, 2, 3], 2); 809 | // => [1, 2] 810 | 811 | take([1, 2, 3], 5); 812 | // => [1, 2, 3] 813 | 814 | take([1, 2, 3], 0); 815 | // => [] 816 | ``` 817 | 818 | 思路:直接调用原生的 slice() 方法,注意默认参数。 819 | 820 | **[⬆ 返回顶部](#lodash-函数列表)** 821 | 822 | ### takeRight 823 | 824 | 返回一个新数组,包含原数组中后 n 个元素 825 | 826 | ```js 827 | const takeRight = (arr, n = 1) => arr.slice(-n); 828 | 829 | takeRight([1, 2, 3]); 830 | // => [3] 831 | 832 | takeRight([1, 2, 3], 2); 833 | // => [2, 3] 834 | 835 | takeRight([1, 2, 3], 5); 836 | // => [1, 2, 3] 837 | 838 | takeRight([1, 2, 3], 0); 839 | // => [] 840 | ``` 841 | 842 | 思路:同样直接调用原生的 slice() 方法,注意负数下标的使用。 843 | 844 | **[⬆ 返回顶部](#lodash-函数列表)** 845 | 846 | ### union 847 | 848 | 返回一个新数组,包含所有数组中的不重复元素 849 | 850 | ```js 851 | const union = (...args) => [...new Set(args.flat())]; 852 | 853 | union([2], [1, 2]); 854 | // => [2, 1] 855 | ``` 856 | 857 | 思路:flat() 方法将多维数组转为一维,Set 数据结构去重,再转回数组即可。 858 | 859 | **[⬆ 返回顶部](#lodash-函数列表)** 860 | 861 | ### uniq 862 | 863 | 返回一个新数组,包含所有数组中的不重复元素 864 | 865 | ```js 866 | const uniq = arr => [...new Set(arr)]; 867 | 868 | uniq([2, 1, 2]); 869 | // => [2, 1] 870 | ``` 871 | 872 | 思路:同样利用 Set() 数据结构去重。 873 | 874 | **[⬆ 返回顶部](#lodash-函数列表)** 875 | 876 | ### without 877 | 878 | 返回一个新数组,去掉原数组中指定的元素 879 | 880 | ```js 881 | const without = (arr, ...args) => arr.filter(item => !args.includes(item)); 882 | 883 | without([2, 1, 2, 3], 1, 2); 884 | // => [3] 885 | ``` 886 | 887 | 思路:同 pull 方法。 888 | 889 | **[⬆ 返回顶部](#lodash-函数列表)** 890 | 891 | ### xor 892 | 893 | 返回一个新数组,包含只在其中一个数组中出现过的元素 894 | 895 | ```js 896 | const xor = (...args) => 897 | args 898 | .flat() 899 | .filter( 900 | item => args.flat().indexOf(item) === args.flat().lastIndexOf(item) 901 | ); 902 | 903 | xor([2, 1], [2, 3]); 904 | // => [1, 3] 905 | ``` 906 | 907 | 思路:flat() 方法转换成一维数组,然后利用 filter() 方法和 indexOf()、lastIndexOf() 方法判断出只在一个数组中出现过的元素。 908 | 909 | **[⬆ 返回顶部](#lodash-函数列表)** 910 | 911 | ### zip 912 | 913 | 将多个数组的同一位置的元素合并为一个数组 914 | 915 | ```js 916 | const zip = (...arrays) => 917 | arrays[0].map((_, i) => arrays.map(array => array[i])); 918 | 919 | zip(['fred', 'barney'], [30, 40], [true, false]); 920 | // => [['fred', 30, true], ['barney', 40, false]] 921 | ``` 922 | 923 | 思路:使用了 Rest 参数,首先取出第一个数组,然后使用 map 遍历第一个数组的长度,通过索引遍历所有数组的该索引元素,将其组合成一个新的数组并返回。 924 | 925 | **[⬆ 返回顶部](#lodash-函数列表)** 926 | 927 | ### unzip 928 | 929 | 将 zip 函数生成的数组还原成原始的数组 930 | 931 | ```js 932 | const unzip = array => 933 | array.reduce( 934 | (acc, val) => (val.forEach((v, i) => acc[i].push(v)), acc), 935 | Array.from({ length: Math.max(...array.map(a => a.length)) }).map(() => []) 936 | ); 937 | 938 | const zipped = zip(['fred', 'barney'], [30, 40], [true, false]); 939 | // => [['fred', 30, true], ['barney', 40, false]] 940 | 941 | unzip(zipped); 942 | // => [['fred', 'barney'], [30, 40], [true, false]] 943 | ``` 944 | 945 | 思路:使用 reduce 遍历 zip 函数生成的数组,将每个元素的每个值取出来,根据索引组成新的数组返回。在 reduce 函数的初始值中,使用了 Math.max 获取所有元素中最大长度,并通过 Array.from 创建对应长度的二维数组。 946 | 947 | **[⬆ 返回顶部](#lodash-函数列表)** 948 | 949 | ### dropWhile 950 | 951 | 返回一个新数组,去掉原数组中从开始到第一个符合条件的元素之间的元素 952 | 953 | ```js 954 | const dropWhile = (array, predicate) => 955 | array.slice(array.findIndex(val => !predicate(val))); 956 | 957 | const users = [ 958 | { user: 'barney', active: false }, 959 | { user: 'fred', active: false }, 960 | { user: 'pebbles', active: true }, 961 | ]; 962 | 963 | dropWhile(users, o => !o.active); 964 | // => objects for ['pebbles'] 965 | 966 | // The `matches` iteratee shorthand. 967 | dropWhile(users, { user: 'barney', active: false }); 968 | // => objects for ['fred', 'pebbles'] 969 | 970 | // The `matchesProperty` iteratee shorthand. 971 | dropWhile(users, ['active', false]); 972 | // => objects for ['pebbles'] 973 | 974 | // The `property` iteratee shorthand. 975 | dropWhile(users, 'active'); 976 | // => objects for ['barney', 'fred', 'pebbles'] 977 | ``` 978 | 979 | 思路:使用 findIndex 函数找到第一个不符合条件的元素,然后使用 slice 函数截取该元素之后的数组并返回。 980 | 981 | **[⬆ 返回顶部](#lodash-函数列表)** 982 | 983 | ### intersectionBy 984 | 985 | 与 intersection 类似,但是可以指定一个函数对比数组中的元素 986 | 987 | ```js 988 | const intersectionBy = (arr1, arr2, compareFn) => 989 | arr1.filter(item => 990 | arr2.some(compareItem => compareFn(item) === compareFn(compareItem)) 991 | ); 992 | 993 | intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor); 994 | // => [2.1] 995 | 996 | // The `property` iteratee shorthand. 997 | intersectionBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], 'x'); 998 | // => [{ 'x': 1 }] 999 | ``` 1000 | 1001 | 思路:使用数组的 filter 方法遍历第一个数组,并使用参数指定的函数对比第二个数组的每一个元素,最终返回两个数组的交集。 1002 | 1003 | **[⬆ 返回顶部](#lodash-函数列表)** 1004 | 1005 | ### pullAll 1006 | 1007 | 与 pull 类似,但是接收一个数组作为参数 1008 | 1009 | ```js 1010 | const pullAll = (arr, values) => arr.filter(item => !values.includes(item)); 1011 | 1012 | const array = [1, 2, 3, 1, 2, 3]; 1013 | 1014 | pullAll(array, [2, 3]); 1015 | console.log(array); 1016 | // => [1, 1] 1017 | ``` 1018 | 1019 | 思路:使用数组的 filter 方法遍历原数组,排除传入的数组中存在的元素。 1020 | 1021 | **[⬆ 返回顶部](#lodash-函数列表)** 1022 | 1023 | ### pullAllBy 1024 | 1025 | 与 pullBy 类似,但是接收一个数组作为参数 1026 | 1027 | ```js 1028 | const pullAllBy = (arr, values, compareFn) => 1029 | arr.filter( 1030 | item => 1031 | !values.some(compareItem => compareFn(item) === compareFn(compareItem)) 1032 | ); 1033 | 1034 | const array = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 1 }]; 1035 | 1036 | pullAllBy(array, [{ x: 1 }, { x: 3 }], 'x'); 1037 | console.log(array); 1038 | // => [{ 'x': 2 }] 1039 | ``` 1040 | 1041 | 思路:使用数组的 filter 方法遍历原数组,排除传入的数组中存在的元素,使用参数指定的函数对比两个数组中的元素。 1042 | 1043 | **[⬆ 返回顶部](#lodash-函数列表)** 1044 | 1045 | ### pullAllWith 1046 | 1047 | 与 pullWith 类似,但是接收一个数组作为参数 1048 | 1049 | ```js 1050 | const pullAllWith = (arr, values, compareFn) => 1051 | arr.filter(item => !values.some(compareItem => compareFn(item, compareItem))); 1052 | 1053 | const array = [ 1054 | { x: 1, y: 2 }, 1055 | { x: 3, y: 4 }, 1056 | { x: 5, y: 6 }, 1057 | ]; 1058 | 1059 | pullAllWith(array, [{ x: 3, y: 4 }], isEqual); 1060 | console.log(array); 1061 | // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }] 1062 | ``` 1063 | 1064 | 思路:使用数组的 filter 方法遍历原数组,排除传入的数组中存在的元素,使用参数指定的函数对比两个数组中的元素。 1065 | 1066 | **[⬆ 返回顶部](#lodash-函数列表)** 1067 | 1068 | ### sortedIndexOf 1069 | 1070 | 与 indexOf 类似,但是可以在已排序的数组中使用 1071 | 1072 | ```js 1073 | const sortedIndexOf = (arr, value) => { 1074 | let left = 0; 1075 | let right = arr.length - 1; 1076 | while (left <= right) { 1077 | const mid = Math.floor((left + right) / 2); 1078 | if (arr[mid] === value) { 1079 | return mid; 1080 | } 1081 | if (arr[mid] < value) { 1082 | left = mid + 1; 1083 | } else { 1084 | right = mid - 1; 1085 | } 1086 | } 1087 | return -1; 1088 | }; 1089 | 1090 | sortedIndexOf([4, 5, 5, 5, 6], 5); 1091 | // => 1 1092 | ``` 1093 | 1094 | 思路:使用二分查找算法在已排序的数组中查找指定元素的位置。 1095 | 1096 | **[⬆ 返回顶部](#lodash-函数列表)** 1097 | 1098 | ### sortedLastIndexOf 1099 | 1100 | 与 lastIndexOf 类似,但是可以在已排序的数组中使用 1101 | 1102 | ```js 1103 | const sortedLastIndexOf = (arr, value) => { 1104 | let left = 0; 1105 | let right = arr.length - 1; 1106 | let lastIndex = -1; 1107 | while (left <= right) { 1108 | const mid = Math.floor((left + right) / 2); 1109 | if (arr[mid] === value) { 1110 | lastIndex = mid; 1111 | left = mid + 1; 1112 | } else if (arr[mid] < value) { 1113 | left = mid + 1; 1114 | } else { 1115 | right = mid - 1; 1116 | } 1117 | } 1118 | return lastIndex; 1119 | }; 1120 | 1121 | sortedLastIndex([4, 5, 5, 5, 6], 5); 1122 | // => 4 1123 | ``` 1124 | 1125 | 思路:使用二分查找算法在已排序的数组中查找指定元素的最后一个位置。 1126 | 1127 | **[⬆ 返回顶部](#lodash-函数列表)** 1128 | 1129 | ### sortedUniq 1130 | 1131 | 与 uniq 类似,但是可以在已排序的数组中使用 1132 | 1133 | ```js 1134 | const sortedUniq = arr => 1135 | arr.reduce((result, item) => { 1136 | if (result.length === 0 || result[result.length - 1] !== item) { 1137 | result.push(item); 1138 | } 1139 | return result; 1140 | }, []); 1141 | 1142 | sortedUniq([1, 1, 2]); 1143 | // => [1, 2] 1144 | ``` 1145 | 1146 | 思路:使用数组的 reduce 方法和 indexOf 方法过滤掉重复元素,并返回新数组。 1147 | 1148 | **[⬆ 返回顶部](#lodash-函数列表)** 1149 | 1150 | ### sortedUniqBy 1151 | 1152 | 与 uniqBy 类似,但是可以在已排序的数组中使用 1153 | 1154 | ```js 1155 | const sortedUniqBy = (array, iteratee) => 1156 | array.reduce( 1157 | (result, value) => 1158 | result.length && iteratee(value) === iteratee(result[result.length - 1]) 1159 | ? result 1160 | : [...result, value], 1161 | [] 1162 | ); 1163 | 1164 | sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor); 1165 | // => [1.1, 2.3] 1166 | ``` 1167 | 1168 | 思路:使用 reduce 方法,对原数组进行遍历,将每个元素通过指定函数转化为一个值,并使用 Set 对象来去重,最终返回去重后的数组。 1169 | 1170 | **[⬆ 返回顶部](#lodash-函数列表)** 1171 | 1172 | ### takeWhile 1173 | 1174 | 返回一个新数组,包含原数组中从开始到第一个不符合条件的元素之间的元素 1175 | 1176 | ```js 1177 | const takeWhile = (array, predicate) => 1178 | array.slice( 1179 | 0, 1180 | array.findIndex(element => !predicate(element)) 1181 | ); 1182 | ``` 1183 | 1184 | 思路:使用 findIndex 方法,查找第一个不符合条件的元素的索引,然后使用 slice 方法截取原数组中符合条件的部分,返回新数组。 1185 | 1186 | **[⬆ 返回顶部](#lodash-函数列表)** 1187 | 1188 | ### takeRightWhile 1189 | 1190 | 返回一个新数组,包含原数组中从最后一个不符合条件的元素到结尾之间的元素 1191 | 1192 | ```js 1193 | const takeRightWhile = (array, predicate) => 1194 | array 1195 | .reverse() 1196 | .slice( 1197 | 0, 1198 | array.findIndex(element => !predicate(element)) 1199 | ) 1200 | .reverse(); 1201 | 1202 | const users = [ 1203 | { user: 'barney', active: true }, 1204 | { user: 'fred', active: false }, 1205 | { user: 'pebbles', active: false }, 1206 | ]; 1207 | 1208 | takeRightWhile(users, o => !o.active); 1209 | // => objects for ['fred', 'pebbles'] 1210 | 1211 | // The `matches` iteratee shorthand. 1212 | takeRightWhile(users, { user: 'pebbles', active: false }); 1213 | // => objects for ['pebbles'] 1214 | 1215 | // The `matchesProperty` iteratee shorthand. 1216 | takeRightWhile(users, ['active', false]); 1217 | // => objects for ['fred', 'pebbles'] 1218 | 1219 | // The `property` iteratee shorthand. 1220 | takeRightWhile(users, 'active'); 1221 | // => [] 1222 | ``` 1223 | 1224 | 思路:使用 reverse 方法反转原数组,然后使用 findIndex 方法查找反转后数组中第一个不符合条件的元素的索引,最终使用 slice 方法截取原数组中从该索引到结尾的部分,返回新数组。 1225 | 1226 | **[⬆ 返回顶部](#lodash-函数列表)** 1227 | 1228 | ### unionBy 1229 | 1230 | 与 union 类似,但是可以指定一个函数对比数组中的元素 1231 | 1232 | ```js 1233 | const unionBy = (...arrays) => { 1234 | const iteratee = arrays.pop(); 1235 | const unionSet = new Set( 1236 | arrays.reduce((result, array) => [...result, ...array], []).map(iteratee) 1237 | ); 1238 | return Array.from(unionSet); 1239 | }; 1240 | 1241 | unionBy([2.1], [1.2, 2.3], Math.floor); 1242 | // => [2.1, 1.2] 1243 | 1244 | // The `property` iteratee shorthand. 1245 | unionBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], 'x'); 1246 | // => [{ 'x': 1 }, { 'x': 2 }] 1247 | ``` 1248 | 1249 | 思路:使用 Set 对象进行数组去重,通过指定函数将多个数组中的元素转化为同一值,最终将去重后的值转化为数组返回。 1250 | 1251 | **[⬆ 返回顶部](#lodash-函数列表)** 1252 | 1253 | ### uniqBy 1254 | 1255 | 与 uniq 类似,但是可以指定一个函数对比数组中的元素 1256 | 1257 | ```js 1258 | const uniqBy = (array, iteratee) => { 1259 | const uniqSet = new Set(array.map(iteratee)); 1260 | return Array.from(uniqSet).map(value => 1261 | array.find(element => iteratee(element) === value) 1262 | ); 1263 | }; 1264 | 1265 | uniqBy([2.1, 1.2, 2.3], Math.floor); 1266 | // => [2.1, 1.2] 1267 | 1268 | // The `property` iteratee shorthand. 1269 | uniqBy([{ x: 1 }, { x: 2 }, { x: 1 }], 'x'); 1270 | // => [{ 'x': 1 }, { 'x': 2 }] 1271 | ``` 1272 | 1273 | 思路:同 unionBy 方法实现思路类似。 1274 | 1275 | **[⬆ 返回顶部](#lodash-函数列表)** 1276 | 1277 | ### unzipWith 1278 | 1279 | 与 unzip 类似,但是可以指定一个函数来处理 zip 函数生成的数组 1280 | 1281 | ```js 1282 | const unzipWith = (array, iteratee) => 1283 | array.reduce( 1284 | (result, value) => ( 1285 | value.forEach((innerValue, index) => 1286 | result[index].push(iteratee ? iteratee(innerValue) : innerValue) 1287 | ), 1288 | result 1289 | ), 1290 | Array.from({ length: Math.max(...array.map(value => value.length)) }).map( 1291 | () => [] 1292 | ) 1293 | ); 1294 | 1295 | const zipped = zip([1, 2], [10, 20], [100, 200]); 1296 | // => [[1, 10, 100], [2, 20, 200]] 1297 | 1298 | unzipWith(zipped, add); 1299 | // => [3, 30, 300] 1300 | ``` 1301 | 1302 | 思路:使用 reduce 方法,对传入的数组进行遍历,将每个子数组进行传入的函数处理后,将处理后的值存储到对应索引位置上,最终返回处理后的数组。 1303 | 1304 | **[⬆ 返回顶部](#lodash-函数列表)** 1305 | 1306 | ### xorBy 1307 | 1308 | 与 xor 类似,但是可以指定一个函数对比数组中的元素 1309 | 1310 | ```js 1311 | const xorBy = (...arrays) => { 1312 | const iteratee = arrays.pop(); 1313 | const valueCount = new Map(); 1314 | arrays 1315 | .reduce((result, array) => [...result, ...array], []) 1316 | .forEach((value) => { 1317 | const newValue = iteratee(value); 1318 | 1319 | xorBy([2.1, 1.2], [2.3, 3.4], Math.floor); 1320 | // => [1.2, 3.4] 1321 | 1322 | // The `property` iteratee shorthand. 1323 | xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); 1324 | // => [{ 'x': 2 }] 1325 | ``` 1326 | 1327 | 思路:使用 reduce 方法,对传入的多个数组进行遍历,将每个数组中的元素转化为指定值,并使用 Map 对象统计每个值出现的次数,最后返回出现次数为 1 的元素组成的数组。 1328 | 1329 | **[⬆ 返回顶部](#lodash-函数列表)** 1330 | 1331 | ### zipObject 1332 | 1333 | 将两个数组转化为一个对象 1334 | 1335 | ```js 1336 | const zipObject = (keys, values) => 1337 | keys.reduce((obj, key, i) => ({ ...obj, [key]: values[i] }), {}); 1338 | 1339 | zipObject(['a', 'b'], [1, 2]); 1340 | // => { 'a': 1, 'b': 2 } 1341 | ``` 1342 | 1343 | 思路:使用 reduce 函数遍历 keys 数组,每次将 keys 数组中的一个元素作为属性名,values 数组中相同位置的元素作为属性值,然后将它们存入一个新对象中,最后返回该对象。 1344 | 1345 | **[⬆ 返回顶部](#lodash-函数列表)** 1346 | 1347 | ### zipObjectDeep 1348 | 1349 | 将两个数组转化为一个嵌套对象 1350 | 1351 | ```js 1352 | const zipObjectDeep = (keys, values) => 1353 | keys.reduce((obj, key, i) => { 1354 | const path = key.split('.'); 1355 | const lastKey = path.pop(); 1356 | const nestedObj = path.reduceRight( 1357 | (nested, prop) => ({ [prop]: nested }), 1358 | values[i] 1359 | ); 1360 | return mergeWith(obj, { [lastKey]: nestedObj }, customizer); 1361 | }, {}); 1362 | 1363 | zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]); 1364 | // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } } 1365 | ``` 1366 | 1367 | 思路:使用 reduce 函数遍历 keys 数组,每次将 keys 数组中的一个元素拆分成一个路径数组,使用 reduceRight 函数从路径数组的最后一个元素开始遍历,每次将该元素作为属性名,前一个元素对应的对象作为属性值,然后将它们存入一个新对象中,最后返回该对象。 1368 | 1369 | **[⬆ 返回顶部](#lodash-函数列表)** 1370 | 1371 | ### 集合 1372 | 1373 | ### countBy 1374 | 1375 | 统计数组中每个元素出现的次数 1376 | 1377 | ```js 1378 | const countBy = (arr, fn) => 1379 | arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => { 1380 | acc[val] = (acc[val] || 0) + 1; 1381 | return acc; 1382 | }, {}); 1383 | 1384 | countBy([6.1, 4.2, 6.3], Math.floor); 1385 | // => { '4': 1, '6': 2 } 1386 | 1387 | // The `property` iteratee shorthand. 1388 | countBy(['one', 'two', 'three'], 'length'); 1389 | // => { '3': 2, '5': 1 } 1390 | ``` 1391 | 1392 | 思路:使用 map 将数组每个元素进行映射,fn 是函数时直接执行,否则取对应属性,再使用 reduce 将元素出现的次数进行累加 1393 | 1394 | ### each 1395 | 1396 | 遍历数组或对象,并对每个元素执行指定的函数 1397 | 1398 | ```js 1399 | // each -> forEach 1400 | const each = (collection, fn) => { 1401 | if (Array.isArray(collection)) { 1402 | for (let i = 0; i < collection.length; i++) { 1403 | fn(collection[i], i, collection); 1404 | } 1405 | } else { 1406 | for (const key in collection) { 1407 | if (collection.hasOwnProperty(key)) { 1408 | fn(collection[key], key, collection); 1409 | } 1410 | } 1411 | } 1412 | }; 1413 | 1414 | forEach([1, 2], value => { 1415 | console.log(value); 1416 | }); 1417 | // => Logs `1` then `2`. 1418 | 1419 | forEach({ a: 1, b: 2 }, (value, key) => { 1420 | console.log(key); 1421 | }); 1422 | // => Logs 'a' then 'b' (iteration order is not guaranteed). 1423 | ``` 1424 | 1425 | 思路:如果是数组,则使用 for 循环遍历,对每个元素执行 fn 函数;如果是对象,则使用 for-in 循环遍历,对每个属性执行 fn 函数 1426 | 1427 | **[⬆ 返回顶部](#lodash-函数列表)** 1428 | 1429 | ### filter 1430 | 1431 | 遍历数组或对象,返回符合条件的元素 1432 | 1433 | ```js 1434 | const filter = (collection, predicate) => 1435 | collection.filter( 1436 | typeof predicate === 'function' ? predicate : val => val[predicate] 1437 | ); 1438 | 1439 | const users = [ 1440 | { user: 'barney', age: 36, active: true }, 1441 | { user: 'fred', age: 40, active: false }, 1442 | ]; 1443 | 1444 | filter(users, o => !o.active); 1445 | // => objects for ['fred'] 1446 | 1447 | // The `matches` iteratee shorthand. 1448 | filter(users, { age: 36, active: true }); 1449 | // => objects for ['barney'] 1450 | 1451 | // The `matchesProperty` iteratee shorthand. 1452 | filter(users, ['active', false]); 1453 | // => objects for ['fred'] 1454 | 1455 | // The `property` iteratee shorthand. 1456 | filter(users, 'active'); 1457 | // => objects for ['barney'] 1458 | ``` 1459 | 1460 | 思路:使用 filter 方法过滤符合条件的元素,predicate 是函数时直接执行,否则判断元素是否具有对应的属性值 1461 | 1462 | **[⬆ 返回顶部](#lodash-函数列表)** 1463 | 1464 | ### find 1465 | 1466 | 遍历数组或对象,返回第一个符合条件的元素 1467 | 1468 | ```js 1469 | const find = (collection, predicate) => 1470 | collection.filter( 1471 | typeof predicate === 'function' ? predicate : val => val[predicate] 1472 | )[0]; 1473 | 1474 | const users = [ 1475 | { user: 'barney', age: 36, active: true }, 1476 | { user: 'fred', age: 40, active: false }, 1477 | { user: 'pebbles', age: 1, active: true }, 1478 | ]; 1479 | 1480 | find(users, o => o.age < 40); 1481 | // => object for 'barney' 1482 | 1483 | // The `matches` iteratee shorthand. 1484 | find(users, { age: 1, active: true }); 1485 | // => object for 'pebbles' 1486 | 1487 | // The `matchesProperty` iteratee shorthand. 1488 | find(users, ['active', false]); 1489 | // => object for 'fred' 1490 | 1491 | // The `property` iteratee shorthand. 1492 | find(users, 'active'); 1493 | // => object for 'barney' 1494 | ``` 1495 | 1496 | 思路:使用 filter 方法过滤符合条件的元素,并返回第一个符合条件的元素,predicate 是函数时直接执行,否则判断元素是否具有对应的属性值 1497 | 1498 | **[⬆ 返回顶部](#lodash-函数列表)** 1499 | 1500 | ### flatMap 1501 | 1502 | 遍历数组,将每个元素映射成一个新的数组,再将多个数组合并成一个新数组 1503 | 1504 | ```js 1505 | const flatMap = (arr, fn) => arr.reduce((acc, val) => acc.concat(fn(val)), []); 1506 | 1507 | function duplicate(n) { 1508 | return [n, n]; 1509 | } 1510 | 1511 | flatMap([1, 2], duplicate); 1512 | // => [1, 1, 2, 2] 1513 | ``` 1514 | 1515 | 思路: 1516 | 1517 | **[⬆ 返回顶部](#lodash-函数列表)** 1518 | 1519 | ### flatMapDeep 1520 | 1521 | 遍历数组,将每个元素映射成一个新的数组,递归进行,再将多个数组合并成一个新数组 1522 | 1523 | ```js 1524 | const flatMapDeep = (arr, fn) => 1525 | arr.reduce( 1526 | (acc, val) => 1527 | acc.concat(Array.isArray(val) ? flatMapDeep(val, fn) : fn(val)), 1528 | [] 1529 | ); 1530 | 1531 | function duplicate(n) { 1532 | return [[[n, n]]]; 1533 | } 1534 | 1535 | flatMapDeep([1, 2], duplicate); 1536 | // => [1, 1, 2, 2] 1537 | ``` 1538 | 1539 | 思路:使用 reduce 遍历数组,将每个元素映射成一个新的数组,如果是数组则递归进行,最后使用 concat 将多个数组合并成一个新数组 1540 | 1541 | **[⬆ 返回顶部](#lodash-函数列表)** 1542 | 1543 | ### flatMapDepth 1544 | 1545 | 遍历数组,将每个元素映射成一个新的数组,指定递归的深度,再将多个数组合并成一个新数组 1546 | 1547 | ```js 1548 | const flatMapDepth = (array, iteratee, depth = 1) => 1549 | depth > 0 1550 | ? array.reduce((acc, cur) => { 1551 | const mapped = iteratee(cur); 1552 | return acc.concat( 1553 | Array.isArray(mapped) 1554 | ? flatMapDepth(mapped, iteratee, depth - 1) 1555 | : mapped 1556 | ); 1557 | }, []) 1558 | : array.slice(); 1559 | 1560 | function duplicate(n) { 1561 | return [[[n, n]]]; 1562 | } 1563 | 1564 | flatMapDepth([1, 2], duplicate, 2); 1565 | // => [[1, 1], [2, 2]] 1566 | ``` 1567 | 1568 | 思路:使用递归的方式实现深度优先遍历数组,然后将每个元素映射成一个新的数组,最后将多个数组合并成一个新数组。 1569 | 1570 | **[⬆ 返回顶部](#lodash-函数列表)** 1571 | 1572 | ### forEach 1573 | 1574 | 遍历数组或对象,并对每个元素执行指定的函数,与 each 函数类似 1575 | 1576 | ```js 1577 | const forEach = (collection, iteratee) => { 1578 | for (const value of collection) { 1579 | iteratee(value); 1580 | } 1581 | }; 1582 | 1583 | _([1, 2]).forEach(value => { 1584 | console.log(value); 1585 | }); 1586 | // => Logs `1` then `2`. 1587 | 1588 | forEach({ a: 1, b: 2 }, (value, key) => { 1589 | console.log(key); 1590 | }); 1591 | // => Logs 'a' then 'b' (iteration order is not guaranteed). 1592 | ``` 1593 | 1594 | 思路:使用 for...of 循环遍历数组或对象,并对每个元素执行指定的函数。 1595 | 1596 | **[⬆ 返回顶部](#lodash-函数列表)** 1597 | 1598 | ### groupBy:按照指定的方式对数组进行分组 1599 | 1600 | ```js 1601 | const groupBy = (array, iteratee) => 1602 | array.reduce((acc, cur) => { 1603 | const key = typeof iteratee === 'function' ? iteratee(cur) : cur[iteratee]; 1604 | if (!acc[key]) { 1605 | acc[key] = []; 1606 | } 1607 | acc[key].push(cur); 1608 | return acc; 1609 | }, {}); 1610 | 1611 | groupBy([6.1, 4.2, 6.3], Math.floor); 1612 | // => { '4': [4.2], '6': [6.1, 6.3] } 1613 | 1614 | // The `property` iteratee shorthand. 1615 | groupBy(['one', 'two', 'three'], 'length'); 1616 | // => { '3': ['one', 'two'], '5': ['three'] } 1617 | ``` 1618 | 1619 | 思路:使用 reduce 方法遍历数组,按照指定的方式对数组进行分组。 1620 | 1621 | **[⬆ 返回顶部](#lodash-函数列表)** 1622 | 1623 | ### includes 1624 | 1625 | 判断一个元素是否在数组或对象中 1626 | 1627 | ```js 1628 | const includes = (collection, value) => collection.includes(value); 1629 | 1630 | includes([1, 2, 3], 1); 1631 | // => true 1632 | 1633 | includes([1, 2, 3], 1, 2); 1634 | // => false 1635 | 1636 | includes({ user: 'fred', age: 40 }, 'fred'); 1637 | // => true 1638 | 1639 | includes('pebbles', 'eb'); 1640 | // => true 1641 | ``` 1642 | 1643 | 思路:使用 Array.includes 方法判断一个元素是否在数组或对象中。 1644 | 1645 | **[⬆ 返回顶部](#lodash-函数列表)** 1646 | 1647 | ### invokeMap 1648 | 1649 | 对数组中的每个元素调用指定的方法,并返回结果 1650 | 1651 | ```js 1652 | const invokeMap = (array, path, ...args) => 1653 | array.map(value => { 1654 | const method = typeof path === 'function' ? path : value[path]; 1655 | return method.apply(value, args); 1656 | }); 1657 | 1658 | invokeMap( 1659 | [ 1660 | [5, 1, 7], 1661 | [3, 2, 1], 1662 | ], 1663 | 'sort' 1664 | ); 1665 | // => [[1, 5, 7], [1, 2, 3]] 1666 | 1667 | invokeMap([123, 456], String.prototype.split, ''); 1668 | // => [['1', '2', '3'], ['4', '5', '6']] 1669 | ``` 1670 | 1671 | 思路:使用 Array.map 方法对数组中的每个元素调用指定的方法,并返回结果。 1672 | 1673 | **[⬆ 返回顶部](#lodash-函数列表)** 1674 | 1675 | ### keyBy 1676 | 1677 | 将数组转化为对象,对象的键值是指定属性的值,值是该元素 1678 | 1679 | ```js 1680 | const keyBy = (arr, key) => 1681 | arr.reduce((acc, val) => ((acc[val[key]] = val), acc), {}); 1682 | 1683 | const array = [ 1684 | { dir: 'left', code: 97 }, 1685 | { dir: 'right', code: 100 }, 1686 | ]; 1687 | 1688 | keyBy(array, o => String.fromCharCode(o.code)); 1689 | // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } 1690 | 1691 | keyBy(array, 'dir'); 1692 | // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } 1693 | ``` 1694 | 1695 | 思路:使用 reduce 方法遍历数组,将数组转化为对象,对象的键值是指定属性的值,值是该元素。 1696 | 1697 | **[⬆ 返回顶部](#lodash-函数列表)** 1698 | 1699 | ### map:遍历数组或对象,将每个元素映射成一个新的元素 1700 | 1701 | ```js 1702 | const map = (arr, func) => arr.reduce((acc, val) => [...acc, func(val)], []); 1703 | 1704 | function square(n) { 1705 | return n * n; 1706 | } 1707 | 1708 | map([4, 8], square); 1709 | // => [16, 64] 1710 | 1711 | map({ a: 4, b: 8 }, square); 1712 | // => [16, 64] (iteration order is not guaranteed) 1713 | 1714 | const users = [{ user: 'barney' }, { user: 'fred' }]; 1715 | 1716 | // The `property` iteratee shorthand. 1717 | map(users, 'user'); 1718 | // => ['barney', 'fred'] 1719 | ``` 1720 | 1721 | 思路:使用 reduce 方法遍历数组,将每个元素映射成一个新的元素。 1722 | 1723 | **[⬆ 返回顶部](#lodash-函数列表)** 1724 | 1725 | ### orderBy 1726 | 1727 | 按照指定的方式对数组进行排序 1728 | 1729 | ```js 1730 | const orderBy = (arr, props, orders) => 1731 | [...arr].sort((a, b) => 1732 | props.reduce((acc, prop, i) => { 1733 | if (acc === 0) { 1734 | const [p1, p2] = 1735 | orders && orders[i] === 'desc' 1736 | ? [b[prop], a[prop]] 1737 | : [a[prop], b[prop]]; 1738 | acc = p1 > p2 ? 1 : p1 < p2 ? -1 : 0; 1739 | } 1740 | return acc; 1741 | }, 0) 1742 | ); 1743 | 1744 | const users = [ 1745 | { user: 'fred', age: 48 }, 1746 | { user: 'barney', age: 34 }, 1747 | { user: 'fred', age: 40 }, 1748 | { user: 'barney', age: 36 }, 1749 | ]; 1750 | 1751 | // 以 `user` 升序排序 再 `age` 以降序排序。 1752 | orderBy(users, ['user', 'age'], ['asc', 'desc']); 1753 | // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] 1754 | ``` 1755 | 1756 | 思路:使用 sort 方法对数组进行排序,props 参数表示排序的属性,orders 参数表示排序的顺序。 1757 | 1758 | **[⬆ 返回顶部](#lodash-函数列表)** 1759 | 1760 | ### partition 1761 | 1762 | 按照指定的条件对数组进行分割 1763 | 1764 | ```js 1765 | const partition = (arr, func) => 1766 | arr.reduce((acc, val) => (acc[func(val) ? 0 : 1].push(val), acc), [[], []]); 1767 | 1768 | const users = [ 1769 | { user: 'barney', age: 36, active: false }, 1770 | { user: 'fred', age: 40, active: true }, 1771 | { user: 'pebbles', age: 1, active: false }, 1772 | ]; 1773 | 1774 | partition(users, o => o.active); 1775 | // => objects for [['fred'], ['barney', 'pebbles']] 1776 | 1777 | // The `matches` iteratee shorthand. 1778 | partition(users, { age: 1, active: false }); 1779 | // => objects for [['pebbles'], ['barney', 'fred']] 1780 | 1781 | // The `matchesProperty` iteratee shorthand. 1782 | partition(users, ['active', false]); 1783 | // => objects for [['barney', 'pebbles'], ['fred']] 1784 | 1785 | // The `property` iteratee shorthand. 1786 | partition(users, 'active'); 1787 | // => objects for [['fred'], ['barney', 'pebbles']] 1788 | ``` 1789 | 1790 | 思路:使用 reduce 方法遍历数组,按照指定的条件对数组进行分割。 1791 | 1792 | **[⬆ 返回顶部](#lodash-函数列表)** 1793 | 1794 | ### reduce 1795 | 1796 | 遍历数组或对象,累加每个元素到累加器中 1797 | 1798 | ```js 1799 | const reduce = (arr, func, initVal) => { 1800 | let acc = initVal; 1801 | for (let i = 0; i < arr.length; i++) { 1802 | acc = func(acc, arr[i], i, arr); 1803 | } 1804 | return acc; 1805 | }; 1806 | 1807 | reduce([1, 2], (sum, n) => sum + n, 0); 1808 | // => 3 1809 | 1810 | reduce( 1811 | { a: 1, b: 2, c: 1 }, 1812 | (result, value, key) => { 1813 | (result[value] || (result[value] = [])).push(key); 1814 | return result; 1815 | }, 1816 | {} 1817 | ); 1818 | // => { '1': ['a', 'c'], '2': ['b'] } (无法保证遍历的顺序) 1819 | ``` 1820 | 1821 | 思路:使用 for 循环遍历数组,累加每个元素到累加器中。 1822 | 1823 | **[⬆ 返回顶部](#lodash-函数列表)** 1824 | 1825 | ### reduceRight 1826 | 1827 | 与 reduce 类似,但是从数组的末尾开始遍历 1828 | 1829 | ```js 1830 | const reduceRight = (arr, func, initVal) => { 1831 | let acc = initVal; 1832 | for (let i = arr.length - 1; i >= 0; i--) { 1833 | acc = func(acc, arr[i], i, arr); 1834 | } 1835 | return acc; 1836 | }; 1837 | 1838 | const array = [ 1839 | [0, 1], 1840 | [2, 3], 1841 | [4, 5], 1842 | ]; 1843 | 1844 | reduceRight(array, (flattened, other) => flattened.concat(other), []); 1845 | // => [4, 5, 2, 3, 0, 1] 1846 | ``` 1847 | 1848 | 思路:与 reduce 类似,但是从数组的末尾开始遍历。 1849 | 1850 | **[⬆ 返回顶部](#lodash-函数列表)** 1851 | 1852 | ### reject 1853 | 1854 | 遍历数组或对象,返回不符合条件的元素 1855 | 1856 | ```js 1857 | const reject = (arr, fn) => arr.filter(x => !fn(x)); 1858 | 1859 | const users = [ 1860 | { user: 'barney', age: 36, active: false }, 1861 | { user: 'fred', age: 40, active: true }, 1862 | ]; 1863 | 1864 | reject(users, o => !o.active); 1865 | // => objects for ['fred'] 1866 | 1867 | // `matches` 迭代简写 1868 | reject(users, { age: 40, active: true }); 1869 | // => objects for ['barney'] 1870 | 1871 | // `matchesProperty` 迭代简写 1872 | reject(users, ['active', false]); 1873 | // => objects for ['fred'] 1874 | 1875 | // `property` 迭代简写 1876 | reject(users, 'active'); 1877 | // => objects for ['barney'] 1878 | ``` 1879 | 1880 | 思路:使用 filter 方法遍历数组,返回不符合条件的元素即可。 1881 | 1882 | **[⬆ 返回顶部](#lodash-函数列表)** 1883 | 1884 | ### sample 1885 | 1886 | 随机返回数组或对象中的一个元素 1887 | 1888 | ```js 1889 | const sample = arr => arr[Math.floor(Math.random() * arr.length)]; 1890 | 1891 | sample([1, 2, 3, 4]); 1892 | // => 2 1893 | ``` 1894 | 1895 | 思路:使用 Math.random 方法生成随机数,再根据数组的长度随机获取一个元素。 1896 | 1897 | **[⬆ 返回顶部](#lodash-函数列表)** 1898 | 1899 | ### sampleSize 1900 | 1901 | 随机返回数组或对象中的多个元素 1902 | 1903 | ```js 1904 | const sampleSize = (arr, n = 1) => 1905 | arr.sort(() => Math.random() - 0.5).slice(0, n); 1906 | 1907 | sampleSize([1, 2, 3], 2); 1908 | // => [3, 1] 1909 | 1910 | sampleSize([1, 2, 3], 4); 1911 | // => [2, 3, 1] 1912 | ``` 1913 | 1914 | 思路:使用 sort 方法和 Math.random 方法打乱数组顺序,再使用 slice 方法截取指定数量的元素。 1915 | 1916 | **[⬆ 返回顶部](#lodash-函数列表)** 1917 | 1918 | ### shuffle 1919 | 1920 | 随机打乱数组或对象中的元素 1921 | 1922 | ```js 1923 | const shuffle = arr => arr.sort(() => Math.random() - 0.5); 1924 | 1925 | shuffle([1, 2, 3, 4]); 1926 | // => [4, 1, 3, 2] 1927 | ``` 1928 | 1929 | 思路:使用 sort 方法和 Math.random 方法打乱数组顺序即可。 1930 | 1931 | **[⬆ 返回顶部](#lodash-函数列表)** 1932 | 1933 | ### size 1934 | 1935 | 返回数组或对象的长度或元素个数 1936 | 1937 | ```js 1938 | const size = obj => Object.keys(obj).length; 1939 | 1940 | size([1, 2, 3]); 1941 | // => 3 1942 | 1943 | size({ a: 1, b: 2 }); 1944 | // => 2 1945 | 1946 | size('pebbles'); 1947 | // => 7 1948 | ``` 1949 | 1950 | 思路:使用 Object.keys 方法获取对象属性的数组,再使用 length 属性获取长度即可。 1951 | 1952 | **[⬆ 返回顶部](#lodash-函数列表)** 1953 | 1954 | ### some 1955 | 1956 | 遍历数组或对象,判断是否至少有一个元素符合条件 1957 | 1958 | ```js 1959 | const some = (arr, fn) => arr.some(fn); 1960 | 1961 | some([null, 0, 'yes', false], Boolean); 1962 | // => true 1963 | 1964 | const users = [ 1965 | { user: 'barney', active: true }, 1966 | { user: 'fred', active: false }, 1967 | ]; 1968 | 1969 | // The `matches` iteratee shorthand. 1970 | some(users, { user: 'barney', active: false }); 1971 | // => false 1972 | 1973 | // The `matchesProperty` iteratee shorthand. 1974 | some(users, ['active', false]); 1975 | // => true 1976 | 1977 | // The `property` iteratee shorthand. 1978 | some(users, 'active'); 1979 | // => true 1980 | ``` 1981 | 1982 | 思路:使用 some 方法判断是否至少有一个元素符合条件。 1983 | 1984 | **[⬆ 返回顶部](#lodash-函数列表)** 1985 | 1986 | ### sortBy 1987 | 1988 | 按照指定的方式对数组进行排序 1989 | 1990 | ```js 1991 | const sortBy = (arr, fn) => arr.sort((a, b) => fn(a) - fn(b)); 1992 | 1993 | const users = [ 1994 | { user: 'fred', age: 48 }, 1995 | { user: 'barney', age: 36 }, 1996 | { user: 'fred', age: 40 }, 1997 | { user: 'barney', age: 34 }, 1998 | ]; 1999 | 2000 | sortBy(users, o => o.user); 2001 | // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] 2002 | 2003 | sortBy(users, ['user', 'age']); 2004 | // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] 2005 | 2006 | sortBy(users, 'user', o => Math.floor(o.age / 10)); 2007 | // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] 2008 | ``` 2009 | 2010 | 思路:使用 sort 方法和传入的函数进行排序即可。 2011 | 2012 | **[⬆ 返回顶部](#lodash-函数列表)** 2013 | 2014 | ### 函数 2015 | 2016 | ### after 2017 | 2018 | 指定一个函数在被调用多少次后执行 2019 | 2020 | ```js 2021 | const after = (n, func) => { 2022 | let count = 0; 2023 | return (...args) => { 2024 | count++; 2025 | if (count >= n) { 2026 | return func(...args); 2027 | } 2028 | }; 2029 | }; 2030 | 2031 | const saves = ['profile', 'settings']; 2032 | 2033 | const done = after(saves.length, () => { 2034 | console.log('done saving!'); 2035 | }); 2036 | 2037 | forEach(saves, type => { 2038 | asyncSave({ type, complete: done }); 2039 | }); 2040 | // => Logs 'done saving!' after the two async saves have completed. 2041 | ``` 2042 | 2043 | 思路:返回一个函数,该函数在被调用 n 次后执行指定的函数。利用闭包记录当前已调用次数,判断是否达到执行条件。 2044 | 2045 | **[⬆ 返回顶部](#lodash-函数列表)** 2046 | 2047 | ### ary:对指定函数进行封装,指定最多接收多少个参数 2048 | 2049 | ```js 2050 | const ary = 2051 | (func, n = func.length) => 2052 | (...args) => 2053 | func(...args.slice(0, n)); 2054 | 2055 | map(['6', '8', '10'], ary(parseInt, 1)); 2056 | // => [6, 8, 10] 2057 | ``` 2058 | 2059 | 思路:对指定函数进行封装,指定最多接收 n 个参数。返回一个新函数,限制函数参数个数。 2060 | 2061 | **[⬆ 返回顶部](#lodash-函数列表)** 2062 | 2063 | ### before:指定一个函数在被调用多少次前执行 2064 | 2065 | ```js 2066 | const before = (n, func) => { 2067 | let count = 0; 2068 | return (...args) => { 2069 | count++; 2070 | if (count < n) { 2071 | return func(...args); 2072 | } 2073 | }; 2074 | }; 2075 | 2076 | jQuery(element).on('click', before(5, addContactToList)); 2077 | // => Allows adding up to 4 contacts to the list. 2078 | ``` 2079 | 2080 | 思路:返回一个函数,该函数在被调用 n 次前执行指定的函数。利用闭包记录当前已调用次数,判断是否达到执行条件。 2081 | 2082 | **[⬆ 返回顶部](#lodash-函数列表)** 2083 | 2084 | ### bind 2085 | 2086 | 绑定函数的 this 值和指定的参数,并返回一个新的函数 2087 | 2088 | ```js 2089 | const bind = 2090 | (func, thisArg, ...boundArgs) => 2091 | (...args) => 2092 | func.apply(thisArg, [...boundArgs, ...args]); 2093 | 2094 | const greet = function (greeting, punctuation) { 2095 | return `${greeting} ${this.user}${punctuation}`; 2096 | }; 2097 | 2098 | const object = { user: 'fred' }; 2099 | 2100 | var bound = bind(greet, object, 'hi'); 2101 | bound('!'); 2102 | // => 'hi fred!' 2103 | 2104 | // Bound with placeholders. 2105 | var bound = bind(greet, object, _, '!'); 2106 | bound('hi'); 2107 | // => 'hi fred!' 2108 | ``` 2109 | 2110 | 思路:绑定函数的 this 值和指定的参数,并返回一个新的函数。利用 apply 和 bind 方法实现。 2111 | 2112 | **[⬆ 返回顶部](#lodash-函数列表)** 2113 | 2114 | ### bindKey 2115 | 2116 | 与 bind 类似,但是绑定的是对象上的指定方法 2117 | 2118 | ```js 2119 | const bindKey = 2120 | (object, key, ...args) => 2121 | (...args2) => 2122 | object[key].apply(object, [...args, ...args2]); 2123 | 2124 | const object = { 2125 | user: 'fred', 2126 | greet(greeting, punctuation) { 2127 | return `${greeting} ${this.user}${punctuation}`; 2128 | }, 2129 | }; 2130 | 2131 | var bound = bindKey(object, 'greet', 'hi'); 2132 | bound('!'); 2133 | // => 'hi fred!' 2134 | 2135 | object.greet = function (greeting, punctuation) { 2136 | return `${greeting}ya ${this.user}${punctuation}`; 2137 | }; 2138 | 2139 | bound('!'); 2140 | // => 'hiya fred!' 2141 | 2142 | // Bound with placeholders. 2143 | var bound = bindKey(object, 'greet', _, '!'); 2144 | bound('hi'); 2145 | // => 'hiya fred!' 2146 | ``` 2147 | 2148 | 思路:与 bind 类似,但是绑定的是对象上的指定方法。利用 apply 和 bind 方法实现。 2149 | 2150 | **[⬆ 返回顶部](#lodash-函数列表)** 2151 | 2152 | ### curry 2153 | 2154 | 对指定函数进行柯里化 2155 | 2156 | ```js 2157 | const curry = (func, arity = func.length, ...args) => 2158 | arity <= args.length ? func(...args) : curry.bind(null, func, arity, ...args); 2159 | 2160 | const abc = function (a, b, c) { 2161 | return [a, b, c]; 2162 | }; 2163 | 2164 | const curried = curry(abc); 2165 | 2166 | curried(1)(2)(3); 2167 | // => [1, 2, 3] 2168 | 2169 | curried(1, 2)(3); 2170 | // => [1, 2, 3] 2171 | 2172 | curried(1, 2, 3); 2173 | // => [1, 2, 3] 2174 | 2175 | // Curried with placeholders. 2176 | curried(1)(_, 3)(2); 2177 | // => [1, 2, 3] 2178 | ``` 2179 | 2180 | 思路:对指定函数进行柯里化。返回一个新函数,当参数数量不足时,继续返回一个新函数,直到参数数量足够执行原函数。 2181 | 2182 | **[⬆ 返回顶部](#lodash-函数列表)** 2183 | 2184 | ### curryRight 2185 | 2186 | 与 curry 类似,但是从右到左处理参数 2187 | 2188 | ```js 2189 | const curryRight = (func, arity = func.length, ...args) => 2190 | arity <= args.length 2191 | ? func(...args.reverse()) 2192 | : curryRight.bind(null, func, arity, ...args); 2193 | 2194 | const abc = function (a, b, c) { 2195 | return [a, b, c]; 2196 | }; 2197 | 2198 | const curried = curryRight(abc); 2199 | 2200 | curried(3)(2)(1); 2201 | // => [1, 2, 3] 2202 | 2203 | curried(2, 3)(1); 2204 | // => [1, 2, 3] 2205 | 2206 | curried(1, 2, 3); 2207 | // => [1, 2, 3] 2208 | 2209 | // Curried with placeholders. 2210 | curried(3)(1, _)(2); 2211 | // => [1, 2, 3] 2212 | ``` 2213 | 2214 | 思路:与 curry 类似,但是从右到左处理参数。 2215 | 2216 | **[⬆ 返回顶部](#lodash-函数列表)** 2217 | 2218 | ### debounce 2219 | 2220 | 对指定函数进行防抖处理 2221 | 2222 | ```js 2223 | const debounce = (func, wait, immediate = false) => { 2224 | let timeoutId; 2225 | return (...args) => { 2226 | if (immediate && !timeoutId) { 2227 | func(...args); 2228 | } 2229 | clearTimeout(timeoutId); 2230 | timeoutId = setTimeout(() => { 2231 | if (!immediate) { 2232 | func(...args); 2233 | } 2234 | timeoutId = null; 2235 | }, wait); 2236 | }; 2237 | }; 2238 | 2239 | // 避免窗口在变动时出现昂贵的计算开销。 2240 | jQuery(window).on('resize', debounce(calculateLayout, 150)); 2241 | 2242 | // 当点击时 `sendMail` 随后就被调用。 2243 | jQuery(element).on( 2244 | 'click', 2245 | debounce(sendMail, 300, { 2246 | leading: true, 2247 | trailing: false, 2248 | }) 2249 | ); 2250 | 2251 | // 确保 `batchLog` 调用1次之后,1秒内会被触发。 2252 | const debounced = debounce(batchLog, 250, { maxWait: 1000 }); 2253 | const source = new EventSource('/stream'); 2254 | jQuery(source).on('message', debounced); 2255 | 2256 | // 取消一个 trailing 的防抖动调用 2257 | jQuery(window).on('popstate', debounced.cancel); 2258 | ``` 2259 | 2260 | 思路:对指定函数进行防抖处理。返回一个新函数,在一段时间内只执行一次。 2261 | 2262 | **[⬆ 返回顶部](#lodash-函数列表)** 2263 | 2264 | ### defer 2265 | 2266 | 将指定函数延迟执行 2267 | 2268 | ```js 2269 | const defer = (func, ...args) => setTimeout(func, 1, ...args); 2270 | 2271 | defer(text => { 2272 | console.log(text); 2273 | }, 'deferred'); 2274 | // => 一毫秒或更久一些输出 'deferred'。 2275 | ``` 2276 | 2277 | 思路:将指定函数延迟执行。利用 setTimeout 实现。 2278 | 2279 | **[⬆ 返回顶部](#lodash-函数列表)** 2280 | 2281 | ### delay 2282 | 2283 | 将指定函数延迟一段时间后执行 2284 | 2285 | ```js 2286 | const delay = (func, wait, ...args) => setTimeout(func, wait, ...args); 2287 | 2288 | delay( 2289 | text => { 2290 | console.log(text); 2291 | }, 2292 | 1000, 2293 | 'later' 2294 | ); 2295 | // => 一秒后输出 'later'。 2296 | ``` 2297 | 2298 | 思路:将指定函数延迟一段时间后执行。利用 setTimeout 实现。 2299 | 2300 | **[⬆ 返回顶部](#lodash-函数列表)** 2301 | 2302 | ### flip 2303 | 2304 | 对指定函数的参数进行反转 2305 | 2306 | ```js 2307 | const flip = 2308 | fn => 2309 | (...args) => 2310 | fn(...args.reverse()); 2311 | 2312 | const flipped = flip(function () { 2313 | return toArray(arguments); 2314 | }); 2315 | 2316 | flipped('a', 'b', 'c', 'd'); 2317 | // => ['d', 'c', 'b', 'a'] 2318 | ``` 2319 | 2320 | 思路:反转函数的参数顺序,返回一个新的函数 2321 | 2322 | **[⬆ 返回顶部](#lodash-函数列表)** 2323 | 2324 | ### memoize 2325 | 2326 | 对指定函数进行记忆化处理,缓存函数的计算结果 2327 | 2328 | ```js 2329 | const memoize = fn => { 2330 | const cache = new Map(); 2331 | return (...args) => { 2332 | const key = JSON.stringify(args); 2333 | return cache.has(key) 2334 | ? cache.get(key) 2335 | : cache.set(key, fn(...args)).get(key); 2336 | }; 2337 | }; 2338 | 2339 | const object = { a: 1, b: 2 }; 2340 | const other = { c: 3, d: 4 }; 2341 | 2342 | var values = memoize(values); 2343 | values(object); 2344 | // => [1, 2] 2345 | 2346 | values(other); 2347 | // => [3, 4] 2348 | 2349 | object.a = 2; 2350 | values(object); 2351 | // => [1, 2] 2352 | 2353 | // 修改结果缓存。 2354 | values.cache.set(object, ['a', 'b']); 2355 | values(object); 2356 | // => ['a', 'b'] 2357 | 2358 | // 替换 `memoize.Cache`。 2359 | memoize.Cache = WeakMap; 2360 | ``` 2361 | 2362 | 思路:缓存函数的计算结果,返回一个新的函数 2363 | 2364 | **[⬆ 返回顶部](#lodash-函数列表)** 2365 | 2366 | ### negate 2367 | 2368 | 对指定函数进行封装,返回原函数的否定值 2369 | 2370 | ```js 2371 | const negate = 2372 | fn => 2373 | (...args) => 2374 | !fn(...args); 2375 | 2376 | function isEven(n) { 2377 | return n % 2 == 0; 2378 | } 2379 | 2380 | filter([1, 2, 3, 4, 5, 6], negate(isEven)); 2381 | // => [1, 3, 5] 2382 | ``` 2383 | 2384 | 思路:返回一个新的函数,该函数执行原函数的结果取反 2385 | 2386 | **[⬆ 返回顶部](#lodash-函数列表)** 2387 | 2388 | ### once 2389 | 2390 | 指定一个函数只能被调用一次 2391 | 2392 | ```js 2393 | const once = fn => { 2394 | let called = false; 2395 | return (...args) => { 2396 | if (!called) { 2397 | called = true; 2398 | return fn(...args); 2399 | } 2400 | }; 2401 | }; 2402 | 2403 | const initialize = once(createApplication); 2404 | initialize(); 2405 | initialize(); 2406 | // `initialize` 只能调用 `createApplication` 一次。 2407 | ``` 2408 | 2409 | 思路:返回一个新的函数,该函数只能被调用一次 2410 | 2411 | **[⬆ 返回顶部](#lodash-函数列表)** 2412 | 2413 | ### overArgs 2414 | 2415 | 对指定函数进行封装,转换参数的形式 2416 | 2417 | ```js 2418 | const overArgs = 2419 | (fn, transforms) => 2420 | (...args) => 2421 | fn(...args.map((arg, index) => transforms[index](arg))); 2422 | 2423 | function doubled(n) { 2424 | return n * 2; 2425 | } 2426 | 2427 | function square(n) { 2428 | return n * n; 2429 | } 2430 | 2431 | const func = overArgs((x, y) => [x, y], [square, doubled]); 2432 | 2433 | func(9, 3); 2434 | // => [81, 6] 2435 | 2436 | func(10, 5); 2437 | // => [100, 10] 2438 | ``` 2439 | 2440 | 思路:返回一个新的函数,该函数对原函数的指定参数进行转换 2441 | 2442 | **[⬆ 返回顶部](#lodash-函数列表)** 2443 | 2444 | ### partial 2445 | 2446 | 对指定函数进行部分应用,指定部分参数 2447 | 2448 | ```js 2449 | const partial = 2450 | (fn, ...args) => 2451 | (...newArgs) => 2452 | fn(...args, ...newArgs); 2453 | 2454 | const greet = function (greeting, name) { 2455 | return `${greeting} ${name}`; 2456 | }; 2457 | 2458 | const sayHelloTo = partial(greet, 'hello'); 2459 | sayHelloTo('fred'); 2460 | // => 'hello fred' 2461 | 2462 | // 使用了占位符。 2463 | const greetFred = partial(greet, _, 'fred'); 2464 | greetFred('hi'); 2465 | // => 'hi fred' 2466 | ``` 2467 | 2468 | 思路:返回一个新的函数,该函数部分应用原函数的指定参数 2469 | 2470 | **[⬆ 返回顶部](#lodash-函数列表)** 2471 | 2472 | ### partialRight 2473 | 2474 | 与 partial 类似,但是从右到左指定部分参数 2475 | 2476 | ```js 2477 | const partialRight = 2478 | (fn, ...args) => 2479 | (...newArgs) => 2480 | fn(...newArgs, ...args); 2481 | 2482 | const greet = function (greeting, name) { 2483 | return `${greeting} ${name}`; 2484 | }; 2485 | 2486 | const greetFred = partialRight(greet, 'fred'); 2487 | greetFred('hi'); 2488 | // => 'hi fred' 2489 | 2490 | // 使用了占位符。 2491 | const sayHelloTo = partialRight(greet, 'hello', _); 2492 | sayHelloTo('fred'); 2493 | // => 'hello fred' 2494 | ``` 2495 | 2496 | 思路:返回一个新的函数,该函数从右到左部分应用原函数的指定参数 2497 | 2498 | **[⬆ 返回顶部](#lodash-函数列表)** 2499 | 2500 | ### rearg 2501 | 2502 | 对指定函数进行封装,调整参数的位置 2503 | 2504 | ```js 2505 | const rearg = 2506 | (fn, indexes) => 2507 | (...args) => 2508 | fn(...indexes.map(index => args[index])); 2509 | 2510 | const rearged = rearg((a, b, c) => [a, b, c], [2, 0, 1]); 2511 | 2512 | rearged('b', 'c', 'a'); 2513 | // => ['a', 'b', 'c'] 2514 | ``` 2515 | 2516 | 思路:返回一个新的函数,该函数调整原函数的参数顺序 2517 | 2518 | **[⬆ 返回顶部](#lodash-函数列表)** 2519 | 2520 | ### rest 2521 | 2522 | 对指定函数进行封装,将参数集合成一个数组传入原函数 2523 | 2524 | ```js 2525 | const rest = 2526 | fn => 2527 | (...args) => 2528 | fn(args); 2529 | 2530 | const say = rest( 2531 | (what, names) => 2532 | `${what} ${initial(names).join(', ')}${size(names) > 1 ? ', & ' : ''}${last( 2533 | names 2534 | )}` 2535 | ); 2536 | 2537 | say('hello', 'fred', 'barney', 'pebbles'); 2538 | // => 'hello fred, barney, & pebbles' 2539 | ``` 2540 | 2541 | 思路:返回一个新的函数,该函数将原函数的参数集合成一个数组传入 2542 | 2543 | **[⬆ 返回顶部](#lodash-函数列表)** 2544 | 2545 | ### spread 2546 | 2547 | 对指定函数进行封装,将参数数组展开作为多个参数传入原函数 2548 | 2549 | ```js 2550 | const spread = fn => args => fn(...args); 2551 | 2552 | const say = spread((who, what) => `${who} says ${what}`); 2553 | 2554 | say(['fred', 'hello']); 2555 | // => 'fred says hello' 2556 | 2557 | const numbers = Promise.all([Promise.resolve(40), Promise.resolve(36)]); 2558 | 2559 | numbers.then(spread((x, y) => x + y)); 2560 | // => a Promise of 76 2561 | ``` 2562 | 2563 | 思路:返回一个新的函数,该函数将原函数的参数数组展开作为多个参数传入 2564 | 2565 | **[⬆ 返回顶部](#lodash-函数列表)** 2566 | 2567 | ### throttle 2568 | 2569 | 对指定函数进行节流处理 2570 | 2571 | ```js 2572 | const throttle = (fn, time) => { 2573 | let timer; 2574 | return (...args) => { 2575 | if (!timer) { 2576 | timer = setTimeout(() => { 2577 | fn(...args); 2578 | timer = null; 2579 | }, time); 2580 | } 2581 | }; 2582 | }; 2583 | 2584 | // 避免在滚动时过分的更新定位 2585 | jQuery(window).on('scroll', throttle(updatePosition, 100)); 2586 | 2587 | // 点击后就调用 `renewToken`,但5分钟内超过1次。 2588 | const throttled = throttle(renewToken, 300000, { trailing: false }); 2589 | jQuery(element).on('click', throttled); 2590 | 2591 | // 取消一个 trailing 的节流调用。 2592 | jQuery(window).on('popstate', throttled.cancel); 2593 | ``` 2594 | 2595 | 思路:返回一个新的函数,该函数对原函数进行节流处理 2596 | 2597 | **[⬆ 返回顶部](#lodash-函数列表)** 2598 | 2599 | ### 对象 2600 | 2601 | ### assign 2602 | 2603 | 合并对象的属性,后面的对象的属性会覆盖前面的对象 2604 | 2605 | ```js 2606 | const assign = (...objs) => 2607 | objs.reduce((result, obj) => Object.assign(result, obj), {}); 2608 | 2609 | function Foo() { 2610 | this.a = 1; 2611 | } 2612 | 2613 | function Bar() { 2614 | this.c = 3; 2615 | } 2616 | 2617 | Foo.prototype.b = 2; 2618 | Bar.prototype.d = 4; 2619 | 2620 | assign({ a: 0 }, new Foo(), new Bar()); 2621 | // => { 'a': 1, 'c': 3 } 2622 | ``` 2623 | 2624 | 思路:使用 reduce 方法遍历每个对象,将属性合并到目标对象中。 2625 | 2626 | ### defaults 2627 | 2628 | 对指定对象进行封装,将默认值合并进去 2629 | 2630 | ```js 2631 | const defaults = (obj, defaultProps) => ({ ...defaultProps, ...obj }); 2632 | 2633 | defaults({ a: 1 }, { b: 2 }, { a: 3 }); 2634 | // => { 'a': 1, 'b': 2 } 2635 | ``` 2636 | 2637 | 思路:使用 Object.assign 方法将默认值对象合并到目标对象上,如果目标对象中已经存在相同属性,则不会被覆盖。 2638 | 2639 | **[⬆ 返回顶部](#lodash-函数列表)** 2640 | 2641 | ### defaultsDeep 2642 | 2643 | 与 defaults 类似,但是支持嵌套对象 2644 | 2645 | ```js 2646 | const defaultsDeep = (obj, defaultProps) => { 2647 | const mergeDeep = (target, source) => { 2648 | Object.keys(source).forEach(key => { 2649 | const targetValue = target[key]; 2650 | const sourceValue = source[key]; 2651 | if (typeof targetValue === 'object' && typeof sourceValue === 'object') { 2652 | target[key] = mergeDeep(targetValue, sourceValue); 2653 | } else { 2654 | target[key] = targetValue === undefined ? sourceValue : targetValue; 2655 | } 2656 | }); 2657 | return target; 2658 | }; 2659 | return mergeDeep({ ...defaultProps }, obj); 2660 | }; 2661 | 2662 | defaultsDeep({ a: { b: 2 } }, { a: { b: 1, c: 3 } }); 2663 | // => { 'a': { 'b': 2, 'c': 3 } } 2664 | ``` 2665 | 2666 | 思路:使用 Object.assign 和 typeof 方法进行递归遍历,将嵌套的对象也合并进去。 2667 | 2668 | **[⬆ 返回顶部](#lodash-函数列表)** 2669 | 2670 | ### findKey 2671 | 2672 | 遍历对象,返回第一个符合条件的键名 2673 | 2674 | ```js 2675 | const findKey = (obj, predicate) => { 2676 | for (const key in obj) { 2677 | if (predicate(obj[key], key, obj)) { 2678 | return key; 2679 | } 2680 | } 2681 | }; 2682 | 2683 | const users = { 2684 | barney: { age: 36, active: true }, 2685 | fred: { age: 40, active: false }, 2686 | pebbles: { age: 1, active: true }, 2687 | }; 2688 | 2689 | findKey(users, o => o.age < 40); 2690 | // => 'barney' (iteration order is not guaranteed) 2691 | 2692 | // The `matches` iteratee shorthand. 2693 | findKey(users, { age: 1, active: true }); 2694 | // => 'pebbles' 2695 | 2696 | // The `matchesProperty` iteratee shorthand. 2697 | findKey(users, ['active', false]); 2698 | // => 'fred' 2699 | 2700 | // The `property` iteratee shorthand. 2701 | findKey(users, 'active'); 2702 | // => 'barney' 2703 | ``` 2704 | 2705 | 思路:使用 for...in 循环遍历对象,对每个属性调用指定的函数进行判断,如果返回真值则返回当前属性名。 2706 | 2707 | **[⬆ 返回顶部](#lodash-函数列表)** 2708 | 2709 | ### findLastKey 2710 | 2711 | 与 findKey 类似,但是从对象的末尾开始 2712 | 2713 | ```js 2714 | const findLastKey = (obj, predicate) => 2715 | findKey(obj, predicate, Object.keys(obj).reverse()); 2716 | 2717 | const users = { 2718 | barney: { age: 36, active: true }, 2719 | fred: { age: 40, active: false }, 2720 | pebbles: { age: 1, active: true }, 2721 | }; 2722 | 2723 | findLastKey(users, o => o.age < 40); 2724 | // => returns 'pebbles' assuming `findKey` returns 'barney' 2725 | 2726 | // The `matches` iteratee shorthand. 2727 | findLastKey(users, { age: 36, active: true }); 2728 | // => 'barney' 2729 | 2730 | // The `matchesProperty` iteratee shorthand. 2731 | findLastKey(users, ['active', false]); 2732 | // => 'fred' 2733 | 2734 | // The `property` iteratee shorthand. 2735 | findLastKey(users, 'active'); 2736 | // => 'pebbles' 2737 | ``` 2738 | 2739 | 思路:使用 Object.keys 方法获取对象的键名数组,然后使用 reverse 方法翻转数组,再使用 findKey 函数进行查找。 2740 | 2741 | **[⬆ 返回顶部](#lodash-函数列表)** 2742 | 2743 | ### forIn 2744 | 2745 | 遍历对象,对每个属性调用指定的函数 2746 | 2747 | ```js 2748 | const forIn = (obj, iteratee) => { 2749 | for (const key in obj) { 2750 | if (iteratee(obj[key], key, obj) === false) { 2751 | break; 2752 | } 2753 | } 2754 | return obj; 2755 | }; 2756 | 2757 | function Foo() { 2758 | this.a = 1; 2759 | this.b = 2; 2760 | } 2761 | 2762 | Foo.prototype.c = 3; 2763 | 2764 | forIn(new Foo(), (value, key) => { 2765 | console.log(key); 2766 | }); 2767 | // => Logs 'a', 'b', then 'c' (无法保证遍历的顺序)。 2768 | ``` 2769 | 2770 | 思路:使用 for...in 循环遍历对象,对每个属性调用指定的函数。 2771 | 2772 | **[⬆ 返回顶部](#lodash-函数列表)** 2773 | 2774 | ### forInRight 2775 | 2776 | 与 forIn 类似,但是从对象的末尾开始遍历 2777 | 2778 | ```js 2779 | const forInRight = (obj, fn) => { 2780 | for (const key in obj) { 2781 | if (obj.hasOwnProperty(key)) { 2782 | fn(obj[key], key, obj); 2783 | } 2784 | } 2785 | }; 2786 | 2787 | function Foo() { 2788 | this.a = 1; 2789 | this.b = 2; 2790 | } 2791 | 2792 | Foo.prototype.c = 3; 2793 | 2794 | forInRight(new Foo(), (value, key) => { 2795 | console.log(key); 2796 | }); 2797 | // => 输出 'c', 'b', 然后 'a', `forIn` 会输出 'a', 'b', 然后 'c'。 2798 | ``` 2799 | 2800 | 思路:使用 for...in 循环倒序遍历对象的所有属性,并对每个属性调用指定的函数。 2801 | 2802 | **[⬆ 返回顶部](#lodash-函数列表)** 2803 | 2804 | ### forOwn 2805 | 2806 | 遍历对象自身的可枚举属性,对每个属性调用指定的函数 2807 | 2808 | ```js 2809 | const forOwn = (obj, func) => { 2810 | for (const key in obj) { 2811 | if (obj.hasOwnProperty(key)) { 2812 | func(obj[key], key, obj); 2813 | } 2814 | } 2815 | }; 2816 | 2817 | function Foo() { 2818 | this.a = 1; 2819 | this.b = 2; 2820 | } 2821 | 2822 | Foo.prototype.c = 3; 2823 | 2824 | forOwn(new Foo(), (value, key) => { 2825 | console.log(key); 2826 | }); 2827 | // => 输出 'a' 然后 'b' (无法保证遍历的顺序)。 2828 | ``` 2829 | 2830 | 思路:遍历对象自身的可枚举属性,对每个属性调用指定的函数,使用 for-in 循环遍历对象的所有属性,判断属性是否是自身的可枚举属性,如果是则调用指定的函数。 2831 | 2832 | **[⬆ 返回顶部](#lodash-函数列表)** 2833 | 2834 | ### forOwnRight 2835 | 2836 | 与 forOwn 类似,但是从对象的末尾开始遍历 2837 | 2838 | ```js 2839 | const forOwnRight = (obj, func) => { 2840 | const keys = Object.keys(obj).reverse(); 2841 | for (let i = 0; i < keys.length; i++) { 2842 | func(obj[keys[i]], keys[i], obj); 2843 | } 2844 | }; 2845 | 2846 | function Foo() { 2847 | this.a = 1; 2848 | this.b = 2; 2849 | } 2850 | 2851 | Foo.prototype.c = 3; 2852 | 2853 | forOwnRight(new Foo(), (value, key) => { 2854 | console.log(key); 2855 | }); 2856 | // => 输出 'b' 然后 'a', `forOwn` 会输出 'a' 然后 'b' 2857 | ``` 2858 | 2859 | 思路:与 forOwn 类似,但是从对象的末尾开始遍历,可以将对象的键数组进行 reverse 操作后再遍历。 2860 | 2861 | **[⬆ 返回顶部](#lodash-函数列表)** 2862 | 2863 | ### functions 2864 | 2865 | 返回指定对象上的所有函数名 2866 | 2867 | ```js 2868 | const functions = obj => 2869 | Object.keys(obj).filter(key => typeof obj[key] === 'function'); 2870 | 2871 | function Foo() { 2872 | this.a = constant('a'); 2873 | this.b = constant('b'); 2874 | } 2875 | 2876 | Foo.prototype.c = constant('c'); 2877 | 2878 | functions(new Foo()); 2879 | // => ['a', 'b'] 2880 | ``` 2881 | 2882 | 思路:返回指定对象上的所有函数名,使用 Object.keys()获取对象的所有属性名,再使用 filter()方法筛选出属性值的类型为 function 的属性名。 2883 | 2884 | **[⬆ 返回顶部](#lodash-函数列表)** 2885 | 2886 | ### get 2887 | 2888 | 获取对象上的属性,支持使用点和方括号的方式指定属性路径 2889 | 2890 | ```js 2891 | const get = (obj, path) => 2892 | path.split(/[.[\]]/).reduce((acc, cur) => (cur ? acc[cur] : acc), obj); 2893 | 2894 | const object = { a: [{ b: { c: 3 } }] }; 2895 | 2896 | get(object, 'a[0].b.c'); 2897 | // => 3 2898 | 2899 | get(object, ['a', '0', 'b', 'c']); 2900 | // => 3 2901 | 2902 | get(object, 'a.b.c', 'default'); 2903 | // => 'default' 2904 | ``` 2905 | 2906 | 思路:使用 reduce 函数将属性路径分割后进行遍历并获取对应属性值,支持使用点和方括号的方式指定属性路径 2907 | 2908 | **[⬆ 返回顶部](#lodash-函数列表)** 2909 | 2910 | ### has 2911 | 2912 | 判断对象上是否有指定属性 2913 | 2914 | ```js 2915 | const has = (obj, key) => key in obj; 2916 | 2917 | const object = { a: { b: 2 } }; 2918 | const other = create({ a: create({ b: 2 }) }); 2919 | 2920 | has(object, 'a'); 2921 | // => true 2922 | 2923 | has(object, 'a.b'); 2924 | // => true 2925 | 2926 | has(object, ['a', 'b']); 2927 | // => true 2928 | 2929 | has(other, 'a'); 2930 | // => false 2931 | ``` 2932 | 2933 | 思路:使用 in 操作符判断对象上是否有指定属性 2934 | 2935 | **[⬆ 返回顶部](#lodash-函数列表)** 2936 | 2937 | ### hasIn 2938 | 2939 | 判断对象上是否有指定路径的属性 2940 | 2941 | ```js 2942 | const hasIn = (obj, path) => get(obj, path) !== undefined; 2943 | 2944 | const object = create({ a: create({ b: 2 }) }); 2945 | 2946 | hasIn(object, 'a'); 2947 | // => true 2948 | 2949 | hasIn(object, 'a.b'); 2950 | // => true 2951 | 2952 | hasIn(object, ['a', 'b']); 2953 | // => true 2954 | 2955 | hasIn(object, 'b'); 2956 | // => false 2957 | ``` 2958 | 2959 | 思路:使用 get 函数获取属性值,如果返回 undefined 则表示不存在指定路径属性 2960 | 2961 | **[⬆ 返回顶部](#lodash-函数列表)** 2962 | 2963 | ### invert 2964 | 2965 | 对指定对象的属性和值进行反转 2966 | 2967 | ```js 2968 | const invert = obj => 2969 | Object.entries(obj).reduce((acc, [key, val]) => { 2970 | acc[val] = key; 2971 | return acc; 2972 | }, {}); 2973 | 2974 | const object = { a: 1, b: 2, c: 1 }; 2975 | 2976 | invert(object); 2977 | // => { '1': 'c', '2': 'b' } 2978 | ``` 2979 | 2980 | 思路:遍历对象并将属性值作为键名,属性名作为键值生成新对象 2981 | 2982 | **[⬆ 返回顶部](#lodash-函数列表)** 2983 | 2984 | ### invertBy 2985 | 2986 | 与 invert 类似,但是支持指定反转后值的集合 2987 | 2988 | ```js 2989 | const invertBy = (obj, fn) => 2990 | Object.entries(obj).reduce((acc, [key, val]) => { 2991 | const invertedKey = fn(val); 2992 | if (!acc[invertedKey]) { 2993 | acc[invertedKey] = []; 2994 | } 2995 | acc[invertedKey].push(key); 2996 | return acc; 2997 | }, {}); 2998 | 2999 | const object = { a: 1, b: 2, c: 1 }; 3000 | 3001 | invertBy(object); 3002 | // => { '1': ['a', 'c'], '2': ['b'] } 3003 | 3004 | invertBy(object, value => `group${value}`); 3005 | // => { 'group1': ['a', 'c'], 'group2': ['b'] } 3006 | ``` 3007 | 3008 | 思路:遍历对象并将属性值经过回调函数处理后作为键名,属性名作为键值生成新对象 3009 | 3010 | **[⬆ 返回顶部](#lodash-函数列表)** 3011 | 3012 | ### invoke 3013 | 3014 | 对指定对象上的方法进行调用 3015 | 3016 | ```js 3017 | const invoke = (obj, methodName, ...args) => 3018 | Object.values(obj).forEach(func => 3019 | typeof func[methodName] === 'function' ? func[methodName](...args) : null 3020 | ); 3021 | 3022 | const object = { a: [{ b: { c: [1, 2, 3, 4] } }] }; 3023 | 3024 | invoke(object, 'a[0].b.c.slice', 1, 3); 3025 | // => [2, 3] 3026 | ``` 3027 | 3028 | 思路:遍历对象并调用指定方法名的方法 3029 | 3030 | **[⬆ 返回顶部](#lodash-函数列表)** 3031 | 3032 | ### keys 3033 | 3034 | 返回对象上的所有可枚举属性名 3035 | 3036 | ```js 3037 | const keys = obj => Object.keys(obj); 3038 | 3039 | function Foo() { 3040 | this.a = 1; 3041 | this.b = 2; 3042 | } 3043 | 3044 | Foo.prototype.c = 3; 3045 | 3046 | keys(new Foo()); 3047 | // => ['a', 'b'] (iteration order is not guaranteed) 3048 | 3049 | keys('hi'); 3050 | // => ['0', '1'] 3051 | ``` 3052 | 3053 | 思路:使用 Object.keys 函数返回对象上的所有可枚举属性名 3054 | 3055 | **[⬆ 返回顶部](#lodash-函数列表)** 3056 | 3057 | ### keysIn 3058 | 3059 | 返回对象上的所有属性名,包括不可枚举属性 3060 | 3061 | ```js 3062 | const keysIn = obj => { 3063 | const result = []; 3064 | for (const key in obj) { 3065 | result.push(key); 3066 | } 3067 | return result; 3068 | }; 3069 | 3070 | function Foo() { 3071 | this.a = 1; 3072 | this.b = 2; 3073 | } 3074 | 3075 | Foo.prototype.c = 3; 3076 | 3077 | keysIn(new Foo()); 3078 | // => ['a', 'b', 'c'] (iteration order is not guaranteed) 3079 | ``` 3080 | 3081 | 思路:遍历对象的所有属性名,将其添加到一个数组中,并返回该数组。 3082 | 3083 | **[⬆ 返回顶部](#lodash-函数列表)** 3084 | 3085 | ### mapKeys 3086 | 3087 | 遍历对象上的每个属性,返回一个新对象,其中每个属性的名称由指定的函数计算得出 3088 | 3089 | ```js 3090 | const mapKeys = (obj, fn) => 3091 | Object.keys(obj).reduce((result, key) => { 3092 | result[fn(obj[key], key, obj)] = obj[key]; 3093 | return result; 3094 | }, {}); 3095 | 3096 | mapKeys({ a: 1, b: 2 }, (value, key) => key + value); 3097 | // => { 'a1': 1, 'b2': 2 } 3098 | ``` 3099 | 3100 | 思路:使用 reduce 遍历对象的属性名,将新的属性名通过指定函数计算得出,并与原属性值一起添加到一个新的对象中,并返回该新对象。 3101 | 3102 | **[⬆ 返回顶部](#lodash-函数列表)** 3103 | 3104 | ### mapValues 3105 | 3106 | 遍历对象上的每个属性,返回一个新对象,其中每个属性的值由指定的函数计算得出 3107 | 3108 | ```js 3109 | const mapValues = (obj, fn) => 3110 | Object.keys(obj).reduce((result, key) => { 3111 | result[key] = fn(obj[key], key, obj); 3112 | return result; 3113 | }, {}); 3114 | 3115 | const users = { 3116 | fred: { user: 'fred', age: 40 }, 3117 | pebbles: { user: 'pebbles', age: 1 }, 3118 | }; 3119 | 3120 | mapValues(users, o => o.age); 3121 | // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) 3122 | 3123 | // The `property` iteratee shorthand. 3124 | mapValues(users, 'age'); 3125 | // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) 3126 | ``` 3127 | 3128 | 思路:使用 reduce 遍历对象的属性名,通过指定函数计算每个属性值,并将计算后的新属性值添加到一个新的对象中,并返回该新对象。 3129 | 3130 | **[⬆ 返回顶部](#lodash-函数列表)** 3131 | 3132 | ### merge 3133 | 3134 | 合并对象和源对象的属性,并返回合并后的对象 3135 | 3136 | ```js 3137 | const merge = (obj, src) => ({ ...obj, ...src }); 3138 | 3139 | const object = { 3140 | a: [{ b: 2 }, { d: 4 }], 3141 | }; 3142 | 3143 | const other = { 3144 | a: [{ c: 3 }, { e: 5 }], 3145 | }; 3146 | 3147 | merge(object, other); 3148 | // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } 3149 | ``` 3150 | 3151 | 思路:使用 Object.assign 将源对象的属性值合并到目标对象上,并返回合并后的新对象。 3152 | 3153 | **[⬆ 返回顶部](#lodash-函数列表)** 3154 | 3155 | ### mergeWith 3156 | 3157 | 与 merge 类似,但是指定合并函数,用于处理冲突的属性值 3158 | 3159 | ```js 3160 | const mergeWith = (obj, src, customizer) => { 3161 | const result = { ...obj, ...src }; 3162 | Object.keys(result).forEach(key => { 3163 | result[key] = customizer(obj[key], src[key], key, obj, src); 3164 | }); 3165 | return result; 3166 | }; 3167 | 3168 | function customizer(objValue, srcValue) { 3169 | if (isArray(objValue)) { 3170 | return objValue.concat(srcValue); 3171 | } 3172 | } 3173 | 3174 | const object = { a: [1], b: [2] }; 3175 | const other = { a: [3], b: [4] }; 3176 | 3177 | mergeWith(object, other, customizer); 3178 | // => { 'a': [1, 3], 'b': [2, 4] } 3179 | ``` 3180 | 3181 | 思路:使用 Object.assign 将源对象的属性值合并到目标对象上,并遍历合并后的新对象,通过指定函数自定义处理冲突的属性值,并返回处理后的新对象。 3182 | 3183 | **[⬆ 返回顶部](#lodash-函数列表)** 3184 | 3185 | ### omit 3186 | 3187 | 返回一个新对象,其中省略了指定属性的属性值 3188 | 3189 | ```js 3190 | const omit = (obj, props) => { 3191 | const newObj = { ...obj }; 3192 | props.forEach(prop => { 3193 | delete newObj[prop]; 3194 | }); 3195 | return newObj; 3196 | }; 3197 | 3198 | const object = { a: 1, b: '2', c: 3 }; 3199 | 3200 | omit(object, ['a', 'c']); 3201 | // => { 'b': '2' } 3202 | ``` 3203 | 3204 | 思路:使用 Object.assign 将原对象的属性值复制到一个新对象上,遍历指定省略的属性,将其从新对象中删除,并返回该新对象。 3205 | 3206 | **[⬆ 返回顶部](#lodash-函数列表)** 3207 | 3208 | ### omitBy 3209 | 3210 | 与 omit 类似,但是根据指定函数判断是否省略属性 3211 | 3212 | ```js 3213 | const omitBy = (obj, predicate) => { 3214 | const newObj = { ...obj }; 3215 | Object.keys(newObj).forEach(key => { 3216 | if (predicate(newObj[key])) { 3217 | delete newObj[key]; 3218 | } 3219 | }); 3220 | return newObj; 3221 | }; 3222 | 3223 | const object = { a: 1, b: '2', c: 3 }; 3224 | 3225 | omitBy(object, isNumber); 3226 | // => { 'b': '2' } 3227 | ``` 3228 | 3229 | 思路:使用 Object.assign 将原对象的属性值复制到一个新对象上,遍历新对象的每个属性,根据指定函数判断是否需要删除该属性,并返回处理后的新对象。 3230 | 3231 | **[⬆ 返回顶部](#lodash-函数列表)** 3232 | 3233 | ### pick 3234 | 3235 | 返回一个新对象,其中只包含指定属性的属性值 3236 | 3237 | ```js 3238 | const pick = (obj, props) => 3239 | props.reduce((result, prop) => { 3240 | if (prop in obj) { 3241 | result[prop] = obj[prop]; 3242 | } 3243 | return result; 3244 | }, {}); 3245 | 3246 | const object = { a: 1, b: '2', c: 3 }; 3247 | 3248 | pick(object, ['a', 'c']); 3249 | // => { 'a': 1, 'c': 3 } 3250 | ``` 3251 | 3252 | 思路:使用 reduce 遍历指定需要选取的属性,将其添加到一个新的对象中,并返回该新对象。 3253 | 3254 | **[⬆ 返回顶部](#lodash-函数列表)** 3255 | 3256 | ### pickBy 3257 | 3258 | 与 pick 类似,但是根据指定函数判断是否保留属性 3259 | 3260 | ```js 3261 | const pickBy = (obj, fn) => 3262 | Object.keys(obj).reduce((acc, key) => { 3263 | if (fn(obj[key])) acc[key] = obj[key]; 3264 | return acc; 3265 | }, {}); 3266 | 3267 | const object = { a: 1, b: '2', c: 3 }; 3268 | 3269 | pickBy(object, isNumber); 3270 | // => { 'a': 1, 'c': 3 } 3271 | ``` 3272 | 3273 | 思路:使用 Object.keys 和 Array.prototype.reduce 方法,返回一个新的对象。 3274 | 3275 | **[⬆ 返回顶部](#lodash-函数列表)** 3276 | 3277 | ### result 3278 | 3279 | 获取对象上指定路径的值,并根据情况进行函数调用 3280 | 3281 | ```js 3282 | const result = (obj, path, defaultValue) => 3283 | path.split('.').reduce((acc, cur) => (acc ? acc[cur] : undefined), obj) ?? 3284 | defaultValue; 3285 | 3286 | const object = { a: [{ b: { c1: 3, c2: constant(4) } }] }; 3287 | 3288 | result(object, 'a[0].b.c1'); 3289 | // => 3 3290 | 3291 | result(object, 'a[0].b.c2'); 3292 | // => 4 3293 | 3294 | result(object, 'a[0].b.c3', 'default'); 3295 | // => 'default' 3296 | 3297 | result(object, 'a[0].b.c3', constant('default')); 3298 | // => 'default' 3299 | ``` 3300 | 3301 | 思路:使用 Array.prototype.reduce 方法和 typeof 运算符,支持获取多层路径的值。 3302 | 3303 | **[⬆ 返回顶部](#lodash-函数列表)** 3304 | 3305 | ### set 3306 | 3307 | 设置对象上指定路径的属性值 3308 | 3309 | ```js 3310 | const set = (obj, path, value) => { 3311 | const keys = path.split(/[,[\].]+?/); 3312 | const lastKeyIndex = keys.length - 1; 3313 | keys.reduce((acc, key, index) => { 3314 | if (index === lastKeyIndex) acc[key] = value; 3315 | else acc[key] ?? (acc[key] = {}); 3316 | return acc[key]; 3317 | }, obj); 3318 | return obj; 3319 | }; 3320 | 3321 | const object = { a: [{ b: { c: 3 } }] }; 3322 | 3323 | set(object, 'a[0].b.c', 4); 3324 | console.log(object.a[0].b.c); 3325 | // => 4 3326 | 3327 | set(object, ['x', '0', 'y', 'z'], 5); 3328 | console.log(object.x[0].y.z); 3329 | // => 5 3330 | ``` 3331 | 3332 | 思路:使用 Array.prototype.reduce 方法,支持设置多层路径的值。 3333 | 3334 | **[⬆ 返回顶部](#lodash-函数列表)** 3335 | 3336 | ### setWith 3337 | 3338 | 与 set 类似,但是指定自定义函数用于设置属性值 3339 | 3340 | ```js 3341 | const setWith = (obj, path, value, customizer) => { 3342 | const keys = path.split(/[,[\].]+?/); 3343 | const lastKeyIndex = keys.length - 1; 3344 | keys.reduce((acc, key, index) => { 3345 | const newValue = index === lastKeyIndex ? customizer(acc[key], value) : {}; 3346 | acc[key] = typeof acc[key] === 'object' ? acc[key] : newValue; 3347 | return acc[key]; 3348 | }, obj); 3349 | return obj; 3350 | }; 3351 | 3352 | const object = {}; 3353 | 3354 | setWith(object, '[0][1]', 'a', Object); 3355 | // => { '0': { '1': 'a' } } 3356 | ``` 3357 | 3358 | 思路:使用 Array.prototype.reduce 方法,支持设置多层路径的值。 3359 | 3360 | **[⬆ 返回顶部](#lodash-函数列表)** 3361 | 3362 | ### toPairs 3363 | 3364 | 将对象转化为键值对数组 3365 | 3366 | ```js 3367 | const toPairs = obj => Object.entries(obj); 3368 | 3369 | function Foo() { 3370 | this.a = 1; 3371 | this.b = 2; 3372 | } 3373 | 3374 | Foo.prototype.c = 3; 3375 | 3376 | toPairs(new Foo()); 3377 | // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) 3378 | ``` 3379 | 3380 | 思路:使用 Object.entries 方法,返回一个由键值对组成的数组。 3381 | 3382 | **[⬆ 返回顶部](#lodash-函数列表)** 3383 | 3384 | ### toPairsIn 3385 | 3386 | 将对象转化为键值对数组,包括不可枚举属性 3387 | 3388 | ```js 3389 | const toPairsIn = obj => { 3390 | const result = []; 3391 | for (const key in obj) { 3392 | result.push([key, obj[key]]); 3393 | } 3394 | return result; 3395 | }; 3396 | 3397 | function Foo() { 3398 | this.a = 1; 3399 | this.b = 2; 3400 | } 3401 | 3402 | Foo.prototype.c = 3; 3403 | 3404 | toPairsIn(new Foo()); 3405 | // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) 3406 | ``` 3407 | 3408 | 思路:使用 Object.getOwnPropertyNames 方法,返回一个由键值对组成的数组。 3409 | 3410 | **[⬆ 返回顶部](#lodash-函数列表)** 3411 | 3412 | ### transform 3413 | 3414 | 对指定对象进行封装,指定转换函数,处理对象上的属性 3415 | 3416 | ```js 3417 | const transform = (obj, fn, acc) => 3418 | Object.entries(obj).reduce( 3419 | (result, [key, value]) => fn(result, value, key, obj), 3420 | acc 3421 | ); 3422 | 3423 | transform( 3424 | [2, 3, 4], 3425 | (result, n) => { 3426 | result.push((n *= n)); 3427 | return n % 2 == 0; 3428 | }, 3429 | [] 3430 | ); 3431 | // => [4, 9] 3432 | 3433 | transform( 3434 | { a: 1, b: 2, c: 1 }, 3435 | (result, value, key) => { 3436 | (result[value] || (result[value] = [])).push(key); 3437 | }, 3438 | {} 3439 | ); 3440 | // => { '1': ['a', 'c'], '2': ['b'] } 3441 | ``` 3442 | 3443 | 思路:使用 Object.entries 方法和 Array.prototype.reduce 方法,返回一个由转换后的对象组成的数组。 3444 | 3445 | **[⬆ 返回顶部](#lodash-函数列表)** 3446 | 3447 | ### unset 3448 | 3449 | 删除对象上指定路径的属性值 3450 | 3451 | ```js 3452 | 3453 | ``` 3454 | 3455 | 思路: 3456 | 3457 | **[⬆ 返回顶部](#lodash-函数列表)** 3458 | 3459 | ### update:获取对象上指定路径的值,并根据情况进行函数调用,最后将值设置回去 3460 | 3461 | ```js 3462 | 3463 | ``` 3464 | 3465 | 思路: 3466 | 3467 | **[⬆ 返回顶部](#lodash-函数列表)** 3468 | 3469 | ### updateWith:与 update 类似,但是指定自定义函数用于更新属性值 3470 | 3471 | ```js 3472 | 3473 | ``` 3474 | 3475 | 思路: 3476 | 3477 | **[⬆ 返回顶部](#lodash-函数列表)** 3478 | 3479 | ### values:返回对象上的所有可枚举属性值 3480 | 3481 | ```js 3482 | 3483 | ``` 3484 | 3485 | 思路: 3486 | 3487 | **[⬆ 返回顶部](#lodash-函数列表)** 3488 | 3489 | ### valuesIn:返回对象上的所有属性值,包括不可枚举属性值 3490 | 3491 | ```js 3492 | 3493 | ``` 3494 | 3495 | 思路: 3496 | 3497 | **[⬆ 返回顶部](#lodash-函数列表)** 3498 | 3499 | ```js 3500 | 3501 | ``` 3502 | 3503 | 思路: 3504 | 3505 | **[⬆ 返回顶部](#lodash-函数列表)** 3506 | 3507 | ### 集合 3508 | 3509 | ### countBy:遍历集合,返回一个对象,其中键为指定函数计算得出的值,值为该值出现的次数 3510 | 3511 | ### each:遍历集合,对每个元素调用指定的函数 3512 | 3513 | ```js 3514 | 3515 | ``` 3516 | 3517 | 思路: 3518 | 3519 | **[⬆ 返回顶部](#lodash-函数列表)** 3520 | 3521 | ### eachRight:与 each 类似,但是从集合的末尾开始遍历 3522 | 3523 | ```js 3524 | 3525 | ``` 3526 | 3527 | 思路: 3528 | 3529 | **[⬆ 返回顶部](#lodash-函数列表)** 3530 | 3531 | ### every:遍历集合,返回一个布尔值,指示是否所有元素均满足指定的函数 3532 | 3533 | ```js 3534 | 3535 | ``` 3536 | 3537 | 思路: 3538 | 3539 | **[⬆ 返回顶部](#lodash-函数列表)** 3540 | 3541 | ### filter:遍历集合,返回一个新集合,其中只包含满足指定函数的元素 3542 | 3543 | ```js 3544 | 3545 | ``` 3546 | 3547 | 思路: 3548 | 3549 | **[⬆ 返回顶部](#lodash-函数列表)** 3550 | 3551 | ### find:遍历集合,返回第一个满足指定函数的元素 3552 | 3553 | ```js 3554 | 3555 | ``` 3556 | 3557 | 思路: 3558 | 3559 | **[⬆ 返回顶部](#lodash-函数列表)** 3560 | 3561 | ### findLast:与 find 类似,但是从集合的末尾开始遍历 3562 | 3563 | ```js 3564 | 3565 | ``` 3566 | 3567 | 思路: 3568 | 3569 | **[⬆ 返回顶部](#lodash-函数列表)** 3570 | 3571 | ### flatMap:遍历集合,对每个元素调用指定函数,并将结果展平到一个新集合中 3572 | 3573 | ```js 3574 | 3575 | ``` 3576 | 3577 | 思路: 3578 | 3579 | **[⬆ 返回顶部](#lodash-函数列表)** 3580 | 3581 | ### flatMapDeep:与 flatMap 类似,但是对每个元素调用迭代器的结果进行深度展平 3582 | 3583 | ```js 3584 | 3585 | ``` 3586 | 3587 | 思路: 3588 | 3589 | **[⬆ 返回顶部](#lodash-函数列表)** 3590 | 3591 | ### flatMapDepth:与 flatMap 类似,但是可以指定展平的深度 3592 | 3593 | ```js 3594 | 3595 | ``` 3596 | 3597 | 思路: 3598 | 3599 | **[⬆ 返回顶部](#lodash-函数列表)** 3600 | 3601 | ### forEach:遍历集合,对每个元素调用指定的函数 3602 | 3603 | ```js 3604 | 3605 | ``` 3606 | 3607 | 思路: 3608 | 3609 | **[⬆ 返回顶部](#lodash-函数列表)** 3610 | 3611 | ### forEachRight:与 forEach 类似,但是从集合的末尾开始遍历 3612 | 3613 | ```js 3614 | 3615 | ``` 3616 | 3617 | 思路: 3618 | 3619 | **[⬆ 返回顶部](#lodash-函数列表)** 3620 | 3621 | ### groupBy:遍历集合,返回一个对象,其中键为指定函数计算得出的值,值为该值所对应的元素集合 3622 | 3623 | ```js 3624 | 3625 | ``` 3626 | 3627 | 思路: 3628 | 3629 | **[⬆ 返回顶部](#lodash-函数列表)** 3630 | 3631 | ### includes:判断集合中是否包含指定元素 3632 | 3633 | ```js 3634 | 3635 | ``` 3636 | 3637 | 思路: 3638 | 3639 | **[⬆ 返回顶部](#lodash-函数列表)** 3640 | 3641 | ### invokeMap:对集合中的每个元素调用指定方法,并返回结果 3642 | 3643 | ```js 3644 | 3645 | ``` 3646 | 3647 | 思路: 3648 | 3649 | **[⬆ 返回顶部](#lodash-函数列表)** 3650 | 3651 | ### keyBy:遍历集合,返回一个对象,其中键由指定函数计算得出,值为该元素 3652 | 3653 | ```js 3654 | 3655 | ``` 3656 | 3657 | 思路: 3658 | 3659 | **[⬆ 返回顶部](#lodash-函数列表)** 3660 | 3661 | ### map:遍历集合,返回一个新集合,其中每个元素由指定函数计算得出 3662 | 3663 | ```js 3664 | 3665 | ``` 3666 | 3667 | 思路: 3668 | 3669 | **[⬆ 返回顶部](#lodash-函数列表)** 3670 | 3671 | ### orderBy:将集合按指定顺序进行排序 3672 | 3673 | ```js 3674 | 3675 | ``` 3676 | 3677 | 思路: 3678 | 3679 | **[⬆ 返回顶部](#lodash-函数列表)** 3680 | 3681 | ### partition:遍历集合,返回一个包含满足和不满足指定函数的元素集合的数组 3682 | 3683 | ```js 3684 | 3685 | ``` 3686 | 3687 | 思路: 3688 | 3689 | **[⬆ 返回顶部](#lodash-函数列表)** 3690 | 3691 | ### reduce:遍历集合,将集合元素进行累积,并返回最终结果 3692 | 3693 | ```js 3694 | 3695 | ``` 3696 | 3697 | 思路: 3698 | 3699 | **[⬆ 返回顶部](#lodash-函数列表)** 3700 | 3701 | ### reduceRight:与 reduce 类似,但是从集合的末尾开始遍历 3702 | 3703 | ```js 3704 | 3705 | ``` 3706 | 3707 | 思路: 3708 | 3709 | **[⬆ 返回顶部](#lodash-函数列表)** 3710 | 3711 | ### reject:遍历集合,返回一个新集合,其中不包含满足指定函数的元素 3712 | 3713 | ```js 3714 | 3715 | ``` 3716 | 3717 | 思路: 3718 | 3719 | **[⬆ 返回顶部](#lodash-函数列表)** 3720 | 3721 | ### sample:返回集合中的随机一个元素 3722 | 3723 | ```js 3724 | 3725 | ``` 3726 | 3727 | 思路: 3728 | 3729 | **[⬆ 返回顶部](#lodash-函数列表)** 3730 | 3731 | ### sampleSize:返回集合中的指定数量的随机元素 3732 | 3733 | ```js 3734 | 3735 | ``` 3736 | 3737 | 思路: 3738 | 3739 | **[⬆ 返回顶部](#lodash-函数列表)** 3740 | 3741 | ### shuffle:返回一个随机排列的集合 3742 | 3743 | ```js 3744 | 3745 | ``` 3746 | 3747 | 思路: 3748 | 3749 | **[⬆ 返回顶部](#lodash-函数列表)** 3750 | 3751 | ### size:返回集合的大小 3752 | 3753 | ```js 3754 | 3755 | ``` 3756 | 3757 | 思路: 3758 | 3759 | **[⬆ 返回顶部](#lodash-函数列表)** 3760 | 3761 | ### some:遍历集合,返回一个布尔值,指示是否有任何一个元素满足指定的函数 3762 | 3763 | ```js 3764 | 3765 | ``` 3766 | 3767 | 思路: 3768 | 3769 | **[⬆ 返回顶部](#lodash-函数列表)** 3770 | 3771 | ### sortBy:遍历集合,按指定顺序对每个元素调用指定函数,并返回结果 3772 | 3773 | ```js 3774 | 3775 | ``` 3776 | 3777 | 思路: 3778 | 3779 | **[⬆ 返回顶部](#lodash-函数列表)** 3780 | 3781 | ### toArray:将集合转化为一个数组 3782 | 3783 | ```js 3784 | 3785 | ``` 3786 | 3787 | 思路: 3788 | 3789 | **[⬆ 返回顶部](#lodash-函数列表)** 3790 | 3791 | ### toPairs:将集合转化为键值对数组 3792 | 3793 | ```js 3794 | 3795 | ``` 3796 | 3797 | 思路: 3798 | 3799 | **[⬆ 返回顶部](#lodash-函数列表)** 3800 | 3801 | ### union:将多个集合合并成一个集合,同时去重 3802 | 3803 | ```js 3804 | 3805 | ``` 3806 | 3807 | 思路: 3808 | 3809 | **[⬆ 返回顶部](#lodash-函数列表)** 3810 | 3811 | ### uniq:返回一个新集合,其中不包含重复的元素 3812 | 3813 | ```js 3814 | 3815 | ``` 3816 | 3817 | 思路: 3818 | 3819 | **[⬆ 返回顶部](#lodash-函数列表)** 3820 | 3821 | ### uniqBy:与 uniq 类似,但是根据指定函数进行去重 3822 | 3823 | ```js 3824 | 3825 | ``` 3826 | 3827 | 思路: 3828 | 3829 | **[⬆ 返回顶部](#lodash-函数列表)** 3830 | 3831 | ### uniqWith:与 uniq 类似,但是使用指定的比较函数进行去重 3832 | 3833 | ```js 3834 | 3835 | ``` 3836 | 3837 | 思路: 3838 | 3839 | **[⬆ 返回顶部](#lodash-函数列表)** 3840 | 3841 | ### unzip:将分组元素返回到打包前的结构 3842 | 3843 | ```js 3844 | 3845 | ``` 3846 | 3847 | 思路: 3848 | 3849 | **[⬆ 返回顶部](#lodash-函数列表)** 3850 | 3851 | ### unzipWith:与 unzip 类似,但是使用指定的函数对每个组元素进行合并 3852 | 3853 | ```js 3854 | 3855 | ``` 3856 | 3857 | 思路: 3858 | 3859 | **[⬆ 返回顶部](#lodash-函数列表)** 3860 | 3861 | ### without:返回一个新集合,其中不包含指定的元素 3862 | 3863 | ```js 3864 | 3865 | ``` 3866 | 3867 | 思路: 3868 | 3869 | **[⬆ 返回顶部](#lodash-函数列表)** 3870 | 3871 | ### xor:返回一个新集合,其中包含只在其中一个集合中出现的元素 3872 | 3873 | ```js 3874 | 3875 | ``` 3876 | 3877 | 思路: 3878 | 3879 | **[⬆ 返回顶部](#lodash-函数列表)** 3880 | 3881 | ### zip:将多个集合合并成一个元素组成的新集合,每个元素由每个原集合相应位置的元素组成 3882 | 3883 | ```js 3884 | 3885 | ``` 3886 | 3887 | 思路: 3888 | 3889 | **[⬆ 返回顶部](#lodash-函数列表)** 3890 | 3891 | ### zipObject:将键数组和值数组组合成一个新对象 3892 | 3893 | ```js 3894 | 3895 | ``` 3896 | 3897 | 思路: 3898 | 3899 | **[⬆ 返回顶部](#lodash-函数列表)** 3900 | 3901 | ### zipWith:与 zip 类似,但是使用指定的函数对每个组元素进行合并 3902 | 3903 | ```js 3904 | 3905 | ``` 3906 | 3907 | 思路: 3908 | 3909 | **[⬆ 返回顶部](#lodash-函数列表)** 3910 | 3911 | ## Ramda 3912 | 3913 | > 一款实用的 JavaScript 函数式编程库 3914 | 3915 | ### Ramda 函数列表 3916 | 3917 | ### JavaScript 高级程序设计第 4 版学习笔记 3918 | 3919 | ![](https://raw.githubusercontent.com/niexq/picbed/main/picgo/JavaScriptredbook.jpg) 3920 | 3921 | 👆 上述是截图,点击无效 👆 3922 | 3923 | 👇 详细内容请点击,或留言联系获取笔记源文件 👇 3924 | 语雀地址: 3925 | 3926 | ## 常见的 React 面试题 3927 | 3928 | ### React 面试题 3929 | 3930 | | 序号 | 问题 | 3931 | | ---- | ----------------------------------------------------------------------------------------------------- | 3932 | | 1 | [key 属性有什么作用?](#key-属性有什么作用) | 3933 | | 2 | [refs 属性有什么作用?](#refs-属性有什么作用) | 3934 | | 3 | [PureComponent 组件有什么作用?](#purecomponent-组件有什么作用) | 3935 | | 4 | [memo 方法有什么作用?](#memo-方法有什么作用) | 3936 | | 5 | [错误边界有什么作用?](#错误边界有什么作用) | 3937 | | 6 | [什么是受控组件和非受控组件?](#什么是受控组件和非受控组件) | 3938 | | 7 | [什么是高阶组件?](#什么是高阶组件) | 3939 | | 8 | [生命周期方法有哪些和它们的执行顺序是什么?](#生命周期方法有哪些和它们的执行顺序是什么) | 3940 | | 9 | [getDerivedStateFromProps 生命周期方法有什么作用?](#getderivedstatefromprops-生命周期方法有什么作用) | 3941 | | 10 | [shouldComponentUpdate 生命周期方法有什么作用?](#shouldcomponentupdate-生命周期方法有什么作用) | 3942 | | 11 | [getSnapshotBeforeUpdate 生命周期方法有什么作用?](#getsnapshotbeforeupdate-生命周期方法有什么作用) | 3943 | | 12 | [什么是 React context?](#什么是-react-context) | 3944 | | 13 | [React Hook 中的 useState 是什么?](#react-hook-中的-usestate-是什么) | 3945 | 3946 | 1. ### key 属性有什么作用? 3947 | 3948 | `key` 属性用于识别列表中的每个子元素,以便在添加、移动或删除元素时更有效地更新 DOM。`key` 属性应该是一个唯一的字符串,最好是基于列表中元素的唯一标识符生成的。 3949 | 3950 | ```jsx 3951 | function App() { 3952 | const items = [ 3953 | { id: 1, name: 'foo' }, 3954 | { id: 2, name: 'bar' }, 3955 | ]; 3956 | 3957 | return ( 3958 | 3963 | ); 3964 | } 3965 | ``` 3966 | 3967 | **[⬆ 返回顶部](#react-面试题)** 3968 | 3969 | 2. ### refs 属性有什么作用? 3970 | 3971 | `refs` 属性用于引用组件或 DOM 元素。它可以在函数组件和类组件中使用。 3972 | 3973 | ```jsx 3974 | function App() { 3975 | const buttonRef = useRef(); 3976 | 3977 | function handleClick() { 3978 | buttonRef.current.disabled = true; 3979 | } 3980 | 3981 | return ( 3982 | <> 3983 | 3986 | 3987 | ); 3988 | } 3989 | ``` 3990 | 3991 | **[⬆ 返回顶部](#react-面试题)** 3992 | 3993 | 3. ### PureComponent 组件有什么作用? 3994 | 3995 | `PureComponent` 是一个基于 Component 的优化版本,它会在 shouldComponentUpdate 生命周期方法中使用浅比较来判断是否需要重新渲染。如果所有的 props 和 state 都没有改变,`PureComponent` 将不会重新渲染。 3996 | 3997 | ```jsx 3998 | class MyComponent extends React.PureComponent { 3999 | render() { 4000 | return
Hello, {this.props.name}!
; 4001 | } 4002 | } 4003 | ``` 4004 | 4005 | **[⬆ 返回顶部](#react-面试题)** 4006 | 4007 | 4. ### memo 方法有什么作用? 4008 | 4009 | `memo` 方法是一个高阶组件,用于对函数组件进行浅比较优化。它接受一个函数组件并返回一个新的组件,该组件在 props 没有改变的情况下将使用以前的结果。 4010 | 4011 | ```jsx 4012 | function MyComponent(props) { 4013 | return
Hello, {props.name}!
; 4014 | } 4015 | 4016 | const MemoizedComponent = React.memo(MyComponent); 4017 | ``` 4018 | 4019 | **[⬆ 返回顶部](#react-面试题)** 4020 | 4021 | 5. ### 错误边界有什么作用? 4022 | 4023 | 错误边界是一种 React 组件,用于捕获和处理子组件的 JavaScript 错误。错误边界会捕获在渲染期间发生的错误,但不会捕获事件处理程序、异步代码和服务端渲染中的错误。 4024 | 4025 | ```jsx 4026 | class ErrorBoundary extends React.Component { 4027 | constructor(props) { 4028 | super(props); 4029 | this.state = { hasError: false }; 4030 | } 4031 | 4032 | static getDerivedStateFromError(error) { 4033 | return { hasError: true }; 4034 | } 4035 | 4036 | componentDidCatch(error, info) { 4037 | console.error(error, info); 4038 | } 4039 | 4040 | render() { 4041 | if (this.state.hasError) { 4042 | return
Something went wrong.
; 4043 | } 4044 | 4045 | return this.props.children; 4046 | } 4047 | 4048 | function App() { 4049 | return ( 4050 | 4051 | 4052 | 4053 | ); 4054 | } 4055 | ``` 4056 | 4057 | **[⬆ 返回顶部](#react-面试题)** 4058 | 4059 | 6. ### 什么是受控组件和非受控组件? 4060 | 4061 | 在 React 中,表单元素(如 input、textarea 和 select)通常分为受控组件和非受控组件。 4062 | 4063 | `受控组件`是指表单元素的值受到 React 组件的状态的控制。当用户输入内容时,React 会更新组件的状态,从而实时更新表单元素的值。此时,表单元素的值由 React 负责维护,而与 DOM 本身无关。受控组件通常需要实现 onChange 事件处理函数,以便在用户输入内容时更新组件的状态。 4064 | 4065 | 以下是一个使用受控组件的示例代码: 4066 | 4067 | ```jsx 4068 | class Input extends React.Component { 4069 | constructor(props) { 4070 | super(props); 4071 | this.state = { 4072 | value: '', 4073 | }; 4074 | } 4075 | 4076 | handleChange = event => { 4077 | this.setState({ 4078 | value: event.target.value, 4079 | }); 4080 | }; 4081 | 4082 | render() { 4083 | return ( 4084 | 4089 | ); 4090 | } 4091 | } 4092 | ``` 4093 | 4094 | 在上述代码中,我们定义了一个 Input 组件作为受控组件,它的值由组件的状态 value 控制。当用户输入内容时,onChange 事件处理函数 handleChange 会被调用,更新组件的状态,从而实时更新表单元素的值。 4095 | 4096 | 相反,`非受控组件`是指表单元素的值由 DOM 本身维护,而不受 React 组件的控制。当用户输入内容时,表单元素的值会直接更新到 DOM 上,而不需要在 React 中进行任何处理。此时,组件无法直接获取表单元素的值,而需要通过 ref 来获取。 4097 | 4098 | 以下是一个使用非受控组件的示例代码: 4099 | 4100 | ```jsx 4101 | class Input extends React.Component { 4102 | constructor(props) { 4103 | super(props); 4104 | this.inputRef = React.createRef(); 4105 | } 4106 | 4107 | handleClick = () => { 4108 | console.log(this.inputRef.current.value); 4109 | }; 4110 | 4111 | render() { 4112 | return ( 4113 |
4114 | 4115 | 4116 |
4117 | ); 4118 | } 4119 | } 4120 | ``` 4121 | 4122 | 在上述代码中,我们定义了一个 Input 组件作为非受控组件,它的值由 DOM 本身维护。当用户点击按钮时,我们可以通过 ref 获取表单元素的值,从而进行后续处理。 4123 | 4124 | **[⬆ 返回顶部](#react-面试题)** 4125 | 4126 | 7. ### 什么是高阶组件 4127 | 4128 | `高阶组件`(Higher-Order Component,简称 HOC)是指一个函数,它接受一个组件作为参数,返回一个新的组件。HOC 本质上是一种组件复用的方式,用于增强组件的功能,或者封装一些通用的逻辑,从而实现代码复用。 4129 | 4130 | 以下是一个使用高阶组件的示例代码: 4131 | 4132 | ```jsx 4133 | function withLogger(WrappedComponent) { 4134 | return class extends React.Component { 4135 | componentDidMount() { 4136 | console.log(`Component ${WrappedComponent.name} mounted`); 4137 | } 4138 | 4139 | render() { 4140 | return ; 4141 | } 4142 | }; 4143 | } 4144 | 4145 | class MyComponent extends React.Component { 4146 | render() { 4147 | return
Hello, world!
; 4148 | } 4149 | } 4150 | 4151 | const EnhancedComponent = withLogger(MyComponent); 4152 | 4153 | ReactDOM.render(, document.getElementById('root')); 4154 | ``` 4155 | 4156 | 在上述代码中,我们定义了一个高阶组件 withLogger,它接受一个组件作为参数,返回一个新的组件。新的组件在挂载时会输出组件名称,然后渲染传入的组件。 4157 | 4158 | 我们定义了一个 MyComponent 组件,并使用 withLogger 高阶组件对其进行增强,得到一个新的增强后的组件 EnhancedComponent。最终,我们将 EnhancedComponent 渲染到 DOM 中。 4159 | 4160 | 在上述示例中,withLogger 高阶组件用于记录组件的挂载信息,以便在开发调试时更加方便。当我们需要对多个组件进行类似的操作时,就可以使用 withLogger 高阶组件,而不需要在每个组件中都编写相同的挂载逻辑。这样,我们就可以实现代码的复用,同时使代码更加简洁易懂。 4161 | 4162 | **[⬆ 返回顶部](#react-面试题)** 4163 | 4164 | 8. ### 生命周期方法有哪些和它们的执行顺序是什么? 4165 | 4166 | React 中的生命周期方法可以分为三类:挂载、更新和卸载。它们的执行顺序如下: 4167 | 4168 | - 挂载: 4169 | - constructor 4170 | - getDerivedStateFromProps 4171 | - render 4172 | - componentDidMount 4173 | - 更新: 4174 | - getDerivedStateFromProps 4175 | - shouldComponentUpdate 4176 | - render 4177 | - getSnapshotBeforeUpdate 4178 | - componentDidUpdate 4179 | - 卸载: 4180 | - componentWillUnmount 4181 | 4182 | 其中,`getDerivedStateFromProps` 生命周期方法是一个静态方法,用于在 props 改变时更新组件的 state。它应该返回一个对象来更新 state,或者返回 null 来表示不需要更新。 4183 | 4184 | ```jsx 4185 | class MyComponent extends React.Component { 4186 | static getDerivedStateFromProps(props, state) { 4187 | if (props.value !== state.value) { 4188 | return { value: props.value }; 4189 | } 4190 | return null; 4191 | } 4192 | 4193 | constructor(props) { 4194 | super(props); 4195 | this.state = { value: props.value }; 4196 | } 4197 | 4198 | render() { 4199 | return
{this.state.value}
; 4200 | } 4201 | } 4202 | ``` 4203 | 4204 | 其次,`shouldComponentUpdate` 生命周期方法用于在 props 或 state 发生改变时决定是否需要重新渲染组件。它应该返回一个布尔值,表示组件是否需要更新。默认情况下,`shouldComponentUpdate` 返回 true。 4205 | 4206 | ```jsx 4207 | class MyComponent extends React.Component { 4208 | shouldComponentUpdate(nextProps, nextState) { 4209 | return this.props.value !== nextProps.value; 4210 | } 4211 | 4212 | render() { 4213 | return
{this.props.value}
; 4214 | } 4215 | } 4216 | ``` 4217 | 4218 | 最后,`getSnapshotBeforeUpdate` 生命周期方法在组件更新之前被调用,它可以用于在 DOM 更新之前捕获一些信息。它应该返回一个值,作为 componentDidUpdate 方法的第三个参数。 4219 | 4220 | ```jsx 4221 | class MyComponent extends React.Component { 4222 | constructor(props) { 4223 | super(props); 4224 | this.listRef = React.createRef(); 4225 | } 4226 | 4227 | getSnapshotBeforeUpdate(prevProps, prevState) { 4228 | if (prevProps.items.length < this.props.items.length) { 4229 | const list = this.listRef.current; 4230 | return list.scrollHeight - list.scrollTop; 4231 | } 4232 | return null; 4233 | } 4234 | 4235 | componentDidUpdate(prevProps, prevState, snapshot) { 4236 | if (snapshot !== null) { 4237 | const list = this.listRef.current; 4238 | list.scrollTop = list.scrollHeight - snapshot; 4239 | } 4240 | } 4241 | 4242 | render() { 4243 | return ( 4244 |
4245 | {this.props.items.map(item => ( 4246 |
{item.name}
4247 | ))} 4248 |
4249 | ); 4250 | } 4251 | } 4252 | ``` 4253 | 4254 | **[⬆ 返回顶部](#react-面试题)** 4255 | 4256 | 9. ### getDerivedStateFromProps 生命周期方法有什么作用? 4257 | 4258 | `getDerivedStateFromProps` 是 React 组件的一个静态生命周期方法。 4259 | 其主要作用是让组件在接收到新的`props`时,能够同步更新组件的`state`。 4260 | 4261 | 此方法在以下两种情况下会被 React 调用: 4262 | 在组件实例化后、渲染(render)前 4263 | 4264 | 在接收新的`props`之前,无论是父组件引起的还是通过外部 API 获取的`props`变化 4265 | `getDerivedStateFromProps`方法接收两个参数: 4266 | 4267 | `props`:最新的`props` 4268 | 4269 | `state`:当前的`state` 4270 | 4271 | 该方法应该返回一个对象来更新`state`,或者返回`null`以不更新任何状态。 4272 | 4273 | ```jsx 4274 | class MyComponent extends React.Component { 4275 | static getDerivedStateFromProps(props, state) { 4276 | // 你可以基于props的变化来更新state 4277 | if (props.value !== state.value) { 4278 | return { 4279 | value: props.value, 4280 | }; 4281 | } 4282 | 4283 | // 返回 null 不更新 state 4284 | return null; 4285 | } 4286 | } 4287 | ``` 4288 | 4289 | `关键点`: 4290 | `getDerivedStateFromProps`是一个纯函数,不应该产生副作用,如进行网络请求或订阅。 4291 | 4292 | 这个方法不推荐频繁使用,因为它可能会导致代码变得复杂和难以维护。 4293 | 4294 | 在许多场景下,可以使用其他生命周期方法或 React 新引入的`Hooks`。 4295 | 4296 | 当 props 的改变需要映射到`state`时,可以考虑使用它,但在不少情况下,可以直接从`props`计算得到渲染内容而不需要使用`state`。 4297 | 4298 | 自 React `v16.3`引入此方法,同时遗弃了`componentWillReceiveProps`,这是因为它更安全,不会被未来的异步渲染特性影响。在 React 的未来版本中,使用基于类的生命周期方法将逐渐让位于使用`Hooks`的函数式组件。 4299 | 4300 | **[⬆ 返回顶部](#react-面试题)** 4301 | 4302 | 10. ### shouldComponentUpdate 生命周期方法有什么作用? 4303 | 4304 | `shouldComponentUpdate` 是 React 类组件的一个生命周期方法,其主要作用是决定一个组件的输出是否需要更新,即当组件的`props`或`state`变化时,`shouldComponentUpdate`方法会在渲染执行前被调用,用以指示 React 是否应该继续执行渲染过程。 4305 | 4306 | 这个方法默认返回 `true`,意味着每次状态变化组件都会重新渲染。但是,通过返回 `false`,你可以阻止组件不必要的渲染,这可以提高应用的性能,尤其是当组件树非常大时或者计算量很大时。 4307 | 4308 | `shouldComponentUpdate` 接收两个参数: 4309 | 4310 | `nextProps`:将要接收的新的`props` 4311 | 4312 | `nextState`:将要更新的新的`state` 4313 | 4314 | ```jsx 4315 | class MyComponent extends React.Component { 4316 | shouldComponentUpdate(nextProps, nextState) { 4317 | // 比较新旧props或state,只有当它们实际发生变化时才更新组件 4318 | return ( 4319 | nextProps.someValue !== this.props.someValue || 4320 | nextState.someState !== this.state.someState 4321 | ); 4322 | } 4323 | } 4324 | ``` 4325 | 4326 | `注意`: 4327 | 4328 | 它只在组件`更新过程中`被调用,不会在`首次渲染时`被调用。 4329 | 4330 | 如果返回`false`,那么组件不会执行更新操作,`render`方法不会被调用,同时也跳过子组件的渲染。 4331 | 它不应该产生任何副作用,应该是一个纯函数。 4332 | 4333 | 在大多数情况下,你不需要手动去编写`shouldComponentUpdate`方法。如果你需要优化性能,推荐使用`React.PureComponent`,它已经实现了一个和`shouldComponentUpdate`类似的浅层比较。 4334 | 4335 | 从 React `16.3`版本开始,引入了新的“生命周期”API. 如果你在使用新的生命周期方法,或者打算迁移到函数组件和`Hooks`,`shouldComponentUpdate`可能会变得不再常用 4336 | 4337 | 特别是`React.memo`对于函数组件是类似 PureComponent 的工作方式,提供了类似的性能提升。 4338 | 4339 | **[⬆ 返回顶部](#react-面试题)** 4340 | 4341 | 11. ### getSnapshotBeforeUpdate 生命周期方法有什么作用? 4342 | 4343 | `getSnapshotBeforeUpdate`是 React 类组件中的一个生命周期方法,它允许您在最新的渲染输出被提交到`DOM`之前,捕获组件的某些信息(例如,滚动位置)。 4344 | 4345 | 这个生命周期方法在新的渲染输出被绘制之前被调用,它可以返回一个值或`null`。如果返回的值不是`null`,这个返回的值将会作为第三个参数传递给`componentDidUpdate`。 4346 | 4347 | 这种机制特别有用,因为有时候更新`DOM`后,您可能需要根据之前的状态来调整滚动位置,或执行类似的操作以保持用户的视图状态不变。 4348 | 4349 | `getSnapshotBeforeUpdate` 接收两个参数: 4350 | 4351 | `prevProps`:更新前的`props` 4352 | 4353 | `prevState`:更新前的`state` 4354 | 4355 | 如果你的组件没有使用`getSnapshotBeforeUpdate`,就不需要实现它;只有当你确实需要在更新前捕获一些信息,并在更新后应用这些信息时,才使用它。 4356 | 4357 | ```jsx 4358 | class MyComponent extends React.Component { 4359 | getSnapshotBeforeUpdate(prevProps, prevState) { 4360 | // 检查 prevProps 或 prevState 是否满足特定条件 4361 | // 例如,你可以捕获旧的滚动位置: 4362 | if (prevProps.list.length < this.props.list.length) { 4363 | const list = document.getElementById('list'); 4364 | return list.scrollHeight - list.scrollTop; 4365 | } 4366 | return null; 4367 | } 4368 | 4369 | componentDidUpdate(prevProps, prevState, snapshot) { 4370 | // 如果 `getSnapshotBeforeUpdate` 返回的 `snapshot` 不是 `null` 4371 | // 可以使用 `snapshot` 做一些事情 4372 | if (snapshot !== null) { 4373 | const list = document.getElementById('list'); 4374 | list.scrollTop = list.scrollHeight - snapshot; 4375 | } 4376 | } 4377 | } 4378 | ``` 4379 | 4380 | 在上面的示例中,`getSnapshotBeforeUpdate`用于捕获增加新项到列表前的滚动位置,然后通过`componentDidUpdate`使用这个快照信息(`snapshot`)来调整滚动条,以保持滚动位置相对于底部的距离不变,即使列表长度发生变化。 4381 | 4382 | 需要注意的是,`getSnapshotBeforeUpdate`和`componentDidUpdate`一起使用时,可以很好地处理那些需要在 DOM 更新后立即执行的操作。 4383 | 4384 | **[⬆ 返回顶部](#react-面试题)** 4385 | 4386 | 12. ### 什么是 React context? 4387 | 4388 | React context 是一种跨组件层级共享数据的方式,可以避免通过 props 层层传递数据的麻烦。它由两部分组成:`Provider` 和 `Consumer`。 4389 | 4390 | `Provider` 是一个组件,它接受一个 value 属性,表示共享的数据,将它包裹的子组件的 `Consumer` 都可以访问这个数据。 4391 | 4392 | ```jsx 4393 | const MyContext = React.createContext(); 4394 | 4395 | class MyComponent extends React.Component { 4396 | render() { 4397 | return ( 4398 | 4399 | 4400 | 4401 | ); 4402 | } 4403 | } 4404 | 4405 | function ChildComponent() { 4406 | return ( 4407 | 4408 | {value =>
The answer is {value}.
} 4409 |
4410 | ); 4411 | } 4412 | ``` 4413 | 4414 | **[⬆ 返回顶部](#react-面试题)** 4415 | 4416 | 13. ### React Hook 中的 useState 是什么? 4417 | 4418 | useState 是 React Hook 中的一种,它可以让我们在函数组件中使用状态。 4419 | 4420 | ```jsx 4421 | import React, { useState } from 'react'; 4422 | 4423 | function Counter() { 4424 | const [count, setCount] = useState(0); 4425 | return ( 4426 |
4427 |

{count}

4428 | 4429 |
4430 | ); 4431 | } 4432 | ``` 4433 | 4434 | **[⬆ 返回顶部](#react-面试题)** 4435 | -------------------------------------------------------------------------------- /__tests__/lodash.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable import/no-unresolved */ 3 | /* eslint-disable no-undef */ 4 | /* eslint-disable import/extensions */ 5 | /* eslint-disable import/no-unresolved */ 6 | import _ from 'lodash'; 7 | import { 8 | chunk, 9 | compact, 10 | concat, 11 | difference, 12 | differenceBy, 13 | // differenceWith, 14 | drop, 15 | dropRight, 16 | dropRightWhile, 17 | fill, 18 | findIndex, 19 | findLastIndex, 20 | head, 21 | flatten, 22 | flattenDeep, 23 | fromPairs, 24 | indexOf, 25 | initial, 26 | intersection, 27 | join, 28 | last, 29 | // lastIndexOf, 30 | pull, 31 | // pullAt, 32 | reverse, 33 | // slice, 34 | sortedIndex, 35 | } from '../src/lodash'; 36 | 37 | describe('lodash all function', () => { 38 | test("chunk(['a', 'b', 'c', 'd'], 2)", () => { 39 | expect(chunk(['a', 'b', 'c', 'd'], 2)).toStrictEqual( 40 | _.chunk(['a', 'b', 'c', 'd'], 2) 41 | ); 42 | }); 43 | test("chunk(['a', 'b', 'c', 'd'], 3)", () => { 44 | expect(chunk(['a', 'b', 'c', 'd'], 3)).toStrictEqual( 45 | _.chunk(['a', 'b', 'c', 'd'], 3) 46 | ); 47 | }); 48 | 49 | test("compact([0, 1, false, 2, '', 3])", () => { 50 | expect(compact([0, 1, false, 2, '', 3])).toStrictEqual( 51 | _.compact([0, 1, false, 2, '', 3]) 52 | ); 53 | }); 54 | 55 | test('concat([1], 2, [3], [[4]])', () => { 56 | expect(concat([1], 2, [3], [[4]])).toStrictEqual( 57 | _.concat([1], 2, [3], [[4]]) 58 | ); 59 | }); 60 | 61 | test('difference([3, 2, 1], [4, 2])', () => { 62 | expect(difference([3, 2, 1], [4, 2])).toStrictEqual( 63 | _.difference([3, 2, 1], [4, 2]) 64 | ); 65 | }); 66 | 67 | test('differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor)', () => { 68 | expect(differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor)).toStrictEqual( 69 | _.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor) 70 | ); 71 | }); 72 | test("differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x')", () => { 73 | expect(differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], 'x')).toStrictEqual( 74 | _.differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], 'x') 75 | ); 76 | }); 77 | 78 | // test("differenceWith(objects, [{ 'x': 1, 'y': 2 }], isEqual)", () => { 79 | // expect(differenceWith(objects, [{ 'x': 1, 'y': 2 }], isEqual)).toStrictEqual(_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x')); 80 | // }); 81 | 82 | test('drop([1, 2, 3])', () => { 83 | expect(drop([1, 2, 3])).toStrictEqual(_.drop([1, 2, 3])); 84 | }); 85 | test('drop([1, 2, 3], 2)', () => { 86 | expect(drop([1, 2, 3], 2)).toStrictEqual(_.drop([1, 2, 3], 2)); 87 | }); 88 | test('drop([1, 2, 3], 5)', () => { 89 | expect(drop([1, 2, 3], 5)).toStrictEqual(_.drop([1, 2, 3], 5)); 90 | }); 91 | test('drop([1, 2, 3], 0)', () => { 92 | expect(drop([1, 2, 3], 0)).toStrictEqual(_.drop([1, 2, 3], 0)); 93 | }); 94 | 95 | test('dropRight([1, 2, 3])', () => { 96 | expect(dropRight([1, 2, 3])).toStrictEqual(_.dropRight([1, 2, 3])); 97 | }); 98 | test('dropRight([1, 2, 3], 2)', () => { 99 | expect(dropRight([1, 2, 3], 2)).toStrictEqual(_.dropRight([1, 2, 3], 2)); 100 | }); 101 | test('dropRight([1, 2, 3], 5)', () => { 102 | expect(dropRight([1, 2, 3], 5)).toStrictEqual(_.dropRight([1, 2, 3], 5)); 103 | }); 104 | test('dropRight([1, 2, 3], 0)', () => { 105 | expect(dropRight([1, 2, 3], 0)).toStrictEqual(_.dropRight([1, 2, 3], 0)); 106 | }); 107 | 108 | test('dropRightWhile(users, (o) => !o.active)', () => { 109 | const users = [ 110 | { user: 'barney', active: true }, 111 | { user: 'fred', active: false }, 112 | { user: 'pebbles', active: false }, 113 | ]; 114 | expect(dropRightWhile(users, o => !o.active)).toStrictEqual( 115 | _.dropRightWhile(users, o => !o.active) 116 | ); 117 | }); 118 | test("dropRightWhile(users, { 'user': 'pebbles', 'active': false })", () => { 119 | const users = [ 120 | { user: 'barney', active: true }, 121 | { user: 'fred', active: false }, 122 | { user: 'pebbles', active: false }, 123 | ]; 124 | expect( 125 | dropRightWhile(users, { user: 'pebbles', active: false }) 126 | ).toStrictEqual( 127 | _.dropRightWhile(users, { user: 'pebbles', active: false }) 128 | ); 129 | }); 130 | test("dropRightWhile(users, ['active', false])", () => { 131 | const users = [ 132 | { user: 'barney', active: true }, 133 | { user: 'fred', active: false }, 134 | { user: 'pebbles', active: false }, 135 | ]; 136 | expect(dropRightWhile(users, ['active', false])).toStrictEqual( 137 | _.dropRightWhile(users, ['active', false]) 138 | ); 139 | }); 140 | test("dropRightWhile(users, 'active')", () => { 141 | const users = [ 142 | { user: 'barney', active: true }, 143 | { user: 'fred', active: false }, 144 | { user: 'pebbles', active: false }, 145 | ]; 146 | expect(dropRightWhile(users, 'active')).toStrictEqual( 147 | _.dropRightWhile(users, 'active') 148 | ); 149 | }); 150 | 151 | test("fill(array, 'a')", () => { 152 | const array = [1, 2, 3]; 153 | expect(fill(array, 'a')).toStrictEqual(_.fill(array, 'a')); 154 | }); 155 | test('fill(Array(3), 2)', () => { 156 | expect(fill(Array(3), 2)).toStrictEqual(_.fill(Array(3), 2)); 157 | }); 158 | test("fill([4, 6, 8, 10], '*', 1, 3)", () => { 159 | expect(fill([4, 6, 8, 10], '*', 1, 3)).toStrictEqual( 160 | _.fill([4, 6, 8, 10], '*', 1, 3) 161 | ); 162 | }); 163 | 164 | test("findIndex(users, function(o) { return o.user == 'barney'; })", () => { 165 | const users = [ 166 | { user: 'barney', active: false }, 167 | { user: 'fred', active: false }, 168 | { user: 'pebbles', active: true }, 169 | ]; 170 | expect(findIndex(users, o => o.user === 'barney')).toStrictEqual( 171 | _.findIndex(users, o => o.user === 'barney') 172 | ); 173 | }); 174 | test("findIndex(users, { 'user': 'fred', 'active': false })", () => { 175 | const users = [ 176 | { user: 'barney', active: false }, 177 | { user: 'fred', active: false }, 178 | { user: 'pebbles', active: true }, 179 | ]; 180 | expect(findIndex(users, { user: 'fred', active: false })).toStrictEqual( 181 | _.findIndex(users, { user: 'fred', active: false }) 182 | ); 183 | }); 184 | test("findIndex(users, ['active', false])", () => { 185 | const users = [ 186 | { user: 'barney', active: false }, 187 | { user: 'fred', active: false }, 188 | { user: 'pebbles', active: true }, 189 | ]; 190 | expect(findIndex(users, ['active', false])).toStrictEqual( 191 | _.findIndex(users, ['active', false]) 192 | ); 193 | }); 194 | test("findIndex(users, 'active')", () => { 195 | const users = [ 196 | { user: 'barney', active: false }, 197 | { user: 'fred', active: false }, 198 | { user: 'pebbles', active: true }, 199 | ]; 200 | expect(findIndex(users, 'active')).toStrictEqual( 201 | _.findIndex(users, 'active') 202 | ); 203 | }); 204 | 205 | test("findLastIndex(users, function(o) { return o.user == 'pebbles'; })", () => { 206 | const users = [ 207 | { user: 'barney', active: true }, 208 | { user: 'fred', active: false }, 209 | { user: 'pebbles', active: false }, 210 | ]; 211 | expect(findLastIndex(users, o => o.user === 'pebbles')).toStrictEqual( 212 | _.findLastIndex(users, o => o.user === 'pebbles') 213 | ); 214 | }); 215 | test("findIndex(users, { 'user': 'fred', 'active': false })", () => { 216 | const users = [ 217 | { user: 'barney', active: true }, 218 | { user: 'fred', active: false }, 219 | { user: 'pebbles', active: false }, 220 | ]; 221 | expect(findIndex(users, { user: 'fred', active: false })).toStrictEqual( 222 | _.findIndex(users, { user: 'fred', active: false }) 223 | ); 224 | }); 225 | test("findIndex(users, ['active', false])", () => { 226 | const users = [ 227 | { user: 'barney', active: true }, 228 | { user: 'fred', active: false }, 229 | { user: 'pebbles', active: false }, 230 | ]; 231 | expect(findIndex(users, ['active', false])).toStrictEqual( 232 | _.findIndex(users, ['active', false]) 233 | ); 234 | }); 235 | test("findIndex(users, 'active')", () => { 236 | const users = [ 237 | { user: 'barney', active: true }, 238 | { user: 'fred', active: false }, 239 | { user: 'pebbles', active: false }, 240 | ]; 241 | expect(findIndex(users, 'active')).toStrictEqual( 242 | _.findIndex(users, 'active') 243 | ); 244 | }); 245 | 246 | test("findLastIndex(users, (o) => o.user === 'pebbles')", () => { 247 | const users = [ 248 | { user: 'barney', active: true }, 249 | { user: 'fred', active: false }, 250 | { user: 'pebbles', active: false }, 251 | ]; 252 | expect(findLastIndex(users, o => o.user === 'pebbles')).toStrictEqual( 253 | _.findLastIndex(users, o => o.user === 'pebbles') 254 | ); 255 | }); 256 | test("findLastIndex(users, { 'user': 'barney', 'active': true })", () => { 257 | const users = [ 258 | { user: 'barney', active: true }, 259 | { user: 'fred', active: false }, 260 | { user: 'pebbles', active: false }, 261 | ]; 262 | expect( 263 | findLastIndex(users, { user: 'barney', active: true }) 264 | ).toStrictEqual(_.findLastIndex(users, { user: 'barney', active: true })); 265 | }); 266 | test("findLastIndex(users, ['active', false])", () => { 267 | const users = [ 268 | { user: 'barney', active: true }, 269 | { user: 'fred', active: false }, 270 | { user: 'pebbles', active: false }, 271 | ]; 272 | expect(findLastIndex(users, ['active', false])).toStrictEqual( 273 | _.findLastIndex(users, ['active', false]) 274 | ); 275 | }); 276 | test("findLastIndex(users, 'active')", () => { 277 | const users = [ 278 | { user: 'barney', active: true }, 279 | { user: 'fred', active: false }, 280 | { user: 'pebbles', active: false }, 281 | ]; 282 | expect(findLastIndex(users, 'active')).toStrictEqual( 283 | _.findLastIndex(users, 'active') 284 | ); 285 | }); 286 | 287 | test('flatten([1, [2, [3, [4]], 5]])', () => { 288 | expect(flatten([1, [2, [3, [4]], 5]])).toStrictEqual( 289 | _.flatten([1, [2, [3, [4]], 5]]) 290 | ); 291 | }); 292 | 293 | test('head([1, 2, 3])', () => { 294 | expect(head([1, 2, 3])).toStrictEqual(_.head([1, 2, 3])); 295 | }); 296 | test('head([])', () => { 297 | expect(head([])).toStrictEqual(_.head([])); 298 | }); 299 | 300 | test('flattenDeep([1, [2, [3, [4]], 5]])', () => { 301 | expect(flattenDeep([1, [2, [3, [4]], 5]])).toStrictEqual( 302 | _.flattenDeep([1, [2, [3, [4]], 5]]) 303 | ); 304 | }); 305 | 306 | test("fromPairs([['a', 1], ['b', 2]])", () => { 307 | expect( 308 | fromPairs([ 309 | ['a', 1], 310 | ['b', 2], 311 | ]) 312 | ).toStrictEqual( 313 | _.fromPairs([ 314 | ['a', 1], 315 | ['b', 2], 316 | ]) 317 | ); 318 | }); 319 | 320 | test('indexOf([1, 2, 1, 2], 2)', () => { 321 | expect(indexOf([1, 2, 1, 2], 2)).toStrictEqual(_.indexOf([1, 2, 1, 2], 2)); 322 | }); 323 | test('indexOf([1, 2, 1, 2], 2, 2)', () => { 324 | expect(indexOf([1, 2, 1, 2], 2, 2)).toStrictEqual( 325 | _.indexOf([1, 2, 1, 2], 2, 2) 326 | ); 327 | }); 328 | 329 | test('initial([1, 2, 3])', () => { 330 | expect(initial([1, 2, 3])).toStrictEqual(_.initial([1, 2, 3])); 331 | }); 332 | 333 | test('intersection([2, 1], [4, 2], [1, 2])', () => { 334 | expect(intersection([2, 1], [4, 2], [1, 2])).toStrictEqual( 335 | _.intersection([2, 1], [4, 2], [1, 2]) 336 | ); 337 | }); 338 | 339 | test("join(['a', 'b', 'c'], '~')", () => { 340 | expect(join(['a', 'b', 'c'], '~')).toStrictEqual( 341 | _.join(['a', 'b', 'c'], '~') 342 | ); 343 | }); 344 | 345 | test('last([1, 2, 3])', () => { 346 | expect(last([1, 2, 3])).toStrictEqual(_.last([1, 2, 3])); 347 | }); 348 | 349 | // TODO: no pass 350 | // test("lastIndexOf([1, 2, 1, 2], 2)", () => { 351 | // expect(lastIndexOf([1, 2, 1, 2], 2)).toStrictEqual(_.lastIndexOf([1, 2, 1, 2], 2)); 352 | // }); 353 | // test("lastIndexOf([1, 2, 1, 2], 2, 2)", () => { 354 | // expect(lastIndexOf([1, 2, 1, 2], 2, 2)).toStrictEqual(_.lastIndexOf([1, 2, 1, 2], 2, 2)); 355 | // }); 356 | 357 | test('pull(array, 2, 3)', () => { 358 | const array = [1, 2, 3, 1, 2, 3]; 359 | expect(pull(array, 2, 3)).toStrictEqual(_.pull(array, 2, 3)); 360 | }); 361 | 362 | // TODO: no pass 363 | // test("pullAt(array, 1, 3)", () => { 364 | // const array = [5, 10, 15, 20]; 365 | // expect(pullAt(array, 1, 3)).toStrictEqual(_.pullAt(array, 1, 3)); 366 | // }); 367 | 368 | test('reverse(array)', () => { 369 | const array = [1, 2, 3]; 370 | expect(reverse(array)).toStrictEqual(_.reverse(array)); 371 | }); 372 | 373 | test('sortedIndex([30, 50], 40)', () => { 374 | expect(sortedIndex([30, 50], 40)).toStrictEqual( 375 | _.sortedIndex([30, 50], 40) 376 | ); 377 | }); 378 | }); 379 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['**/__tests__/**/*.test.js'], 3 | modulePaths: ['/src'], 4 | transform: { 5 | '^.+\\.js$': 'babel-jest', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coding-interview-questions", 3 | "version": "1.0.0", 4 | "description": "前端常用工具库及高频面试题", 5 | "keywords": [ 6 | "interview", 7 | "interview-questions", 8 | "qianduan", 9 | "qianduanmianshiti", 10 | "javascript", 11 | "react", 12 | "node", 13 | "lodash", 14 | "ramda" 15 | ], 16 | "main": "dist/index.js", 17 | "scripts": { 18 | "test": "jest", 19 | "dev": "esbuild src/index.ts --watch --bundle --minify --sourcemap --platform=node --outfile=dist/index.js", 20 | "dev-tsc": "tsc -w", 21 | "lint": "eslint --ext .ts,.js packages/ --max-warnings 0", 22 | "prettier": "prettier .", 23 | "format": "npm run prettier -- --write" 24 | }, 25 | "dependencies": { 26 | "lodash": "^4.17.21" 27 | }, 28 | "devDependencies": { 29 | "@babel/preset-env": "^7.20.2", 30 | "@types/node": "^18.14.1", 31 | "assert": "^2.0.0", 32 | "babel-jest": "^29.4.3", 33 | "esbuild": "^0.15.10", 34 | "eslint": "^7.32.0 || ^8.2.0", 35 | "eslint-config-airbnb-base": "^15.0.0", 36 | "eslint-config-prettier": "^8.5.0", 37 | "eslint-plugin-import": "^2.27.5", 38 | "eslint-plugin-markdown": "^3.0.0", 39 | "eslint-plugin-prettier": "^4.2.1", 40 | "jest": "^29.4.3", 41 | "lint-staged": "^13.0.3", 42 | "prettier": "^2.8.4", 43 | "typescript": "^4.9.5", 44 | "yorkie": "^2.0.0" 45 | }, 46 | "gitHooks": { 47 | "pre-commit": "lint-staged" 48 | }, 49 | "lint-staged": { 50 | "*.{js,json,yml,yaml,css,scss,ts,tsx,md}": [ 51 | "prettier --write" 52 | ], 53 | "*.{ts,tsx,js,jsx}": [ 54 | "eslint --cache --fix --max-warnings 0" 55 | ] 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "git+https://github.com/niexq/coding-interview-questions.git" 60 | }, 61 | "author": "niexq", 62 | "license": "MIT", 63 | "bugs": { 64 | "url": "https://github.com/niexq/coding-interview-questions/issues" 65 | }, 66 | "homepage": "https://github.com/niexq/coding-interview-questions#readme" 67 | } 68 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as lodash } from './lodash'; 2 | -------------------------------------------------------------------------------- /src/lodash.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable consistent-return */ 2 | /* eslint-disable no-loop-func */ 3 | /* eslint-disable no-plusplus */ 4 | /* eslint-disable max-len */ 5 | const chunk = (arr, size) => 6 | Array.from({ length: Math.ceil(arr.length / size) }, (v, i) => 7 | arr.slice(i * size, i * size + size) 8 | ); 9 | const compact = arr => arr.filter(Boolean); 10 | const concat = (...args) => [].concat(...args); 11 | const difference = (arr, ...args) => 12 | arr.filter(item => args.every(arg => !arg.includes(item))); 13 | const differenceBy = (array, values, iteratee) => { 14 | const fn = typeof iteratee === 'function' ? iteratee : item => item[iteratee]; 15 | const valuesSet = new Set(values.map(fn)); 16 | return array.filter(item => !valuesSet.has(fn(item))); 17 | }; 18 | const differenceWith = (array, values, comparator) => 19 | array.filter(item => !values.some(value => comparator(item, value))); 20 | const drop = (arr, n = 1) => arr.slice(n); 21 | const dropRight = (arr, n = 1) => 22 | n >= arr.length ? [] : arr.slice(0, arr.length - n); 23 | const dropRightWhile = (array, iteratee) => { 24 | let right = array.length - 1; 25 | if (typeof iteratee === 'function') { 26 | while (iteratee(array[right])) { 27 | right--; 28 | } 29 | } 30 | if (typeof iteratee === 'object' && !Array.isArray(iteratee)) { 31 | const entries = Object.entries(iteratee); 32 | while (entries.every(([key, value]) => array[right][key] === value)) { 33 | right--; 34 | } 35 | } 36 | if (Array.isArray(iteratee) && iteratee.length === 2) { 37 | const [key, value] = iteratee; 38 | while (array[right][key] === value) { 39 | right--; 40 | } 41 | } 42 | return array.slice(0, right + 1); 43 | }; 44 | const fill = (arr, value, start = 0, end = arr.length) => 45 | arr.fill(value, start, end); 46 | const findIndex = (arr, iteratee) => { 47 | if (typeof iteratee === 'function') { 48 | return arr.findIndex(iteratee); 49 | } 50 | if (Array.isArray(iteratee) && iteratee.length === 2) { 51 | const [key, value] = iteratee; 52 | return arr.findIndex(item => item[key] === value); 53 | } 54 | if (typeof iteratee === 'string') { 55 | return arr.findIndex(item => item[iteratee]); 56 | } 57 | if (typeof iteratee === 'object') { 58 | return arr.findIndex(item => 59 | Object.keys(iteratee).every(key => item[key] === iteratee[key]) 60 | ); 61 | } 62 | }; 63 | const findLastIndex = (arr, predicate) => { 64 | if (typeof predicate === 'function') { 65 | for (let i = arr.length - 1; i >= 0; i--) { 66 | if (predicate(arr[i], i, arr)) { 67 | return i; 68 | } 69 | } 70 | } else if (Array.isArray(predicate)) { 71 | const [key, value] = predicate; 72 | for (let i = arr.length - 1; i >= 0; i--) { 73 | if (arr[i][key] === value) { 74 | return i; 75 | } 76 | } 77 | } else if (typeof predicate === 'object') { 78 | for (let i = arr.length - 1; i >= 0; i--) { 79 | const keys = Object.keys(predicate); 80 | const match = keys.every(key => predicate[key] === arr[i][key]); 81 | if (match) { 82 | return i; 83 | } 84 | } 85 | } else { 86 | for (let i = arr.length - 1; i >= 0; i--) { 87 | if (arr[i] && arr[i][predicate]) { 88 | return i; 89 | } 90 | } 91 | } 92 | return -1; 93 | }; 94 | const head = arr => arr[0]; 95 | const flatten = arr => [].concat(...arr); 96 | const flattenDeep = arr => 97 | [].concat(...arr.map(v => (Array.isArray(v) ? flattenDeep(v) : v))); 98 | const fromPairs = arr => 99 | arr.reduce((obj, [key, val]) => ({ ...obj, [key]: val }), {}); 100 | const indexOf = (arr, val, fromIndex = 0) => 101 | arr.findIndex((item, index) => index >= fromIndex && item === val); 102 | const initial = arr => arr.slice(0, -1); 103 | const intersection = (...arr) => [ 104 | ...new Set(arr.reduce((a, b) => a.filter(v => b.includes(v)))), 105 | ]; 106 | const join = (arr, separator = ',') => 107 | arr.reduce((res, val, i) => `${res}${i ? separator : ''}${val}`, ''); 108 | const last = arr => arr[arr.length - 1]; 109 | const lastIndexOf = (arr, val) => arr.lastIndexOf(val); 110 | const pull = (arr, ...args) => arr.filter(item => !args.includes(item)); 111 | const pullAt = (arr, ...args) => args.map(index => arr.splice(index, 1)[0]); 112 | const reverse = arr => [...arr].reverse(); 113 | const slice = (arr, start, end) => arr.slice(start, end); 114 | const sortedIndex = (arr, value) => { 115 | let left = 0; 116 | let right = arr.length; 117 | while (left < right) { 118 | const mid = Math.floor((left + right) / 2); 119 | if (arr[mid] < value) { 120 | left = mid + 1; 121 | } else { 122 | right = mid; 123 | } 124 | } 125 | return right; 126 | }; 127 | 128 | module.exports = { 129 | chunk, 130 | compact, 131 | concat, 132 | difference, 133 | differenceBy, 134 | differenceWith, 135 | drop, 136 | dropRight, 137 | dropRightWhile, 138 | fill, 139 | findIndex, 140 | findLastIndex, 141 | head, 142 | flatten, 143 | flattenDeep, 144 | fromPairs, 145 | indexOf, 146 | initial, 147 | intersection, 148 | join, 149 | last, 150 | lastIndexOf, 151 | pull, 152 | pullAt, 153 | reverse, 154 | slice, 155 | sortedIndex, 156 | }; 157 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "preserveConstEnums": true, 6 | "outDir": "./dist", 7 | "traceResolution": false, 8 | "sourceMap": true, 9 | "baseUrl": ".", 10 | "rootDir": "./src", 11 | "declaration": true, 12 | "declarationDir": "types", 13 | "types": ["lodash", "node"], 14 | "moduleResolution": "node", 15 | "typeRoots": ["node_modules/@types"], 16 | "resolveJsonModule": true, 17 | "allowSyntheticDefaultImports": true, 18 | "strict": true, 19 | "esModuleInterop": true 20 | }, 21 | "include": ["./src/**/*"], 22 | "exclude": ["node_modules"], 23 | "compileOnSave": false 24 | } 25 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // export { default as lodash } from '../src/lodash'; 2 | --------------------------------------------------------------------------------