30道高频JS手撕面试题,web开发基本步骤

return newArr;

}

myFlat(arr); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]

14.使用reduce手动实现数组扁平化flat方法


根据Array.isArray逐个判断数组里的每一项是否为数组元素

const myFlat = arr => {

return arr.reduce((pre, cur) => {

return pre.concat(Array.isArray(cur) ? myFlat(cur) : cur);

}, []);

};

console.log(myFlat(arr));

// [12, 23, 34, 56, 78, 90, 100, 110, 120, 130, 140]

15.用不同的三种思想实现数组去重?


思想一:数组最后一项元素替换掉当前项元素,并删除最后一项元素

let arr = [12, 23, 12, 15, 25, 23, 16, 25, 16];

for(let i = 0; i < arr.length - 1; i++) {

let item = arr[i]; // 取得当前数组中的每一项

let remainArgs = arr.slice(i+1); // 从 i+1项开始截取数组中剩余元素,包括i+1位置的元素

if (remainArgs.indexOf(item) > -1) { // 数组的后面元素 包含当前项

arr[i] = arr[arr.length - 1]; // 用数组最后一项替换当前项

arr.length–; // 删除数组最后一项

i–; // 仍从当前项开始比较

}

}

console.log(arr); // [ 16, 23, 12, 15, 25 ]

思想二:新容器存储思想——对象键值对

思想:

把数组元素作为对象属性,通过遍历数组,判断数组元素是否已经是对象的属性,如果对象属性定义过,则证明是重复元素,进而删除重复元素

let obj = {};

for (let i=0; i < arr.length; i++) {

let item = arr[i]; // 取得当前项

if (typeof obj[item] !== ‘undefined’) {

// obj 中存在当前属性,则证明当前项 之前已经是 obj属性了

// 删除当前项

arr[i] = arr[arr.length-1];

arr.length–;

i–;

}

obj[item] = item; // obj {10: 10, 16: 16, 25: 25 …}

}

obj = null; // 垃圾回收

console.log(arr); // [ 16, 23, 12, 15, 25 ]

思想三:相邻项的处理方案思想——基于正则

let arr = [12, 23, 12, 15, 25, 23, 16, 25, 16];

arr.sort((a,b) => a-b);

arrStr = arr.join(‘@’) + ‘@’;

let reg = /(\d+@)\1*/g,

newArr = [];

arrStr.replace(reg, (val, group1) => {

// newArr.push(Number(group1.slice(0, group1.length-1)));

newArr.push(parseFloat(group1));

})

console.log(newArr); // [ 12, 15, 16, 23, 25 ]

16.基于Generator函数实现async/await原理


核心: 传递给我一个Generator函数,把函数中的内容基于Iterator迭代器的特点一步步的执行

function readFile(file) {

return new Promise(resolve => {

setTimeout(() => {

resolve(file);

}, 1000);

})

};

function asyncFunc(generator) {

const iterator = generator(); // 接下来要执行next

// data为第一次执行之后的返回结果,用于传给第二次执行

const next = (data) => {

let { value, done } = iterator.next(data); // 第二次执行,并接收第一次的请求结果 data

if (done) return; // 执行完毕(到第三次)直接返回

// 第一次执行next时,yield返回的 promise实例 赋值给了 value

value.then(data => {

next(data); // 当第一次value 执行完毕且成功时,执行下一步(并把第一次的结果传递下一步)

});

}

next();

};

asyncFunc(function* () {

// 生成器函数:控制代码一步步执行

let data = yield readFile(‘a.js’); // 等这一步骤执行执行成功之后,再往下走,没执行完的时候,直接返回

data = yield readFile(data + ‘b.js’);

return data;

})

17.基于Promise封装Ajax


思路

  • 返回一个新的Promise实例

  • 创建HMLHttpRequest异步对象

  • 调用open方法,打开url,与服务器建立链接(发送前的一些处理)

  • 监听Ajax状态信息

    • xhr.status == 200,返回resolve状态
  • xhr.status == 404,返回reject状态

  • 如果xhr.readyState == 4(表示服务器响应完成,可以获取使用服务器的响应了)

  • xhr.readyState !== 4,把请求主体的信息基于send发送给服务器

function ajax(url, method) {

return new Promise((resolve, reject) => {

const xhr = new XMLHttpRequest()

xhr.open(url, method, true)

xhr.onreadystatechange = function () {

if (xhr.readyState === 4) {

if (xhr.status === 200) {

resolve(xhr.responseText)

} else if (xhr.status === 404) {

reject(new Error(‘404’))

}

} else {

reject(‘请求数据失败’)

}

}

xhr.send(null)

})

}

