前端面试中JS高频手写代码(一)

作为一个前端程序媛,在秋招的浪潮中越挫越勇,从最初一到手撕代码环节就紧张到后来的游刃有余,再到引导面试官往自己的长处发问,最后通过总结和整理面经来不断提高自己的知识储备。

前端的手撕代码环节,除了掌握Leetcode和牛客的简单、中等类型的算法题外,还需要掌握一些函数的底层原理,还要学以致用、举一反三,迅速实现这些函数,比如常见的new函数、sqrt函数、双向绑定、节流防抖、柯里化函数、洋葱模型等。

1. 实现sqrt函数

思路:二分法
其中Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。

//实现sqrt函数
function sqrt(num) {
	function sqrtWrapper(min, max) {
		let current = (min + max) / 2;
		let nextMin = min, nextMax = max;
		if (current * current > num) {
			   nextMax = current;
		} else {
			   nextMin = current;
		}
		if (min === nextMin && max === nextMax) {
			   return current
		}
		else if (nextMax - nextMin < Number.EPSILON) {
			 return current;
		} else {
			 return sqrtWrapper(nextMin, nextMax);
		}
	}
	return sqrtWrapper(0, num).toFixed(2);
}
console.log(sqrt(5));	

2. new函数

//js实现new函数
function copy_new(){
	//1、创建一个新的对象
	let obj={};
	//2、创建一个构造函数
	let Con=[].shift.call(arguments);
	//3、连接到原型链
	obj.__proto__=Con.prototype;
	//4、绑定this值,使obj能够访问构造函数的属性
	var res=Con.apply(obj,arguments);
	//5、返回新的对象
	return res instanceof Object ? res : obj;
}

3. 节流函数

节流就是一个函数执行一次后,只有大于设定的执行周期后才会执行第二次。
有个需要频繁触发的函数,处于优化性能的角度考虑,在给定的时间内,只让函数触发的第一次生效,后面不生效-

function throttle (fn, delay) {
	// 利用闭包保存时间
	let prev = 0 ;
	return function () {
		let context = this
		let arg = arguments
		let now = +Date.now()
		if (now - prev >= delay) {
			 fn.apply(context, arg)
			 prev = Date.now()
		}
	}
}

//验证
let th=throttle(function(){console.log("每 5s 触发一次!")},5000);
setInterval(th,1000);

4. 防抖函数

防抖就是在一段时间触发或调用函数时,只执行一次; 也可以理解为触发n毫秒之后才会调用一次,从而优化系统性能

function debounce(fn , delay){
	let timer = null;
	return function(){
		let context = this;
		let arg = arguments;
		clearTimeout(timer);
		timer = setTimeout(function(){
			fn.apply(context , arg);
		} , delay);
	}
}
function fn(){
	console.log("防抖!");
}
		
//验证
addEventListener('click'  , debounce(fn,5000));

5. 实现深拷贝

//...和concat针对于数组等基本类型来说是深拷贝
//针对于 对象类型的数组来说是浅拷贝
let a={x:1};
let copy1 = {...a};
a.x=7;
console.log(copy1);
console.log(a);
			
//ES5:深拷贝:JSON.parse(JSON.stringify(obj));
let obj = {a : 1 , b : { m : 3 , n :4 } };
let obj1 = JSON.parse(JSON.stringify(obj));
obj.b.n=12;
console.log(obj);
console.log(obj1);
			
//ES5:递归实现深拷贝
function deep_copy(obj){
	let newobj = obj instanceof Array ? [] : {} ;
		for(i in obj){
			newobj[i] = typeof obj[i] === 'object' ? deep_copy(obj[i]) : obj[i];
		}
	return newobj;
}
console.log(deep_copy(obj));

6. 实现双向绑定

<body>
	<input type="text" id="input1" placeholder="请输入..."/>
	<span id="sp1"></span>
</body>
	<script type="text/javascript">
		//双向绑定
		let obj = {} ;
		let input = document.getElementById('input1');
		let span = document.getElementById('sp1');
		Object.defineProperty(obj , 'text' , {
			configurable : true ,
			enumerable : true ,
			get(){
				console.log('获取数据!');
			},
			set(newVal){
				console.log('设置数据!');
				input.value = newVal;
				span.innerText= newVal;
			}
		});
		input.addEventListener('keyup' , function(e){
			obj.text = e.target.value;
		})
	</script>

7. 实现类的继承

class child extends person {
	constructor (name,age,sex) {
		/*  执行父类的构造函数 
		子类必须在构造函数中掉用super
		*/
		super(name,age)
		/* 使用this一定要在super 之后 */
		this.sex = sex
	}
}
let p = new child('czklove','23','man')
console.log(p)
child.prototype = new person();
person.call(this,name,age)

8. 柯里化函数

柯里化函数:延迟函数的执行(在没有足够参数的情况下返回函数,足够参数返回结果)

function curry(fn , arr=[]){
	return (...arg) => {
		return (a => {
			return a.length === fn.length ? fn(...a) : curry(fn , a);
		})([...arr , ...arg])
	}
}
let c=curry((a,b,c,d) => a+b+c+d);
console.log(c(1,2,4)(4));

9. 洋葱模型koa

function fn1(next) {
	console.log(1);
	fn2();
	console.log(2);
} 
function fn2(next) {
	console.log(3);
	fn3();
	console.log(4);
}		 
function fn3(next) {
	console.log(5);
	if(next) next();
	console.log(6);
}

//第一种方法:递归实现
let middlewares = [fn1 , fn2 , fn3];
function dispatch(index){
	if (index === middlewares.length) return ;
	let m = middlewares[index++];
	return () => m(dispatch(index));
}
dispatch(0)();
	
//第二种方法:箭头函数
let finalFn = () => fn1( () => fn2( () => fn3()));
finalFn();

10.洋葱模型koa2

function compose (middleware) {
	//  参数是一个中间件数组、判断中间件列表是否为数组,每一项是否是函数
	if (!Array.isArray(middleware)) 
		throw new TypeError('Middleware stack must be an array!')
	for (const fn of middleware) {
		if (typeof fn !== 'function') 
			throw new TypeError('Middleware must be composed of functions!')
	} 
	return function (context, next) {
	// 这里next指的是洋葱模型的中心函数
	// context是一个配置对象,保存着一些配置,当然也可以利用context将一些参数往下一个中间传递
	// last called middleware #
		let index = -1  // index是记录执行的中间件的索引
		return dispatch(0)  // 执行第一个中间件  然后通过第一个中间件递归调用下一个中间件
		function dispatch (i) {
			// 这里是保证同个中间件中一个next()不被调用多次调用 
			// 当next()函数被调用两次的时候,i会小于index,然后抛出错误
			if (i <= index) return Promise.reject(new Error('next() called multiple times'))
			index = i
			let fn = middleware[i] // 取出要执行的中间件
			if (i === middleware.length) fn = next  // 如果i 等于 中间件的长度,即到了洋葱模型的中心(最后一个中间件)
			if (!fn) return Promise.resolve()  // 如果中间件为空,即直接resolve
			try {
			   //  递归执行下一个中间件 (下面会重点分析这个)
			   return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
			 } catch (err) {
			     return Promise.reject(err)
			   }
			}
		}
	}
  • 0
    点赞
  • 3
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

安萌萌萌萌萌

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值