【前端面试手撕题】

125 阅读8分钟

1、数组去重

去除数组参数中的重复项并返回该数组。

(1)简单数据数组去重

Set

  • set是一种集合数据结构,集合内部的元素是唯一的,不可重复。
  • 无法去重数据:对象
// 先将数组转化为set,去除重复数据
// 再通过Array.from将类数组对象set,转化为数组
const _deleteRepeat = array => {
     return Array.from(new Set(array)) // 也可以使用 return [...new Set(array)]
}

_deleteRepeat([1, 1, true, true, 'true', 'true', null, undefined, undefined, null, NaN, NaN, {}, {}]) 
// [1, true, 'true', null, undefined, NaN, {}, {}]

indexOf、includes

  • 遍历一次原数组,将原数组的数据逐个插入新数组,插入时判断是否是重复数据。
  • 无法去重数据:对象
  • 如果使用indexOf,那么NaN无法去重
    • [NaN].indexOf(NaN) // -1
    • indexOf使用严格相等比较:NaN不等于任何值
    • [NaN].includes(NaN) // true
    • includes使用的SameValueZero比较算法:NaN被认为等于NaN,Set内部也是使用这个算法,因此Set可以去重NaN
const _deleteRepeat = array => {
// 使用filter筛选数组:查找第一次出现的数据下标等于当前下标
    return array.filter((item, index) => {
        return array.indexOf(item, 0) === index
    })
}

_deleteRepeat([1, 1, true, true, 'true', 'true', null, undefined, undefined, null, NaN, NaN, {}, {}])
// [1, true, 'true', null, undefined, {}, {}]

-----------------------------------------------------------------------------------------------

const _deleteRepeat = array => {
    return array.reduce((pre, next ) => {
        // 这里也可以使用indexOf判断next是否在pre数组中
        return pre.includes(next) ? pre : pre.concat(next)
    }, [])
}

_deleteRepeat([1, 1, true, true, 'true', 'true', null, undefined, undefined, null, NaN, NaN, {}, {}])
// [1, true, 'true', null, undefined, NaN, {}, {}]

---------------------------------------------------------------------------------------------

const _deleteRepeat = array => {
    const newArr = []
    array.forEach(item => {
        // 这里也可以使用indexOf判断
        if(!newArr.includes(item)) {
            newArr.push(item)
        }
    })
    return newArr
}

_deleteRepeat([1, 1, true, true, 'true', 'true', null, undefined, undefined, null, NaN, NaN, {}, {}])
// [1, true, 'true', null, undefined, NaN, {}, {}]

splice

  • 双重循环,寻找重复数据,并删除
  • 无法去重数据:对象,NaN(因为严格相等比较:NaN不等于任何值)
const _deleteRepeat = array => {
    for(let i = 0; i < array.length ; i++) {
        // 寻找元素右侧是否有重复数据,有的话删除数据
        for(let j = i + 1; j < array.length; j++) {
            if(array[i] === array[j]) {
                array.splice(j, 1);
                j--;
            }
         }
     } 
     return array
}

_deleteRepeat([1, 1, true, true, 'true', 'true', null, undefined, undefined, null, NaN, NaN, {}, {}])
// [1, true, 'true', null, undefined, NaN, NaN, {}, {}]

sort

  • 先排序,其次比较相邻数据是否相等
  • 无法去重数据:对象,NaN(因为严格相等比较:NaN不等于任何值)
const _deleteRepeat = array => {
    array.sort()
    for(let i = 0; i < array.length - 1; i++) {
        if(array[i] === array[i + 1]) {
            array.splice(i + 1, 1);
            i--;
        }
    }
    return array
}

_deleteRepeat([1, 1, true, true, 'true', 'true', null, undefined, undefined, null, NaN, NaN, {}, {}])
// [ 1, NaN, NaN, {}, {}, null, true, 'true', undefined ]

(2)对象数组去重(数组元素有对象)

将对象转化为JSON字符串

  • 将数组中的对象先转化为JSON字符串,之后可以采用上述目录(一)中的方法 进行处理
  • 无法去重对象:NaN,NaN序列化之后的值是'null',
  • 数组不可包含undefined,undefined序列化之后的值仍是undefined,不能被JSON.parse解析
const _deleteRepeat = (array) => {
	return Array.from(new Set(array.map(JSON.stringify))).map(JSON.parse);
};

