前端八股总结-手写实现

Last updated on November 7, 2024 pm

Promise、任务调度器、LRU缓存、深拷贝、new

(1)手写Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class MyPromise {
constructor(executor){
this.state="pending"; //初始状态为pending
this.value=undifined;
this.reason=undifined;
//存储then中的成功和失败回调
this.onFulfilledCallbacks=[];
this.onRejectedCallbacks=[];

const resolve=(value)=>{
if(this.state==="pending"){
this.state="fulfilled";
this.value=value;
//执行所有成功的回调函数
this.onFulfilledCallbacks.forEach(callback=>callback(this.value))
}
};

const reject=(reason)=>{
if(this.state==="pending"){
this.state="rejected";
this.reason=reason;

this.onRejectedCallbacks.forEach(callback=>callback(this.reason))
}
};
//捕捉executor执行中的错误,并调用reject
try {
executor(resolve,reject);
}catch(error){
reject(error);
}
}

then(onFulfilled,onRejected){
//如果成功的回调函数没有传递,则设置一个默认函数,直接返回值
onFulfilled=typeof onFulfilled==="function"?onFulfilled:value=>value
//如果失败的回调函数没有传递,则设置一个默认函数,抛出错误
onRejected=typeof onRejected==="function"?onRejected:reason=>{
throw reason}

return new MyPromise((resolve,reject)=>{
if(this.state==="fulfilled"){
//异步执行回调
setTimeout(()=>{
try{
const result=onFulfilled(this.value);
resolve(result);
}catch(error){
reject(error);
}
});
}

if(this.state==="rejected"){
setTimeout(()=>{
try{
const reason=onRejected(this.reason);
reject(reason);
}catch(error){
reject(error);
}
});
}

if(this.state==="pending"){
//将成功回调和失败回调存储起来,等待状态改变后调用
this.onFulfilledCallbacks.push(()=>{
setTimeout(()=>{
try{
const result=onFulfilled(this.value);
resolve(result);
}catch(error){
reject(error);
}
});
});

this.onRejectedCallbacks.push(()=>{
setTimeout(()=>{
try{
const reason=onRejected(this.reason);
reject(reason);
}catch(error){
reject(error);
}
});
});
}
});
}

catch(onRejected){
//调用then方法,只传递失败的回调
return this.then(null,onRejected);
}
}

(2)手写任务调度器

支持按照指定时间间隔执行任务,以及暂停、恢复和取消任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class TaskScheduler {
//this.tasks是一个Map实例,用于存储调度器的任务
constructor(){
this.tasks=new Map(); //初始化
}
//添加任务
addTask(taskName,callback,interval){
//如果任务名已经存在,不可添加
if(this.tasks.has(taskName)){
console.log(`This task has already exited`);
return;
}
//任务对象
const task = {
callback, //运行函数
interval, //时间间隔
timer: null, //由于需要访问callback,所以在下面建立
isPaused: false, //是否被暂停
};

task.timer = setInterval(()=>{
if(!task.isPaused) {
task.callback(); //只有没被暂停才会执行
}
},interval);
//在Map中添加一个新的条目
this.tasks.set(taskName,task);
console.log(`Task ${taskName} has been added`) ;
}

pauseTask(taskName) {
const task=this.tasks.get(taskName);
if(task){
task.isPaused=true;
console.log(`Task ${taskName} is paused`);
}else{
console.log(`Task ${taskName} doesn't exited`);
}
}

resumeTask(taskName){
const task=this.tasks.get(taskName);
if(task){
task.isPaused=false;
console.log(`Task ${taskName} is unpaused`);
}else{
console.log(`Task ${taskName} doesn't exited`);
}
}

cancelTask(taskName){
const task=this.tasks.get(taskName);
if(task){
clearInterval(task.timer);
this.tasks.delete(taskName);
console.log(`Task ${taskName} is canceled`);
}else{
console.log(`Task ${taskName} doesn't exited`);
}
}
}

(3)手写LRU

