10个常考的前端手写题,你全都会吗?(下)

news2024/11/17 22:15:42

前言

 📫 大家好,我是南木元元,热爱技术和分享,欢迎大家交流,一起学习进步!

 🍅 个人主页:南木元元

今天接着上篇再来分享一下10个常见的JavaScript手写功能。


目录

1.实现继承

ES5继承(寄生组合式继承)

ES6继承

2.获取URL参数

split方法

URLSearchParams方法

3.手写reduce

4.实现观察者模式

5.实现发布-订阅模式

6.异步控制并发数

7.实现Promise.all

8.使用setTimeout实现setInterval

9.实现每隔一秒打印 1,2,3,4

10.循环打印红黄绿

结语


1.实现继承

ES5继承(寄生组合式继承)

寄生组合式继承是对组合式继承(调用了2次父构造方法)的改进,使用父类的原型的副本来作为子类的原型,这样就只调用一次父构造函数,避免了创建不必要的属性。

function Parent (name) {
   this.name = name;
   this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
   console.log(this.name)
}
function Child (name, age) {
   Parent.call(this, name);//借用构造函数的方式来实现属性的继承和传参
   this.age = age;
}

//这里不用Child.prototype = new Parent()原型链方式的原因是会调用2次父类的构造方法,导致子类的原型上多了不需要的父类属性
Child.prototype = Object.create(Parent.prototype);//这里就是对组合继承的改进,创建了父类原型的副本
Child.prototype.constructor = Child;//把子类的构造指向子类本身

var child1 = new Child('kevin', '18');
console.log(child1.colors);//[ 'red', 'blue', 'green' ]
child1.getName();//kevin

测试结果:

ES6继承

ES6中,可以使用class类去实现继承。使用extends表明继承自哪个父类,并且在子类构造函数中必须调用super。

class Parent {
  constructor(name) {
    this.name = name;
  }
  getName() {
    console.log(this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    //使用this之前必须先调用super(),它调用父类的构造函数并绑定父类的属性和方法
    super(name);
    //之后子类的构造函数再进一步访问和修改 this
    this.age = age;
  }
}

// 测试
let child = new Child("kevin", 18);
console.log(child.name); // kevin
console.log(child.age); // 18
child.getName(); // kevin

测试结果:

es5继承和es6继承的区别

  • ES5继承是先创建子类的实例对象,然后再将父类方法添加到this(Parent.call(this))上。
  • ES6的继承不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

2.获取URL参数

split方法

function getParams(url) {
  const res = {}
  //判断字符串中是否可以找到?
  if (url.includes('?')) {
    const str = url.split('?')[1]
    const arr = str.split('&')
    arr.forEach(item => {
      const key = item.split('=')[0]
      const val = item.split('=')[1]
      res[key] = decodeURIComponent(val) // 中文需解码
    })
  }
  return res
}

// 测试
const user = getParams('http://www.baidu.com?user=%E9%98%BF%E9%A3%9E&age=16')
console.log(user) // { user: '阿飞', age: '16' }

测试结果:

URLSearchParams方法

URLSearchParams方法返回一个 URLSearchParams 对象,来处理 URL 的查询字符串。

// 该url的url.search为"?foo=1&bar=2"
let url = new URL("https://example.com?foo=1&bar=2"); 
// 创建一个URLSearchParams实例,即URLSearchParams { 'foo' => '1', 'bar' => '2' }
let searchParams = new URLSearchParams(url.search); 

// 键值对列表URLSearchParams Iterator { [ 'foo', '1' ], [ 'bar', '2' ] }
console.log(searchParams.entries()); 

// 将键值对列表转换为一个对象
let res = Object.fromEntries(searchParams.entries()); 
console.log(res); //{ foo: '1', bar: '2' }

测试结果:

3.手写reduce

reduce的使用

//普通数组求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.reduce((prev, cur) => { return prev + cur }, 0)//55
//多维数组求和
let arr = [1,2,3,[[4,5],6],7,8,9]
arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0)//45
//对象数组求和
let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] 
arr.reduce((prev, cur) => {
    return prev + cur["a"];//13 求对象数组中所有属性为a的和
}, 0)

reduce的实现

Array.prototype.myReduce = function (cb, initialValue) {
  const arr = this; //this就是调用reduce方法的数组
  let total = initialValue ? initialValue : arr[0]; //不传默认取数组第一项
  let startIndex = initialValue ? 0 : 1; // 有初始值的话从0遍历,否则从1遍历
  for (let i = startIndex; i < arr.length; i++) {
    total = cb(total, arr[i], i, arr); //参数为初始值、当前值、索引、当前数组
  }
  return total;
};