18.手动实现JSONP跨域


思路:

  • 创建script标签

  • 设置script标签的src属性,以问号传递参数,设置好回调函数callback名称

  • 插入到html文本中

  • 调用回调函数,res参数就是获取的数据

let script = document.createElement(‘script’);

script.src = ‘http://www.baidu.cn/login?username=JasonShu&callback=callback’;

document.body.appendChild(script);

function callback (res) {

console.log(res);

}

19.手动实现sleep


某个时间过后,就去执行某个函数,基于Promise封装异步任务。

await后面的代码都会放到微任务队列中去异步执行。

/**

*

* @param {*} fn 要执行的函数

* @param {*} wait 等待的时间

*/

function sleep(wait) {

return new Promise((resolve, reject) => {

setTimeout(() => {

resolve();

}, wait)

})

}

let sayHello = (name) => console.log(hello ${name});

async function autoRun() {

await sleep(3000);

let demo1 = sayHello(‘时光屋小豪’);

let demo2 = sayHello(‘掘友们’);

let demo3 = sayHello(‘公众号的朋友们’);

};

autoRun();

20.ES5手动实现数组reduce


特点:

  • 初始值不传时的特殊处理:会默认使用数组中的第一个元素

  • 函数的返回结果会作为下一次循环的prev

  • 回调函数一共接受四个参数

arr.reduce(prev, next, currentIndex, array))

    • prev:上一次调用回调时返回的值
  • 正在处理的元素

  • 正在处理的元素的索引

  • 正在遍历的集合对象

Array.prototype.myReduce = function(fn, prev) {

for (let i = 0; i < this.length; i++) {

if (typeof prev === ‘undefined’) {

prev = fn(this[i], this[i+1], i+1, this);

++i;

} else {

prev = fn(prev, this[i], i, this);

}

}

return prev

}

测试用例

let sum = [1, 2, 3].myReduce((prev, next) => {

return prev + next

});

console.log(sum); // 6

21.手动实现通用柯理化函数


柯理化函数含义: 是给函数分步传递参数,每次传递部分参数,并返回一个更具体的函数接收剩下的参数,这中间可嵌套多层这样的接收部分参数的函数,直至返回最后结果。

// add的参数不固定,看有几个数字累计相加

function add (a,b,c,d) {

return a+b+c+d

}

function currying (fn, …args) {

// fn.length 回调函数的参数的总和

// args.length currying函数 后面的参数总和

// 如:add (a,b,c,d)  currying(add,1,2,3,4)

if (fn.length === args.length) {

return fn(…args)

} else {

// 继续分步传递参数 newArgs 新一次传递的参数

return function anonymous(…newArgs) {

// 将先传递的参数和后传递的参数 结合在一起

let allArgs = […args, …newArgs]

return currying(fn, …allArgs)

}

}

}

let fn1 = currying(add, 1, 2) // 3

let fn2 = fn1(3)  // 6

let fn3 = fn2(4)  // 10

23.ES5实现一个继承


寄生组合继承(ES5继承的最佳方式)

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的形式来继承方法

只调用了一次父类构造函数,效率更高。避免在子类.prototype上面创建不必要的、多余的属性,与其同时,原型链还能保持不变。

function Parent(name) {

this.name = name;

this.colors = [‘red’, ‘blue’, ‘green’];

}

Parent.prototype.getName = function () {

return this.name;

}

function Child(name, age) {

Parent.call(this, name); // 调用父类的构造函数,将父类构造函数内的this指向子类的实例

this.age = age;

}

//寄生组合式继承

Child.prototype = Object.create(Parent.prototype);

Child.prototype.constructor = Child;

Child.prototype.getAge = function () {

return this.age;

}

let girl = new Child(‘Lisa’, 18);

girl.getName();

24.手动实现发布订阅


发布订阅的核心:: 每次event. emit(发布),就会触发一次event. on(注册)

class EventEmitter {

constructor() {

// 事件对象,存放订阅的名字和事件

this.events = {};

}

// 订阅事件的方法

on(eventName,callback) {

if (!this.events[eventName]) {

// 注意数据,一个名字可以订阅多个事件函数

this.events[eventName] = [callback];

} else  {

// 存在则push到指定数组的尾部保存

this.events[eventName].push(callback)

}

}

// 触发事件的方法

emit(eventName) {

// 遍历执行所有订阅的事件

this.events[eventName] && this.events[eventName].forEach(cb => cb());

}

}

测试用例

let em = new EventEmitter();

function workDay() {

console.log(“每天工作”);

}