_deleteRepeat([1, 1, true, true, 'true', 'true', null, null, NaN, NaN, { a: { b: 1 } }, { a: { b: 2 } }, { a: { b: 1 } }])
// [ 1, true, 'true', null, { a: { b: 1 } }, { a: { b: 2 } } ]

2、new的实现

function myNew (Func, ...args) {
	const obj = {}

	obj.__proto__ = Func.prototype
	const result = Func.apply(obj, args)

	return result instanceof Object ? result : obj
}

3、构造函数拦截(不使用new调用)

function Person (name) {
	// 拦截构造函数,判断是否使用new调用的构造函数
	// 使用new则new.target为构造函数,否则为undefined
	// 类只能使用new创建实例
	if (!new.target)
	{
		throw new Error('请使用new调用构造函数')
	}
	this.name = name
}

4、instanceof实现

function myInstanceOf (target, origin) {
	if (typeof target !== 'object' || target === null) return false

	let proto = Object.getPrototypeOf(target)

	while (proto)
	{
		if (proto === origin.prototype)
		{
			return true
		}
		proto = Object.getPrototypeOf(proto)
	}

	return false
}

console.log(myInstanceOf([], Object))

5、Object.create实现

function create (obj) {
	function F () { }

	F.prototype = obj
	F.prototype.constructor = F

	return new F()
}

6、call、apply、bind实现

var name = '汤姆'

function Say (...args) {
	console.log(`我叫${this.name}${args}`)
}

Person = {
	name: '大黄蜂'
}

Function.prototype.MyCall = function (context) {
	if (typeof this !== 'function')
	{
		throw new Error('请使用函数调用call')
	}
	// say方法调用的MyCall,因而this指向say方法
	// console.log(this === Person.say) // true
	// console.log([...arguments]); // [ { name: '大黄蜂' }, 1, 2, 3 ]
	// 在新的上下文上扩展这个方法,并调用
	context = context || window
	context.fn = this
	const result = context.fn(...[...arguments].slice(1))

	delete context.fn

	return result
}

Say.MyCall(Person, '你说的对Call')

Function.prototype.myApply = function (context) {
	if (typeof this !== 'function')
	{
		throw new Error('请使用函数调用apply')
	}

	context = context || window
	context.fn = this
	const result = context.fn(...arguments[1])

	delete context.fn

	return result
}

Say.myApply(Person, ['你说的对Apply'])

Function.prototype.myBind = function (context) {
	if (typeof this !== 'function')
	{
		throw new Error('请使用函数调用Bind')
	}

	context = context || window

	const args = [...arguments].slice(1)
	const fn = this

	return function Fn () {
		// bind返回值是一个函数,函数是可以new的,new会改变当前的this指向
		if (this instanceof Fn)
		{
			return new fn(...arguments)
		}

		return fn.apply(context, args.concat(...arguments))
	}
}

const bound = Say.myBind(Person, 'bind参数1')
bound(('bind参数2'))
bound(('bind参数3'))

new bound('bind参数4') // this指向Say构造函数的实例(一个空对象)

7、防抖和节流

// 防抖,指定秒数内重新计时
function debounce (func, wait) {
	let timer = null

	return function () {
		if (timer)
		{
			clearTimeout(timer)
			timer = null
		}

		timer = setTimeout(() => {
			func.apply(this, arguments)
		}, wait);
	}
}

// 节流,指定秒数执行一次
function throttle (func, delay) {
	let timer = null
	
	return function () {
		if (timer) return

		timer = setTimeout(() => {
			timer = null
			func.apply(this, arguments)
		}, delay);
	}
}

8、类型判断typeof

function myTypeOf (value) {
	if (value === null)
	{
		return null + ''
	}
	if (typeof value === 'object')
	{
		return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
	} else
	{
		return typeof value;
	}
}

9、浅拷贝与深拷贝

// 对象浅拷贝
const obj = {}
const obj1 = { a: 1, b: 2 }
const obj2 = { c: 3 }

const o = { ...obj1, ...obj2 }

Object.assign(obj, obj1, obj2)

Object.create(obj1)

// 数组浅拷贝
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]

const a = [...arr1, ...arr2]

const a1 = arr1.slice()

const a2 = arr1.concat(arr2)

// 手动实现浅拷贝
function shadowCopy (object) {
	if (!object || typeof object !== 'object') return

	const newObj = {}
	for (let key in object)
	{
		if (object.hasOwnProperty(key))
			newObj[key] = object[key]
	}

	return newObj
}