LRU缓存的特点是,当缓存达到容量限制时,最少被使用的缓存项会被移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class LRUCache {
constructor(capacity){
this.capacity=capacity; //缓存容量
this.cache=new Map();
}
//获取缓存
getCache(key) {
if(!this.cache.has(key)){
return -1; //不存在该缓存
}

const value=this.cache.get(key);
this.cache.delete(key);
this.cache.set(key,value); //重新插入以更新顺序

return value; //返回value
}
//设置缓存
put(key,value) {
if(this.cache.has(key)){
this.cache.delete(key); //如果已经存在该缓存,删除旧值
}else if(this.cache.size>=this.capacity){
this.cache.delete(this.cache.keys().next().value);
}//删除的是cache中第一个值,也就是最久未使用的那个

this.cache.set(ke,value);
}
}

//示例
const lruCache = new LRUCache(2);

Map.keys()

this.cache.keys()这个方法返回一个迭代器(Iterator),用于遍历 Map 中所有的键。这个迭代器是按插入顺序生成的;

next()是迭代器的方法,用于获取迭代器的下一个值。调用 next() 方法会返回一个对象,包含两个属性:

  • value:下一个键的值。
  • done:一个布尔值,表示迭代器是否已完成。

通用性:任何 Map 对象都可以使用 .keys() 方法,无论其中存储的键是什么类型(字符串、数字、对象等)。

返回的迭代器:这个迭代器的行为是统一的,能够通过 next() 方法逐个访问键,并在遍历完成时返回一个 { done: true } 的对象。

(4)手写深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function deepClone(obj,hash=new WeakMap()) {
//处理基本数据类型
if(obj===null||typeof obj!="object") return obj;
//处理Date
if(obj instanceof Date) return new Date(obj);
//处理RegExp
if(obj instanceof RegExp) return new RegExp(obj);
//检测是否已经处理过
if(hash.has(obj)) return hash.get(obj);
//判断是数组还是对象
const cloneObj=Array.isArray(obj)?[]:{};
//确保不会产生循环调用
hash.set(obj,cloneObj);
//如果对象/数组中的值还是对象,还要继续对其进行深拷贝
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
cloneObj[key]=deepClone(obj[key],hash);
}
}

return cloneObj;
}
  • hasOwnProperty 方法用于检查 obj 是否具有指定的属性 key。这是为了确保只拷贝对象的自有属性(即直接在该对象上定义的属性),而不包括继承自原型链的属性。
  • 例如,假设有一个对象 obj,它继承了某些属性,但我们只想拷贝自己定义的属性,使用 hasOwnProperty 可以过滤掉那些继承的属性。

(5)手写new

步骤
  • 创建一个新的对象:空对象
  • 设置原型:将新对象的原型指向构造函数的原型
  • 绑定this:将构造函数的this绑定到新创建的对象上
  • 执行构造函数:调用构造函数,并将新对象作为上下文(this)
  • 返回对象:如果构造函数返回了一个对象,则返回该对象;否则返回新创建的对象
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function myNew(cunstructor,...args) {
const obj={};
Object.setPrototypeOf(obj,constructor.prototype);
const result=constructor.apply(obj,args);
return (result&&typeof result==='object')?result:obj;
}

//示例:定义一个构造函数
function Person(name,age) {
this.name=name;
this.age=age;
}

const alice=myNew(Person,"Alice",20);

console.log(alice.name);
console.log(alice instanceof Person);

(6)写一个重复执行的函数

1、方法一:使用setInterval
1
2
3
4
5
6
7
8
9
function repeatFunction() {
console.log("函数被重复执行");
}
//每隔一秒执行一次
const intervalId=setInterval(repeatFunction,1000);
//如果需要在一定时间后停止,可以使用setTimeout
setTimeout(()=>{
clearInterval(intervalId);
},5000);
2、setTimeout递归调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function repeatFunction(){
console.log("函数被重复执行");
setTimeout(repeatFunction,1000);
}
//开始执行
repeatFunction();
//如果需要在一定时间后停止,可以设置条件变量
let count=0;
const maxCount=5; //最多执行五次