function makeMoney() {

console.log(“赚100万”);

}

function sayLove() {

console.log(“向喜欢的人示爱”);

}

em.on(“money”,makeMoney);

em.on(“love”,sayLove);

em.on(“work”, workDay);

em.emit(“money”);

em.emit(“love”);

em.emit(“work”);

26.手动实现观察者模式


观察者模式(基于发布订阅模式) 有观察者,也有被观察者

观察者需要放到被观察者中被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式,收集观察者,状态变化后要主动通知观察者

class Subject { // 被观察者 学生

constructor(name) {

this.state = ‘开心的’

this.observers = []; // 存储所有的观察者

}

// 收集所有的观察者

attach(o){ // Subject. prototype. attch

this.observers.push(o)

}

// 更新被观察者 状态的方法

setState(newState) {

this.state = newState; // 更新状态

// this 指被观察者 学生

this.observers.forEach(o => o.update(this)) // 通知观察者 更新它们的状态

}

}

class Observer{ // 观察者 父母和老师

constructor(name) {

this.name = name

}

update(student) {

console.log(‘当前’ + this.name + ‘被通知了’, ‘当前学生的状态是’ + student.state)

}

}

let student = new Subject(‘学生’);

let parent = new Observer(‘父母’);

let teacher = new Observer(‘老师’);

// 被观察者存储观察者的前提,需要先接纳观察者

student. attach(parent);

student. attach(teacher);

student. setState(‘被欺负了’);

27.手动实现Object.freeze


Object.freeze冻结一个对象,让其不能再添加/删除属性,也不能修改该对象已有属性的可枚举性、可配置可写性,也不能修改已有属性的值和它的原型属性,最后返回一个和传入参数相同的对象。

function myFreeze(obj){

// 判断参数是否为Object类型,如果是就封闭对象,循环遍历对象。去掉原型属性,将其writable特性设置为false

if(obj instanceof Object){

Object.seal(obj);  // 封闭对象

for(let key in obj){

if(obj.hasOwnProperty(key)){

Object.defineProperty(obj,key,{

writable:false   // 设置只读

})

// 如果属性值依然为对象,要通过递归来进行进一步的冻结

myFreeze(obj[key]);

}

}

}

}

28.手动实现Promise.all


Promise.all:有一个promise任务失败就全部失败

Promise.all方法返回的是一个promise

function isPromise (val) {

return typeof val.then === ‘function’; // (123).then => undefined

}

Promise.all = function(promises) {

return new Promise((resolve, reject) => {

let arr = []; // 存放 promise执行后的结果

let index = 0; // 计数器,用来累计promise的已执行次数

const processData = (key, data) => {

arr[key] = data; // 不能使用数组的长度来计算

/*

if (arr.length == promises.length) {

resolve(arr);  // [null, null , 1, 2] 由于Promise异步比较慢,所以还未返回

}

*/

if (++index === promises.length) {

// 必须保证数组里的每一个

resolve(arr);

}

}

// 遍历数组依次拿到执行结果

for (let i = 0; i < promises.length; i++) {

let result = promises[i];

if(isPromise(result)) {

// 让里面的promise执行,取得成功后的结果

// data promise执行后的返回结果

result.then((data) => {

// 处理数据,按照原数组的顺序依次输出

processData(i ,data)

}, reject)  // reject本事就是个函数 所以简写了

} else {

// 1 , 2

processData(i ,result)

}

}

})

}

测试用例

let fs = require(‘fs’).promises;

let getName = fs.readFile(‘./name.txt’, ‘utf8’);

let getAge = fs.readFile(‘./age.txt’, ‘utf8’);

Promise.all([1, getName, getAge, 2]).then(data => {

console.log(data); // [ 1, ‘name’, ‘11’, 2 ]

})

29.手动实现Promise.allSettled


MDN: Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

【译】Promise.allSettledPromise.all类似, 其参数接受一个Promise的数组, 返回一个新的Promise, 唯一的不同在于, 其不会进行短路, 也就是说当Promise全部处理完成后我们可以拿到每个Promise的状态, 而不管其是否处理成功。

用法 | 测试用例

let fs = require(‘fs’).promises;

let getName = fs.readFile(‘./name.txt’, ‘utf8’); // 读取文件成功

let getAge = fs.readFile(‘./age.txt’, ‘utf8’);

Promise.allSettled([1, getName, getAge, 2]).then(data => {

console.log(data);

});

// 输出结果

/*

[

{ status: ‘fulfilled’, value: 1 },

{ status: ‘fulfilled’, value: ‘zf’ },

{ status: ‘fulfilled’, value: ‘11’ },

{ status: ‘fulfilled’, value: 2 }

]

*/