// 深拷贝
JOSN.parse(JSON.stringify(obj)) // 无法拷贝undefined、Symbol、Bigint、function,无法处理循环引用

function deepClone (obj, cache = new WeakMap()) {
	if (!obj || typeof obj !== "object") return obj;
	// 检查是否已经拷贝过该对象,避免循环引用导致无限递归
	if (cache.has(obj)) return cache.get(obj)

	let cloneObj = Array.isArray(obj) ? [] : {}
	cache.set(obj, cloneObj)

	if (Array.isArray(obj))
	{
		obj.forEach(((item, index) => {
			cloneObj[index] = deepClone(item, cache)
		}))
	}
	if (obj instanceof Object)
	{
		for (let key in obj)
		{
			if (obj.hasOwnProperty(key))
			{
				cloneObj[key] = deepClone(obj[key], cache)
			}
		}
	}
	return cloneObj;
}

10、最大子序和

// 动态规划法
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function (nums) {
	let pre = 0; let max = nums[0]
	nums.forEach(num => {
		pre = Math.max(pre + num, num)
		max = Math.max(max, pre)
	})
	return max
};

11、爬楼梯

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    let pre = 1, preOfPre = 1, current = 1;
    for(let i = 2; i <= n; i++){
        current = pre + preOfPre;
        preOfPre = pre;
        pre = current;
    }
    return current
};

12、字符串相加

/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
var addStrings = function(num1, num2) {
    let i = num1.length - 1, j = num2.length -1, add = 0, ans = '';
    while(i >= 0 || j >= 0 || add !=  0) {
        const x = i >= 0 ? num1.charAt(i) - '0' : 0;
        const y = j >= 0 ? num2.charAt(j) - '0' : 0;
        res = x + y + add;
        ans += res % 10;
        add = Math.floor(res / 10);
        i--;
        j--;
    }

    return ans.split('').reverse().join('')
};

13、爱吃香蕉的柯柯

/**
 * @param {number[]} piles
 * @param {number} h
 * @return {number}
 */
var minEatingSpeed = function (piles, h) {
	let l = 1;
	let r = Math.max(...piles);

	while (l <= r)
	{
		const mid = Math.floor(l + (r - l) / 2);

		if (pass(mid))
		{
			r = mid - 1;
		} else
		{
			l = mid + 1;
		}
	}

	return l;

	function pass (mid) {
		let t = 0;
		for (let i = 0; i < piles.length; i++)
		{
			t += Math.ceil(piles[i] / mid);
		}
		return t <= h;
	}
};

14、有效的字母异位词

/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isAnagram = function(s, t) {
    if(s.length !== t.length || s === t) return false
    const sArr = s.split('').sort()
    const tArr = t.split('').sort()
    return sArr.join('') === tArr.join('')
};

15、只出现一次的数字

/**
 * @param {number[]} nums
 * @return {number}
 */

var singleNumber = function (nums) {
	let ans = 0;
	for (const num of nums)
	{
		ans ^= num;
	}
	return ans
};

16、promise

// 模拟实现Promise
// Promise利用三大手段解决回调地狱:
// 1. 回调函数延迟绑定
// 2. 返回值穿透
// 3. 错误冒泡

// 定义三种状态
const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