//测试
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let res = arr.myReduce((total, cur) => {
  return total + cur;
}, 0);
console.log(res);//55

测试结果:

4.实现观察者模式

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

// 被观察者 学生
class Subject {
  constructor() {
    this.state = "happy";
    this.observers = []; // 存储所有的观察者
  }
  //新增观察者
  add(o) {
    this.observers.push(o);
  }
  //获取状态
  getState() {
    return this.state;
  }
  // 更新状态并通知
  setState(newState) {
    this.state = newState;
    this.notify();
  }
  //通知所有的观察者
  notify() {
    this.observers.forEach((o) => o.update(this));
  }
}

// 观察者 父母和老师
class Observer {
  constructor(name) {
    this.name = name;
  }
  //更新
  update(student) {
    console.log(`亲爱的${this.name} 通知您当前学生的状态是${student.getState()}`);
  }
}

let student = new Subject();
let parent = new Observer("父母");
let teacher = new Observer("老师");
//添加观察者
student.add(parent);
student.add(teacher);
//设置被观察者的状态
student.setState("sad");

测试结果:

详细可以去看我的这篇文章——JavaScript 简单实现观察者模式和发布-订阅模式

5.实现发布-订阅模式

发布订阅模式跟观察者模式很像,但它的发布和订阅是不互相依赖的,因为有一个统一调度中心。

class EventBus {
  constructor() {
    // 缓存列表,用来存放注册的事件与回调
    this.cache = {};
  }

  // 订阅事件
  on(name, cb) {
    // 如果当前事件没有订阅过,就给事件创建一个队列
    if (!this.cache[name]) {
      this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
    }
    this.cache[name].push(cb); 
  }

  // 触发事件
  emit(name, ...args) {
    // 检查目标事件是否有监听函数队列
    if (this.cache[name]) {
      // 逐个调用队列里的回调函数
      this.cache[name].forEach((callback) => {
        callback(...args);
      });
    }
  }

  // 取消订阅
  off(name, cb) {
    const callbacks = this.cache[name]; 
    const index = callbacks.indexOf(cb); 
    if (index !== -1) {
      callbacks.splice(index, 1); 
    }
  }

  // 只订阅一次
  once(name, cb) {
    // 执行完第一次回调函数后,自动删除当前订阅事件
    const fn = (...args) => {
      cb(...args); 
      this.off(name, fn); 
    };
    this.on(name, fn);
  }
}

// 测试
let eventBus = new EventBus();
let event1 = function (...args) {
  console.log(`通知1-订阅者小陈老师,小明同学当前心情状态:${args}`)
};
// 订阅事件,只订阅一次
eventBus.once("teacherName1", event1);
// 发布事件
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");

测试结果:

6.异步控制并发数

场景:实现一个带并发限制的异度调度器,保证同时运行的任务最多有两个。

class Controll {
  constructor(limit) {
    // 待执行的任务队列
    this.waitTasks = []; 
    // 正在执行的任务队列
    this.runTasks = []; 
    // 允许同时运行的任务数量
    this.limit = limit; 
  }
  //添加任务
  add(task) {
    //当前执行任务队列小于并发限制,就执行该任务
    if (this.runTasks.length < this.limit) {
      this.run(task);
    } else {
      //将该任务添加到等待队列
      this.waitTasks.push(task);
    }
  }
  //执行任务
  run(task) {
    //push添加元素并返回新的数组长度
    const len = this.runTasks.push(task);
    const index = len - 1;
    task().then(() => {
      //执行完后删除任务
      this.runTasks.splice(index, 1);
      if (this.waitTasks.length > 0) {
        //执行并删除等待队列中的第一个任务
        this.run(this.waitTasks.shift());
      }
    });
  }
}

//测试:允许同时执行2个任务
const scheduler = new Controll(2);
//添加任务
const addTask = (time, order) => {
  scheduler.add(async () => {
    //定时器打印
    await new Promise((resolve) => {
      setTimeout(resolve, time);//定时器时间到了就resolve执行成功,然后打印结果
    });
    console.log(order);
  });
};
//添加任务,参数为定时器时间和打印数字
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");

测试结果:

7.实现Promise.all

function all(promises) {
  return new Promise(function(resolve, reject) {
    //传入参数为一个空的可迭代对象,直接resolve
    if (promises.length === 0) {
      resolve([]);
    } else {
      const res = [];
      let count = 0;
      for (let i = 0; i < promises.length; i++) {
          //为什么不直接promise[i].then, 因为promise[i]可能不是一个promise, 也可能是普通值
          Promise.resolve(promises[i]).then((data) => {
              res[i] = data;
              count++;
              if (count === promises.length) {
                  resolve(res);//如果所有Promise都成功,则返回成功结果数组
              }
          }).catch((err) => {
              reject(err);//如果有一个Promise失败,则返回这个失败结果
          });
      }
    }
  })
}

