Skip to content

封装一个深拷贝

写的时候一点一点调试,从最简单的开始写

深拷贝需要递归,所以写一个递归函数 deepClone()

先数组、再对象

然后考虑循环引用

然后再考虑 正则、日期、map、set

最后考虑函数,函数需要考虑有参数、没参数、箭头函数,需要使用到eval

js
const isObject = (target) => {
	return (typeof target === 'object' || typeof target === 'function') && target !== null
}

function deepClone(target, map = new WeakMap()) {
	// 基本类型和null直接返回
	if (!isObject(target)) {
		return target
	}

	if (map.get(target)) {
		return target
	}

	map.set(target, true)

	let result

	// 函数
	if (Object.prototype.toString.call(target) === '[object Function]') {
		const fnStr = target.toString()
		if (target.prototype) {
			// 普通函数
			const index1 = fnStr.indexOf('(')
			const index2 = fnStr.indexOf(')')
			const index3 = fnStr.indexOf('{')
			// 找params
			const params = fnStr.slice(index1 + 1, index2)
			// 找body
			const body = fnStr.slice(index3 + 1, -1)
			if (params) {
				return new Function(params, body)
			} else {
				return new Function(body)
			}
		} else {
			// 箭头函数
			return eval(fnStr)
		}
	}

	// 正则
	if (Object.prototype.toString.call(target) === '[object RegExp]') {
		result = new RegExp(target.source, target.flags)
		return result
	}

	// 日期
	if (Object.prototype.toString.call(target) === '[object Date]') {
		result = new Date(target)
		return result
	}

	// set
	if (Object.prototype.toString.call(target) === '[object Set]') {
		result = new Set()

		target.forEach((value) => {
			result.add(value)
		})

		return result
	}

	// map
	if (Object.prototype.toString.call(target) === '[object Map]') {
		result = new Map()

		target.forEach((value, key) => {
			result.set(key, deepClone(value, map))
		})

		return result
	}

	// 数组
	if (Array.isArray(target)) {
		result = []

		target.forEach((item) => {
			result.push(deepClone(item, map))
		})

		return result
	}

	// 对象
	if (typeof target === 'object') {
		result = {}
		for (const key in target) {
			if (Object.hasOwnProperty.call(target, key)) {
				result[key] = deepClone(target[key], map)
			}
		}
		return result
	}
}

封装一个防抖函数

高频触发的函数 加个防抖操作,只在最后一次执行

名字叫 debounce

写一个最简单的

优化版的可以加个参数 控制是滚动一开始执行 还是滚动结束后执行

简单版

js
const debounce = (fn, delay) => {
	let timer
	return (...args) => {
		clearTimeout(timer)
		timer = setTimeout(() => {
			fn.apply(this, args)
		}, delay)
	}
}

复杂版

js
const debounce = (fn, delay, immediate) => {
	let timer
	return (...args) => {
		if (immediate && !timer) {
			fn.apply(this, args)
		}

		clearTimeout(timer)
		timer = setTimeout(() => {
			timer = null
			if (!immediate) {
				fn.apply(this, args)
			}
		}, delay)
	}
}

封装一个节流函数

高频触发的函数,降低触发频率 一段时间内只执行一次 实现节流

js
const throttle = (fn, delay) => {
	let start = 0
	return (...args) => {
		const now = new Date().getTime()

		if (now - start > delay) {
			fn.apply(this, args)
			start = now
		}
	}
}

实现 a == 1 && a == 2 && a == 3 返回true

Symbol.toPrimitive valueOf toString 三个方法,有任意一个都行。

如果有 Symbol.toPrimitive ,优先 Symbol.toPrimitive

然后就是 toString 或者 valueOf 当需要隐式转换为 string 就会先调 toString,如果没有的话 就valueOf

隐式转换为number一样,先valueOf,如果没有的话 就toString

js
const a = {
	count: 1,
	[Symbol.toPrimitive](hint) {
		console.log(this.count, 'toPrimitive', hint)
		return this.count++
	},
	valueOf() {
		console.log(this.count, 'valueOf')
		return this.count++
	},
	toString() {
		console.log(this.count, 'toString')
		return this.count++
	},
}

if (a == 1 && a == 2 && a == 3) {
	console.log('进来了')
} else {
	console.log('没进来')
}

封装一个版本号对比函数

长度可能有长有短 先把长度都保持一致 短的往后补0 然后遍历 落后版本 返回-1 优先版本 返回 1 版本一样 返回0