class Promise {
	constructor(exector) {
		// 初始化状态
		this.status = PENDING;
		// 将成功、失败结果放在this上,便于then、catch访问
		this.value = undefined;
		this.reason = undefined;
		// 成功态回调函数队列
		this.onFulfilledCallbacks = [];
		// 失败态回调函数队列
		this.onRejectedCallbacks = [];

		const resolve = value => {
			// 只有进行中状态才能更改状态
			if (this.status === PENDING)
			{
				this.status = FULFILLED;
				this.value = value;
				// 成功态函数依次执行
				this.onFulfilledCallbacks.forEach(fn => fn(this.value));
			}
		}
		const reject = reason => {
			// 只有进行中状态才能更改状态
			if (this.status === PENDING)
			{
				this.status = REJECTED;
				this.reason = reason;
				// 失败态函数依次执行
				this.onRejectedCallbacks.forEach(fn => fn(this.reason))
			}
		}
		try
		{
			// 立即执行executor
			// 把内部的resolve和reject传入executor,用户可调用resolve和reject
			exector(resolve, reject);
		} catch (e)
		{
			// executor执行出错,将错误内容reject抛出去
			reject(e);
		}
	}
	then (onFulfilled, onRejected) {
		onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
		onRejected = typeof onRejected === 'function' ? onRejected :
			reason => { throw new Error(reason instanceof Error ? reason.message : reason) }
		const self = this;
		return new Promise((resolve, reject) => {
			if (self.status === PENDING)
			{
				self.onFulfilledCallbacks.push(() => {
					try
					{
						// 模拟微任务
						setTimeout(() => {
							const result = onFulfilled(self.value);
							result instanceof Promise ? result.then(resolve, reject) : resolve(result);
						})
					} catch (e)
					{
						reject(e);
					}
				});
				self.onRejectedCallbacks.push(() => {
					try
					{
						setTimeout(() => {
							const result = onRejected(self.reason);
							result instanceof Promise ? result.then(resolve, reject) : resolve(result);
						})
					} catch (e)
					{
						reject(e);
					}
				})
			} else if (self.status === FULFILLED)
			{
				try
				{
					setTimeout(() => {
						const result = onFulfilled(self.value);
						result instanceof Promise ? result.then(resolve, reject) : resolve(result);
					});
				} catch (e)
				{
					reject(e);
				}
			} else if (self.status === REJECTED)
			{
				try
				{
					setTimeout(() => {
						const result = onRejected(self.reason);
						result instanceof Promise ? result.then(resolve, reject) : resolve(result);
					})
				} catch (e)
				{
					reject(e);
				}
			}
		});
	}
	catch (onRejected) {
		return this.then(null, onRejected);
	}
	static resolve (value) {
		if (value instanceof Promise)
		{
			return value;
		} else
		{
			// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
			return new Promise((resolve, reject) => resolve(value));
		}
	}
	static reject (reason) {
		return new Promise((resolve, reject) => {
			reject(reason);
		})
	}
	static all (promiseArr) {
		const len = promiseArr.length
		const values = new Array(len)
		let count = 0
		return new Promise((resolve, reject) => {
			promiseArr.forEach((p, i) => {
				Promise.resolve(p).then((res) => {
					values[i] = res
					count++
					if (len === count)
					{
						resolve(values)
					}
				}).catch(err => {
					reject(err)
				})
			})
		})
	}
	static race (promiseArr) {
		return new Promise((resolve, reject) => {
			promiseArr.forEach(p => {
				Promise.resolve(p).then(
					val => resolve(val),
					err => reject(err),
				)
			})
		})
	}
}

// 使用promise封装一个加载图片的方法
function loadImg (url) {
	return new Promise((resolve, reject) => {
		const img = new Image();
		img.onload = () => {
			resolve(img)
		}
		img.error = () => {
			reject('图片加载失败')
		}
		img.src = url
	})
}

// promise的限制并发数
var urls = [
	"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
	"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
	"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
	"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
	"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
	"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
	"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
	"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];

function limitLoad (urls, func, limit) {
	let sequence = [].concat(urls);

	// 初始化 promises 这个"容器"
	let promises = sequence.splice(0, limit).map((url, index) => {
		return func(url).then(() => {
			// 返回下标是为了知道数组中是哪一项最先完成
			return index;
		});
	});

	// 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
	return sequence
		.reduce((pList, url) => {
			return pList
				.then(() => {
					return Promise.race(promises); // 返回已经完成的下标
				})
				.then(fastestIndex => { // 获取到已经完成的下标
					// 将"容器"内已经完成的那一项替换
					promises[fastestIndex] = func(url).then(
						() => {
							return fastestIndex; // 要继续将这个下标返回,以便下一次变量
						}
					);
				})
				.catch(err => {
					console.error(err);
				});
		}, Promise.resolve())
}

limitLoad(urls, loadImg, 2)
	.then(() => {
		console.log("图片全部加载完毕");
	})
	.catch(err => {
		console.error(err);
	});

// 把传入的数组按顺序先后执行(红绿灯)
function red () {
	console.log("red");
}
function green () {
	console.log("green");
}
function yellow () {
	console.log("yellow");
}
const light = function (timer, cb) {
	return new Promise(resolve => {
		setTimeout(() => {
			cb()
			resolve()
		}, timer)
	})
}
const step = function () {
	Promise.resolve().then(() => {
		return light(3000, red)
	}).then(() => {
		return light(2000, green)
	}).then(() => {
		return light(1000, yellow)
	}).then(() => {
		return step()
	})
}

step();