个人整理的前端面试代码题


前言

本文适合在找前端实习或应届生入职的同学看
本篇文章为个人整理的前端面试中较高几率出现的手撕代码题,有些我的确在面试中遇到过,其中一些写出来了,一些因为少接触吃过亏,其他的是我暂时没遇见但是同学面经有出现,现在开一篇文章记录一下吧,帮自己也希望能帮到读者。
本文会根据个人所遇所见不时更新,如有错误或建议,欢迎私信或留言!


一、JS

1. 扁平化数组

描述:将多层嵌套数组降低层级,比如[ 1, 2, [3, [4, [5]]] ]扁平到[ 1, 2, 3, 4, 5 ]或者[ 1, 2, 3, [4,[5]] ]等等

emm很经典的一道代码题,我在面试中遇过,这道题我会介绍2种写法,分别是递归与concat函数写法。
递归写法(复杂一点)

		// 原数组
		const arr = [ 1, 2, [3, [4, [5] ] ] ];
		// 自定义深度
		const deep = 2;
		// 结果数组
		let result= [];
		// 思想: 提取原数组每一项元素,然后判断元素为数字或数组,数字则推入暂存数组,数组则递归,判断推入
		const myFlat = (arrInput, curDeep) => {
			// 退出
			if(curDeep==0){
				return arrInput;
			}
			// 存放本层结果
			let tempArr = [];
			for(let i=0;i<arrInput.length;i++){
				let item = arrInput[i];
				if(Array.isArray(item)){
					// 向下递归,结果层层返回
					let returnArr = myFlat(item,curDeep-1)
					tempArr = tempArr.concat(returnArr);
				} else {
					tempArr.push(item);
				}
			}
			return tempArr;
		};
		result = myFlat(arr, deep);
		console.log('DEEP: ',deep);
		console.log('RESULT: ',JSON.stringify(result));

concat方法(推荐,内有我写的注释)

		function useConcatToFlatten(arr, curDeep){
			for(let i=0;i<curDeep;i++){
				// 1. concat是合并返回一个新的数组
				// 2. ...arr的作用等同于切割每一项元素成为独立参数,以逗号分开,控制台输出时不显示逗号
				// 3. [].concat(...arr) = [].concat(n1,n2,n3...)
				// 4. concat又可以合并嵌套数组,去掉每一项元素的一层嵌套,如果是[].concat(1, [2]) -> [[], 1, [2]] -> [1, 2]
				// 5. arr = [].concat(...arr) 这种写法可以更新覆盖掉arr原本的结果,再走下一步运算。
				arr = [].concat(...arr);
				// console.log(...arr);// 这里可以自行查看
			}
			return arr;
		}
		let res2 = useConcatToFlatten([ 1, 2, [3, [4, [5] ] ] ], 2);
		console.log('JSON_RESULT2: ',JSON.stringify(res2));

结果展示
扁平化数组输出结果

2. 检测页面用了哪些标签

描述:获取页面所用标签,比如应含有<html><head><meta><style><body>等标签

同样,这里可以我所知道的有2种方法:从<html>开始宽度优先遍历,和通配符筛选。
关键知识:

  1. Node.childElementCount可以返回某节点子节点个数,Node.children为子节点集。
  2. document.getElementsByTagName(‘*’)可以获取所有标签

BFS遍历 从<html>标签开始

		// 检测用了哪些标签...
		let html = document.querySelector('html');
		// console.log(html.nodeType) // type=1 代表是是元素节点
		let resArr = [];
		// 要 一个队列 + 一个集合去重 有点BFS的意思
		// 只需要nodeName.toLowerCase()作为答案
		let set = new Set();
		//  shift() 前  pop()后  并且还会返回那个元素哦
		let queue = [];
		queue.push(html);
		// BFS
		while(queue.length !== 0){
			// Array.shift返回第一个元素并弹出
			let nowNode = queue.shift();
			let nowNodeName = nowNode.nodeName;
			if(!set.has(nowNodeName)){
				resArr.push(nowNodeName.toLowerCase());
				set.add(nowNodeName);
			} 
			// 然后是把子元素推入队列
			let childCount = nowNode.childElementCount;
			for(let i=0;i<childCount;i++){
				let cur = nowNode.children[i];
				queue.push(cur);
			}
		}
		console.log(resArr);

通配符号并去重

		// ByTagName'*' 是匹配全部 去重即可
		console.log('-----------------');
		let resOfTagName = document.getElementsByTagName('*');
		console.log(resOfTagName)
		let nodeArr = [];
		for(let i=0;i<resOfTagName.length;i++){
			nodeArr.push(resOfTagName[i].nodeName);
		}
		let resArray = [];
		// 排序,然后通过和后者对比去重 当然这里也可以建立Set
		nodeArr.sort();
		for(let i=0;i<nodeArr.length;i++){
			if(nodeArr[i]==nodeArr[i+1]){
				continue;
			} else {
				resArray.push(nodeArr[i]);
			}
		}
		console.log(resArray);