let getName = fs.readFile(‘./name123.txt’, ‘utf8’); // 读取文件失败

let getAge = fs.readFile(‘./age.txt’, ‘utf8’);

// 输出结果

/*

[

{ status: ‘fulfilled’, value: 1 },

{

status: ‘rejected’,

value: [Error: ENOENT: no such file or directory, open ‘./name123.txt’] {

errno: -2,

code: ‘ENOENT’,

syscall: ‘open’,

path: ‘./name123.txt’

}

},

{ status: ‘fulfilled’, value: ‘11’ },

{ status: ‘fulfilled’, value: 2 }

]

*/

实现

function isPromise (val) {

return typeof val.then === ‘function’; // (123).then => undefined

}

Promise.allSettled = function(promises) {

return new Promise((resolve, reject) => {

let arr = [];

let times = 0;

const setData = (index, data) => {

arr[index] = data;

if (++times === promises.length) {

resolve(arr);

}

console.log(‘times’, times)

}

for (let i = 0; i < promises.length; i++) {

let current = promises[i];

if (isPromise(current)) {

current.then((data) => {

setData(i, { status: ‘fulfilled’, value: data });

}, err => {

setData(i, { status: ‘rejected’, value: err })

})

} else {

setData(i, { status: ‘fulfilled’, value: current })

}

}

})

}

30.手动实现Promise.prototype.finally


前面的promise不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then(说明它还是一个then方法是关键),并且会将初始的promise值原封不动的传递给后面的then.

Promise.prototype.finally最大的作用
  1. finally里的函数,无论如何都会执行,并会把前面的值原封不动传递给下一个then方法中

(相当于起了一个中间过渡的作用)——对应情况1,2,3

  1. 如果finally函数中有promise等异步任务,会等它们全部执行完毕,再结合之前的成功与否状态,返回值

Promise.prototype.finally六大情况用法

// 情况1

Promise.resolve(123).finally((data) => { // 这里传入的函数,无论如何都会执行

console.log(data); // undefined

})

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

最后

中年危机是真实存在的,即便有技术傍身,还是难免对自己的生存能力产生质疑和焦虑,这些年职业发展,一直在寻求消除焦虑的依靠。

  • 技术要深入到什么程度?

  • 做久了技术总要转型管理?

  • 我能做什么,我想做什么?

  • 一技之长,就是深耕你的专业技能,你的专业技术。(重点)

  • 独立做事,当你的一技之长达到一定深度的时候,需要开始思考如何独立做事。(创业)

  • 拥有事业,选择一份使命,带领团队实现它。(创业)

一技之长分五个层次

  • 栈内技术 - 是指你的前端专业领域技术

  • 栈外技术 - 是指栈内技术的上下游,领域外的相关专业知识

  • 工程经验 - 是建设专业技术体系的“解决方案”

  • 带人做事 - 是对团队协作能力的要求

  • 业界发声 - 工作经验总结对外分享,与他人交流

永远不要放弃一技之长,它值得你长期信仰持有

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

主要内容包括html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue 等等。


23).finally((data) => { // 这里传入的函数,无论如何都会执行

console.log(data); // undefined

})

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-1hkZgE1c-1710585435911)]
[外链图片转存中…(img-xVMyWKDb-1710585435912)]
[外链图片转存中…(img-uGtpKb6d-1710585435912)]
[外链图片转存中…(img-PGQJhwkW-1710585435913)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
[外链图片转存中…(img-abM4uS6i-1710585435913)]

最后

中年危机是真实存在的,即便有技术傍身,还是难免对自己的生存能力产生质疑和焦虑,这些年职业发展,一直在寻求消除焦虑的依靠。

  • 技术要深入到什么程度?

  • 做久了技术总要转型管理?

  • 我能做什么,我想做什么?

  • 一技之长,就是深耕你的专业技能,你的专业技术。(重点)

  • 独立做事,当你的一技之长达到一定深度的时候,需要开始思考如何独立做事。(创业)

  • 拥有事业,选择一份使命,带领团队实现它。(创业)

一技之长分五个层次

  • 栈内技术 - 是指你的前端专业领域技术

  • 栈外技术 - 是指栈内技术的上下游,领域外的相关专业知识

  • 工程经验 - 是建设专业技术体系的“解决方案”

  • 带人做事 - 是对团队协作能力的要求

  • 业界发声 - 工作经验总结对外分享,与他人交流

永远不要放弃一技之长,它值得你长期信仰持有

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

主要内容包括html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue 等等。

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值