ts
const compareVersion = (v1: string, v2: string) => {
	const v1Arr = v1.split('.')
	const v2Arr = v2.split('.')

	/* 有可能有长有短 统一长度 */
	const diffLength = v1Arr.length - v2Arr.length

	if (diffLength > 0) {
		v2Arr.push(...new Array(diffLength).fill(0))
	} else if (diffLength < 0) {
		v1Arr.push(...new Array(Math.abs(diffLength)).fill(0))
	}

	for (let i = 0; i < v1Arr.length; i++) {
		if (v1Arr[i] > v2Arr[i]) {
			return 1
		} else if (v1Arr[i] < v2Arr[i]) {
			return -1
		}
	}

	return 0
}

参考: 手写 JavaScript 代码系列 —— 比较版本号

['a', 'b', 'c'] 数组全排列

数组全排列使用递归 先排好一个,再排好两个,再排好3个,当排好一次之后,回溯,排下一个

递归记得先写出口,然后再写递归逻辑

ts
const permutation = (arr: string[]) => {
	const result = [] // 结果
	/**
	 * @param inOrder 排好序的数组
	 * @param left 剩余的
	 */
	const recursion = (inOrder: string[], left: string[]) => {
		// 出口
		if (inOrder.length === arr.length) { // 都排好序了
			// 收集结果
			result.push(inOrder.join(''))
		} else {
			left.forEach((item, index) => {
				let temp = [...left]
				temp.splice(index, 1)
				recursion(inOrder.concat(item), temp)
			})
		}
	}

	recursion([], arr)
	return result
}

参考: js实现排列组合算法(全排列)

写一个函数 将数字展示为 万、亿、万亿

设置一个基准,假如单位是万,基准就是万

判断目标值可以有几个基准阶乘 floor(log(num) / log(base))

定义单位数组 ['万', '亿', '万亿']

又一个的话 单位就是万,两个的话 单位就是亿

ts
const bigNumTransform = (num: number) => {
	const base = 10000 // 基准
	const result = {
		value: '',
		unit: '',
	} // 存放结果

	// 1. 判断是否超过基准
	if (num < base) {
		return num
	}

	// 2. 超过基准,开始转换
	const units = ['万', '亿', '万亿']

	// 3. 计算单位
	const i = Math.floor(Math.log10(num) / Math.log10(base))
	result.unit = units[i - 1]
	// 4. 计算值
	result.value = (num / Math.pow(base, i)).toFixed(2)

	return `${result.value}${result.unit}`
}

封装一个函数使用正则表达式,获取url上的参数

关键正则:/([^?&=]+)=([^?&=]*)/g

解释:

[^?&=]+ 匹配一个或者多个 不是 ? &=的字符,一个或者多个

= 匹配 =

[^?&=]* 匹配一个或者多个 不是 ? &=的字符,0个或者多个

() 括号创建捕获组

g 全局搜索

使用replace方法,拼装结果。

ts
export function getParams(url: string): any {
	const params: any = {};
	const reg = /([^?&=]+)=([^?&=]*)/g;
	url.replace(reg, (match, key, value) => {
		params[key] = value;
		return match;
	});
	return params;
}

const url = 'https://www.baidu.com?name=zs&age=18';
console.log(getParams(url)); // { name: 'zs', age: '18' }

写一个冒泡排序

将最大的值冒泡到最后,每次排好序一个

ts
/* 
冒泡排序
两层for循环 将最大的数冒泡到尾部 每一次排好序一个
*/

function bubbleSort(arr: number[]) {
	for (let i = 0; i < arr.length - 1; i++) {
		for (let j = 0; j < arr.length - i - 1; j++) {
			if (arr[j] > arr[j + 1]) {
				;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
			}
		}
	}
}

const arr = [3, 5, 1, 4, 2]
bubbleSort(arr)
console.log(arr) // [1, 2, 3, 4, 5]

写一个快排

ts
/* 
 快速排序
 1. 从数列中挑出一个元素,称为 “基准”(pivot);
 2. 小于基准的元素移动到左边,大于基准的元素移动到右边;
 3. 递归地对左右两部分进行排序。

 出口条件:数组长度小于等于1
*/

function quickSort(arr: number[]): number[] {
	if (arr.length <= 1) {
		return arr;
	}

	// 选择基准
	const pivotIndex = Math.floor(arr.length / 2);
	const pivot = arr.splice(pivotIndex, 1)[0];
	const left = [];
	const right = [];

	// 分区
	for (let i = 0; i < arr.length; i++) {
		if (arr[i] < pivot) {
			left.push(arr[i]);
		} else {
			right.push(arr[i]);
		}
	}

	// 递归
	return quickSort(left).concat([pivot], quickSort(right));

}

const arr = [3, 5, 1, 6, 4, 7, 2];
console.log(quickSort(arr)); // [1, 2, 3, 4, 5, 6, 7]