刷刷前端手写题
闭包用途
闭包
闭包让你可以在一个内层函数中访问到其外层函数的作用域
防抖
描述
前面所有触发都被取消,最后一次执行,在规定时间之后才会触发,也就是说如果连续快速的触发,用户操作频繁,但只会执行一次 。
常用场景:输入框输入
代码实现
1、lodash的debounce函数
2、 当用户点击按钮时,debounce
包装的 getBtnValue
函数会延迟 3000 毫秒执行。如果在这 3000 毫秒内用户再次点击按钮,那么之前的定时器会被清除,重新开始计时。因此,getBtnValue
函数只会在用户停止点击 3000 毫秒后才执行
function debounce(fn,apply){let timer;// 初始状态下,timer是undefinedreturn function(){// 如果timer有值,清除之前的定时器if(timer) {clearTimeout(timer);}timer = setTimeout(()=>{fn.apply(this,args)},delay)}
}
function getValue(e){console.log('1111')
}
const btn = document.createElement('button')
btn.innerHTML = 'btn'
document.body.appendChild(btn)
btn.onclick = debounce(getValue, 3000)
浏览器环境:timer 会被赋值为一个整数,例如 1、2、3 等;Node.js 环境:timer 会被赋值为一个 Timeout 对象。
节流
描述
有规律执行,减少时间执行次数,拖放,滚屏;
只会在第一个点击时执行一次,后续点击将被忽略,直到 delay时间过去后才能再次执行
代码实现
function throttle(func, delay) {let timer; // 用于保存定时器标识符return function() {if (timer) return; // 如果 timer 已经存在,说明在 delay 时间内已经触发过,直接返回,跳过本次调用const args = arguments; // 保存当前的参数const context = this; // 保存当前的执行上下文// 设置一个定时器,在 delay 毫秒后执行 functimer = setTimeout(() => {func.apply(context, args); // 执行原始函数,传递当前上下文和参数timer = null; // 重置 timer,表示可以再次触发 func}, delay);};}const btn = document.createElement('button')btn.innerHTML = 'btn'document.body.appendChild(btn)function handleClick() {console.log('Button clicked!');}btn.onclick = throttle(handleClick, 3000);
- 点击第一次:创建
timer
,设置delay
毫秒后执行func
。 - 在
delay
期间再次点击:由于timer
存在,函数直接返回,不会再次执行func
。 delay
时间到达:func
被执行,timer
被重置为null
。- 允许新的点击执行:可以再次创建新的
timer
并触发func。
因此,尽管多次点击,只有第一次点击时创建的定时器会生效!!
函数柯里化
描述
将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
代码实现
function curry(fn){let args = []return function res(...rest){if(arguments.length==0){//当没有参数传递给 res 时于是调用 fn(...args)return fn(...args)}else{args.push(...rest)return res}}
}
var sumFn = function(...arr){return arr.reduce((prev,cur)=>{return prev+cur})
}
let res = curry(sumFn)(1)(2)(3,4)() //15
手写New
描述
new
操作符的主要作用是生成一个新对象,并将这个对象与构造函数的原型连接起来,同时构造函数中的代码会在新对象的上下文中执行,给新对象赋予属性和方法。
主要流程是:const person1 = new Person('Alice', 25);
- 新建一个对象:
const person1 = {};
- 设置原型:
person1.__proto__ = Person.prototype;//隐式原型指向构造函数的显示原型
- 绑定
this
:执行Person
构造函数时,this
被绑定到person1
。 - 执行构造函数:在
Person
函数中,this.name = name;
将name
赋值给person1
。 - 返回对象:如果没有显式返回对象,
new
操作符会返回person1
。
代码实现
function Person(a){// 检查 this 是否是 Person 的实例//if (!(this instanceof Person)) {//throw new Error("Person 只能通过 new 关键字调用");//}this.a=a
}
function myNew(fn,...args){const obj={};obj._proto_=fn.prototypefn.apply(obj,args)return obj
}
const obj = myNew(Person,123)
数组去重
描述
顾名思义:console.log(uniqueArray([1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5])); // [1, 2, 3, 4, 5, 6]
代码实现
const uniqueArray = (arr) => {return [...new Set(arr)]
}
实现正则切分千分位(js)
描述
顾名思义:console.log(formatThousands(123456789)); // 输出: 123,456,789
代码实现
function format(num){//将数字转为字符串并分割整数和小数部分let arr = String(num).split(.)let char = arr[0].split('').reverse()return char.reduce((pre,cur,curIndex)=>{if(curIndex+1) %3 ===0 && curIndex !==char.length -1){return ','+cur+pre}return cur+pre},"")
}
手写call/apply
描述:通过使用call/apply方法,可以将一个对象的方法应用到另一个对象上,区别是传入参数一个是参数列表,一个是数组;都是立即执行
代码实现
Function.prototype.myCall = function(context,...args){if (typeof this!=="function"){throw new TypeError("被调用的必须是函数")}context||globalThis//用Symbol来创建唯一的fn,防止名字冲突const fn = Symbol('key')// this是调用myCall的函数test,将函数绑定到上下文对象的新属性上context[fn] = thisconst res = context[fn](...args)//hello,worlddelete context[fn]return res
}const test = {name:"xxx",hello: function(){console.log(`hello,${this.name}!`);}
}
const obj = {name:"world"};
test.hello.myCall(obj);//hello,world
手写Bind
描述
与call和apply方法不同,bind方法并不会立即执行函数,而是返回一个新函数,可以稍后调用;
且参数可以分多次传入
代码实现
Function.prototype.myBind = function(thisArg,...args1){const fn = this;return function(...args2){if(typeof fn!=="function"){throw new TypeError("被调用的是函数")}thisArg = thisArg||globalThis;let uniqueFn = Symbol("fn")thisArg[uniqueFn] = fn;//合并参数并调用函数const res = thisArg[uniqueFn](...args1,...args2)delete thisArg[uniqueFn]return res}
}
function greet(param1,param2) {console.log(`${param1},${this.name}${param2}`)
}
const boundGreet = greet.myBind(obj,"Hey");
boundGreet("!!")// "Hey, Alice!!"
扁平化
描述
将多维转为一维
代码实现--数组
遍历,检查是数组,递归,不是数组扔进去
let arr = [1, [2, 3], [4, [5, 6, [7, 8]]]];
function flatten(arr){let res = []let len = arr.lengthfor (let i=0;i<len;i++){if(Array.isArray(arr[i])){res.concat(flatten(arr[i]))}else{res.push(arr[i])}}return res
}
代码实现--对象
function flattenObj(){let res = {}for (let key in obj){if(typeof obj[key] ==='object'&& obj[key] !==null){flatten(res,obj[key],`${key}`)}else{res[key]=obj[key]}}function flatten(res,obj,keyname){for(let key in obj){if(typeof obj[key] ==='object'&& obj[key] !==null){flatten(res,obj[key],`${keyname}.${key}`)}else{res[`${keyname}.${key}`]=obj[key]}}}return res
}const obj = {a: 1,b: [1, 2, { c: true }],c: { e: 2, f: 3 },g: null,};
let res = flattenObj(obj)
结果:{a: 1,'b.0': 1,'b.1': 2,'b.2.c': true,'c.e': 2,'c.f': 3,g: null
}
模拟Promise.all()
描述
入参是个由Promise
实例组成的数组,返回值是个promise
,因为可以使用.then,
如果全部成功,状态变为resolved
, 并且返回值组成一个数组传给回调,但凡有一个失败,状态变为rejected
, 并将error
返回给回调
代码实现
// 添加一个自定义的静态方法
Promise.MyAll = function(promises){let arr = []count = 0return new Promise((resolve,reject)=>{promises.forEach((item,i)=>{//将 item 转换为一个 PromisePromise.resolve(item).then(res=>{arr[i]=rescount+=1if(count === promises.length) resolve(arr)}).catch(reject)})})
}
测试
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {setTimeout(() => {resolve('p2')}, 500)
})
Promise.MyAll([p1, p2]).then(res => console.log(res))//['p1','p2'].catch(err => console.log(err))
模拟Promise.race()
描述
返回状态以最快的那个为准
代码实现
Promise.MyRace(promises=>{return new Promise((resolve,reject)=>{for(const x of promises){Promise.resolve(x).then(resolve,reject)}})
})
观察者模式
描述
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新
例如公司(被观察者)发中秋福利给员工(观察者)
代码实现
//被观察者
class Subject{constructor(){this.observerList = []}addObserver(observer){this.observerList.push(observer)}removeObserver(observer){const index = this.observerList.findIndex(o => o.name===observer.name)this.observerList.splice(index,1)}notifyObservers(message){const observers = this.observerList;observers.forEach(observer=>observer.notified(message))}
}
//观察者
class Observer{constructor(name,subject){this.name = name;if(subject){subject.addObserver(this)}}notified(message){console.log(this.name,message)}
}
测试
const subject = new Subject();
const observerA = new Observer('observerA', subject);
subject.notifyObservers('Hello from subject');//observerA Hello from subject
常见使用场景
数据绑定机制通常采用观察者模式。当数据模型变化时,所有绑定了该数据的组件(观察者)都会自动更新。例如,一个购物车系统,当用户添加商品到购物车时,购物车总价会自动更新。这个过程可以通过观察者模式来实现
订阅者模式
描述
发布-订阅是一种消息范式,消息的发送者(发布者)不会将消息直接发送给特定的接收者(订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在
例如:公司发快递给消费者
代码实现
class Publisher {constructor(name, context) {this.name = name; // 设置发布者的名称this.context = context; // 设置发布者所属的 PubSub 实例}publish(type, content) {this.context.publish(type, content); // 调用 PubSub 实例的 publish 方法,发布消息}
}class Subscriber {constructor(name, context) {this.name = name; // 设置订阅者的名称this.context = context; // 设置订阅者所属的 PubSub 实例}subscribe(type, cb) {this.context.subscribe(type, cb); // 调用 PubSub 实例的 subscribe 方法,订阅某个消息类型}
}class PubSub {constructor() {this.messages = {}; // 存储已发布的消息,按消息类型存储this.listeners = {}; // 存储订阅者回调函数,按消息类型存储}publish(type, content) {const existContent = this.messages[type]; // 检查是否已有该类型的消息if (!existContent) {this.messages[type] = []; // 如果没有,初始化一个数组来存储该类型的消息}this.messages[type].push(content); // 将消息内容添加到对应类型的消息数组中}subscribe(type, cb) {const existListener = this.listeners[type]; // 检查是否已有订阅者监听该类型的消息if (!existListener) {this.listeners[type] = []; // 如果没有,初始化一个数组来存储该类型的订阅者回调函数}this.listeners[type].push(cb); // 将订阅者的回调函数添加到对应类型的监听数组中}notify(type) {const messages = this.messages[type]; // 获取该类型的所有已发布的消息const subscribers = this.listeners[type] || []; // 获取该类型的所有订阅者回调函数subscribers.forEach((cb, index) => cb(messages[index])); // 将对应的消息传递给订阅者的回调函数}
}
测试
const TYPE_A = 'music'; // 定义一个消息类型
const pubsub = new PubSub(); // 创建一个 PubSub 实例const publisherA = new Publisher('publisherA', pubsub); // 创建一个发布者,并与 PubSub 实例关联
publisherA.publish(TYPE_A, 'we are young'); // 发布者发布一条类型为 'music' 的消息,内容是 'we are young'const subscriberA = new Subscriber('subscriberA', pubsub); // 创建一个订阅者,并与 PubSub 实例关联
subscriberA.subscribe(TYPE_A, res => {console.log('subscriberA received', res); // 订阅者订阅 'music' 类型的消息,并定义回调函数处理接收到的消息
});pubsub.notify(TYPE_A); // 通知所有订阅了 'music' 类型的订阅者,调用他们的回调函数并传递消息
常见使用场景
事件总线:不同组件之间不直接通信,而是通过事件总线来发布和订阅事件。这使得组件之间高度解耦,组件可以独立发展
手写vuex
描述
Vuex 是 Vue.js 的状态管理模式,主要解决组件之间共享状态时的问题