前端函数工具包
2025年4月9日...大约 6 分钟前端开发JavaScript
数组的随机排序
最近做一个翻牌游戏,其中需要将牌组打乱,在网络上找到了一个要求不高的简易随机排序实现函数:
randomArray() {
this.resList = this.paramList.sort(() => {
return Math.random() > 0.5 ? 1 : -1
})
}
然而后来进一步查阅后发现,尽管这种洗牌算法能够满足我的使用场景要求,但这种方法并不是一种公平的洗牌算法,它可能会导致某些排列出现的概率比其他排列高,这是因为 sort()
方法可能不会交换所有的元素对,而且比较函数的随机性并不保证每个元素都有相同的机会到达数组中的每个位置。
以下是一个正确的洗牌算法,它的特点是高效且公平,确保每个元素在每个位置出现的概率都是相等的:
// Fisher-Yates算法
// 给定一个数组 array,其长度为 n
// 对于 i 从 n-1 下降到 1 做以下步骤:
// 产生一个从 0 到 i 的随机数 j
// 交换 array[i] 和 array[j]
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
// 产生一个从 0 到 i 的随机数
const j = Math.floor(Math.random() * (i + 1));
// 交换 array[i] 和 array[j]
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// 使用示例
const myArray = [1, 2, 3, 4, 5];
shuffleArray(myArray);
console.log(myArray); // 输出将是随机排列的数组
Fisher-Yates 算法的时间复杂度是 O(n),因为它只需要遍历数组一次,每个元素只需要交换一次位置。这使得它非常适合用于需要随机排列大数组的场景。
uni-app 小程序开发时添加音频的方法
自用的:
setAudioContext(audioContext, audioSrc) {
// 定义一个方法来处理单个音频上下文的初始化
const createAudioContext = (src) => {
const audioContext = uni.createInnerAudioContext()
audioContext.src = src
audioContext.onCanplay(() => {
audioContext.pause()
})
return audioContext
}
// 销毁并重置音频上下文
const resetAudioContext = (audioContext) => {
if (audioContext) {
try {
audioContext.pause()
audioContext.destroy()
} catch (e) {
console.log(e)
} finally {
audioContext = null
}
}
}
// 销毁现有的音频上下文
resetAudioContext(audioContext)
// 创建新的音频上下文
this.firstFlipAudioContext = createAudioContext(audioSrc)
},
滚动跳转至锚点
在有 header 的情况下,一般需要对定位的锚点做偏移,才能滚动到正确的位置:
scrollToRef(option) {
const ref = this.$refs[option]
if (ref) {
const elementTop = ref.offsetTop
const targetScrollTop = elementTop - 50 // 向上偏移50px
window.scrollTo({
top: targetScrollTop,
behavior: "smooth"
})
}
},
数组函数
ES6 数组函数常用笔记
1. map()
- 作用:遍历数组,对每个元素执行相同操作,返回新数组。
- 语法:
array.map(callback(currentValue, index, array))
- 示例:2.
const numbers = [1, 2, 3]; const doubled = numbers.map((n) => n * 2); // [2, 4, 6]
filter()
- 作用:过滤数组,返回满足条件的元素组成的新数组。
- 语法:
array.filter(callback(element, index, array))
- 示例:3.
const numbers = [1, 2, 3, 4]; const evens = numbers.filter((n) => n % 2 === 0); // [2, 4]
reduce()
- 作用:对数组元素进行累积操作,返回单一结果。
- 语法:
array.reduce(callback(accumulator, currentValue, index, array), initialValue)
- 示例:
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((acc, n) => acc + n, 0); // 10 [^1] ``` **4. `forEach()`**
- 作用:遍历数组,对每个元素执行操作,无返回值。
- 语法:
array.forEach(callback(currentValue, index, array))
- 示例:5.
const numbers = [1, 2, 3]; numbers.forEach((n) => console.log(n)); // 打印1, 2, 3
find()
- 作用:查找数组中满足条件的第一个元素。
- 语法:
array.find(callback(element, index, array))
- 示例:6.
const numbers = [1, 2, 3, 4]; const firstEven = numbers.find((n) => n % 2 === 0); // 2
some()
- 作用:检查数组中是否至少有一个元素满足条件,返回布尔值。
- 语法:
array.some(callback(element, index, array))
- 示例:7.
const numbers = [1, 2, 3, 4]; const hasEven = numbers.some((n) => n % 2 === 0); // true
every()
- 作用:检查数组中所有元素是否都满足条件,返回布尔值。
- 语法:
array.every(callback(element, index, array))
- 示例:注意:
const numbers = [2, 4, 6]; const allEven = numbers.every((n) => n % 2 === 0); // true
- 这些函数都不会改变原数组,而是返回新数组或结果。
callback
函数中的参数可以根据需要选择使用。initialValue
在reduce()
中是可选的,表示累积的初始值。
使用 jsencrypt
进行 RSA 加密
import JSEncrypt from 'jsencrypt'
rsaEncrypt(plainText) {
const publicKey = `-----BEGIN PUBLIC KEY-
your publicKey
-----END PUBLIC KEY-----`
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey)
return encryptor.encrypt(plainText)
}
// 使用
await this.rsaEncrypt(params)
一般解密需要私钥,不会放到前端进行解密。
关于公钥可以提到一个常量文件里,避免同功能逻辑一起冗杂在一个项目文件里。
防抖
methods: {
// 防抖功能三步实现法
async handleSubmit() {
// 1. 阻止重复提交
if (this.debounceTimer) {
clearTimeout(this.debounceTimer) // 清除旧定时器
}
// 2. 创建新定时器(500ms间隔)
this.debounceTimer = setTimeout(async () => {
// 实际业务逻辑...
// 3. 执行完成后重置状态
this.debounceTimer = null
this.submitting = false
}, 500) // 时间间隔按需调整
}
}
防抖原理图示:
[点击] --> [等待500ms] --> [执行]
└→ [再次点击] --> [重新计时]
内存泄漏预防:
beforeDestroy() {
clearTimeout(this.debounceTimer) // 组件销毁时清理
}
异步操作的特殊处理:
// 在async函数中需手动重置状态
this.debounceTimer = setTimeout(async () => {
try {
await fetchData();
} finally {
this.debounceTimer = null;
}
}, 500);
防抖和节流的区别:
- 防抖:等最后一个 🌰(比如电梯等人)
- 节流:按固定频率 💦(如水龙头滴水)
TS 代码转 JS 代码
# 使用原生TypeScript编译器(需先安装typescript)
npx tsc yourfile.ts
# 使用Babel转换(需安装@babel/preset-typescript)
npx babel yourfile.ts --presets=@babel/preset-typescript --extensions .ts
JS 中带连字符属性访问的合法方式
// 非法写法(会报语法错误)
kvImgData.kv - img - src;
// 合法写法(必须使用方括号)
kvImgData["kv-img-src"];
使用场景示例:
✅ 正确方式
<img :src="kvImgData['kv-img-src']" />
❌ 错误方式
<img :src="kvImgData.kv-img-src" />
自定义滑动窗口滑块
记录一个自己手搓的微信小程序上滑动窗口的滑块:
<scroll-view
class="scroll-session"
scroll-x
@scroll="handleScroll"
@scrolltolower="handleScrollToLower"
@scrolltoupper="handleScrollToUpper"
>
<view class="scroll-content">
<view
class="session-item"
:class="{ active: index === activeSessionIndex, disabled: item.capacityFull }"
v-for="(item, index) in sessionsData"
:key="index"
>
<view class="data-wrapper" @click="handleSession(index)">
<view class="name">{{ item.sessionName }}</view>
<view class="time">{{ item.sessionDate }}</view>
<view class="disabled" v-if="item.capacityFull">(已约满)</view>
</view>
</view>
</view>
</scroll-view>
<!-- 新增自定义滚动条 -->
<view class="custom-scrollbar">
<view
class="scroll-thumb"
:style="{
transform: `translateX(${scrollRatio * (100 - scrollThumbWidth)}%)`,
width: `${scrollThumbWidth + fixWidth}%`,
}"
></view>
</view>
handleScroll(e) {
if (this.scrollStateLock) return
const { scrollLeft, scrollWidth } = e.detail
const maxScroll = scrollWidth - this.containerWidth
// 添加边界情况处理
this.scrollRatio = maxScroll > 0 ? Math.min(scrollLeft / maxScroll, 1) : 0
// 同步更新滑块宽度(保持与初始化时相同逻辑)
const thumbWidth = (this.containerWidth / scrollWidth) * 100
this.scrollThumbWidth = Math.max(10, Math.min(100, thumbWidth))
this.fixWidth = (scrollWidth - this.containerWidth) / (this.paddingLeft + this.paddingRight)
},
// 新增尺寸测量
measureScrollSize() {
uni
.createSelectorQuery()
.in(this)
.select('.scroll-session')
.boundingClientRect((res) => {
this.containerWidth = res.width
// 改为测量内容容器的实际宽度
uni
.createSelectorQuery()
.in(this)
.select('.scroll-content')
.fields(
{
rect: true,
computedStyle: ['paddingLeft', 'paddingRight'],
},
(contentRes) => {
this.paddingLeft = parseFloat(contentRes.paddingLeft)
this.paddingRight = parseFloat(contentRes.paddingRight)
},
)
.exec()
uni
.createSelectorQuery()
.in(this)
.select('.scroll-content')
.boundingClientRect((contentRes) => {
const scrollWidth = contentRes.width
const thumbWidth = (this.containerWidth / scrollWidth) * 100
this.scrollThumbWidth = Math.max(10, Math.min(100, thumbWidth))
})
.exec()
})
.exec()
},
.custom-scrollbar {
width: 158rpx;
height: 2rpx;
background-color: #dcdcdc;
margin: 24rpx 0;
position: relative;
overflow: hidden;
.scroll-thumb {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: #000000;
transition: transform 0.2s;
transform: translateX(0%);
}
}