// 测试
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "foo");
});

all([promise1, promise2, promise3]).then((values) => {
  console.log(values); //[3, 42, "foo"]
});

测试结果:

8.使用setTimeout实现setInterval

setInterval的缺点:setInterval 的作用是每隔一段时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。

针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。

实现思路是使用递归函数,不断地去执行setTimeout从而达到setInterval的效果。

function mySetInterval(fn, timeout) {
  // 控制器,控制定时器是否继续执行
  var timer = {
    flag: true,
  };
  // 设置递归函数,模拟定时器执行
  function interval() {
    if (timer.flag) {
      fn();
      setTimeout(interval, timeout);//递归
    }
  }
  // 启动定时器
  setTimeout(interval, timeout);
  // 返回控制器
  return timer;
}

let timer = mySetInterval(() => {
  console.log("1");
}, 1000);
//3秒后停止定时器
setTimeout(() => (timer.flag = false), 3000);

测试结果:

9.实现每隔一秒打印 1,2,3,4

// 1.使用 let 块级作用域
for (let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

// 2.使用闭包实现
for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(() => {
      console.log(j);
    }, j * 1000);
  })(i);
}

测试结果:

10.循环打印红黄绿

场景:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?

三个亮灯函数:

function red() {
    console.log('red');
}
function green() {
    console.log('green');
}
function yellow() {
    console.log('yellow');
}

用async/await实现:

const task = (timer, light) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (light === "red") {
        red();
      } else if (light === "green") {
        green();
      } else if (light === "yellow") {
        yellow();
      }
      resolve();//注意,要resolve让Promise状态变成fulfilled,不然会一直是pending,无法往下执行
    }, timer);
  });
};
const taskRunner = async () => {
  await task(3000, "red");
  await task(2000, "green");
  await task(1000, "yellow");
  taskRunner();	//递归
};
taskRunner();

测试结果:

结语

本文总结了前端常见的一些手写功能,你是不是全都掌握了呢,欢迎在评论区交流。

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏✍️评论支持一下博主~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1407363.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏2(附项目源码)

本节最终效果演示 文章目录 本节最终效果演示系列目录前言添加小动物模型动画动物AI脚本效果 添加石头石头模型拾取物品效果 源码完结 系列目录 【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏1&#xff08;附项目源码&#xff09; 【制作100个unity游戏之23】…

卓振江:我的大数据能力提升之路 | 提升之路系列(二)

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

x-cmd pkg | perl - 具有强大的文本处理能力的通用脚本语言

目录 介绍首次用户技术特点竞品进一步阅读 介绍 Perl 是一种动态弱类型编程语言。Perl 内部集成了正则表达式的功能&#xff0c;以及巨大的第三方代码库 CPAN;在处理文本领域,是最有竞争力的一门编程语言之一 生态系统&#xff1a;综合 Perl 档案网络 (CPAN) 提供了超过 25,0…

【江科大】STM32:MPU6050介绍

文章目录 MPU6050介绍结构图MPU6050参数硬件电路模块内部结构框图数据帧格式寄存器地址 MPU6050介绍 MPU6050是一个6轴姿态传感器&#xff0c;可以测量芯片自身X、Y、Z轴的加速度、角速度参数&#xff0c;通过数据融合&#xff0c;可进一步得到姿态角&#xff0c;常应用于平衡…

maven配置阿里镜像源

在用户设置settings.xml文件里找到mirrors配置部分&#xff0c;大概在146行&#xff0c;添加如下配置&#xff1a; <mirror><id>alimaven</id><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/groups/public/</u…

防火墙子接口配置

目录 拓扑需求 配置DMZ区域配置IP 总公司IP配置生产区办公区 总公司配置子接口网关生产区网关办公区网关 配置安全策略&#xff08;trust to DMZ&#xff09; 测试 拓扑 需求 配置总公司区域配置DMZ区域配置总公司区域到DMZ区域互通&#xff08;trust to DMZ&#xff09; 配置…

基于springboot+vue的学科竞赛管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…

实时渲染 -- 几何(Geometry)

几何表示&#xff08;Geometry Representation&#xff09; 隐式表面&#xff08;Implicit Surface&#xff09; 一个函数定义一个隐式几何 f(x,y,z)0。​ 容易判断一个点是在几何体内部&#xff08;f<0&#xff09;还是外部&#xff08;f>0&#xff09; 显式表面&…