结果展示
在这里插入图片描述

3. 写一个函数判断传值是否为对象

描述:如题,注意JS分基本类型和引用类型,引用类型中Function和Array不算是对象

这题我给的解法不一定是最好的,这道题我面试中遇到过,可惜当初卡住了没能做到完美。
我的做法是:
1.先用typeof可以筛选null以外的基本数据类型+function,留下的null,array,object在typeof下都被分为object。
2. 因为null是基本数据类型,所以此时可以用 === 判断变量是否和null等,进而筛选。
3. 此时剩下的array和object,可以用Array.isArray()判断是否为数组。
4. 以上步骤到最后都通过才是Object的对象,才是true,不然其他算false。

		// 判断对象
		const t1 = "Ouch!";
		const t2 = 114514;
		const t3 = null;
		const t4 = [1, 2, 3, 4];
		const t5 = {name: 'Ouch', age: 21};
		function myJudge(obj){
			// 结果变量
			let isObj = false;
			// 筛选剩下null array object
			if(typeof obj == "object"){
				// 筛选null
				if(obj !== null){
					// 筛选array
					if(!Array.isArray(obj)){
						isObj = true;
					}
				}
			}
			// 输出
			console.log(isObj);
		}
		myJudge(t1);
		myJudge(t2);
		myJudge(t3);
		myJudge(t4);
		myJudge(t5);

输出结果如右侧控制台
在这里插入图片描述

4. setTimeout+Promise问题

描述:写一个函数,利用Promise在2秒后返回一个值

