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