收集整理2022年最新前端面试题及答案,方便平时翻看记忆,欢迎各位大佬们补充。
一般来说,把下面基础中的高频题写熟练就差不多了。当然去面大厂这些远远不够,还要再刷一些算法题。
基础
高频
1.手写 instanceof
// 原理:验证当前类的原型prototype是否会出现在实例的原型链proto上,只要在它的原型链上,则结果都为truefunction myinstanceOf_(obj, class_name) {// let proto = obj.__proto__;let proto = Object.getPrototypeOf(obj)let prototype = class_name.prototypewhile (true) {if (proto == null) return falseif (proto == prototype) return true// proto = proto.__proto__;proto = Object.getPrototypeOf(proto)}}
2.手写 new 操作符
function myNew(){//1.创建一个新的对象let obj=new Object();//获得构造函数let con = [].shift.call(arguments); //[]为Array构造函数的实例 将类数组转化为真正的数组//2.新对象的隐式原型__proto__链接到构造函数的显式原型prototypeobj.__proto__ = con.prototype;//3.构造函数内部的 this 绑定到这个新创建的对象 执行构造函数let result = con.apply(obj, arguments)//4.如果构造函数没有返回非空对象,则返回创建的新对象return typeof result == 'object' ? result:obj;}var test_create = myNew(Car, 'a', 'b', 'c');console.log(test_create)
3.手写 call、apply、bind 函数
// 给函数的原型添加 _call 方法,使得所有函数都能调用 _call// thisArg 就是要绑定的那个this;...args 扩展操作符传参,适合不定长参数,args是一个数组Function.prototype._call = function(thisArg,...args){// 1.获取需要执行的函数fn = this// 2.将 thisArg 转成对象类型(防止它传入的是非对象类型,例如123数字)thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window// 3.使用 thisArg 调用函数,绑定 thisthisArg.fn = fnlet result = thisArg.fn(...args)delete thisArg.fn// 4.返回结果return result}
- apply(thisArg, argsArray)
Function.prototype._apply = function(thisArg,argArray){// 1.获取需要执行的函数fn = this// 2.将 thisArg 转成对象类型(防止它传入的是非对象类型,例如123数字)thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window// 判断一些边界情况argArray = argArray || []// 3.使用 thisArg 调用函数,绑定 thisthisArg.fn = fn// 将传递过来的数组(可迭代对象)拆分,传给函数let result = thisArg.fn(...argArray)delete thisArg.fn// 4.返回结果return result}
Function.prototype._call = function(thisArg,...args){fn = thisthisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : windowthisArg.fn = fnlet result = thisArg.fn(...args)delete thisArg.fnreturn result}// 利用 call 模拟 bindFunction.prototype._bind = function(thisArg,...args){let fn = this // 需要调用的那个函数的引用// bind 需要返回一个函数return function(){return fn._call(thisArg, ...args)}}
4.手写深拷贝
PS:浅拷贝也可以用一样的模板,当然深拷贝考得多
function deepCopy(object) {if (!object || typeof object !== "object") return object;let newObject = Array.isArray(object) ? [] : {};for (let key in object) {if (object.hasOwnProperty(key)) {newObject[key] = deepCopy(object[key]);}}return newObject;}
进阶:解决循环引用的深拷贝
function deepClone(obj, hash = new WeakMap()) {if (!object || typeof object !== "object") return object;// 是对象的话就要进行深拷贝,遇到循环引用,将引用存储起来,如果存在就不再拷贝if (hash.get(obj)) return hash.get(obj);let cloneObj = Array.isArray(object) ? [] : {};hash.set(obj, cloneObj);for (let key in obj) {if (obj.hasOwnProperty(key)) {// 实现一个递归拷贝cloneObj[key] = deepClone(obj[key], hash);}}return cloneObj;}
5.手写防抖节流
function debounce(func, delay) {// 这里使用了闭包,所以 timer 不会轻易被销毁let timer = null// 生成一个新的函数并返回return function (...args) {// 清空定时器if (timer) {clearTimeout(timer)}// 重新启动定时器timer = setTimeout(() => {func.call(this, ...args)}, delay)}}function throttle(func, delay) {let timer = null// 在 delay 时间内,最多执行一次 funcreturn function (...args) {if (!timer) {timer = setTimeout(() => {func.call(this, ...args)// 完成一次计时,清空,待下一次触发timer = null}, delay)}}}
6.手写Ajax请求
function ajax(url) {// 创建一个 XHR 对象return new Promise((resolve,reject) => {const xhr = new XMLHttpRequest()// 指定请求类型,请求URL,和是否异步xhr.open('GET', url, true)xhr.onreadystatechange = funtion() {// 表明数据已就绪if(xhr.readyState === 4) {if(xhr.status === 200){// 回调resolve(JSON.stringify(xhr.responseText))}else{reject('error')}}}// 发送定义好的请求xhr.send(null)})}
7.手写数组去重
// 1.Set + 数组复制fuction unique1(array){// Array.from(),对一个可迭代对象进行浅拷贝return Array.from(new Set(array))}// 2.Set + 扩展运算符浅拷贝function unique2(array){// ... 扩展运算符return [...new Set(array)]}// 3.filter,判断是不是首次出现,如果不是就过滤掉function unique3(array){return array.filter((item,index) => {return array.indexOf(item) === index})}// 4.创建一个新数组,如果之前没加入就加入function unique4(array){let res = []array.forEach(item => {if(res.indexOf(item) === -1){res.push(item)}})return res}
进阶:如果数组内有数组和对象,应该怎么去重(此时对象的地址不同,用Set去不了重)
需要开通正版 WebStorm 的可以联系我,56元一年,正版授权激活,官网可查有效期,有需要的加我微信:poxiaozhiai6,备注:915。
8.手写数组扁平
// 方法1-3:递归function flat1(array){// reduce(): 对数组的每一项执行归并函数,这个归并函数的返回值会作为下一次调用时的参数,即 preValue// concat(): 合并两个数组,并返回一个新数组return array.reduce((preValue,curItem) => {return preValue.concat(Array.isArray(curItem) ? flat1(curItem) : curItem)},[])}function flat2(array){let res = []array.forEach(item => {if(Array.isArray(item)){// res.push(...flat2(item))// 如果遇到一个数组,递归res = res.concat(flat2(item))}else{res.push(item)}})return res}function flat3(array){// some(): 对数组的每一项都运行传入的函数,如果有一项返回 TRUE,则这个方法返回 TRUEwhile(array.some(item => Array.isArray(item))){// ES6 增加了扩展运算符,用于取出参数对象的所有可遍历属性,拷贝到当前对象之中:array = [].concat(...array)console.log(...array)}return array}// 方法4、5:先转成字符串,再变回数组function flat4(array){//[1,[2,3]].toString() => 1,2,3return array.toString().split(',').map(item => parseInt(item))}function flat5(array){return array.join(',').split(',').map(item => Number(item))}
9.手写数组乱序
// 方法1: sort + Math.random()function shuffle1(arr){return arr.sort(() => Math.random() - 0.5);// }// 方法2:时间复杂度 O(n^2)// 随机拿出一个数(并在原数组中删除),放到新数组中function randomSortArray(arr) {let backArr = [];while (arr.length) {let index = parseInt(Math.random() * arr.length);backArr.push(arr[index]);arr.splice(index, 1);}return backArr;}// 方法3:时间复杂度 O(n)// 随机选一个放在最后,交换function randomSortArray2(arr) {let lenNum = arr.length - 1;for (let i = 0; i < lenNum; i++) {let index = parseInt(Math.random() * (lenNum + 1 - i));[a[index],a[lenNum - i]] = [a[lenNum - i],a[index]]}return arr;}
10.手写 Promise.all()、Promise.race()
PS: 有能力的可以去写下 Promise 和其他的 Promise 方法
function myAll(promises){// 问题关键:什么时候要执行resolve,什么时候要执行 rejectreturn new Promise((resolve,reject) => {values = []// 迭代数组中的 Promise,将每个 promise 的结果保存到一个数组里promises.forEach(promise => {// 如果不是 Promise 类型要先包装一下// 调用 then 得到结果Promise.resolve(promise).then(res => {values.push(res)// 如果全部成功,状态变为 fulfilledif(values.length === promises.length){resolve(values)}},err => { // 如果出现了 rejected 状态,则调用 reject() 返回结果reject(err)})})})}
11.手撕快排
PS: 常见的排序算法,像冒泡,选择,插入排序这些最好也背一下,堆排序归并排序能写则写。万一考到了呢,要是写不出就直接回去等通知了。
const _quickSort = array => {// 补全代码quickSort(array, 0, array.length - 1)// 别忘了返回数组return array}const quickSort = (array, start, end) => {// 注意递归边界条件if(end - start < 1) return// 取第一个数作为基准const base = array[start]let left = startlet right = endwhile(left < right){// 从右往左找小于基准元素的数,并赋值给右指针 array[right]while(left < right && array[right] >= base) right--array[left] = array[right]// 从左往右找大于基准元素的数,并赋值给左指针 array[left]while(left < right && array[left] <= base) left++array[right] = array[left]}// 双指针重合处,将基准元素填到这个位置。基准元素已经事先保存下来了,因此不用担心上面的赋值操作会覆盖掉基准元素的值// array[left] 位置已经确定,左边的都比它小,右边的都比它大array[left] = basequickSort(array, start, left - 1)quickSort(array, left + 1, end)return array}
12.手写 JSONP
// 动态的加载js文件function addScript(src) {const script = document.createElement('script');script.src = src;script.type = "text/javascript";document.body.appendChild(script);}addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");// 设置一个全局的callback函数来接收回调结果function handleRes(res) {console.log(res);}// 接口返回的数据格式,加载完js脚本后会自动执行回调函数handleRes({a: 1, b: 2});
13.手写寄生组合继承
PS: 组合继承也要能写出来
function Parent(name) {this.name = name;this.say = () => {console.log(111);};}Parent.prototype.play = () => {console.log(222);};function Children(name,age) {Parent.call(this,name);this.age = age}Children.prototype = Object.create(Parent.prototype);Children.prototype.constructor = Children;// let child = new Children("111");// // console.log(child.name);// // child.say();// // child.play();
14.数组/字符串操作题
可以自己找些基础的练一下,就不一一列举了
15.手写二分查找
// 迭代版function search(nums, target) {// write code hereif(nums.length === 0) return -1let left = 0,right = nums.length - 1// 注意这里的边界,有等号while(left <= right){let mid = Math.floor((left + right) / 2)if(nums[mid] < target) left = mid + 1else if(nums[mid] > target) right = mid - 1else return mid}return -1}// 递归版function binary_search(arr, low, high, key) {if (low > high) {return -1;}var mid = parseInt((high + low) / 2);if (arr[mid] == key) {return mid;} else if (arr[mid] > key) {high = mid - 1;return binary_search(arr, low, high, key);} else if (arr[mid] < key) {low = mid + 1;return binary_search(arr, low, high, key);}};
16.手写函数柯里化
function sum(x,y,z) {return x + y + z}function hyCurrying(fn) {// 判断当前已经接收的参数的个数,和函数本身需要接收的参数是否一致function curried(...args) {// 1.当已经传入的参数 大于等于 需要的参数时,就执行函数if(args.length >= fn.length){// 如果调用函数时指定了this,要将其绑定上去return fn.apply(this, args)}else{// 没有达到个数时,需要返回一个新的函数,继续来接收参数return function(...args2) {//return curried.apply(this, [...args, ...args2])// 接收到参数后,需要递归调用 curried 来检查函数的个数是否达到return curried.apply(this, args.concat(args2))}}}return curried}var curryAdd = hyCurry(add1)curryAdd(10,20,30)curryAdd(10,20)(30)curryAdd(10)(20)(30)
其他
1.手写事件委托
2.手写组合函数
3.常见DOM操作
4.手写数组常见方法 Array.filter/map/fill/reduce
5.手写Object.create()
6.手写Object.is()
7.手写Object.freeze()
8.手写列表转树
9.数字千分位分割
10.下划线转驼峰
11.大数相加
场景模拟题
高频
1.实现 sleep 函数
async function test() {console.log('开始')await sleep(4000)console.log('结束')}function sleep(ms) {return new Promise(resolve => {setTimeout(() => {resolve()}, ms)})}test()
2.setTimeout 实现 setInterval
function setInterval(fn, time){var interval = function(){// time时间过去,这个异步被执行,而内部执行的函数正是interval,就相当于进了一个循环setTimeout(interval, time);// 同步代码fn();}//interval被延迟time时间执行setTimeout(interval,time); }
3.异步循环打印 1,2,3
var sleep = function (time, i) {return new Promise(function (resolve, reject) {setTimeout(function () {resolve(i);}, time);})};var start = async function () {for (let i = 1; i <= 3; i++) {let result = await sleep(1000, i);console.log(result);}};start();
4.循环打印红、黄、绿
function red() {console.log('red');}function green() {console.log('green');}function yellow() {console.log('yellow');}const task = (timer, light) => {new Promise((resolve, reject) => {setTimeout(() => {if (light === 'red') {red()}else if (light === 'green') {green()}else if (light === 'yellow') {yellow()}resolve()}, timer)})}const taskRunner = async () => {await task(3000, 'red')await task(2000, 'green')await task(2100, 'yellow')taskRunner()}taskRunner()
其他
Promise 并发控制相关的题目,例如实现有并行限制的 Promise 调度器
更多 Promise 的面试题在这里:要就来45道Promise面试题一次爽到底,面大厂的兄弟可以看看
进阶
1.手写 Promise(重要)
2.手写发布-订阅模式
3.手写观察者模式
4.手写双向绑定
5.手写 Event Bus
6.手写 LRU