function repeatWithLimit() {
if(count<maxCount){
console.log();
count++;
setTimeout(repeatWithLimit,1000);
}else{
console.log();
}
}

(7)写函数统计页面上所有的DOM元素,以对象的形式返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function countDOMElements() {

const allElements=document.querySelectorAll('*');
const elementCount={};

allElements.forEach((element)=>{
const tagName=element.tagName.toLowerCase();
if(elementCount[tagName]){
elementCount[tagName]++;
}else{
elementCount[tagName]=1;
}
});


}

(8)数组拍平

1
2
3
4
5
6
7
8
9
10
11
function customFlat(arr,depth=1){
if(depth<1) return arr.slice();
return arr.reduce((acc,cur)=>{
if(Array.isArray(cur)){
return acc.concat(customFlat(cur,depth-1));
}
else{
return acc.concat(cur);
}
},[]);
}

(9)函数柯里化

函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function curry(fn,args=[]){
return function(){
//使用rest合并当前已经存储的参数和新传入的参数
let rest=[...args,...arguments];
//检查合并参数的数量是否小于目标函数的参数数量
if(rest.length<fn.length){
//如果数量不足,递归调用curry,继续收集参数
return curry.call(this,fn,rest);
}else{
//如果参数足够,调用目标函数并返回结果
return fn.apply(this,rest);
}
}
}

(10)sum(1)(2)(3)

1
2
3
4
5
6
7
8
function sum(...args){
const total=args.reduce((acc,cur)=>acc+cur,0);
const inner=(...newArgs)=>{
return sum(total+newArgs.reduce((acc,cur)=>acc+cur,0));
};
inner.value=()=>total;
return inner;
}

限定每次调用参数只有一个:

1
2
3
4
5
6
7
function sum(num){
const inner=(nextNum)=>{
return sum(num+nextNum);
};
inner.value=()=>num;
return inner;
}

(11)实现数组去重

1
2
3
function uniq(arry){
return [...new Set(arry)];
}
1
2
3
4
5
6
7
8
9
function uniq(arry){
let result=[];
for(let i=0;i<arry.length;i++){
if(!result.includes(arry[i])){
result.push(arry[i]);
}
}
return result;
}

(12)实现计时器

1
2
3
4
5
6
7
8
class Timer={
constructor(){
this.time=0;
}
increase(){this.time+=1;}
decrease(){this.time-=1;}
getTime(){return time};
}

(13)蛇形打印树的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class TreeNode{
constructor(value){
this.value=value;
this.left=null;
this.right=null;
}
}

function zigzagLevelOrder(root){
if(!root) return[];
const result=[];
const currentStack=[root];
const nextStack;
let leftToRight=true;

while(currentStack.length>0){
const levelValues=[];
while (currentStack.length>0){
const node=currentStack.pop();
levelValues.push(node.value);

if(leftToRight){
if(node.left) nextStack.push(node.left);
if(node.right) nextStack.push(node.right);
}else{
if(node.right) nextStack.push(node.right);
if(node.left) nextStack.push(node.left);
}
}
result.push(levelValues);
[currentStack,nextStack]=[nextStack,currentStack];
leftToRight=!leftToRight;
}

}

(14)链表拍平

1、迭代
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function flatten(head){
if(!head) return head;

let cur=head;
let stack=[];

while(cur){
if(cur.child){//遇到child节点将next入栈并处理child
if(cur.next) stack.push(cur.next);
//将child链接到当前节点的next
cur.next=cur.child;
cur.child=null;
}
//如果没有下一个节点且栈bu
if(!cur.next&&stack.length>0){
cur.next=stack.pop();
}
cur=cur.next;
}
return head;
}

前端八股总结-手写实现
http://example.com/2024/10/26/前端八股总结-手写实现/
Author
Yaodeer
Posted on
October 26, 2024
Licensed under