【C++】位图+布隆过滤器

位图布隆过滤器 1.位图2.布隆过滤器 喜欢的点赞&#xff0c;收藏&#xff0c;关注一下把&#xff01; 1.位图 问: 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中。 可能你会想到下面这几种方式&#…

分享多种vcruntime140_1.dll丢失修复办法,vcruntime140_1.dll文件下载

vcruntime140_1.dll是Windows操作系统中的一个重要系统文件&#xff0c;它与C运行库相关。当计算机上缺少或损坏了vcruntime140_1.dll文件时&#xff0c;可能会导致一系列问题和错误。出现这文件错误&#xff0c;应该很多小伙伴都会想到重新下载vcruntime140_1.dll&#xff0c;…

uniapp微信小程序图片上传功能实现,页面显示文件列表、删除功能

uniapp小程序图片上传功能效果预览 一、template 页面结构 <view class"upload-box"><view class"upload-list"><view class"upload-item" v-for"(item,index) of fileList" :keyindex><image class"img…

2024年可能会用到的几个地图可视化模板

前言 在数字化的过程中&#xff0c;数据可视化变得越来越重要。用户喜欢通过酷炫的视觉效果和直观的数据展示来理解数据。可视化地图组件是数据可视化的重要组成部分。这些地图组件提供多样化的效果&#xff0c;能够更好地展示数据的关系和地理分布&#xff0c;直观地将数据与…

制图新手首选!6款在线软件,让制图变得简单易学!

1. 即时设计 即时设计是一种国内在线UI设计工具&#xff0c;专注于UI设计领域&#xff0c;支持多人合作。即时设计是一种年轻的UI设计工具&#xff0c;前景广阔。UI设计工具的即时设计支持各种主流格式文件的引入&#xff0c;可以很容易地从其他软件转移。即时设计作为新一代U…

ubuntu22.04安装filebeat报错解决

1、查看报错 journalctl -u filebeat 或者 filebeat -c /etc/filebeat/filebeat.yml找到报错信息 runtime/cgo: pthread_create failed: Operation not permitted 2、解决报错 在filebeat.yml配置文件添加如下配置&#xff0c;重启filebeat seccomp:default_action: allow…

生命在于折腾——WeChat机器人的研究和探索

一、前言 2022年&#xff0c;我玩过原神&#xff0c;当时看到了云崽的QQ机器人&#xff0c;很是感兴趣&#xff0c;支持各种插件&#xff0c;查询游戏内角色相关信息&#xff0c;当时我也自己写了几个插件&#xff0c;也看到很多大佬编写的好玩的插件&#xff0c;后来因为QQ不…

Java零基础学习19:集合

编写博客目的&#xff1a;本系列博客均根据B站黑马程序员系列视频学习和编写目的在于记录自己的学习点滴&#xff0c;方便后续回忆和查找相关知识点&#xff0c;不足之处恳请各位有缘的朋友指正。 一、集合和数组的对比 数组和集合很相似&#xff0c;但集合只能存储引用数据类…

3W Star 网易云音乐第三方开源 API 仓库因侵权被要求删除

NeteaseCloudMusicApi是一个使用Node.js编写的非官方网易云音乐API&#xff0c;用于获取网易云音乐平台的歌曲信息。该项目是完全开源的&#xff0c;在GitHub上获得了超过3万的star。 根据公开信息&#xff0c;NeteaseCloudMusicApi的主要目的是整理网易云音乐公开的网页接口&a…

SSL证书是什么,有哪些作用

SSL证书是什么&#xff1f; SSL证书 是一种提供SSL协议的证书&#xff0c;通过在客户端浏览器与WEB服务器之间建立一条SSL安全通道&#xff0c;对网络传输数据进行加密&#xff0c;防止数据被截取或窃听。一份SSL证书包括一个公共密钥和一个私用密钥&#xff1a;公共密钥主要用…

红队视角下的公有云基础组件安全(二)

前言 我们已经发过一篇红队视角下的公有云基础组件安全的文章&#xff0c;这篇是对上一篇内容的补充&#xff0c;主要为国外公有云如AWS、Google Cloud、Azure。 本文主要从红队视角讲述公有云基本服务中一些因配置问题产生的安全风险。 目录 ● 云存储 ● 云计算 ● 云网…

【C++记忆站】类和对象(三)

文章目录 再谈构造函数构造函数赋值并非初始化初始化列表explicit关键字 static成员概念特性一、静态成员为所有类对象所共享&#xff0c;不属于某个具体的对象二、静态成员变量必须在类外定义&#xff0c;定义时不添加static关键字三、静态成员函数没有隐藏的this指针&#xf…