唔,我在面试遇到过,大意为↑描述,当时一紧张忘了一些细节,比如setTimeout其实可以接受三个和更多数量的参数,前2个必须的是代码或者函数名用以在指定延迟时间执行,和指定的延迟时间,后面的是传值给第一个参数的代码或函数。
那么Promise呢,Promise最常见的用法就是在函数里return new Promise((resolve, reject) => {…}),
而最后结束是调用参数中的resolve或者reject函数,并且resolve和reject可以带参数回调,举例↓
在这里插入图片描述
回到题目,2秒后返回某值,肯定要用到setTimeout,剩下的问题就是谁包裹谁,完整的一个Promise操作是new Promise + resolve/reject + then回调,这里setTimeout只能包裹到resolve/reject才能有回调走完,看看代码↓
在这里插入图片描述
复盘又懊悔一遍当初没写出来,慢慢从鶸变强吧:(

5. 手写Promise.all方法

描述:实现Promise.all功能

Promise.all详细介绍MDN写的很清楚啦,我的长话短说就是接受一个Promise数组,然后最终合成一个Promise实例返回,后续then回调,其中数组里如果全为resolve则最后结果是resolve带回来的参数的数组,如果有一个reject,那么只能返回reject所带的参数。
其实关键要点是,我怎么知道某个Promise里写的是resolve还是reject呢?
我的一个思路是,不管你写什么,resolve/reject都得运行,反正最后都得走then或者catch结束,根据这个思路,怎么跑一个Promise实例呢,有个方法是Promise.resolve(),长话短说就是参数如果是数字或者是Promise中调用的resolve,那么Promise.resolve()回调走then(),如果输入的参数是Promise调用reject,那么Promise.resolve()回调走catch()。这时候如果是合规的走then塞进一个数组里,满了之后调resolve跳出,否则有一个错就终止并用reject跳出
这里的一个坑是,分清Promise.resolve() 和 Promise((resolve, reject) => {...}) 同样叫resolve,一个是方法(函数),一个是参数(函数)。当然这里解法如果有更好的欢迎留言或私信告知我呀

		const promise1 = Promise.resolve(3);
		const promise2 = 42;
		const promise3 = new Promise((resolve, reject) => {
			// resolve('RESOLVE2');
			reject('REJECT2');
		});
		const promise4 = new Promise((resolve, reject) => {
			resolve('OK2');
		});
		function myPromiseAll(args){
			return new Promise((resolve, reject) => {
				if(Array.isArray(args)){
					const resultArray = [];
					for(let item of args){
						Promise.resolve(item)
						.then(v => {
							resultArray.push(v);
							if(resultArray.length === args.length){
								resolve(resultArray);
							}
						}).catch(err => {
							reject(err)
						})
					}
				} else {
					resolve(args);
				}
			})
		}
		myPromiseAll([promise1, promise2, promise3, promise4])
		.then(values => console.log(values))
		.catch(err => console.log(err))

在promise3中注释resolve(‘RESOLVE2’),结果就是REJECT2,因为promise3走reject,如果注释掉reject(‘REJECT2’),那么结果就是[3, 42, “RESOLVE2”, “OK2”]

6. 手写new

描述:如题,写一个函数实现new

首先,new做了什么?
1.新建一个对象
2.建立原型链
3.执行构造函数方法,
4.绑定this值
5.返回这个新对象
那么根据这个思路…

		function myNew(Class, ...rest){
		  // 创建新对象以及建立原型链 obj.__proto__ = Class.protype
		  const newObj = Object.create(Class.prototype);
		  // 执行构造函数方法以及绑定this值,apply结果为指定 this 值和参数的函数的结果。
		  const result = Class.apply(newObj, rest);
		  // 最后返回一个对象
		  return result;
		}
		const obj = myNew(Array, 1, 2, 3);
		console.log(obj);

结果为控制台输出[1, 2, 3]

7. 手写instanceof

描述:如题,写一个函数实现instanceof

还是先看看instanceof的原理吧家人们↓
instanceof
长话短说版:instanceof判断某类是不是出现在某实例的原型链上,原型链展开说又得纷纷扰扰,这里就不多说啦,记得是obj.__proto__ = Class.prototype就行。
那么既然是链,自然想到链表,是不是也能像链表一样从起点到终点找呢,当然可以

		class People{
			constructor(name) {
			    this.name = name;
			}
		}
		class Student extends People{
			constructor(name,id) {
				super(name);
			    this.id = id;
			}
		}
		let s1 = new Student('Ouch!', 123);
		console.log(s1);
		// 目标 instanceof Student People Object是对的
		console.log(s1 instanceof Student);
		console.log(s1 instanceof People);
		console.log(s1 instanceof Object);
		console.log(s1 instanceof Function);
		console.log(s1 instanceof Number);
		console.log('MY TURN');
		const myInstanceof = (obj, askClass) => {
			if(askClass === obj.constructor){
				return true;
			}
			let askPrototype = askClass.prototype;
			let objProto = obj.__proto__;
			while(true){
				if(objProto === null){
					return false;
				}
				if(objProto === askPrototype){
					return true;
				}
				objProto = objProto.__proto__;
			}
			return false;
		}
		console.log(myInstanceof(s1, Student));
		console.log(myInstanceof(s1, People));
		console.log(myInstanceof(s1, Object));
		console.log(myInstanceof(s1, Function));
		console.log(myInstanceof(s1, Number));

结果把原生instanceof和手写的对比,完全一致
在这里插入图片描述

二、CSS

1. 做一个扇形图形

描述:利用css在网页实现一个扇形图形

扇形是啥,是不是可以看成一个等腰三角形+一个半圆?
三角形怎么来?可以用border实现,当内容为空时,放大border宽度可以看见四个等腰三角形,保留一边即可。然后用border-radius: 50%实现圆边↓
当然这个也有第二种办法,是一个新支持的css属性,已经有很详细的解答啦,请看 clip-path
在这里插入图片描述
在这里插入图片描述

2. 表格点击效果

描述:点击某表格中某一单元格,使得其同列同行凸显

↓实现这种效果↓
挺难的如果是第一次接触,我也不太记得在哪里记录到我的硬盘上了=.=
当初写的注释还蛮详细的,读者可以先自己实现再看看我的代码?给一个提示,如果某个单元格调用了this.cellIndex,那么会返回[0, n-1]中自身的下标,本行共n个格。
在这里插入图片描述
在这里插入图片描述

<!DOCTYPE html>
<html lang="zh">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title></title>
	<style type="text/css">
		table.game {
		    font-size: 14px;
		    border-collapse: collapse;
		    width: 100%;
		    table-layout: fixed;
		}
		table.game td {
		    border: 1px solid #e1e1e1;
		    padding: 0;
		    height: 30px;
		    text-align: center;
		}
		table.game td.current{
		    background: #1890ff;
		}
		table.game td.wrap{
		    background: #f5f5f5;
		}
	</style>
</head>
<body>
	<div id="jsContainer">
	    <table class="game">
	        <tbody>
	            <tr><td></td><td></td><td></td><td></td><td class="wrap"></td><td></td><td></td><td></td><td></td></tr>
	            <tr><td></td><td></td><td></td><td></td><td class="wrap"></td><td></td><td></td><td></td><td></td></tr>
	            <tr><td></td><td></td><td></td><td></td><td class="wrap"></td><td></td><td></td><td></td><td></td></tr>
	            <tr><td></td><td></td><td></td><td></td><td class="wrap"></td><td></td><td></td><td></td><td></td></tr>
	            <tr><td class="wrap"></td><td class="wrap"></td><td class="wrap"></td><td class="wrap"></td><td class="current"></td><td class="wrap"></td><td class="wrap"></td><td class="wrap"></td><td class="wrap"></td></tr>
	            <tr><td></td><td></td><td></td><td></td><td class="wrap"></td><td></td><td></td><td></td><td></td></tr>
	            <tr><td></td><td></td><td></td><td></td><td class="wrap"></td><td></td><td></td><td></td><td></td></tr>
	            <tr><td></td><td></td><td></td><td></td><td class="wrap"></td><td></td><td></td><td></td><td></td></tr>
	            <tr><td></td><td></td><td></td><td></td><td class="wrap"></td><td></td><td></td><td></td><td></td></tr>
	        </tbody>
	    </table>
	</div>
	<script type="text/javascript">
		function bind() {
		    var tr = document.querySelectorAll('tr')
		    var td =document.querySelectorAll('td')
			
			// i为遍历全部td的下标
		    for(var i=0;i<td.length;i++){
				// 使用 addEventListener 绑定事件
		        td[i].addEventListener('click',function(){
					// 先清掉所有其他td的className
		            for(var i =0;i<td.length;i++){
		                td[i].className =""
		            }
					// childNodes指的是返回当前元素子节点的所有类型节点,其中连空格和换行符都会默认文本节点,
					// childern指的是返回当前元素的所有元素节点,比如<p>,<td>
		            var trC = this.parentNode.children
					// 所以这里是同行下,所有<td>都会上类选择器
		            for(var i =0;i<trC.length;i++){
		                trC[i].className="wrap"
		            }
					// 同列 遍历所有行下的 子元素 的指定下标
					// cellIndex 属性可返回一行的单元格集合中单元格的位置。 0 - n-1
		            for(var i = 0;i<tr.length;i++){
		                tr[i].children[this.cellIndex].className="wrap"
		            }
					// 最后置为current就行
		            this.className="current"
		        })   
		    }
	}
	bind();
	</script>
</body>
</html>

3. 无限滚动和懒加载

描述:当滚动条快到达页面或容器底部时,插入内容,再次上拉仍能插入内容,实现无限滚动。同时这也是网页优化的一项,可以理解为懒加载的原理。

唔,这部分挺有趣的,当初是看到腾讯21年还是20年招前端实习生出的一道笔试题,然后我就记录下来啦。
关键知识:scrollHeight:滚动内容的全高度,scrollTop已滚动的距离容器顶端的距离,clientHeight容器高度。当下滚的时候,scrollTop会增加而scrollHeight不会,后者只看滚动容器所有元素的高度,所以scrollTop + clientHeight - scollHeight 会逐渐减少,最终scrollTop+clientHeight可以近似于scrollHeight,所以只要绑定滚动事件,加入判断scrollTop + clientHeight - scollHeight的值小于某指定数值,比如30(px),100(px)时添加内容即可,这样scrollHeight会增长使得滚动继续(好像干巴巴地解释得不太好,读者可以把代码拷到本地html文件浏览器打开看看)

<!DOCTYPE html>
<html lang="zh">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title></title>
	<style type="text/css">
		.box {
		    height: 500px;
		    width: 300px;
		    overflow: scroll;
			padding-top: 20px;
			padding-bottom: 20px;
			background-color: cadetblue;
			color: white;
			margin-top: 48px;
			border-left: 7px solid salmon;
			border-top: 18px solid darkgoldenrod;
		}
		.box > div {
			height: 50px;
			line-height: 50px;
			font-size: 20px;
		}
	</style>
</head>
<body>
	<div class="box" id="test">
	    <div>鸟语花香</div>
	    <div>鸟语花香</div>
	    <div>鸟语花香</div>
		
		<div>鸟语花香</div>
		<div>鸟语花香</div>
		<div>鸟语花香</div>
		
		<div>鸟语花香</div>
		<div>鸟语花香</div>
		<div>鸟语花香</div>
		
		<div>鸟语花香</div>
		<div>鸟语花香</div>
		<div>鸟语花香</div>
	</div>
	<script async="async" type="text/javascript">
		const test = document.querySelector('#test');
		// 绑定滚动事件
		test.addEventListener('scroll', ()=>{
			// 现在容器内的的滚动高度
			let nowScrollHeight = test.scrollHeight;
			// 现在滚动条滚动上距离
			let nowScrollTop = test.scrollTop;
			// 现在滚动容器的外观高度
			const clientHeight = test.clientHeight;
			
			// 距离底部小于内100px就添加
			if( (nowScrollTop+clientHeight) + 100 >= nowScrollHeight){
				test.appendChild(test.lastElementChild.cloneNode(true));
			}
			console.log(test.querySelectorAll('div').length);
		});
	</script>
</body>
</html>

在这里插入图片描述
在这里插入图片描述

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值