misp-ui-library-test 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/dist/theme-chalk/alert/index.scss +152 -0
- package/dist/theme-chalk/avatar/index.scss +51 -0
- package/dist/theme-chalk/backtop/index.scss +38 -0
- package/dist/theme-chalk/badge/index.scss +63 -0
- package/dist/theme-chalk/breadcrumb/index.scss +36 -0
- package/dist/theme-chalk/breadcrumb-item/index.scss +36 -0
- package/dist/theme-chalk/button/index.scss +157 -0
- package/dist/theme-chalk/card/index.scss +38 -0
- package/dist/theme-chalk/chart/index.scss +21 -0
- package/dist/theme-chalk/checkbox/index.scss +358 -0
- package/dist/theme-chalk/col/index.scss +131 -0
- package/dist/theme-chalk/container/index.scss +40 -0
- package/dist/theme-chalk/css/alert.css +484 -0
- package/dist/theme-chalk/css/avatar.css +409 -0
- package/dist/theme-chalk/css/backtop.css +401 -0
- package/dist/theme-chalk/css/badge.css +419 -0
- package/dist/theme-chalk/css/breadcrumb-item.css +395 -0
- package/dist/theme-chalk/css/breadcrumb.css +395 -0
- package/dist/theme-chalk/css/button.css +587 -0
- package/dist/theme-chalk/css/card.css +397 -0
- package/dist/theme-chalk/css/chart.css +386 -0
- package/dist/theme-chalk/css/checkbox.css +648 -0
- package/dist/theme-chalk/css/col.css +2111 -0
- package/dist/theme-chalk/css/container.css +404 -0
- package/dist/theme-chalk/css/dialog.css +505 -0
- package/dist/theme-chalk/css/divider.css +405 -0
- package/dist/theme-chalk/css/drawer.css +546 -0
- package/dist/theme-chalk/css/empty.css +394 -0
- package/dist/theme-chalk/css/form.css +561 -0
- package/dist/theme-chalk/css/icon.css +1534 -0
- package/dist/theme-chalk/css/image.css +400 -0
- package/dist/theme-chalk/css/input-number.css +424 -0
- package/dist/theme-chalk/css/input.css +721 -0
- package/dist/theme-chalk/css/link.css +424 -0
- package/dist/theme-chalk/css/loading.css +483 -0
- package/dist/theme-chalk/css/message-box.css +479 -0
- package/dist/theme-chalk/css/message.css +456 -0
- package/dist/theme-chalk/css/no-data.css +397 -0
- package/dist/theme-chalk/css/notification.css +431 -0
- package/dist/theme-chalk/css/page-header.css +396 -0
- package/dist/theme-chalk/css/pagination.css +565 -0
- package/dist/theme-chalk/css/popover.css +421 -0
- package/dist/theme-chalk/css/popper.css +454 -0
- package/dist/theme-chalk/css/progress.css +451 -0
- package/dist/theme-chalk/css/radio.css +626 -0
- package/dist/theme-chalk/css/rate.css +414 -0
- package/dist/theme-chalk/css/result.css +403 -0
- package/dist/theme-chalk/css/row.css +433 -0
- package/dist/theme-chalk/css/scrollbar.css +422 -0
- package/dist/theme-chalk/css/select.css +1897 -0
- package/dist/theme-chalk/css/skeleton-item.css +407 -0
- package/dist/theme-chalk/css/skeleton.css +390 -0
- package/dist/theme-chalk/css/slider.css +460 -0
- package/dist/theme-chalk/css/spinner.css +403 -0
- package/dist/theme-chalk/css/statistic.css +396 -0
- package/dist/theme-chalk/css/status-timeline-chart.css +388 -0
- package/dist/theme-chalk/css/step.css +496 -0
- package/dist/theme-chalk/css/steps.css +496 -0
- package/dist/theme-chalk/css/switch.css +507 -0
- package/dist/theme-chalk/css/tab-pane.css +457 -0
- package/dist/theme-chalk/css/table.css +461 -0
- package/dist/theme-chalk/css/tabs-navigation.css +925 -0
- package/dist/theme-chalk/css/tabs.css +457 -0
- package/dist/theme-chalk/css/tag.css +599 -0
- package/dist/theme-chalk/css/time-picker.css +683 -0
- package/dist/theme-chalk/css/timeline-item.css +459 -0
- package/dist/theme-chalk/css/timeline.css +459 -0
- package/dist/theme-chalk/css/tooltip.css +485 -0
- package/dist/theme-chalk/css/tree.css +473 -0
- package/dist/theme-chalk/css/upload.css +665 -0
- package/dist/theme-chalk/dialog/index.scss +168 -0
- package/dist/theme-chalk/divider/index.scss +46 -0
- package/dist/theme-chalk/drawer/index.scss +172 -0
- package/dist/theme-chalk/empty/index.scss +36 -0
- package/dist/theme-chalk/fonts/element-icons.ttf +0 -0
- package/dist/theme-chalk/fonts/element-icons.woff +0 -0
- package/dist/theme-chalk/form/index.scss +220 -0
- package/dist/theme-chalk/icon/index.scss +1171 -0
- package/dist/theme-chalk/image/index.scss +39 -0
- package/dist/theme-chalk/index.scss +127 -0
- package/dist/theme-chalk/input/index.scss +363 -0
- package/dist/theme-chalk/input-number/index.scss +71 -0
- package/dist/theme-chalk/link/index.scss +75 -0
- package/dist/theme-chalk/loading/index.scss +131 -0
- package/dist/theme-chalk/message/index.scss +100 -0
- package/dist/theme-chalk/message-box/index.scss +131 -0
- package/dist/theme-chalk/mixins.scss +203 -0
- package/dist/theme-chalk/no-data/index.scss +36 -0
- package/dist/theme-chalk/notification/index.scss +76 -0
- package/dist/theme-chalk/page-header/index.scss +36 -0
- package/dist/theme-chalk/pagination/index.scss +225 -0
- package/dist/theme-chalk/popover/index.scss +56 -0
- package/dist/theme-chalk/popper/index.scss +89 -0
- package/dist/theme-chalk/progress/index.scss +105 -0
- package/dist/theme-chalk/radio/index.scss +328 -0
- package/dist/theme-chalk/rate/index.scss +56 -0
- package/dist/theme-chalk/reset.scss +262 -0
- package/dist/theme-chalk/result/index.scss +46 -0
- package/dist/theme-chalk/row/index.scss +88 -0
- package/dist/theme-chalk/scrollbar/index.scss +67 -0
- package/dist/theme-chalk/select/index.scss +223 -0
- package/dist/theme-chalk/skeleton/index.scss +30 -0
- package/dist/theme-chalk/skeleton-item/index.scss +49 -0
- package/dist/theme-chalk/slider/index.scss +113 -0
- package/dist/theme-chalk/spinner/index.scss +40 -0
- package/dist/theme-chalk/statistic/index.scss +38 -0
- package/dist/theme-chalk/status-timeline-chart/index.scss +23 -0
- package/dist/theme-chalk/step/index.scss +160 -0
- package/dist/theme-chalk/steps/index.scss +160 -0
- package/dist/theme-chalk/switch/index.scss +153 -0
- package/dist/theme-chalk/tab-pane/index.scss +112 -0
- package/dist/theme-chalk/table/index.scss +110 -0
- package/dist/theme-chalk/tabs/index.scss +112 -0
- package/dist/theme-chalk/tabs-navigation/index.scss +631 -0
- package/dist/theme-chalk/tag/index.scss +138 -0
- package/dist/theme-chalk/themes/blue.scss +76 -0
- package/dist/theme-chalk/themes/dark.scss +99 -0
- package/dist/theme-chalk/themes/index.scss +260 -0
- package/dist/theme-chalk/themes/light.scss +99 -0
- package/dist/theme-chalk/time-picker/index.scss +332 -0
- package/dist/theme-chalk/timeline/index.scss +119 -0
- package/dist/theme-chalk/timeline-item/index.scss +119 -0
- package/dist/theme-chalk/tooltip/index.scss +122 -0
- package/dist/theme-chalk/tree/index.scss +125 -0
- package/dist/theme-chalk/upload/index.scss +348 -0
- package/dist/theme-chalk/variables.scss +93 -0
- package/dist/vue2-ui-library.cjs.temp.js.map +1 -0
- package/dist/vue2-ui-library.common.js +28 -0
- package/dist/vue2-ui-library.common.js.map +1 -0
- package/dist/vue2-ui-library.esm.js +29 -0
- package/dist/vue2-ui-library.umd.js +27 -0
- package/package.json +161 -0
- package/src/directives/clickoutside/index.js +111 -0
- package/src/directives/init/index.js +50 -0
- package/src/directives/resize/index.js +69 -0
- package/src/index.js +174 -0
- package/src/mixins/emitter.js +47 -0
- package/src/mixins/focus.js +42 -0
- package/src/mixins/locale.js +24 -0
- package/src/mixins/migrating.js +46 -0
- package/src/packages/alert/index.js +12 -0
- package/src/packages/alert/src/main.vue +98 -0
- package/src/packages/aside/index.js +12 -0
- package/src/packages/aside/src/main.vue +20 -0
- package/src/packages/auto-grid-layout/index.js +18 -0
- package/src/packages/auto-grid-layout/prop.js +92 -0
- package/src/packages/auto-grid-layout/src/components/GridItem.vue +759 -0
- package/src/packages/auto-grid-layout/src/components/GridLayout.vue +367 -0
- package/src/packages/auto-grid-layout/src/main.vue +287 -0
- package/src/packages/auto-grid-layout/src/utils/DOM.js +46 -0
- package/src/packages/auto-grid-layout/src/utils/interact.js +333 -0
- package/src/packages/auto-grid-layout/src/utils/responsiveUtils.js +75 -0
- package/src/packages/auto-grid-layout/src/utils/utils.js +339 -0
- package/src/packages/autocomplete/index.js +12 -0
- package/src/packages/autocomplete/src/main.vue +170 -0
- package/src/packages/avatar/index.js +12 -0
- package/src/packages/avatar/src/main.vue +87 -0
- package/src/packages/backtop/index.js +12 -0
- package/src/packages/backtop/src/main.vue +120 -0
- package/src/packages/badge/index.js +12 -0
- package/src/packages/badge/src/main.vue +53 -0
- package/src/packages/breadcrumb/index.js +12 -0
- package/src/packages/breadcrumb/src/main.vue +34 -0
- package/src/packages/breadcrumb-item/index.js +12 -0
- package/src/packages/breadcrumb-item/src/main.vue +41 -0
- package/src/packages/button/index.js +13 -0
- package/src/packages/button/src/main.vue +106 -0
- package/src/packages/calendar/index.js +12 -0
- package/src/packages/calendar/src/main.vue +173 -0
- package/src/packages/card/index.js +12 -0
- package/src/packages/card/src/main.vue +26 -0
- package/src/packages/carousel/index.js +12 -0
- package/src/packages/carousel/src/main.vue +186 -0
- package/src/packages/carousel-item/index.js +12 -0
- package/src/packages/carousel-item/src/main.vue +34 -0
- package/src/packages/cascader/index.js +12 -0
- package/src/packages/cascader/src/main.vue +232 -0
- package/src/packages/chart/index.js +7 -0
- package/src/packages/chart/src/axis-chart.js +700 -0
- package/src/packages/chart/src/main.vue +828 -0
- package/src/packages/chart/src/utils.js +21 -0
- package/src/packages/checkbox/index.js +44 -0
- package/src/packages/checkbox/src/main.vue +312 -0
- package/src/packages/col/index.js +12 -0
- package/src/packages/col/src/main.vue +85 -0
- package/src/packages/collapse/index.js +12 -0
- package/src/packages/collapse/src/main.vue +69 -0
- package/src/packages/collapse-item/index.js +12 -0
- package/src/packages/collapse-item/src/main.vue +75 -0
- package/src/packages/color-picker/index.js +12 -0
- package/src/packages/color-picker/src/main.vue +206 -0
- package/src/packages/container/index.js +12 -0
- package/src/packages/container/src/main.vue +33 -0
- package/src/packages/date-picker/index.js +12 -0
- package/src/packages/date-picker/src/main.vue +246 -0
- package/src/packages/descriptions/index.js +12 -0
- package/src/packages/descriptions/src/main.vue +89 -0
- package/src/packages/descriptions-item/index.js +12 -0
- package/src/packages/descriptions-item/src/main.vue +26 -0
- package/src/packages/dialog/index.js +12 -0
- package/src/packages/dialog/src/main.vue +336 -0
- package/src/packages/divider/index.js +12 -0
- package/src/packages/divider/src/main.vue +37 -0
- package/src/packages/drawer/index.js +7 -0
- package/src/packages/drawer/src/main.vue +310 -0
- package/src/packages/dropdown/index.js +12 -0
- package/src/packages/dropdown/src/main.vue +82 -0
- package/src/packages/dropdown-menu-item/index.js +12 -0
- package/src/packages/dropdown-menu-item/src/main.vue +29 -0
- package/src/packages/empty/index.js +12 -0
- package/src/packages/empty/src/img-empty.vue +12 -0
- package/src/packages/empty/src/main.vue +49 -0
- package/src/packages/footer/index.js +12 -0
- package/src/packages/footer/src/main.vue +20 -0
- package/src/packages/form/index.js +13 -0
- package/src/packages/form/src/form-field.vue +790 -0
- package/src/packages/form/src/form-item.vue +464 -0
- package/src/packages/form/src/label-wrap.vue +127 -0
- package/src/packages/form/src/main.vue +442 -0
- package/src/packages/form-item/index.js +11 -0
- package/src/packages/header/index.js +12 -0
- package/src/packages/header/src/main.vue +20 -0
- package/src/packages/icon/index.js +12 -0
- package/src/packages/icon/src/main.vue +48 -0
- package/src/packages/image/index.js +12 -0
- package/src/packages/image/src/main.vue +224 -0
- package/src/packages/input/index.js +13 -0
- package/src/packages/input/src/calcTextareaHeight.js +123 -0
- package/src/packages/input/src/main.vue +510 -0
- package/src/packages/input-number/index.js +12 -0
- package/src/packages/input-number/src/main.vue +121 -0
- package/src/packages/link/index.js +12 -0
- package/src/packages/link/src/main.vue +53 -0
- package/src/packages/loading/index.js +17 -0
- package/src/packages/loading/src/directive.js +54 -0
- package/src/packages/loading/src/main.vue +38 -0
- package/src/packages/loading/src/service.js +63 -0
- package/src/packages/main/index.js +12 -0
- package/src/packages/main/src/main.vue +12 -0
- package/src/packages/menu/index.js +12 -0
- package/src/packages/menu/src/main.vue +117 -0
- package/src/packages/menu-item/index.js +12 -0
- package/src/packages/menu-item/src/main.vue +61 -0
- package/src/packages/message/index.js +95 -0
- package/src/packages/message/src/main.vue +131 -0
- package/src/packages/message-box/index.js +75 -0
- package/src/packages/message-box/src/main.vue +207 -0
- package/src/packages/no-data/index.js +8 -0
- package/src/packages/no-data/src/main.vue +23 -0
- package/src/packages/notification/index.js +72 -0
- package/src/packages/notification/src/main.vue +139 -0
- package/src/packages/option/index.js +13 -0
- package/src/packages/page-header/index.js +12 -0
- package/src/packages/page-header/src/main.vue +27 -0
- package/src/packages/pagination/index.js +12 -0
- package/src/packages/pagination/src/main.vue +255 -0
- package/src/packages/popconfirm/index.js +12 -0
- package/src/packages/popconfirm/src/main.vue +89 -0
- package/src/packages/popover/index.js +12 -0
- package/src/packages/popover/src/main.vue +452 -0
- package/src/packages/progress/index.js +12 -0
- package/src/packages/progress/src/main.vue +236 -0
- package/src/packages/radio/index.js +44 -0
- package/src/packages/radio/src/main.vue +291 -0
- package/src/packages/rate/index.js +12 -0
- package/src/packages/rate/src/main.vue +129 -0
- package/src/packages/result/index.js +12 -0
- package/src/packages/result/src/main.vue +52 -0
- package/src/packages/row/index.js +12 -0
- package/src/packages/row/src/main.vue +57 -0
- package/src/packages/scrollbar/index.js +12 -0
- package/src/packages/scrollbar/src/bar.vue +116 -0
- package/src/packages/scrollbar/src/main.vue +124 -0
- package/src/packages/select/index.js +15 -0
- package/src/packages/select/src/clickoutside.js +20 -0
- package/src/packages/select/src/main.vue +1055 -0
- package/src/packages/select/src/navigation-mixin.js +49 -0
- package/src/packages/select/src/option.vue +249 -0
- package/src/packages/select/src/select-dropdown.vue +95 -0
- package/src/packages/select/src/utils.js +5 -0
- package/src/packages/skeleton/index.js +12 -0
- package/src/packages/skeleton/src/main.vue +76 -0
- package/src/packages/skeleton-item/index.js +12 -0
- package/src/packages/skeleton-item/src/img-placeholder.vue +13 -0
- package/src/packages/skeleton-item/src/main.vue +22 -0
- package/src/packages/slider/index.js +12 -0
- package/src/packages/slider/src/main.vue +176 -0
- package/src/packages/spinner/index.js +12 -0
- package/src/packages/spinner/src/main.vue +27 -0
- package/src/packages/statistic/index.js +12 -0
- package/src/packages/statistic/src/main.vue +100 -0
- package/src/packages/status-timeline-chart/index.js +7 -0
- package/src/packages/status-timeline-chart/src/constants.js +20 -0
- package/src/packages/status-timeline-chart/src/main.vue +499 -0
- package/src/packages/status-timeline-chart/src/option-builder.js +475 -0
- package/src/packages/step/index.js +12 -0
- package/src/packages/step/src/main.vue +183 -0
- package/src/packages/steps/index.js +12 -0
- package/src/packages/steps/src/main.vue +57 -0
- package/src/packages/submenu/index.js +12 -0
- package/src/packages/submenu/src/main.vue +68 -0
- package/src/packages/switch/index.js +12 -0
- package/src/packages/switch/src/main.vue +166 -0
- package/src/packages/tab-pane/index.js +12 -0
- package/src/packages/tab-pane/src/main.vue +74 -0
- package/src/packages/table/index.js +8 -0
- package/src/packages/table/src/components/PagePagination.vue +82 -0
- package/src/packages/table/src/main.vue +153 -0
- package/src/packages/tabs/index.js +12 -0
- package/src/packages/tabs/src/main.vue +167 -0
- package/src/packages/tabs-navigation/index.js +7 -0
- package/src/packages/tabs-navigation/src/main.vue +462 -0
- package/src/packages/tag/index.js +12 -0
- package/src/packages/tag/src/main.vue +194 -0
- package/src/packages/time-picker/index.js +17 -0
- package/src/packages/time-picker/src/date-utils.js +455 -0
- package/src/packages/time-picker/src/main.vue +321 -0
- package/src/packages/time-picker/src/pane/date-range.vue +193 -0
- package/src/packages/time-picker/src/pane/date-time-range.vue +362 -0
- package/src/packages/time-picker/src/pane/date-time.vue +362 -0
- package/src/packages/time-picker/src/pane/date.vue +441 -0
- package/src/packages/time-picker/src/pane/time.vue +260 -0
- package/src/packages/time-picker/src/time-input.vue +203 -0
- package/src/packages/time-picker/src/time-range-input.vue +170 -0
- package/src/packages/timeline/index.js +12 -0
- package/src/packages/timeline/src/main.vue +24 -0
- package/src/packages/timeline-item/index.js +12 -0
- package/src/packages/timeline-item/src/main.vue +78 -0
- package/src/packages/tooltip/index.js +12 -0
- package/src/packages/tooltip/src/main.js +367 -0
- package/src/packages/transfer/index.js +8 -0
- package/src/packages/transfer/src/main.vue +229 -0
- package/src/packages/transfer/src/transfer-panel.vue +245 -0
- package/src/packages/transitions/collapse-transition.js +75 -0
- package/src/packages/tree/index.js +12 -0
- package/src/packages/tree/src/main.vue +322 -0
- package/src/packages/tree/src/model/node.js +348 -0
- package/src/packages/tree/src/model/tree-store.js +354 -0
- package/src/packages/tree/src/model/util.js +10 -0
- package/src/packages/tree/src/tree-node.vue +262 -0
- package/src/packages/upload/index.js +9 -0
- package/src/packages/upload/src/main.vue +415 -0
- package/src/playground/mixins/element.js +98 -0
- package/src/playground/types/element.js +197 -0
- package/src/playground/types/layout.js +80 -0
- package/src/plugins/i18n.js +125 -0
- package/src/styles/fonts/element-icons.ttf +0 -0
- package/src/styles/fonts/element-icons.woff +0 -0
- package/src/styles/index.scss +138 -0
- package/src/styles/reset.scss +257 -0
- package/src/styles/themes/blue.scss +75 -0
- package/src/styles/themes/dark.scss +98 -0
- package/src/styles/themes/index.scss +34 -0
- package/src/styles/themes/light.scss +98 -0
- package/src/styles/themes/variables.scss +233 -0
- package/src/styles/variables.scss +146 -0
- package/src/utils/async-validator/index.js +659 -0
- package/src/utils/browser/index.js +39 -0
- package/src/utils/color.js +76 -0
- package/src/utils/dom/index.js +421 -0
- package/src/utils/dom/scrollbar-width.js +49 -0
- package/src/utils/draggable/core.js +379 -0
- package/src/utils/draggable/draggable.js +478 -0
- package/src/utils/draggable/index.js +61 -0
- package/src/utils/draggable/sortable.js +751 -0
- package/src/utils/lodash/index.js +2395 -0
- package/src/utils/moment/index.js +909 -0
- package/src/utils/playground/index.js +37 -0
- package/src/utils/popper/index.js +308 -0
- package/src/utils/popper/popper.js +1132 -0
- package/src/utils/popper/popup-manager.js +288 -0
- package/src/utils/popper/popup.js +344 -0
|
@@ -0,0 +1,2395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @vue2-ui/lodash
|
|
3
|
+
* @description 基于 lodash 的自定义工具函数库(无外部依赖)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ==================== 类型判断 ====================
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 检查值是否为 null 或 undefined
|
|
10
|
+
* @param {*} value
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
* @example
|
|
13
|
+
* isNil(null) // true
|
|
14
|
+
* isNil(undefined) // true
|
|
15
|
+
* isNil(0) // false
|
|
16
|
+
* isNil('') // false
|
|
17
|
+
*/
|
|
18
|
+
export function isNil(value) {
|
|
19
|
+
return value === null || value === undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 检查值是否为 undefined
|
|
24
|
+
* @param {*} value
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
* @example
|
|
27
|
+
* isUndefined(undefined) // true
|
|
28
|
+
* isUndefined(null) // false
|
|
29
|
+
* isUndefined(0) // false
|
|
30
|
+
* isUndefined('') // false
|
|
31
|
+
*/
|
|
32
|
+
export function isUndefined(value) {
|
|
33
|
+
return value === undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 检查值是否为 null
|
|
38
|
+
* @param {*} value
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
* @example
|
|
41
|
+
* isNull(null) // true
|
|
42
|
+
* isNull(undefined) // false
|
|
43
|
+
* isNull(0) // false
|
|
44
|
+
* isNull('') // false
|
|
45
|
+
*/
|
|
46
|
+
export function isNull(value) {
|
|
47
|
+
return value === null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 检查值是否为 boolean 类型
|
|
52
|
+
* @param {*} value
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
* @example
|
|
55
|
+
* isBoolean(true) // true
|
|
56
|
+
* isBoolean(false) // true
|
|
57
|
+
* isBoolean(1) // false
|
|
58
|
+
* isBoolean('true') // false
|
|
59
|
+
*/
|
|
60
|
+
export function isBoolean(value) {
|
|
61
|
+
return typeof value === 'boolean'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 检查值是否为 number 类型
|
|
66
|
+
* @param {*} value
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
* @example
|
|
69
|
+
* isNumber(1) // true
|
|
70
|
+
* isNumber(NaN) // true
|
|
71
|
+
* isNumber('1') // false
|
|
72
|
+
*/
|
|
73
|
+
export function isNumber(value) {
|
|
74
|
+
return typeof value === 'number'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 检查值是否为有限数字
|
|
79
|
+
* @param {*} value
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
* @example
|
|
82
|
+
* isFiniteNumber(1) // true
|
|
83
|
+
* isFiniteNumber(0) // true
|
|
84
|
+
* isFiniteNumber(NaN) // false
|
|
85
|
+
* isFiniteNumber(Infinity) // false
|
|
86
|
+
*/
|
|
87
|
+
export function isFiniteNumber(value) {
|
|
88
|
+
return typeof value === 'number' && isFinite(value)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 检查值是否为整数
|
|
93
|
+
* @param {*} value
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
* @example
|
|
96
|
+
* isInteger(1) // true
|
|
97
|
+
* isInteger(0) // true
|
|
98
|
+
* isInteger(1.5) // false
|
|
99
|
+
* isInteger('1') // false
|
|
100
|
+
*/
|
|
101
|
+
export function isInteger(value) {
|
|
102
|
+
return typeof value === 'number' && Number.isInteger(value)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 检查值是否为 NaN
|
|
107
|
+
* @param {*} value
|
|
108
|
+
* @returns {boolean}
|
|
109
|
+
* @example
|
|
110
|
+
* isNaN(NaN) // true
|
|
111
|
+
* isNaN(0) // false
|
|
112
|
+
* isNaN('NaN') // false
|
|
113
|
+
* isNaN(undefined) // false
|
|
114
|
+
*/
|
|
115
|
+
export function isNaN(value) {
|
|
116
|
+
return Number.isNaN(value)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 检查值是否为字符串类型
|
|
121
|
+
* @param {*} value
|
|
122
|
+
* @returns {boolean}
|
|
123
|
+
* @example
|
|
124
|
+
* isString('hello') // true
|
|
125
|
+
* isString('') // true
|
|
126
|
+
* isString(123) // false
|
|
127
|
+
* isString(null) // false
|
|
128
|
+
*/
|
|
129
|
+
export function isString(value) {
|
|
130
|
+
return typeof value === 'string'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 检查值是否为 Symbol 类型
|
|
135
|
+
* @param {*} value
|
|
136
|
+
* @returns {boolean}
|
|
137
|
+
* @example
|
|
138
|
+
* isSymbol(Symbol('foo')) // true
|
|
139
|
+
* isSymbol(Symbol()) // true
|
|
140
|
+
* isSymbol('foo') // false
|
|
141
|
+
* isSymbol(123) // false
|
|
142
|
+
*/
|
|
143
|
+
export function isSymbol(value) {
|
|
144
|
+
return typeof value === 'symbol'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 检查值是否为函数
|
|
149
|
+
* @param {*} value
|
|
150
|
+
* @returns {boolean}
|
|
151
|
+
* @example
|
|
152
|
+
* isFunction(() => {}) // true
|
|
153
|
+
* isFunction(function() {}) // true
|
|
154
|
+
* isFunction(123) // false
|
|
155
|
+
* isFunction(null) // false
|
|
156
|
+
*/
|
|
157
|
+
export function isFunction(value) {
|
|
158
|
+
return typeof value === 'function'
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 检查值是否为数组
|
|
163
|
+
* @param {*} value
|
|
164
|
+
* @returns {boolean}
|
|
165
|
+
* @example
|
|
166
|
+
* isArray([1, 2, 3]) // true
|
|
167
|
+
* isArray([]) // true
|
|
168
|
+
* isArray('array') // false
|
|
169
|
+
* isArray({ length: 3 }) // false
|
|
170
|
+
*/
|
|
171
|
+
export function isArray(value) {
|
|
172
|
+
return Array.isArray(value)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 检查值是否为类数组(有 length 属性且为数字)
|
|
177
|
+
* @param {*} value
|
|
178
|
+
* @returns {boolean}
|
|
179
|
+
* @example
|
|
180
|
+
* isArrayLike([1, 2, 3]) // true
|
|
181
|
+
* isArrayLike('hello') // true
|
|
182
|
+
* isArrayLike({ length: 3 }) // true
|
|
183
|
+
* isArrayLike(null) // false
|
|
184
|
+
*/
|
|
185
|
+
export function isArrayLike(value) {
|
|
186
|
+
if (value === null || value === undefined) {
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
const length = value.length
|
|
190
|
+
return typeof length === 'number' && length >= 0 && Number.isInteger(length)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 检查值是否为对象类型(排除 null 和数组)
|
|
195
|
+
* @param {*} value
|
|
196
|
+
* @returns {boolean}
|
|
197
|
+
* @example
|
|
198
|
+
* isObject({}) // true
|
|
199
|
+
* isObject([]) // true
|
|
200
|
+
* isObject(() => {}) // true
|
|
201
|
+
* isObject(null) // false
|
|
202
|
+
* isObject(123) // false
|
|
203
|
+
*/
|
|
204
|
+
export function isObject(value) {
|
|
205
|
+
const type = typeof value
|
|
206
|
+
return value !== null && (type === 'object' || type === 'function')
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 检查值是否为纯对象(Object 创建的普通对象)
|
|
211
|
+
* @param {*} value
|
|
212
|
+
* @returns {boolean}
|
|
213
|
+
* @example
|
|
214
|
+
* isPlainObject({}) // true
|
|
215
|
+
* isPlainObject({ a: 1 }) // true
|
|
216
|
+
* isPlainObject([]) // false
|
|
217
|
+
* isPlainObject(new Date()) // false
|
|
218
|
+
* isPlainObject(null) // false
|
|
219
|
+
*/
|
|
220
|
+
export function isPlainObject(value) {
|
|
221
|
+
if (!isObject(value)) {
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
const proto = Object.getPrototypeOf(value)
|
|
225
|
+
return proto === null || proto === Object.prototype
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 检查值是否为类实例(如 Date、RegExp 等)
|
|
230
|
+
* @param {*} value
|
|
231
|
+
* @returns {boolean}
|
|
232
|
+
* @example
|
|
233
|
+
* isObjectLike({}) // true
|
|
234
|
+
* isObjectLike([]) // true
|
|
235
|
+
* isObjectLike(new Date()) // true
|
|
236
|
+
* isObjectLike('string') // false
|
|
237
|
+
* isObjectLike(null) // false
|
|
238
|
+
*/
|
|
239
|
+
export function isObjectLike(value) {
|
|
240
|
+
return value !== null && typeof value === 'object'
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 检查值是否为 Date 对象
|
|
245
|
+
* @param {*} value
|
|
246
|
+
* @returns {boolean}
|
|
247
|
+
* @example
|
|
248
|
+
* isDate(new Date()) // true
|
|
249
|
+
* isDate(new Date('2024-01-01')) // true
|
|
250
|
+
* isDate('2024-01-01') // false
|
|
251
|
+
* isDate(1234567890) // false
|
|
252
|
+
*/
|
|
253
|
+
export function isDate(value) {
|
|
254
|
+
return value instanceof Date
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 检查值是否为 RegExp 对象
|
|
259
|
+
* @param {*} value
|
|
260
|
+
* @returns {boolean}
|
|
261
|
+
* @example
|
|
262
|
+
* isRegExp(/abc/) // true
|
|
263
|
+
* isRegExp(new RegExp('abc')) // true
|
|
264
|
+
* isRegExp('abc') // false
|
|
265
|
+
* isRegExp(123) // false
|
|
266
|
+
*/
|
|
267
|
+
export function isRegExp(value) {
|
|
268
|
+
return value instanceof RegExp
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 检查值是否为 Promise 对象
|
|
273
|
+
* @param {*} value
|
|
274
|
+
* @returns {boolean}
|
|
275
|
+
* @example
|
|
276
|
+
* isPromise(Promise.resolve()) // true
|
|
277
|
+
* isPromise(new Promise(() => {})) // true
|
|
278
|
+
* isPromise(async () => {}) // false (函数)
|
|
279
|
+
* isPromise({ then: () => {} }) // true (thenable)
|
|
280
|
+
*/
|
|
281
|
+
export function isPromise(value) {
|
|
282
|
+
return isObject(value) && isFunction(value.then)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 检查值是否为 Map 对象
|
|
287
|
+
* @param {*} value
|
|
288
|
+
* @returns {boolean}
|
|
289
|
+
* @example
|
|
290
|
+
* isMap(new Map()) // true
|
|
291
|
+
* isMap(new Map([['a', 1]])) // true
|
|
292
|
+
* isMap({}) // false
|
|
293
|
+
* isMap(new WeakMap()) // false
|
|
294
|
+
*/
|
|
295
|
+
export function isMap(value) {
|
|
296
|
+
return value instanceof Map
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 检查值是否为 Set 对象
|
|
301
|
+
* @param {*} value
|
|
302
|
+
* @returns {boolean}
|
|
303
|
+
* @example
|
|
304
|
+
* isSet(new Set()) // true
|
|
305
|
+
* isSet(new Set([1, 2, 3])) // true
|
|
306
|
+
* isSet([]) // false
|
|
307
|
+
* isSet(new WeakSet()) // false
|
|
308
|
+
*/
|
|
309
|
+
export function isSet(value) {
|
|
310
|
+
return value instanceof Set
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 检查值是否为 WeakMap 对象
|
|
315
|
+
* @param {*} value
|
|
316
|
+
* @returns {boolean}
|
|
317
|
+
* @example
|
|
318
|
+
* isWeakMap(new WeakMap()) // true
|
|
319
|
+
* isWeakMap(new Map()) // false
|
|
320
|
+
* isWeakMap({}) // false
|
|
321
|
+
*/
|
|
322
|
+
export function isWeakMap(value) {
|
|
323
|
+
return value instanceof WeakMap
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 检查值是否为 WeakSet 对象
|
|
328
|
+
* @param {*} value
|
|
329
|
+
* @returns {boolean}
|
|
330
|
+
* @example
|
|
331
|
+
* isWeakSet(new WeakSet()) // true
|
|
332
|
+
* isWeakSet(new Set()) // false
|
|
333
|
+
* isWeakSet([]) // false
|
|
334
|
+
*/
|
|
335
|
+
export function isWeakSet(value) {
|
|
336
|
+
return value instanceof WeakSet
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ==================== 语言判断 ====================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* - 检查文本是否包含韩文字符
|
|
343
|
+
* - 通过正则表达式匹配韩文音节(\uAC00-\uD7AF)和韩文字母(\u3130-\u318F)
|
|
344
|
+
* @param {string} text - 要检查的文本
|
|
345
|
+
* @returns {boolean} 如果包含韩文字符返回 true,否则返回 false
|
|
346
|
+
* @example
|
|
347
|
+
* isKorean('안녕하세요') // true
|
|
348
|
+
* isKorean('가나다') // true
|
|
349
|
+
* isKorean('hello') // false
|
|
350
|
+
* isKorean('你好') // false
|
|
351
|
+
* isKorean('123') // false
|
|
352
|
+
*/
|
|
353
|
+
export function isKorean(text) {
|
|
354
|
+
const reg = /([(\uAC00-\uD7AF)|(\u3130-\u318F)])+/gi;
|
|
355
|
+
return reg.test(text);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
// ==================== 值判断 ====================
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 检查值是否为空(null、undefined、空字符串、空数组、空对象)
|
|
363
|
+
* @param {*} value
|
|
364
|
+
* @returns {boolean}
|
|
365
|
+
* @example
|
|
366
|
+
* isEmpty(null) // true
|
|
367
|
+
* isEmpty(undefined) // true
|
|
368
|
+
* isEmpty('') // true
|
|
369
|
+
* isEmpty([]) // true
|
|
370
|
+
* isEmpty({}) // true
|
|
371
|
+
* isEmpty(0) // false
|
|
372
|
+
* isEmpty(' ') // false
|
|
373
|
+
*/
|
|
374
|
+
export function isEmpty(value) {
|
|
375
|
+
if (value === null || value === undefined) {
|
|
376
|
+
return true
|
|
377
|
+
}
|
|
378
|
+
if (isArray(value) || isString(value)) {
|
|
379
|
+
return value.length === 0
|
|
380
|
+
}
|
|
381
|
+
if (isObjectLike(value)) {
|
|
382
|
+
return Object.keys(value).length === 0
|
|
383
|
+
}
|
|
384
|
+
return false
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* 检查两个值是否相等(支持浅比较)
|
|
389
|
+
* @param {*} a
|
|
390
|
+
* @param {*} b
|
|
391
|
+
* @returns {boolean}
|
|
392
|
+
* @example
|
|
393
|
+
* isEqual(1, 1) // true
|
|
394
|
+
* isEqual('a', 'a') // true
|
|
395
|
+
* isEqual([1, 2], [1, 2]) // true
|
|
396
|
+
* isEqual({a: 1}, {a: 1}) // true
|
|
397
|
+
* isEqual(1, '1') // false
|
|
398
|
+
* isEqual([1, 2], [1, 2, 3]) // false
|
|
399
|
+
*/
|
|
400
|
+
export function isEqual(a, b) {
|
|
401
|
+
if (a === b) {
|
|
402
|
+
return true
|
|
403
|
+
}
|
|
404
|
+
if (a === null || b === null) {
|
|
405
|
+
return a === b
|
|
406
|
+
}
|
|
407
|
+
if (typeof a !== typeof b) {
|
|
408
|
+
return false
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 数组比较
|
|
412
|
+
if (isArray(a) && isArray(b)) {
|
|
413
|
+
if (a.length !== b.length) return false
|
|
414
|
+
for (let i = 0; i < a.length; i++) {
|
|
415
|
+
if (!isEqual(a[i], b[i])) return false
|
|
416
|
+
}
|
|
417
|
+
return true
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 对象比较
|
|
421
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
422
|
+
const keysA = Object.keys(a)
|
|
423
|
+
const keysB = Object.keys(b)
|
|
424
|
+
if (keysA.length !== keysB.length) return false
|
|
425
|
+
for (const key of keysA) {
|
|
426
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false
|
|
427
|
+
if (!isEqual(a[key], b[key])) return false
|
|
428
|
+
}
|
|
429
|
+
return true
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return false
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ==================== 数组函数 ====================
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* 查找数组中第一个匹配的元素
|
|
439
|
+
* @template T
|
|
440
|
+
* @param {T[]} array
|
|
441
|
+
* @param {Function|*} predicate - 匹配函数或值
|
|
442
|
+
* @returns {*|undefined}
|
|
443
|
+
* @example
|
|
444
|
+
* find([1, 2, 3, 4], n => n % 2 === 0) // 2
|
|
445
|
+
* find([{name: 'a'}, {name: 'b'}], {name: 'b'}) // {name: 'b'}
|
|
446
|
+
*/
|
|
447
|
+
export function find(array, predicate) {
|
|
448
|
+
if (!isArrayLike(array)) {
|
|
449
|
+
return undefined
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const isFunc = isFunction(predicate)
|
|
453
|
+
const isObj = isPlainObject(predicate)
|
|
454
|
+
|
|
455
|
+
for (let i = 0; i < array.length; i++) {
|
|
456
|
+
const item = array[i]
|
|
457
|
+
if (isFunc) {
|
|
458
|
+
if (predicate(item, i, array)) return item
|
|
459
|
+
} else if (isObj) {
|
|
460
|
+
const keys = Object.keys(predicate)
|
|
461
|
+
const matches = keys.every(key => item[key] === predicate[key])
|
|
462
|
+
if (matches) return item
|
|
463
|
+
} else {
|
|
464
|
+
if (item === predicate) return item
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return undefined
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* 查找数组中第一个匹配元素的索引
|
|
472
|
+
* @template T
|
|
473
|
+
* @param {T[]} array
|
|
474
|
+
* @param {*} value
|
|
475
|
+
* @param {number} [fromIndex=0]
|
|
476
|
+
* @returns {number}
|
|
477
|
+
* @example
|
|
478
|
+
* indexOf([1, 2, 3, 4], 3) // 2
|
|
479
|
+
* indexOf([1, 2, 3, 2], 2) // 1
|
|
480
|
+
* indexOf([1, 2, 3, 2], 2, 2) // 3
|
|
481
|
+
* indexOf([1, 2, 3], 5) // -1
|
|
482
|
+
*/
|
|
483
|
+
export function indexOf(array, value, fromIndex = 0) {
|
|
484
|
+
const len = array ? array.length : 0
|
|
485
|
+
if (!len) return -1
|
|
486
|
+
|
|
487
|
+
const start = fromIndex < 0 ? Math.max(0, len + fromIndex) : fromIndex
|
|
488
|
+
for (let i = start; i < len; i++) {
|
|
489
|
+
if (array[i] === value) return i
|
|
490
|
+
}
|
|
491
|
+
return -1
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* 查找数组中最后匹配元素的索引
|
|
496
|
+
* @template T
|
|
497
|
+
* @param {T[]} array
|
|
498
|
+
* @param {*} value
|
|
499
|
+
* @param {number} [fromIndex=array.length-1]
|
|
500
|
+
* @returns {number}
|
|
501
|
+
* @example
|
|
502
|
+
* lastIndexOf([1, 2, 3, 2], 2) // 3
|
|
503
|
+
* lastIndexOf([1, 2, 3, 2], 2, 2) // 1
|
|
504
|
+
* lastIndexOf([1, 2, 3], 5) // -1
|
|
505
|
+
*/
|
|
506
|
+
export function lastIndexOf(array, value, fromIndex) {
|
|
507
|
+
const len = array ? array.length : 0
|
|
508
|
+
if (!len) return -1
|
|
509
|
+
|
|
510
|
+
const start = fromIndex !== undefined
|
|
511
|
+
? Math.min(fromIndex < 0 ? len + fromIndex : fromIndex, len - 1)
|
|
512
|
+
: len - 1
|
|
513
|
+
|
|
514
|
+
for (let i = start; i >= 0; i--) {
|
|
515
|
+
if (array[i] === value) return i
|
|
516
|
+
}
|
|
517
|
+
return -1
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* 过滤数组中的元素
|
|
522
|
+
* @template T
|
|
523
|
+
* @param {T[]} array
|
|
524
|
+
* @param {Function} predicate
|
|
525
|
+
* @returns {T[]}
|
|
526
|
+
* @example
|
|
527
|
+
* filter([1, 2, 3, 4], n => n % 2 === 0) // [2, 4]
|
|
528
|
+
* filter([{name: 'a', age: 20}, {name: 'b', age: 30}], o => o.age > 25) // [{name: 'b', age: 30}]
|
|
529
|
+
* filter([1, 2, 3], n => n > 1) // [2, 3]
|
|
530
|
+
*/
|
|
531
|
+
export function filter(array, predicate) {
|
|
532
|
+
if (!isArrayLike(array)) {
|
|
533
|
+
return []
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const result = []
|
|
537
|
+
for (let i = 0; i < array.length; i++) {
|
|
538
|
+
if (predicate(array[i], i, array)) {
|
|
539
|
+
result.push(array[i])
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return result
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* 映射数组中的每个元素
|
|
547
|
+
* @template T, R
|
|
548
|
+
* @param {T[]} array
|
|
549
|
+
* @param {Function|string} iteratee - 映射函数或属性名
|
|
550
|
+
* @returns {R[]}
|
|
551
|
+
* @example
|
|
552
|
+
* map([1, 2, 3], n => n * 2) // [2, 4, 6]
|
|
553
|
+
* map([{name: 'a'}, {name: 'b'}], 'name') // ['a', 'b']
|
|
554
|
+
*/
|
|
555
|
+
export function map(array, iteratee) {
|
|
556
|
+
if (!isArrayLike(array)) {
|
|
557
|
+
return []
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const result = []
|
|
561
|
+
const isFunc = isFunction(iteratee)
|
|
562
|
+
const isStringKey = isString(iteratee)
|
|
563
|
+
|
|
564
|
+
for (let i = 0; i < array.length; i++) {
|
|
565
|
+
const item = array[i]
|
|
566
|
+
if (isFunc) {
|
|
567
|
+
result.push(iteratee(item, i, array))
|
|
568
|
+
} else if (isStringKey) {
|
|
569
|
+
result.push(item[iteratee])
|
|
570
|
+
} else {
|
|
571
|
+
result.push(item)
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return result
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* 数组去重
|
|
579
|
+
* @template T
|
|
580
|
+
* @param {T[]} array
|
|
581
|
+
* @returns {T[]}
|
|
582
|
+
* @example
|
|
583
|
+
* uniq([1, 2, 2, 3, 3, 4]) // [1, 2, 3, 4]
|
|
584
|
+
* uniq(['a', 'b', 'a', 'c']) // ['a', 'b', 'c']
|
|
585
|
+
* uniq([1, '1', 2]) // [1, '1', 2]
|
|
586
|
+
*/
|
|
587
|
+
export function uniq(array) {
|
|
588
|
+
if (!isArrayLike(array)) {
|
|
589
|
+
return []
|
|
590
|
+
}
|
|
591
|
+
return [...new Set(array)]
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* 数组扁平化
|
|
596
|
+
* @template T
|
|
597
|
+
* @param {T[]} array
|
|
598
|
+
* @param {number} [depth=1] - 扁平化深度
|
|
599
|
+
* @returns {T[]}
|
|
600
|
+
* @example
|
|
601
|
+
* flatten([1, [2, 3]]) // [1, 2, 3]
|
|
602
|
+
* flatten([1, [2, [3, [4]]]], 2) // [1, 2, 3, [4]]
|
|
603
|
+
* flatten([1, [2, [3, [4]]]], Infinity) // [1, 2, 3, 4]
|
|
604
|
+
*/
|
|
605
|
+
export function flatten(array, depth = 1) {
|
|
606
|
+
if (!isArrayLike(array)) {
|
|
607
|
+
return []
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const result = []
|
|
611
|
+
|
|
612
|
+
function flattenDeep(arr, currentDepth) {
|
|
613
|
+
for (const item of arr) {
|
|
614
|
+
if (isArray(item) && currentDepth < depth) {
|
|
615
|
+
flattenDeep(item, currentDepth + 1)
|
|
616
|
+
} else {
|
|
617
|
+
result.push(item)
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
flattenDeep(array, 0)
|
|
623
|
+
return result
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* 深度扁平化数组
|
|
628
|
+
* @template T
|
|
629
|
+
* @param {T[]} array
|
|
630
|
+
* @returns {T[]}
|
|
631
|
+
* @example
|
|
632
|
+
* flattenDeep([1, [2, [3, [4]]]]) // [1, 2, 3, 4]
|
|
633
|
+
* flattenDeep([1, [2, 3], [[4, 5]]]) // [1, 2, 3, 4, 5]
|
|
634
|
+
* flattenDeep([1, 2, 3]) // [1, 2, 3]
|
|
635
|
+
*/
|
|
636
|
+
export function flattenDeep(array) {
|
|
637
|
+
if (!isArrayLike(array)) {
|
|
638
|
+
return []
|
|
639
|
+
}
|
|
640
|
+
return flatten(array, Infinity)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* 获取数组的第一个元素
|
|
645
|
+
* @template T
|
|
646
|
+
* @param {T[]} array
|
|
647
|
+
* @returns {*|undefined}
|
|
648
|
+
* @example
|
|
649
|
+
* first([1, 2, 3]) // 1
|
|
650
|
+
* first([]) // undefined
|
|
651
|
+
* first(null) // undefined
|
|
652
|
+
*/
|
|
653
|
+
export function first(array) {
|
|
654
|
+
return array && array.length ? array[0] : undefined
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* 获取数组的最后一个元素
|
|
659
|
+
* @template T
|
|
660
|
+
* @param {T[]} array
|
|
661
|
+
* @returns {*|undefined}
|
|
662
|
+
* @example
|
|
663
|
+
* last([1, 2, 3]) // 3
|
|
664
|
+
* last([]) // undefined
|
|
665
|
+
* last(null) // undefined
|
|
666
|
+
*/
|
|
667
|
+
export function last(array) {
|
|
668
|
+
return array && array.length ? array[array.length - 1] : undefined
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* 获取数组除最后一个元素外的所有元素
|
|
673
|
+
* @template T
|
|
674
|
+
* @param {T[]} array
|
|
675
|
+
* @returns {T[]}
|
|
676
|
+
* @example
|
|
677
|
+
* initial([1, 2, 3]) // [1, 2]
|
|
678
|
+
* initial([1]) // []
|
|
679
|
+
* initial([]) // []
|
|
680
|
+
*/
|
|
681
|
+
export function initial(array) {
|
|
682
|
+
if (!array || !array.length) {
|
|
683
|
+
return []
|
|
684
|
+
}
|
|
685
|
+
return array.slice(0, -1)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* 获取数组除第一个元素外的所有元素
|
|
690
|
+
* @template T
|
|
691
|
+
* @param {T[]} array
|
|
692
|
+
* @returns {T[]}
|
|
693
|
+
* @example
|
|
694
|
+
* rest([1, 2, 3]) // [2, 3]
|
|
695
|
+
* rest([1]) // []
|
|
696
|
+
* rest([]) // []
|
|
697
|
+
*/
|
|
698
|
+
export function rest(array) {
|
|
699
|
+
if (!array || !array.length) {
|
|
700
|
+
return []
|
|
701
|
+
}
|
|
702
|
+
return array.slice(1)
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* 获取数组的前 n 个元素
|
|
707
|
+
* @template T
|
|
708
|
+
* @param {T[]} array
|
|
709
|
+
* @param {number} n
|
|
710
|
+
* @returns {T[]}
|
|
711
|
+
* @example
|
|
712
|
+
* take([1, 2, 3], 2) // [1, 2]
|
|
713
|
+
* take([1, 2, 3]) // [1]
|
|
714
|
+
* take([1, 2, 3], 10) // [1, 2, 3]
|
|
715
|
+
*/
|
|
716
|
+
export function take(array, n = 1) {
|
|
717
|
+
if (!array || !array.length) {
|
|
718
|
+
return []
|
|
719
|
+
}
|
|
720
|
+
return array.slice(0, n)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* 获取数组的后 n 个元素
|
|
725
|
+
* @template T
|
|
726
|
+
* @param {T[]} array
|
|
727
|
+
* @param {number} n
|
|
728
|
+
* @returns {T[]}
|
|
729
|
+
* @example
|
|
730
|
+
* takeRight([1, 2, 3], 2) // [2, 3]
|
|
731
|
+
* takeRight([1, 2, 3]) // [3]
|
|
732
|
+
* takeRight([1, 2, 3], 10) // [1, 2, 3]
|
|
733
|
+
*/
|
|
734
|
+
export function takeRight(array, n = 1) {
|
|
735
|
+
if (!array || !array.length) {
|
|
736
|
+
return []
|
|
737
|
+
}
|
|
738
|
+
const start = Math.max(0, array.length - n)
|
|
739
|
+
return array.slice(start)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* 连接多个数组
|
|
744
|
+
* @template T
|
|
745
|
+
* @param {T[]} array
|
|
746
|
+
* @param {...T[]} arrays
|
|
747
|
+
* @returns {T[]}
|
|
748
|
+
* @example
|
|
749
|
+
* concat([1], [2], [3]) // [1, 2, 3]
|
|
750
|
+
* concat([1, 2], 3, [4, 5]) // [1, 2, 3, 4, 5]
|
|
751
|
+
* concat([], 1) // [1]
|
|
752
|
+
*/
|
|
753
|
+
export function concat(array, ...arrays) {
|
|
754
|
+
if (!isArrayLike(array)) {
|
|
755
|
+
array = []
|
|
756
|
+
}
|
|
757
|
+
return array.concat(...arrays.map(a => isArrayLike(a) ? a : [a]))
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* 获取数组的并集
|
|
762
|
+
* @template T
|
|
763
|
+
* @param {T[]} array1
|
|
764
|
+
* @param {T[]} array2
|
|
765
|
+
* @returns {T[]}
|
|
766
|
+
* @example
|
|
767
|
+
* union([1, 2, 3], [3, 4, 5]) // [1, 2, 3, 4, 5]
|
|
768
|
+
* union([1, 2], [2, 3]) // [1, 2, 3]
|
|
769
|
+
* union([1], []) // [1]
|
|
770
|
+
*/
|
|
771
|
+
export function union(array1, array2) {
|
|
772
|
+
return uniq([...(isArrayLike(array1) ? array1 : []), ...(isArrayLike(array2) ? array2 : [])])
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* 获取数组的交集
|
|
777
|
+
* @template T
|
|
778
|
+
* @param {T[]} array1
|
|
779
|
+
* @param {T[]} array2
|
|
780
|
+
* @returns {T[]}
|
|
781
|
+
* @example
|
|
782
|
+
* intersection([1, 2, 3], [2, 3, 4]) // [2, 3]
|
|
783
|
+
* intersection([1, 2], [3, 4]) // []
|
|
784
|
+
* intersection([1, 2, 2], [2, 2, 3]) // [2]
|
|
785
|
+
*/
|
|
786
|
+
export function intersection(array1, array2) {
|
|
787
|
+
if (!isArrayLike(array1) || !isArrayLike(array2)) {
|
|
788
|
+
return []
|
|
789
|
+
}
|
|
790
|
+
const set2 = new Set(array2)
|
|
791
|
+
return uniq(array1.filter(item => set2.has(item)))
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* 获取 array1 相对于 array2 的差集
|
|
796
|
+
* @template T
|
|
797
|
+
* @param {T[]} array1
|
|
798
|
+
* @param {T[]} array2
|
|
799
|
+
* @returns {T[]}
|
|
800
|
+
* @example
|
|
801
|
+
* difference([1, 2, 3], [2, 3]) // [1]
|
|
802
|
+
* difference([1, 2, 3], [1, 2, 3]) // []
|
|
803
|
+
* difference([1, 2, 3], [4, 5]) // [1, 2, 3]
|
|
804
|
+
*/
|
|
805
|
+
export function difference(array1, array2) {
|
|
806
|
+
if (!isArrayLike(array1)) {
|
|
807
|
+
return []
|
|
808
|
+
}
|
|
809
|
+
if (!isArrayLike(array2)) {
|
|
810
|
+
return array1.slice()
|
|
811
|
+
}
|
|
812
|
+
const set2 = new Set(array2)
|
|
813
|
+
return array1.filter(item => !set2.has(item))
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* 将数组分成指定大小的块
|
|
818
|
+
* @template T
|
|
819
|
+
* @param {T[]} array
|
|
820
|
+
* @param {number} size - 每个块的大小
|
|
821
|
+
* @returns {T[][]}
|
|
822
|
+
* @example
|
|
823
|
+
* chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
|
|
824
|
+
* chunk([1, 2, 3, 4], 3) // [[1, 2, 3], [4]]
|
|
825
|
+
* chunk([1, 2, 3], 1) // [[1], [2], [3]]
|
|
826
|
+
*/
|
|
827
|
+
export function chunk(array, size = 1) {
|
|
828
|
+
if (!isArrayLike(array) || size < 1) {
|
|
829
|
+
return []
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const result = []
|
|
833
|
+
for (let i = 0; i < array.length; i += size) {
|
|
834
|
+
result.push(array.slice(i, i + size))
|
|
835
|
+
}
|
|
836
|
+
return result
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* 反转数组
|
|
841
|
+
* @template T
|
|
842
|
+
* @param {T[]} array
|
|
843
|
+
* @returns {T[]}
|
|
844
|
+
* @example
|
|
845
|
+
* reverse([1, 2, 3]) // [3, 2, 1]
|
|
846
|
+
* reverse(['a', 'b', 'c']) // ['c', 'b', 'a']
|
|
847
|
+
* reverse([1]) // [1]
|
|
848
|
+
*/
|
|
849
|
+
export function reverse(array) {
|
|
850
|
+
if (!isArrayLike(array)) {
|
|
851
|
+
return []
|
|
852
|
+
}
|
|
853
|
+
return [...array].reverse()
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* 创建切片数组
|
|
858
|
+
* @template T
|
|
859
|
+
* @param {T[]} array
|
|
860
|
+
* @param {number} [start=0]
|
|
861
|
+
* @param {number} [end=array.length]
|
|
862
|
+
* @returns {T[]}
|
|
863
|
+
* @example
|
|
864
|
+
* slice([1, 2, 3, 4], 1, 3) // [2, 3]
|
|
865
|
+
* slice([1, 2, 3, 4], 2) // [3, 4]
|
|
866
|
+
* slice([1, 2, 3, 4]) // [1, 2, 3, 4]
|
|
867
|
+
*/
|
|
868
|
+
export function slice(array, start = 0, end) {
|
|
869
|
+
if (!isArrayLike(array)) {
|
|
870
|
+
return []
|
|
871
|
+
}
|
|
872
|
+
return array.slice(start, end)
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* 压缩数组(移除 falsy 值)
|
|
877
|
+
* @template T
|
|
878
|
+
* @param {T[]} array
|
|
879
|
+
* @returns {T[]}
|
|
880
|
+
* @example
|
|
881
|
+
* compact([0, 1, false, 2, '', 3, null, 4]) // [1, 2, 3, 4]
|
|
882
|
+
* compact([1, 2, 3]) // [1, 2, 3]
|
|
883
|
+
* compact([null, undefined, 0]) // []
|
|
884
|
+
*/
|
|
885
|
+
export function compact(array) {
|
|
886
|
+
if (!isArrayLike(array)) {
|
|
887
|
+
return []
|
|
888
|
+
}
|
|
889
|
+
return array.filter(item => item)
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* 创建新数组,移除数组开头的所有假值
|
|
894
|
+
* @template T
|
|
895
|
+
* @param {T[]} array
|
|
896
|
+
* @param {number} [n=1]
|
|
897
|
+
* @returns {T[]}
|
|
898
|
+
* @example
|
|
899
|
+
* drop([1, 2, 3], 1) // [2, 3]
|
|
900
|
+
* drop([1, 2, 3], 2) // [3]
|
|
901
|
+
* drop([1, 2, 3]) // [2, 3]
|
|
902
|
+
*/
|
|
903
|
+
export function drop(array, n = 1) {
|
|
904
|
+
if (!isArrayLike(array)) {
|
|
905
|
+
return []
|
|
906
|
+
}
|
|
907
|
+
return array.slice(n)
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* 创建新数组,移除数组末尾的所有假值
|
|
912
|
+
* @template T
|
|
913
|
+
* @param {T[]} array
|
|
914
|
+
* @param {number} [n=1]
|
|
915
|
+
* @returns {T[]}
|
|
916
|
+
* @example
|
|
917
|
+
* dropRight([1, 2, 3], 1) // [1, 2]
|
|
918
|
+
* dropRight([1, 2, 3], 2) // [1]
|
|
919
|
+
* dropRight([1, 2, 3]) // [1, 2]
|
|
920
|
+
*/
|
|
921
|
+
export function dropRight(array, n = 1) {
|
|
922
|
+
if (!isArrayLike(array)) {
|
|
923
|
+
return []
|
|
924
|
+
}
|
|
925
|
+
return array.slice(0, -n)
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// ==================== 对象函数 ====================
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* 获取对象的所有可枚举属性(包括原型链)
|
|
932
|
+
* @param {Object} obj
|
|
933
|
+
* @returns {string[]}
|
|
934
|
+
* @example
|
|
935
|
+
* keys({a: 1, b: 2}) // ['a', 'b']
|
|
936
|
+
* keys([1, 2, 3]) // ['0', '1', '2']
|
|
937
|
+
* keys(null) // []
|
|
938
|
+
*/
|
|
939
|
+
export function keys(obj) {
|
|
940
|
+
if (obj === null || obj === undefined) {
|
|
941
|
+
return []
|
|
942
|
+
}
|
|
943
|
+
if (!isObject(obj)) {
|
|
944
|
+
obj = Object(obj)
|
|
945
|
+
}
|
|
946
|
+
return Object.keys(obj)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* 获取对象的所有可枚举属性值
|
|
951
|
+
* @param {Object} obj
|
|
952
|
+
* @returns {*[]}
|
|
953
|
+
* @example
|
|
954
|
+
* values({a: 1, b: 2}) // [1, 2]
|
|
955
|
+
* values({name: 'John', age: 30}) // ['John', 30]
|
|
956
|
+
* values(null) // []
|
|
957
|
+
*/
|
|
958
|
+
export function values(obj) {
|
|
959
|
+
if (obj === null || obj === undefined) {
|
|
960
|
+
return []
|
|
961
|
+
}
|
|
962
|
+
return Object.keys(obj).map(key => obj[key])
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* 获取对象的所有可枚举属性和值的键值对
|
|
967
|
+
* @param {Object} obj
|
|
968
|
+
* @returns {Array<[string, *]>}
|
|
969
|
+
* @example
|
|
970
|
+
* entries({a: 1, b: 2}) // [['a', 1], ['b', 2]]
|
|
971
|
+
* entries({name: 'John'}) // [['name', 'John']]
|
|
972
|
+
* entries(null) // []
|
|
973
|
+
*/
|
|
974
|
+
export function entries(obj) {
|
|
975
|
+
if (obj === null || obj === undefined) {
|
|
976
|
+
return []
|
|
977
|
+
}
|
|
978
|
+
return Object.entries(obj)
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* 获取对象的属性值
|
|
983
|
+
* @param {Object} obj
|
|
984
|
+
* @param {string|Array<string>} path - 属性路径
|
|
985
|
+
* @param {*} [defaultValue] - 默认值
|
|
986
|
+
* @returns {*}
|
|
987
|
+
* @example
|
|
988
|
+
* get({a: {b: {c: 1}}}, 'a.b.c') // 1
|
|
989
|
+
* get({a: {b: {c: 1}}}, 'a.b.c.d', 'default') // 'default'
|
|
990
|
+
*/
|
|
991
|
+
export function get(obj, path, defaultValue) {
|
|
992
|
+
if (obj === null || obj === undefined) {
|
|
993
|
+
return defaultValue
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const paths = isArray(path) ? path : String(path).split('.')
|
|
997
|
+
|
|
998
|
+
let result = obj
|
|
999
|
+
for (const key of paths) {
|
|
1000
|
+
if (result === null || result === undefined) {
|
|
1001
|
+
return defaultValue
|
|
1002
|
+
}
|
|
1003
|
+
result = result[key]
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
return result === undefined ? defaultValue : result
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* 设置对象的属性值
|
|
1011
|
+
* @param {Object} obj
|
|
1012
|
+
* @param {string|Array<string>} path - 属性路径
|
|
1013
|
+
* @param {*} value - 要设置的值
|
|
1014
|
+
* @returns {Object}
|
|
1015
|
+
* @example
|
|
1016
|
+
* set({a: 1}, 'b', 2) // {a: 1, b: 2}
|
|
1017
|
+
* set({}, 'a.b.c', 1) // {a: {b: {c: 1}}}
|
|
1018
|
+
* set({a: {b: 1}}, 'a.c', 2) // {a: {b: 1, c: 2}}
|
|
1019
|
+
*/
|
|
1020
|
+
export function set(obj, path, value) {
|
|
1021
|
+
if (obj === null || obj === undefined) {
|
|
1022
|
+
return obj
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const paths = isArray(path) ? path : String(path).split('.')
|
|
1026
|
+
let current = obj
|
|
1027
|
+
|
|
1028
|
+
for (let i = 0; i < paths.length - 1; i++) {
|
|
1029
|
+
const key = paths[i]
|
|
1030
|
+
if (!isObject(current[key])) {
|
|
1031
|
+
current[key] = {}
|
|
1032
|
+
}
|
|
1033
|
+
current = current[key]
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
current[paths[paths.length - 1]] = value
|
|
1037
|
+
return obj
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* 删除对象的属性
|
|
1042
|
+
* @param {Object} obj
|
|
1043
|
+
* @param {string|Array<string>} path - 属性路径
|
|
1044
|
+
* @returns {boolean}
|
|
1045
|
+
* @example
|
|
1046
|
+
* unset({a: 1, b: 2}, 'a') // true
|
|
1047
|
+
* unset({a: {b: 1}}, 'a.b') // true
|
|
1048
|
+
* unset({a: 1}, 'b') // true
|
|
1049
|
+
*/
|
|
1050
|
+
export function unset(obj, path) {
|
|
1051
|
+
if (obj === null || obj === undefined) {
|
|
1052
|
+
return true
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const paths = isArray(path) ? path : String(path).split('.')
|
|
1056
|
+
let current = obj
|
|
1057
|
+
|
|
1058
|
+
for (let i = 0; i < paths.length - 1; i++) {
|
|
1059
|
+
const key = paths[i]
|
|
1060
|
+
if (!isObject(current[key])) {
|
|
1061
|
+
return true
|
|
1062
|
+
}
|
|
1063
|
+
current = current[key]
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const lastKey = paths[paths.length - 1]
|
|
1067
|
+
return delete current[lastKey]
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* 检查对象是否有指定的属性
|
|
1072
|
+
* @param {Object} obj
|
|
1073
|
+
* @param {string|Array<string>} path - 属性路径
|
|
1074
|
+
* @returns {boolean}
|
|
1075
|
+
* @example
|
|
1076
|
+
* has({a: 1, b: 2}, 'a') // true
|
|
1077
|
+
* has({a: {b: 1}}, 'a.b') // true
|
|
1078
|
+
* has({a: 1}, 'b') // false
|
|
1079
|
+
*/
|
|
1080
|
+
export function has(obj, path) {
|
|
1081
|
+
if (obj === null || obj === undefined) {
|
|
1082
|
+
return false
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const paths = isArray(path) ? path : String(path).split('.')
|
|
1086
|
+
let current = obj
|
|
1087
|
+
|
|
1088
|
+
for (const key of paths) {
|
|
1089
|
+
if (current === null || current === undefined) {
|
|
1090
|
+
return false
|
|
1091
|
+
}
|
|
1092
|
+
if (!Object.prototype.hasOwnProperty.call(current, key)) {
|
|
1093
|
+
return false
|
|
1094
|
+
}
|
|
1095
|
+
current = current[key]
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return true
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* 获取对象的属性数量
|
|
1103
|
+
* @param {Object} obj
|
|
1104
|
+
* @returns {number}
|
|
1105
|
+
* @example
|
|
1106
|
+
* size({a: 1, b: 2}) // 2
|
|
1107
|
+
* size([1, 2, 3]) // 3
|
|
1108
|
+
* size('hello') // 5
|
|
1109
|
+
* size(null) // 0
|
|
1110
|
+
*/
|
|
1111
|
+
export function size(obj) {
|
|
1112
|
+
if (obj === null || obj === undefined) {
|
|
1113
|
+
return 0
|
|
1114
|
+
}
|
|
1115
|
+
if (isArrayLike(obj)) {
|
|
1116
|
+
return obj.length
|
|
1117
|
+
}
|
|
1118
|
+
return Object.keys(obj).length
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* 判断两个对象是否相等(深度比较)
|
|
1123
|
+
* @param {*} a
|
|
1124
|
+
* @param {*} b
|
|
1125
|
+
* @returns {boolean}
|
|
1126
|
+
* @example
|
|
1127
|
+
* isEqualDeep({a: 1}, {a: 1}) // true
|
|
1128
|
+
* isEqualDeep({a: {b: 1}}, {a: {b: 1}}) // true
|
|
1129
|
+
* isEqualDeep({a: 1}, {a: 2}) // false
|
|
1130
|
+
*/
|
|
1131
|
+
export function isEqualDeep(a, b) {
|
|
1132
|
+
return isEqual(a, b)
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* 合并对象,覆盖源对象重复路径的值,支持 getter/setter
|
|
1137
|
+
* @param {Object} target - 目标对象
|
|
1138
|
+
* @param {...Object} sources - 一个或多个源对象
|
|
1139
|
+
* @returns {Object} 返回目标对象
|
|
1140
|
+
* @example
|
|
1141
|
+
* merge({a: 1}, {b: 2}) // {a: 1, b: 2}
|
|
1142
|
+
* merge({a: {b: 1}}, {a: {c: 2}}) // {a: {b: 1, c: 2}}
|
|
1143
|
+
* merge({a: [1, 2]}, {a: [3, 4]}) // {a: [1, 2, 3, 4]}
|
|
1144
|
+
*/
|
|
1145
|
+
export function merge(target, ...sources) {
|
|
1146
|
+
if (!isObject(target)) {
|
|
1147
|
+
target = {}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
for (const source of sources) {
|
|
1151
|
+
if (!isObject(source)) {
|
|
1152
|
+
continue
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// 获取源对象所有可枚举的自有属性键
|
|
1156
|
+
const keys = Object.keys(source)
|
|
1157
|
+
|
|
1158
|
+
for (const key of keys) {
|
|
1159
|
+
// 获取完整的属性描述符(包含 value, writable, enumerable, configurable, get, set)
|
|
1160
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key)
|
|
1161
|
+
|
|
1162
|
+
// 如果描述符不存在(极少情况),跳过
|
|
1163
|
+
if (!descriptor) continue
|
|
1164
|
+
|
|
1165
|
+
const srcVal = source[key] // 用于逻辑判断的值(如果是访问器,这里会触发 getter)
|
|
1166
|
+
const tgtVal = target[key] // 目标对象当前的值
|
|
1167
|
+
|
|
1168
|
+
// 检查是否为访问器属性 (getter/setter)
|
|
1169
|
+
const isAccessor = 'get' in descriptor || 'set' in descriptor
|
|
1170
|
+
|
|
1171
|
+
if (isAccessor) {
|
|
1172
|
+
Object.defineProperty(target, key, descriptor)
|
|
1173
|
+
} else if (isPlainObject(srcVal) && isPlainObject(tgtVal)) {
|
|
1174
|
+
target[key] = merge(tgtVal, srcVal)
|
|
1175
|
+
} else if (isArray(srcVal) && isArray(tgtVal)) {
|
|
1176
|
+
target[key] = merge([], tgtVal, srcVal)
|
|
1177
|
+
} else {
|
|
1178
|
+
target[key] = srcVal
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
return target
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* 合并对象,将源对象的所有可枚举自有属性复制到目标对象
|
|
1188
|
+
* @param {Object} target - 目标对象
|
|
1189
|
+
* @param {...Object} sources - 一个或多个源对象
|
|
1190
|
+
* @returns {Object} 返回目标对象
|
|
1191
|
+
* @example
|
|
1192
|
+
* assign({ a: 1 }, { b: 2 }) // { a: 1, b: 2 }
|
|
1193
|
+
* assign({ a: 1 }, { a: 2 }, { b: 3 }) // { a: 2, b: 3 }
|
|
1194
|
+
*/
|
|
1195
|
+
export function assign(target, ...sources) {
|
|
1196
|
+
if (target === null || target === undefined) {
|
|
1197
|
+
target = {}
|
|
1198
|
+
}
|
|
1199
|
+
return Object.assign(target, ...sources)
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* 创建对象的部分副本,只包含指定的属性
|
|
1204
|
+
* @param {Object} obj
|
|
1205
|
+
* @param {Array<string>} paths - 要 pick 的属性路径
|
|
1206
|
+
* @returns {Object}
|
|
1207
|
+
* @example
|
|
1208
|
+
* pick({a: 1, b: 2, c: 3}, ['a', 'c']) // {a: 1, c: 3}
|
|
1209
|
+
* pick({a: 1, b: 2}, ['a', 'd']) // {a: 1}
|
|
1210
|
+
* pick({a: {b: 1}}, ['a.b']) // {a: {b: 1}}
|
|
1211
|
+
*/
|
|
1212
|
+
export function pick(obj, paths) {
|
|
1213
|
+
if (!isObject(obj)) {
|
|
1214
|
+
return {}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
const result = {}
|
|
1218
|
+
for (const path of paths) {
|
|
1219
|
+
if (has(obj, path)) {
|
|
1220
|
+
set(result, path, get(obj, path))
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return result
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* 创建对象的部分副本,排除指定的属性
|
|
1228
|
+
* @param {Object} obj
|
|
1229
|
+
* @param {Array<string>} paths - 要排除的属性路径
|
|
1230
|
+
* @returns {Object}
|
|
1231
|
+
* @example
|
|
1232
|
+
* omit({a: 1, b: 2, c: 3}, ['b']) // {a: 1, c: 3}
|
|
1233
|
+
* omit({a: 1, b: 2}, ['c']) // {a: 1, b: 2}
|
|
1234
|
+
* omit({a: 1, b: 2}, ['a', 'b']) // {}
|
|
1235
|
+
*/
|
|
1236
|
+
export function omit(obj, paths) {
|
|
1237
|
+
if (!isObject(obj)) {
|
|
1238
|
+
return {}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const result = {}
|
|
1242
|
+
const allKeys = keys(obj)
|
|
1243
|
+
const excludeKeys = new Set(paths)
|
|
1244
|
+
|
|
1245
|
+
for (const key of allKeys) {
|
|
1246
|
+
if (!excludeKeys.has(key)) {
|
|
1247
|
+
result[key] = obj[key]
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return result
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* 对象键名转换(kebab-case)
|
|
1255
|
+
* @param {Object} obj
|
|
1256
|
+
* @returns {Object}
|
|
1257
|
+
* @example
|
|
1258
|
+
* keysToKebabCase({firstName: 'John'}) // {'first-name': 'John'}
|
|
1259
|
+
* keysToKebabCase({a: {b: 1}}) // {a: {b: 1}}
|
|
1260
|
+
* keysToKebabCase('not an object') // 'not an object'
|
|
1261
|
+
*/
|
|
1262
|
+
export function keysToKebabCase(obj) {
|
|
1263
|
+
if (!isObject(obj)) {
|
|
1264
|
+
return obj
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const result = {}
|
|
1268
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1269
|
+
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '')
|
|
1270
|
+
result[kebabKey] = isObject(value) ? keysToKebabCase(value) : value
|
|
1271
|
+
}
|
|
1272
|
+
return result
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* 对象键名转换(camelCase)
|
|
1277
|
+
* @param {Object} obj
|
|
1278
|
+
* @returns {Object}
|
|
1279
|
+
* @example
|
|
1280
|
+
* keysToCamelCase({'first-name': 'John'}) // {firstName: 'John'}
|
|
1281
|
+
* keysToCamelCase({a_b: 1}) // {aB: 1}
|
|
1282
|
+
* keysToCamelCase('not an object') // 'not an object'
|
|
1283
|
+
*/
|
|
1284
|
+
export function keysToCamelCase(obj) {
|
|
1285
|
+
if (!isObject(obj)) {
|
|
1286
|
+
return obj
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
const result = {}
|
|
1290
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1291
|
+
const camelKey = key.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase())
|
|
1292
|
+
result[camelKey] = isObject(value) ? keysToCamelCase(value) : value
|
|
1293
|
+
}
|
|
1294
|
+
return result
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* 根据路径字符串获取对象中的值
|
|
1299
|
+
* @param {Object} object - 要查询的对象
|
|
1300
|
+
* @param {string} [prop=''] - 属性路径,支持点号分隔(如 'a.b.c')
|
|
1301
|
+
* @returns {*} 如果找到则返回对应的值,否则返回 null
|
|
1302
|
+
* @example
|
|
1303
|
+
* getValueByPath({ a: { b: { c: 1 } } }, 'a.b.c') // 1
|
|
1304
|
+
* getValueByPath({ a: 1 }, 'b') // null
|
|
1305
|
+
* getValueByPath(null, 'a') // null
|
|
1306
|
+
*/
|
|
1307
|
+
export function getValueByPath(object, prop) {
|
|
1308
|
+
const pathStr = prop || '';
|
|
1309
|
+
const paths = pathStr.split('.');
|
|
1310
|
+
let current = object;
|
|
1311
|
+
let result = null;
|
|
1312
|
+
for (let i = 0, j = paths.length; i < j; i++) {
|
|
1313
|
+
const path = paths[i];
|
|
1314
|
+
if (!current) break;
|
|
1315
|
+
|
|
1316
|
+
if (i === j - 1) {
|
|
1317
|
+
result = current[path];
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
current = current[path];
|
|
1321
|
+
}
|
|
1322
|
+
return result;
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* 根据路径字符串获取对象中的属性信息
|
|
1327
|
+
* 支持点号分隔符 (.) 和方括号表示法 ([]),例如 'a.b.c' 或 'a[b][c]'。
|
|
1328
|
+
*
|
|
1329
|
+
* @param {Object} obj - 要查询的目标对象
|
|
1330
|
+
* @param {string} path - 属性路径字符串
|
|
1331
|
+
* @param {boolean} [strict=false] - 是否开启严格模式。
|
|
1332
|
+
* - 如果为 true,当路径无效或中间节点非对象时抛出错误。
|
|
1333
|
+
* - 如果为 false,当路径无效时会中断遍历并返回当前状态。
|
|
1334
|
+
* @returns {Object} 返回一个包含以下属性的对象:
|
|
1335
|
+
* - o: {Object|null} 最后一层有效的父对象。如果路径第一步就失败,则为 null 或原始 obj。
|
|
1336
|
+
* - k: {string|undefined} 最后一层的键名。
|
|
1337
|
+
* - v: {*} 最终获取到的值,如果路径无效则为 undefined 或 null。
|
|
1338
|
+
* @throws {Error} 当 strict 为 true 且路径无效时抛出错误
|
|
1339
|
+
*/
|
|
1340
|
+
export function getPropByPath(obj, path, strict) {
|
|
1341
|
+
// 1. 基础校验
|
|
1342
|
+
if (obj === null || obj === undefined) {
|
|
1343
|
+
if (strict) {
|
|
1344
|
+
throw new Error('please transfer a valid prop path to form item!');
|
|
1345
|
+
}
|
|
1346
|
+
return { o: null, k: undefined, v: undefined };
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
if (typeof path !== 'string' || !path) {
|
|
1350
|
+
if (strict) {
|
|
1351
|
+
throw new Error('please transfer a valid prop path to form item!');
|
|
1352
|
+
}
|
|
1353
|
+
return { o: obj, k: undefined, v: undefined };
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
let tempObj = obj;
|
|
1357
|
+
// 标准化路径:a[b] -> a.b, .a.b -> a.b
|
|
1358
|
+
path = path.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
|
|
1359
|
+
const keyArr = path.split('.');
|
|
1360
|
+
const len = keyArr.length;
|
|
1361
|
+
|
|
1362
|
+
// 如果路径只有一个键,直接处理
|
|
1363
|
+
if (len === 1) {
|
|
1364
|
+
return {
|
|
1365
|
+
o: obj,
|
|
1366
|
+
k: keyArr[0],
|
|
1367
|
+
v: obj[keyArr[0]]
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
let i = 0;
|
|
1372
|
+
// 开始遍历
|
|
1373
|
+
for (; i < len - 1; ++i) {
|
|
1374
|
+
const key = keyArr[i];
|
|
1375
|
+
// 检查当前层级是否存在且为对象/数组(排除 null)
|
|
1376
|
+
// 注意:Array 也是 object,in 操作符对数组有效
|
|
1377
|
+
if (tempObj === null || tempObj === undefined || typeof tempObj !== 'object') {
|
|
1378
|
+
// 非严格模式:中断,返回当前状态
|
|
1379
|
+
// 此时 tempObj 是导致中断的那个非对象值(或 null/undefined)
|
|
1380
|
+
// 为了保持与原 API 兼容,我们返回中断前的状态或者 null
|
|
1381
|
+
if (strict) throw new Error(`Property '${key}' does not exist on ${tempObj} at path '${keyArr.slice(0, i).join('.')}'`);
|
|
1382
|
+
return {
|
|
1383
|
+
o: null,
|
|
1384
|
+
k: key,
|
|
1385
|
+
v: undefined
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (key in tempObj) {
|
|
1390
|
+
tempObj = tempObj[key];
|
|
1391
|
+
} else {
|
|
1392
|
+
// 键不存在,中断
|
|
1393
|
+
if (strict) throw new Error('please transfer a valid prop path to form item!');
|
|
1394
|
+
return {
|
|
1395
|
+
o: tempObj, // 父对象还存在
|
|
1396
|
+
k: key,
|
|
1397
|
+
v: undefined
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
const lastKey = keyArr[i];
|
|
1403
|
+
if (tempObj === null || tempObj === undefined) {
|
|
1404
|
+
if (strict) throw new Error('please transfer a valid prop path to form item!');
|
|
1405
|
+
return { o: null, k: lastKey, v: undefined };
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
return {
|
|
1409
|
+
o: tempObj,
|
|
1410
|
+
k: lastKey,
|
|
1411
|
+
v: tempObj[lastKey]
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// ==================== 字符串函数 ====================
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* 将字符串转换为 kebab-case
|
|
1419
|
+
* @param {string} str
|
|
1420
|
+
* @returns {string}
|
|
1421
|
+
* @example
|
|
1422
|
+
* kebabCase('helloWorld') // 'hello-world'
|
|
1423
|
+
* kebabCase('Hello World') // 'hello-world'
|
|
1424
|
+
* kebabCase('hello_world') // 'hello-world'
|
|
1425
|
+
*/
|
|
1426
|
+
export function kebabCase(str) {
|
|
1427
|
+
if (!isString(str)) {
|
|
1428
|
+
return ''
|
|
1429
|
+
}
|
|
1430
|
+
return str
|
|
1431
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
1432
|
+
.replace(/[\s_]+/g, '-')
|
|
1433
|
+
.toLowerCase()
|
|
1434
|
+
.replace(/^-|-$/g, '')
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* 将字符串转换为 camelCase
|
|
1439
|
+
* @param {string} str
|
|
1440
|
+
* @returns {string}
|
|
1441
|
+
* @example
|
|
1442
|
+
* camelCase('hello-world') // 'helloWorld'
|
|
1443
|
+
* camelCase('Hello World') // 'helloWorld'
|
|
1444
|
+
* camelCase('hello_world') // 'helloWorld'
|
|
1445
|
+
*/
|
|
1446
|
+
export function camelCase(str) {
|
|
1447
|
+
if (!isString(str)) {
|
|
1448
|
+
return ''
|
|
1449
|
+
}
|
|
1450
|
+
return str
|
|
1451
|
+
.replace(/[-_\s]+(.)?/g, (match, char) => char ? char.toUpperCase() : '')
|
|
1452
|
+
.replace(/^[A-Z]/, char => char.toLowerCase())
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/**
|
|
1456
|
+
* 将字符串转换为 PascalCase
|
|
1457
|
+
* @param {string} str
|
|
1458
|
+
* @returns {string}
|
|
1459
|
+
* @example
|
|
1460
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
1461
|
+
* pascalCase('hello world') // 'HelloWorld'
|
|
1462
|
+
* pascalCase('hello_world') // 'HelloWorld'
|
|
1463
|
+
*/
|
|
1464
|
+
export function pascalCase(str) {
|
|
1465
|
+
if (!isString(str)) {
|
|
1466
|
+
return ''
|
|
1467
|
+
}
|
|
1468
|
+
const camel = camelCase(str)
|
|
1469
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1)
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* 将字符串转换为 snake_case
|
|
1474
|
+
* @param {string} str
|
|
1475
|
+
* @returns {string}
|
|
1476
|
+
* @example
|
|
1477
|
+
* snakeCase('helloWorld') // 'hello_world'
|
|
1478
|
+
* snakeCase('Hello World') // 'hello_world'
|
|
1479
|
+
* snakeCase('hello-world') // 'hello_world'
|
|
1480
|
+
*/
|
|
1481
|
+
export function snakeCase(str) {
|
|
1482
|
+
if (!isString(str)) {
|
|
1483
|
+
return ''
|
|
1484
|
+
}
|
|
1485
|
+
return str
|
|
1486
|
+
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
1487
|
+
.replace(/[\s-]+/g, '_')
|
|
1488
|
+
.toLowerCase()
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* 将字符串转换为首字母大写
|
|
1493
|
+
* @param {string} str
|
|
1494
|
+
* @returns {string}
|
|
1495
|
+
* @example
|
|
1496
|
+
* capitalize('hello') // 'Hello'
|
|
1497
|
+
* capitalize('HELLO') // 'Hello'
|
|
1498
|
+
* capitalize('hello world') // 'Hello world'
|
|
1499
|
+
*/
|
|
1500
|
+
export function capitalize(str) {
|
|
1501
|
+
if (!isString(str)) {
|
|
1502
|
+
return ''
|
|
1503
|
+
}
|
|
1504
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* 将字符串重复指定次数
|
|
1509
|
+
* @param {string} str
|
|
1510
|
+
* @param {number} n
|
|
1511
|
+
* @returns {string}
|
|
1512
|
+
* @example
|
|
1513
|
+
* repeat('abc', 3) // 'abcabcabc'
|
|
1514
|
+
* repeat('abc', 0) // ''
|
|
1515
|
+
* repeat('abc', 1) // 'abc'
|
|
1516
|
+
*/
|
|
1517
|
+
export function repeat(str, n = 0) {
|
|
1518
|
+
if (!isString(str) || n < 1) {
|
|
1519
|
+
return ''
|
|
1520
|
+
}
|
|
1521
|
+
return str.repeat(Math.floor(n))
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/**
|
|
1525
|
+
* 将字符串填充到指定长度
|
|
1526
|
+
* @param {string} str
|
|
1527
|
+
* @param {number} length
|
|
1528
|
+
* @param {string} [chars=' ']
|
|
1529
|
+
* @returns {string}
|
|
1530
|
+
* @example
|
|
1531
|
+
* pad('abc', 8) // ' abc '
|
|
1532
|
+
* pad('abc', 8, '_-') // '_-abc_-_'
|
|
1533
|
+
* pad('abc', 3) // 'abc'
|
|
1534
|
+
*/
|
|
1535
|
+
export function pad(str, length, chars = ' ') {
|
|
1536
|
+
if (!isString(str) || length <= str.length) {
|
|
1537
|
+
return str
|
|
1538
|
+
}
|
|
1539
|
+
const totalPad = length - str.length
|
|
1540
|
+
const padStart = Math.floor(totalPad / 2)
|
|
1541
|
+
const padEnd = totalPad - padStart
|
|
1542
|
+
return chars.repeat(padStart) + str + chars.repeat(padEnd)
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
/**
|
|
1546
|
+
* 左侧填充字符串
|
|
1547
|
+
* @param {string} str
|
|
1548
|
+
* @param {number} length
|
|
1549
|
+
* @param {string} [chars=' ']
|
|
1550
|
+
* @returns {string}
|
|
1551
|
+
* @example
|
|
1552
|
+
* padStart('abc', 6) // ' abc'
|
|
1553
|
+
* padStart('abc', 6, '_-') // '_-_abc'
|
|
1554
|
+
* padStart('abc', 3) // 'abc'
|
|
1555
|
+
*/
|
|
1556
|
+
export function padStart(str, length, chars = ' ') {
|
|
1557
|
+
if (!isString(str) || length <= str.length) {
|
|
1558
|
+
return str
|
|
1559
|
+
}
|
|
1560
|
+
return chars.repeat(length - str.length) + str
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/**
|
|
1564
|
+
* 右侧填充字符串
|
|
1565
|
+
* @param {string} str
|
|
1566
|
+
* @param {number} length
|
|
1567
|
+
* @param {string} [chars=' ']
|
|
1568
|
+
* @returns {string}
|
|
1569
|
+
* @example
|
|
1570
|
+
* padEnd('abc', 6) // 'abc '
|
|
1571
|
+
* padEnd('abc', 6, '_-') // 'abc_-_'
|
|
1572
|
+
* padEnd('abc', 3) // 'abc'
|
|
1573
|
+
*/
|
|
1574
|
+
export function padEnd(str, length, chars = ' ') {
|
|
1575
|
+
if (!isString(str) || length <= str.length) {
|
|
1576
|
+
return str
|
|
1577
|
+
}
|
|
1578
|
+
return str + chars.repeat(length - str.length)
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/**
|
|
1582
|
+
* 去除字符串两端的空白
|
|
1583
|
+
* @param {string} str
|
|
1584
|
+
* @returns {string}
|
|
1585
|
+
* @example
|
|
1586
|
+
* trim(' abc ') // 'abc'
|
|
1587
|
+
* trim('abc') // 'abc'
|
|
1588
|
+
* trim(null) // ''
|
|
1589
|
+
*/
|
|
1590
|
+
export function trim(str) {
|
|
1591
|
+
return isString(str) ? str.trim() : ''
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* 去除字符串左端的空白
|
|
1596
|
+
* @param {string} str
|
|
1597
|
+
* @returns {string}
|
|
1598
|
+
* @example
|
|
1599
|
+
* trimStart(' abc ') // 'abc '
|
|
1600
|
+
* trimStart('abc') // 'abc'
|
|
1601
|
+
* trimStart(null) // ''
|
|
1602
|
+
*/
|
|
1603
|
+
export function trimStart(str) {
|
|
1604
|
+
return isString(str) ? str.trimStart() : ''
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* 去除字符串右端的空白
|
|
1609
|
+
* @param {string} str
|
|
1610
|
+
* @returns {string}
|
|
1611
|
+
* @example
|
|
1612
|
+
* trimEnd(' abc ') // ' abc'
|
|
1613
|
+
* trimEnd('abc') // 'abc'
|
|
1614
|
+
* trimEnd(null) // ''
|
|
1615
|
+
*/
|
|
1616
|
+
export function trimEnd(str) {
|
|
1617
|
+
return isString(str) ? str.trimEnd() : ''
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* 将字符串截断到指定长度
|
|
1622
|
+
* @param {string} str
|
|
1623
|
+
* @param {Object} [options]
|
|
1624
|
+
* @param {number} [options.length=30] - 最大长度
|
|
1625
|
+
* @param {string} [options.omission='...'] - 截断标记
|
|
1626
|
+
* @param {boolean} [options.separator=false] - 是否在单词边界截断
|
|
1627
|
+
* @returns {string}
|
|
1628
|
+
* @example
|
|
1629
|
+
* truncate('hello world', {length: 8}) // 'he...'
|
|
1630
|
+
* truncate('hello world', {length: 8, omission: '→'}) // 'hello w→'
|
|
1631
|
+
* truncate('hello world', {length: 15}) // 'hello world'
|
|
1632
|
+
*/
|
|
1633
|
+
export function truncate(str, options = {}) {
|
|
1634
|
+
if (!isString(str)) {
|
|
1635
|
+
return ''
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
const {
|
|
1639
|
+
length: maxLength = 30,
|
|
1640
|
+
omission = '...',
|
|
1641
|
+
separator = false
|
|
1642
|
+
} = options
|
|
1643
|
+
|
|
1644
|
+
if (str.length <= maxLength) {
|
|
1645
|
+
return str
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
let result = str.slice(0, maxLength - omission.length)
|
|
1649
|
+
|
|
1650
|
+
if (separator) {
|
|
1651
|
+
const separatorIndex = result.lastIndexOf(/\s/)
|
|
1652
|
+
if (separatorIndex > 0) {
|
|
1653
|
+
result = result.slice(0, separatorIndex)
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
return result + omission
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* 转义 HTML 字符
|
|
1662
|
+
* @param {string} str
|
|
1663
|
+
* @returns {string}
|
|
1664
|
+
* @example
|
|
1665
|
+
* escape('<script>alert("XSS")</script>') // '<script>alert("XSS")</script>'
|
|
1666
|
+
* escape('a & b') // 'a & b'
|
|
1667
|
+
* escape('hello') // 'hello'
|
|
1668
|
+
*/
|
|
1669
|
+
export function escape(str) {
|
|
1670
|
+
if (!isString(str)) {
|
|
1671
|
+
return ''
|
|
1672
|
+
}
|
|
1673
|
+
const escapes = {
|
|
1674
|
+
'&': '&',
|
|
1675
|
+
'<': '<',
|
|
1676
|
+
'>': '>',
|
|
1677
|
+
'"': '"',
|
|
1678
|
+
"'": '''
|
|
1679
|
+
}
|
|
1680
|
+
return str.replace(/[&<>"']/g, char => escapes[char])
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/**
|
|
1684
|
+
* 反转义 HTML 字符
|
|
1685
|
+
* @param {string} str
|
|
1686
|
+
* @returns {string}
|
|
1687
|
+
* @example
|
|
1688
|
+
* unescape('<script>') // '<script>'
|
|
1689
|
+
* unescape('&') // '&'
|
|
1690
|
+
* unescape('hello') // 'hello'
|
|
1691
|
+
*/
|
|
1692
|
+
export function unescape(str) {
|
|
1693
|
+
if (!isString(str)) {
|
|
1694
|
+
return ''
|
|
1695
|
+
}
|
|
1696
|
+
const escapes = {
|
|
1697
|
+
'&': '&',
|
|
1698
|
+
'<': '<',
|
|
1699
|
+
'>': '>',
|
|
1700
|
+
'"': '"',
|
|
1701
|
+
''': "'"
|
|
1702
|
+
}
|
|
1703
|
+
return str.replace(/&(amp|lt|gt|quot|#39);/g, match => escapes[match])
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/**
|
|
1707
|
+
* 转义正则表达式特殊字符
|
|
1708
|
+
* @param {*} [value=''] - 需要转义的字符串
|
|
1709
|
+
* @returns {string} 转义后的字符串,可安全用于 RegExp 构造函数
|
|
1710
|
+
* @example
|
|
1711
|
+
* escapeRegexpString('hello.world') // 'hello\\.world'
|
|
1712
|
+
* escapeRegexpString('(test)') // '\\(test\\)'
|
|
1713
|
+
* escapeRegexpString('a+b*c?') // 'a\\+b\\*c\\?'
|
|
1714
|
+
*/
|
|
1715
|
+
export function escapeRegexpString(value = '') {
|
|
1716
|
+
return String(value).replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// ==================== 数字函数 ====================
|
|
1720
|
+
|
|
1721
|
+
/**
|
|
1722
|
+
* 将数字四舍五入到指定的小数位数
|
|
1723
|
+
* @param {number} num
|
|
1724
|
+
* @param {number} [precision=0] - 小数位数
|
|
1725
|
+
* @returns {number}
|
|
1726
|
+
* @example
|
|
1727
|
+
* round(4.006) // 4
|
|
1728
|
+
* round(4.006, 2) // 4.01
|
|
1729
|
+
* round(4060, -2) // 4100
|
|
1730
|
+
*/
|
|
1731
|
+
export function round(num, precision = 0) {
|
|
1732
|
+
if (!isFiniteNumber(num)) {
|
|
1733
|
+
return num
|
|
1734
|
+
}
|
|
1735
|
+
const factor = Math.pow(10, precision)
|
|
1736
|
+
return Math.round(num * factor) / factor
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* 将数字向上取整到指定的小数位数
|
|
1741
|
+
* @param {number} num
|
|
1742
|
+
* @param {number} [precision=0]
|
|
1743
|
+
* @returns {number}
|
|
1744
|
+
* @example
|
|
1745
|
+
* ceil(4.006) // 5
|
|
1746
|
+
* ceil(4.006, 2) // 4.01
|
|
1747
|
+
* ceil(4060, -2) // 4100
|
|
1748
|
+
*/
|
|
1749
|
+
export function ceil(num, precision = 0) {
|
|
1750
|
+
if (!isFiniteNumber(num)) {
|
|
1751
|
+
return num
|
|
1752
|
+
}
|
|
1753
|
+
const factor = Math.pow(10, precision)
|
|
1754
|
+
return Math.ceil(num * factor) / factor
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* 将数字向下取整到指定的小数位数
|
|
1759
|
+
* @param {number} num
|
|
1760
|
+
* @param {number} [precision=0]
|
|
1761
|
+
* @returns {number}
|
|
1762
|
+
* @example
|
|
1763
|
+
* floor(4.006) // 4
|
|
1764
|
+
* floor(4.006, 2) // 4
|
|
1765
|
+
* floor(4060, -2) // 4000
|
|
1766
|
+
*/
|
|
1767
|
+
export function floor(num, precision = 0) {
|
|
1768
|
+
if (!isFiniteNumber(num)) {
|
|
1769
|
+
return num
|
|
1770
|
+
}
|
|
1771
|
+
const factor = Math.pow(10, precision)
|
|
1772
|
+
return Math.floor(num * factor) / factor
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
/**
|
|
1776
|
+
* 获取随机数
|
|
1777
|
+
* @param {number} [min=0]
|
|
1778
|
+
* @param {number} [max=1]
|
|
1779
|
+
* @returns {number}
|
|
1780
|
+
* @example
|
|
1781
|
+
* random(0, 5) // 0 到 5 之间的随机数
|
|
1782
|
+
* random(1.2, 5.5) // 1.2 到 5.5 之间的随机数
|
|
1783
|
+
* random() // 0 到 1 之间的随机数
|
|
1784
|
+
*/
|
|
1785
|
+
export function random(min = 0, max = 1) {
|
|
1786
|
+
if (max < min) {
|
|
1787
|
+
[min, max] = [max, min]
|
|
1788
|
+
}
|
|
1789
|
+
return Math.random() * (max - min) + min
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
/**
|
|
1793
|
+
* 获取范围内的随机整数
|
|
1794
|
+
* @param {number} [min=0]
|
|
1795
|
+
* @param {number} [max=1]
|
|
1796
|
+
* @returns {number}
|
|
1797
|
+
* @example
|
|
1798
|
+
* randomInt(0, 5) // 0 到 5 之间的随机整数
|
|
1799
|
+
* randomInt(3) // 0 到 3 之间的随机整数
|
|
1800
|
+
* randomInt() // 0 或 1
|
|
1801
|
+
*/
|
|
1802
|
+
export function randomInt(min = 0, max = 1) {
|
|
1803
|
+
if (max < min) {
|
|
1804
|
+
[min, max] = [max, min]
|
|
1805
|
+
}
|
|
1806
|
+
min = Math.ceil(min)
|
|
1807
|
+
max = Math.floor(max)
|
|
1808
|
+
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
/**
|
|
1812
|
+
* 确保数字在范围内
|
|
1813
|
+
* @param {number} num
|
|
1814
|
+
* @param {number} min
|
|
1815
|
+
* @param {number} max
|
|
1816
|
+
* @returns {number}
|
|
1817
|
+
* @example
|
|
1818
|
+
* clamp(-10, 0, 100) // 0
|
|
1819
|
+
* clamp(50, 0, 100) // 50
|
|
1820
|
+
* clamp(150, 0, 100) // 100
|
|
1821
|
+
*/
|
|
1822
|
+
export function clamp(num, min, max) {
|
|
1823
|
+
return Math.min(Math.max(num, min), max)
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/**
|
|
1827
|
+
* 检查值是否在范围内
|
|
1828
|
+
* @param {number} value
|
|
1829
|
+
* @param {number} min
|
|
1830
|
+
* @param {number} max
|
|
1831
|
+
* @param {Object} [options]
|
|
1832
|
+
* @param {boolean} [options.leftClosed=true]
|
|
1833
|
+
* @param {boolean} [options.rightClosed=true]
|
|
1834
|
+
* @returns {boolean}
|
|
1835
|
+
* @example
|
|
1836
|
+
* isInRange(5, 1, 10) // true
|
|
1837
|
+
* isInRange(1, 1, 10, {leftClosed: false}) // false
|
|
1838
|
+
* isInRange(10, 1, 10, {rightClosed: false}) // false
|
|
1839
|
+
*/
|
|
1840
|
+
export function isInRange(value, min, max, options = {}) {
|
|
1841
|
+
const {
|
|
1842
|
+
leftClosed = true,
|
|
1843
|
+
rightClosed = true
|
|
1844
|
+
} = options
|
|
1845
|
+
|
|
1846
|
+
if (!isFiniteNumber(value) || !isFiniteNumber(min) || !isFiniteNumber(max)) {
|
|
1847
|
+
return false
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
const [minVal, maxVal] = min <= max ? [min, max] : [max, min]
|
|
1851
|
+
|
|
1852
|
+
const leftCheck = leftClosed ? value >= minVal : value > minVal
|
|
1853
|
+
const rightCheck = rightClosed ? value <= maxVal : value < maxVal
|
|
1854
|
+
|
|
1855
|
+
return leftCheck && rightCheck
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// ==================== 函数函数 ====================
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
* 创建一个防抖函数
|
|
1862
|
+
* @param {Function} func
|
|
1863
|
+
* @param {number} wait - 等待毫秒数
|
|
1864
|
+
* @param {Object} [options]
|
|
1865
|
+
* @param {boolean} [options.immediate=false] - 是否立即执行
|
|
1866
|
+
* @returns {Function}
|
|
1867
|
+
* @example
|
|
1868
|
+
* const debouncedFn = debounce(() => console.log('executed'), 300)
|
|
1869
|
+
* debouncedFn() // 300ms 后执行
|
|
1870
|
+
* const immediateDebounce = debounce(fn, 300, {immediate: true}) // 立即执行
|
|
1871
|
+
*/
|
|
1872
|
+
export function debounce(func, wait, options = {}) {
|
|
1873
|
+
const { immediate = false } = options
|
|
1874
|
+
let timeout = null
|
|
1875
|
+
|
|
1876
|
+
return function (...args) {
|
|
1877
|
+
const context = this
|
|
1878
|
+
const later = () => {
|
|
1879
|
+
timeout = null
|
|
1880
|
+
if (!immediate) {
|
|
1881
|
+
func.apply(context, args)
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
const callNow = immediate && !timeout
|
|
1886
|
+
clearTimeout(timeout)
|
|
1887
|
+
timeout = setTimeout(later, wait)
|
|
1888
|
+
|
|
1889
|
+
if (callNow) {
|
|
1890
|
+
func.apply(context, args)
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
/**
|
|
1896
|
+
* 创建一个节流函数
|
|
1897
|
+
* @param {Function} func
|
|
1898
|
+
* @param {number} wait - 等待毫秒数
|
|
1899
|
+
* @param {Object} [options]
|
|
1900
|
+
* @param {boolean} [options.leading=true] - 是否执行开头
|
|
1901
|
+
* @param {boolean} [options.trailing=true] - 是否执行结尾
|
|
1902
|
+
* @returns {Function}
|
|
1903
|
+
* @example
|
|
1904
|
+
* const throttledFn = throttle(() => console.log('executed'), 1000)
|
|
1905
|
+
* throttledFn() // 立即执行,然后 1 秒内不再执行
|
|
1906
|
+
* const trailingFn = throttle(fn, 1000, {leading: false}) // 只执行结尾
|
|
1907
|
+
*/
|
|
1908
|
+
export function throttle(func, wait, options = {}) {
|
|
1909
|
+
const { leading = true, trailing = true } = options
|
|
1910
|
+
let timeout = null
|
|
1911
|
+
let previous = 0
|
|
1912
|
+
let lastArgs = null
|
|
1913
|
+
let lastContext = null
|
|
1914
|
+
|
|
1915
|
+
const invoke = () => {
|
|
1916
|
+
previous = leading ? Date.now() : 0
|
|
1917
|
+
if (trailing && lastArgs) {
|
|
1918
|
+
func.apply(lastContext, lastArgs)
|
|
1919
|
+
lastArgs = null
|
|
1920
|
+
lastContext = null
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
return function (...args) {
|
|
1925
|
+
const now = Date.now()
|
|
1926
|
+
|
|
1927
|
+
if (!previous && !leading) {
|
|
1928
|
+
previous = now
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
const remaining = wait - (now - previous)
|
|
1932
|
+
lastArgs = args
|
|
1933
|
+
lastContext = this
|
|
1934
|
+
|
|
1935
|
+
if (remaining <= 0) {
|
|
1936
|
+
clearTimeout(timeout)
|
|
1937
|
+
timeout = null
|
|
1938
|
+
previous = now
|
|
1939
|
+
func.apply(this, args)
|
|
1940
|
+
} else if (!timeout && trailing) {
|
|
1941
|
+
timeout = setTimeout(invoke, remaining)
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
/**
|
|
1947
|
+
* 创建一个只执行一次的函数
|
|
1948
|
+
* @param {Function} func
|
|
1949
|
+
* @returns {Function}
|
|
1950
|
+
* @example
|
|
1951
|
+
* const initOnce = once(() => console.log('initialized'))
|
|
1952
|
+
* initOnce() // 'initialized'
|
|
1953
|
+
* initOnce() // 无输出
|
|
1954
|
+
*/
|
|
1955
|
+
export function once(func) {
|
|
1956
|
+
let called = false
|
|
1957
|
+
let result
|
|
1958
|
+
|
|
1959
|
+
return function (...args) {
|
|
1960
|
+
if (!called) {
|
|
1961
|
+
called = true
|
|
1962
|
+
result = func.apply(this, args)
|
|
1963
|
+
}
|
|
1964
|
+
return result
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/**
|
|
1969
|
+
* 创建一个取反结果的函数
|
|
1970
|
+
* @param {Function} predicate
|
|
1971
|
+
* @returns {Function}
|
|
1972
|
+
* @example
|
|
1973
|
+
* const isEven = n => n % 2 === 0
|
|
1974
|
+
* const isOdd = negate(isEven)
|
|
1975
|
+
* isOdd(3) // true
|
|
1976
|
+
* isOdd(2) // false
|
|
1977
|
+
*/
|
|
1978
|
+
export function negate(predicate) {
|
|
1979
|
+
return function (...args) {
|
|
1980
|
+
return !predicate.apply(this, args)
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* 空函数,什么都不做
|
|
1986
|
+
* @returns {void}
|
|
1987
|
+
* @example
|
|
1988
|
+
* noop() // undefined
|
|
1989
|
+
* const callback = options.callback || noop
|
|
1990
|
+
*/
|
|
1991
|
+
export function noop() {}
|
|
1992
|
+
|
|
1993
|
+
/**
|
|
1994
|
+
* 返回传入的值(恒等函数)
|
|
1995
|
+
* @param {*} value
|
|
1996
|
+
* @returns {*}
|
|
1997
|
+
* @example
|
|
1998
|
+
* identity(1) // 1
|
|
1999
|
+
* identity({a: 1}) // {a: 1}
|
|
2000
|
+
* [1, 2, 3].map(identity) // [1, 2, 3]
|
|
2001
|
+
*/
|
|
2002
|
+
export function identity(value) {
|
|
2003
|
+
return value
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* 创建一个返回固定值的函数
|
|
2008
|
+
* @param {*} value
|
|
2009
|
+
* @returns {Function}
|
|
2010
|
+
* @example
|
|
2011
|
+
* const returnZero = constant(0)
|
|
2012
|
+
* returnZero() // 0
|
|
2013
|
+
* const defaultOptions = constant({debug: false})
|
|
2014
|
+
*/
|
|
2015
|
+
export function constant(value) {
|
|
2016
|
+
return () => value
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
/**
|
|
2020
|
+
* 创建一个组合函数(从右到左执行)
|
|
2021
|
+
* @param {...Function} funcs
|
|
2022
|
+
* @returns {Function}
|
|
2023
|
+
* @example
|
|
2024
|
+
* const double = n => n * 2
|
|
2025
|
+
* const addOne = n => n + 1
|
|
2026
|
+
* const transform = compose(double, addOne)
|
|
2027
|
+
* transform(5) // 12 (先加 1 得 6,再乘 2 得 12)
|
|
2028
|
+
*/
|
|
2029
|
+
export function compose(...funcs) {
|
|
2030
|
+
return function (arg) {
|
|
2031
|
+
return funcs.reduceRight((result, func) => func(result), arg)
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
/**
|
|
2036
|
+
* 创建一个组合函数(从左到右执行)
|
|
2037
|
+
* @param {...Function} funcs
|
|
2038
|
+
* @returns {Function}
|
|
2039
|
+
* @example
|
|
2040
|
+
* const double = n => n * 2
|
|
2041
|
+
* const addOne = n => n + 1
|
|
2042
|
+
* const transform = pipe(double, addOne)
|
|
2043
|
+
* transform(5) // 11 (先乘 2 得 10,再加 1 得 11)
|
|
2044
|
+
*/
|
|
2045
|
+
export function pipe(...funcs) {
|
|
2046
|
+
return function (arg) {
|
|
2047
|
+
return funcs.reduce((result, func) => func(result), arg)
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// ==================== 日期函数 ====================
|
|
2052
|
+
|
|
2053
|
+
/**
|
|
2054
|
+
* 格式化日期
|
|
2055
|
+
* @param {Date|string|number} date
|
|
2056
|
+
* @param {string} [format='YYYY-MM-DD HH:mm:ss']
|
|
2057
|
+
* @returns {string}
|
|
2058
|
+
* @example
|
|
2059
|
+
* formatDate(new Date('2024-01-15')) // '2024-01-15 00:00:00'
|
|
2060
|
+
* formatDate(new Date(), 'YYYY/MM/DD') // '2024/03/25'
|
|
2061
|
+
* formatDate(1705305600000) // '2024-01-15 00:00:00'
|
|
2062
|
+
*/
|
|
2063
|
+
export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
|
|
2064
|
+
if (!date) {
|
|
2065
|
+
return ''
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const d = new Date(date)
|
|
2069
|
+
if (isNaN(d.getTime())) {
|
|
2070
|
+
return ''
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
const year = d.getFullYear()
|
|
2074
|
+
const month = String(d.getMonth() + 1).padStart(2, '0')
|
|
2075
|
+
const day = String(d.getDate()).padStart(2, '0')
|
|
2076
|
+
const hours = String(d.getHours()).padStart(2, '0')
|
|
2077
|
+
const minutes = String(d.getMinutes()).padStart(2, '0')
|
|
2078
|
+
const seconds = String(d.getSeconds()).padStart(2, '0')
|
|
2079
|
+
const milliseconds = String(d.getMilliseconds()).padStart(3, '0')
|
|
2080
|
+
|
|
2081
|
+
return format
|
|
2082
|
+
.replace('YYYY', year)
|
|
2083
|
+
.replace('MM', month)
|
|
2084
|
+
.replace('DD', day)
|
|
2085
|
+
.replace('HH', hours)
|
|
2086
|
+
.replace('mm', minutes)
|
|
2087
|
+
.replace('ss', seconds)
|
|
2088
|
+
.replace('SSS', milliseconds)
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
/**
|
|
2092
|
+
* 解析日期字符串
|
|
2093
|
+
* @param {string} str
|
|
2094
|
+
* @param {string} [format='YYYY-MM-DD HH:mm:ss']
|
|
2095
|
+
* @returns {Date|null}
|
|
2096
|
+
* @example
|
|
2097
|
+
* parseDate('2024-01-15 12:30:00') // Date 对象
|
|
2098
|
+
* parseDate('2024/01/15', 'YYYY/MM/DD') // Date 对象
|
|
2099
|
+
* parseDate('invalid') // null
|
|
2100
|
+
*/
|
|
2101
|
+
export function parseDate(str, format = 'YYYY-MM-DD HH:mm:ss') {
|
|
2102
|
+
if (!str) {
|
|
2103
|
+
return null
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
const parts = {}
|
|
2107
|
+
const pattern = format
|
|
2108
|
+
.replace('YYYY', '(?<year>\\d{4})')
|
|
2109
|
+
.replace('MM', '(?<month>\\d{2})')
|
|
2110
|
+
.replace('DD', '(?<day>\\d{2})')
|
|
2111
|
+
.replace('HH', '(?<hours>\\d{2})')
|
|
2112
|
+
.replace('mm', '(?<minutes>\\d{2})')
|
|
2113
|
+
.replace('ss', '(?<seconds>\\d{2})')
|
|
2114
|
+
.replace('SSS', '(?<milliseconds>\\d{3})')
|
|
2115
|
+
|
|
2116
|
+
const regex = new RegExp(pattern)
|
|
2117
|
+
const match = str.match(regex)
|
|
2118
|
+
|
|
2119
|
+
if (!match || !match.groups) {
|
|
2120
|
+
return null
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
const { year, month, day, hours, minutes, seconds, milliseconds } = match.groups
|
|
2124
|
+
|
|
2125
|
+
return new Date(
|
|
2126
|
+
parseInt(year),
|
|
2127
|
+
parseInt(month) - 1,
|
|
2128
|
+
parseInt(day),
|
|
2129
|
+
parseInt(hours || 0),
|
|
2130
|
+
parseInt(minutes || 0),
|
|
2131
|
+
parseInt(seconds || 0),
|
|
2132
|
+
parseInt(milliseconds || 0)
|
|
2133
|
+
)
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
/**
|
|
2137
|
+
* 获取两个日期之间的差异
|
|
2138
|
+
* @param {Date} date1
|
|
2139
|
+
* @param {Date} date2
|
|
2140
|
+
* @param {string} [unit='ms'] - 单位:ms, s, m, h, d
|
|
2141
|
+
* @returns {number}
|
|
2142
|
+
* @example
|
|
2143
|
+
* dateDiff(new Date('2024-01-02'), new Date('2024-01-01')) // 86400000
|
|
2144
|
+
* dateDiff(new Date('2024-01-02'), new Date('2024-01-01'), 'd') // 1
|
|
2145
|
+
* dateDiff(new Date('2024-01-02'), new Date('2024-01-01'), 'h') // 24
|
|
2146
|
+
*/
|
|
2147
|
+
export function dateDiff(date1, date2, unit = 'ms') {
|
|
2148
|
+
const diff = new Date(date1).getTime() - new Date(date2).getTime()
|
|
2149
|
+
|
|
2150
|
+
switch (unit) {
|
|
2151
|
+
case 's': return Math.floor(diff / 1000)
|
|
2152
|
+
case 'm': return Math.floor(diff / 60000)
|
|
2153
|
+
case 'h': return Math.floor(diff / 3600000)
|
|
2154
|
+
case 'd': return Math.floor(diff / 86400000)
|
|
2155
|
+
default: return diff
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// ==================== 深度克隆 ====================
|
|
2160
|
+
|
|
2161
|
+
/**
|
|
2162
|
+
* 深度克隆值
|
|
2163
|
+
* @template T
|
|
2164
|
+
* @param {T} obj
|
|
2165
|
+
* @param {WeakMap} [hash] 用于处理循环引用的缓存
|
|
2166
|
+
* @returns {T}
|
|
2167
|
+
* @example
|
|
2168
|
+
* cloneDeep({a: 1, b: {c: 2}}) // 深拷贝对象
|
|
2169
|
+
* cloneDeep([1, [2, [3]]]) // 深拷贝数组
|
|
2170
|
+
* cloneDeep(new Date()) // 深拷贝日期
|
|
2171
|
+
*/
|
|
2172
|
+
export function cloneDeep(obj, hash = new WeakMap()) {
|
|
2173
|
+
if (obj === null || typeof obj !== 'object') {
|
|
2174
|
+
return obj
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
if (hash.has(obj)) {
|
|
2178
|
+
return hash.get(obj)
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
if (obj instanceof Date) {
|
|
2182
|
+
return new Date(obj.getTime())
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
if (obj instanceof RegExp) {
|
|
2186
|
+
return new RegExp(obj.source, obj.flags)
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
if (obj instanceof Map) {
|
|
2190
|
+
const result = new Map()
|
|
2191
|
+
hash.set(obj, result)
|
|
2192
|
+
for (const [key, value] of obj.entries()) {
|
|
2193
|
+
result.set(key, cloneDeep(value, hash))
|
|
2194
|
+
}
|
|
2195
|
+
return result
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
if (obj instanceof Set) {
|
|
2199
|
+
const result = new Set()
|
|
2200
|
+
hash.set(obj, result)
|
|
2201
|
+
for (const value of obj.values()) {
|
|
2202
|
+
result.add(cloneDeep(value, hash))
|
|
2203
|
+
}
|
|
2204
|
+
return result
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
if (Array.isArray(obj)) {
|
|
2208
|
+
const result = []
|
|
2209
|
+
hash.set(obj, result)
|
|
2210
|
+
return obj.map(item => cloneDeep(item, hash))
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
if (obj.constructor === Object) {
|
|
2214
|
+
const result = Object.create(Object.getPrototypeOf(obj))
|
|
2215
|
+
hash.set(obj, result)
|
|
2216
|
+
for (const key in obj) {
|
|
2217
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
2218
|
+
result[key] = cloneDeep(obj[key], hash)
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return result
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
return obj
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
// ==================== 工具类函数 ====================
|
|
2228
|
+
|
|
2229
|
+
/**
|
|
2230
|
+
* 创建一个定时器 Promise
|
|
2231
|
+
* @param {number} ms
|
|
2232
|
+
* @returns {Promise<void>}
|
|
2233
|
+
* @example
|
|
2234
|
+
* await sleep(1000) // 等待 1 秒
|
|
2235
|
+
* sleep(500).then(() => console.log('done')) // 500ms 后打印
|
|
2236
|
+
*/
|
|
2237
|
+
export function sleep(ms) {
|
|
2238
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
/**
|
|
2242
|
+
* 尝试执行函数,失败时返回默认值
|
|
2243
|
+
* @template T
|
|
2244
|
+
* @param {Function} fn
|
|
2245
|
+
* @param {T} defaultValue
|
|
2246
|
+
* @returns {T}
|
|
2247
|
+
* @example
|
|
2248
|
+
* tryCatch(() => JSON.parse('{invalid}'), {}) // {}
|
|
2249
|
+
* tryCatch(() => 1 + 1, 0) // 2
|
|
2250
|
+
* tryCatch(() => { throw new Error() }, 'error') // 'error'
|
|
2251
|
+
*/
|
|
2252
|
+
export function tryCatch(fn, defaultValue) {
|
|
2253
|
+
try {
|
|
2254
|
+
return fn()
|
|
2255
|
+
} catch (e) {
|
|
2256
|
+
return defaultValue
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
/**
|
|
2261
|
+
* 创建一个延迟执行的 Promise
|
|
2262
|
+
* @param {number} ms
|
|
2263
|
+
* @param {*} value
|
|
2264
|
+
* @returns {Promise<*>}
|
|
2265
|
+
* @example
|
|
2266
|
+
* await delay(1000, 'result') // 1 秒后返回 'result'
|
|
2267
|
+
* delay(500, {data: 1}).then(console.log) // 500ms 后打印对象
|
|
2268
|
+
*/
|
|
2269
|
+
export function delay(ms, value) {
|
|
2270
|
+
return new Promise(resolve => setTimeout(() => resolve(value), ms))
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// ==================== 导出默认对象 ====================
|
|
2274
|
+
|
|
2275
|
+
export default {
|
|
2276
|
+
// 类型判断
|
|
2277
|
+
isNil,
|
|
2278
|
+
isUndefined,
|
|
2279
|
+
isNull,
|
|
2280
|
+
isBoolean,
|
|
2281
|
+
isNumber,
|
|
2282
|
+
isFiniteNumber,
|
|
2283
|
+
isInteger,
|
|
2284
|
+
isNaN: isNaN,
|
|
2285
|
+
isString,
|
|
2286
|
+
isSymbol,
|
|
2287
|
+
isFunction,
|
|
2288
|
+
isArray,
|
|
2289
|
+
isArrayLike,
|
|
2290
|
+
isObject,
|
|
2291
|
+
isPlainObject,
|
|
2292
|
+
isObjectLike,
|
|
2293
|
+
isDate,
|
|
2294
|
+
isRegExp,
|
|
2295
|
+
isPromise,
|
|
2296
|
+
isMap,
|
|
2297
|
+
isSet,
|
|
2298
|
+
isWeakMap,
|
|
2299
|
+
isWeakSet,
|
|
2300
|
+
|
|
2301
|
+
// 值判断
|
|
2302
|
+
isEmpty,
|
|
2303
|
+
isEqual,
|
|
2304
|
+
|
|
2305
|
+
// 数组函数
|
|
2306
|
+
find,
|
|
2307
|
+
indexOf,
|
|
2308
|
+
lastIndexOf,
|
|
2309
|
+
filter,
|
|
2310
|
+
map,
|
|
2311
|
+
uniq,
|
|
2312
|
+
flatten,
|
|
2313
|
+
flattenDeep,
|
|
2314
|
+
first,
|
|
2315
|
+
last,
|
|
2316
|
+
initial,
|
|
2317
|
+
rest,
|
|
2318
|
+
take,
|
|
2319
|
+
takeRight,
|
|
2320
|
+
concat,
|
|
2321
|
+
union,
|
|
2322
|
+
intersection,
|
|
2323
|
+
difference,
|
|
2324
|
+
chunk,
|
|
2325
|
+
reverse,
|
|
2326
|
+
slice,
|
|
2327
|
+
compact,
|
|
2328
|
+
drop,
|
|
2329
|
+
dropRight,
|
|
2330
|
+
|
|
2331
|
+
// 对象函数
|
|
2332
|
+
keys,
|
|
2333
|
+
values,
|
|
2334
|
+
entries,
|
|
2335
|
+
get,
|
|
2336
|
+
set,
|
|
2337
|
+
unset,
|
|
2338
|
+
has,
|
|
2339
|
+
size,
|
|
2340
|
+
merge,
|
|
2341
|
+
pick,
|
|
2342
|
+
omit,
|
|
2343
|
+
keysToKebabCase,
|
|
2344
|
+
keysToCamelCase,
|
|
2345
|
+
|
|
2346
|
+
// 字符串函数
|
|
2347
|
+
kebabCase,
|
|
2348
|
+
camelCase,
|
|
2349
|
+
pascalCase,
|
|
2350
|
+
snakeCase,
|
|
2351
|
+
capitalize,
|
|
2352
|
+
repeat,
|
|
2353
|
+
pad,
|
|
2354
|
+
padStart,
|
|
2355
|
+
padEnd,
|
|
2356
|
+
trim,
|
|
2357
|
+
trimStart,
|
|
2358
|
+
trimEnd,
|
|
2359
|
+
truncate,
|
|
2360
|
+
escape,
|
|
2361
|
+
unescape,
|
|
2362
|
+
|
|
2363
|
+
// 数字函数
|
|
2364
|
+
round,
|
|
2365
|
+
ceil,
|
|
2366
|
+
floor,
|
|
2367
|
+
random,
|
|
2368
|
+
randomInt,
|
|
2369
|
+
clamp,
|
|
2370
|
+
isInRange,
|
|
2371
|
+
|
|
2372
|
+
// 函数函数
|
|
2373
|
+
debounce,
|
|
2374
|
+
throttle,
|
|
2375
|
+
once,
|
|
2376
|
+
negate,
|
|
2377
|
+
noop,
|
|
2378
|
+
identity,
|
|
2379
|
+
constant,
|
|
2380
|
+
compose,
|
|
2381
|
+
pipe,
|
|
2382
|
+
|
|
2383
|
+
// 日期函数
|
|
2384
|
+
formatDate,
|
|
2385
|
+
parseDate,
|
|
2386
|
+
dateDiff,
|
|
2387
|
+
|
|
2388
|
+
// 深度克隆
|
|
2389
|
+
cloneDeep,
|
|
2390
|
+
|
|
2391
|
+
// 工具类函数
|
|
2392
|
+
sleep,
|
|
2393
|
+
tryCatch,
|
|
2394
|
+
delay
|
|
2395
|
+
}
|