30道高频JS手撕面试题(2),2024年最新如何在线面试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

const isObject = (target) => (typeof target === ‘object’ || typeof target === ‘function’) && target !== null;

function deepClone (target, map = new Map()) {

// 先判断该引用类型是否被 拷贝过

if (map.get(target)) {

return target;

}

// 获取当前值的构造函数:获取它的类型

let constructor = target.constructor;

// 检测当前对象target是否与 正则、日期格式对象匹配

if (/^(RegExp|Date)$/i.test(constructor.name)){

return new constructor(target); // 创建一个新的特殊对象(正则类/日期类)的实例

}

if (isObject(target)) {

map.set(target, true); // 为循环引用的对象做标记

const cloneTarget = Array.isArray(target) ? [] : {};

for (let prop in target) {

if (target.hasOwnProperty(prop)) {

cloneTarget[prop] = deepClone(target[prop], map);

}

}

return cloneTarget;

} else {

return target;

}

}

4.手动实现instanceOf的机制


思路:

步骤1: 先取得当前类的原型,当前实例对象的原型链

步骤2: 一直循环(执行原型链的查找机制)

  • 取得当前实例对象原型链的原型链(proto = proto.__proto__,沿着原型链一直向上查找)

  • 如果 当前实例的原型链__proto__上找到了当前类的原型prototype,则返回true

  • 如果 一直找到Object.prototype.__proto__ == null,Object的基类(null)上面都没找到,则返回 false

function _instanceof (instanceObject, classFunc) {

let classFunc = classFunc.prototype; // 取得当前类的原型

let proto = instanceObject.proto; // 取得当前实例对象的原型链

while (true) {

if (proto === null) { // 找到了 Object的基类 Object.prototype.proto

return false;

};

if (proto === classFunc) { // 在当前实例对象的原型链上,找到了当前类

return true;

}

proto = proto.proto; // 沿着原型链__ptoto__一层一层向上查找

}

}

优化版 (处理兼容问题)

Object.getPrototypeOf:用来获取某个实例对象的原型(内部[[prototype]]属性的值,包含proto属性)

function _instanceof (instanceObject, classFunc) {

let classFunc = classFunc.prototype; // 取得当前类的原型

let proto = Object.getPrototypeOf(instanceObject); // 取得当前实例对象的原型链上的属性

while (true) {

if (proto === null) { // 找到了 Object的基类 Object.prototype.proto

return false;

};

if (proto === classFunc) { // 在当前实例对象的原型链上,找到了当前类

return true;

}

proto = Object.getPrototypeOf(proto); // 沿着原型链__ptoto__一层一层向上查找

}

}

5. 手动实现防抖函数


实现函数的防抖(目的是频繁触发中只执行一次)

以最后一次触发为标准

/**

* 实现函数的防抖(目的是频繁触发中只执行一次)

* @param {*} func 需要执行的函数

* @param {*} wait 检测防抖的间隔频率

* @param {*} immediate 是否是立即执行  True:第一次,默认False:最后一次

* @return {可被调用执行的函数}

*/

function debounce(func, wati = 500, immediate = false) {

let timer = null

return function anonymous(… params) {

clearTimeout(timer)

timer = setTimeout(_ => {

// 在下一个500ms 执行func之前,将timer = null

//(因为clearInterval只能清除定时器,但timer还有值)

// 为了确保后续每一次执行都和最初结果一样,赋值为null

// 也可以通过 timer 是否 为 null 是否有定时器

timer = null

func.call(this, …params)

}, wait)

}

}

以第一次触发为标准

/**

* 实现函数的防抖(目的是频繁触发中只执行一次)

* @param {*} func 需要执行的函数

* @param {*} wait 检测防抖的间隔频率

* @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次

* @return {可被调用执行的函数}

*/

function debounce(func, wait = 500, immediate = true) {

let timer = null

return function anonymous(… params) {

// 第一点击 没有设置过任何定时器 timer就要为 null

let now = immediate && !timer

clearTimeout(timer)

timer = setTimeout(_ => {

// 在下一个500ms 执行func之前,将timer = null

//(因为clearInterval只能在系统内清除定时器,但timer还有值)

// 为了确保后续每一次执行都和最初结果一样,赋值为null

// 也可以通过 timer 是否 为 null 是否有定时器

timer = null!immediate ? func.call(this, …params) : null

}, wait)

now ? func.call(this, …params) : null

}

}

function func() {

console. log(‘ok’)

}

btn. onclick = debounce(func, 500)

6.手动实现节流函数


实现函数的节流 (目的是频繁触发中缩减频率)

带注释说明版

【第一次触发:reamining是负数,previous被赋值为当前时间】

【第二次触发:假设时间间隔是500ms,第一次执行完之后,20ms之后,立即触发第二次,则remaining = 500 - ( 新的当前时间 - 上一次触发时间 ) = 500 - 20 = 480

/**

* 实现函数的节流 (目的是频繁触发中缩减频率)

* @param {*} func 需要执行的函数

* @param {*} wait 检测节流的间隔频率

* @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次

* @return {可被调用执行的函数}

*/

function throttle(func, wait) {

let timer = null

let previous = 0  // 记录上一次操作的时间点

return function anonymous(… params) {

let now = new Date()  // 当前操作的时间点

remaining = wait - (now - previous) // 剩下的时间

if (remaining <= 0) {

// 两次间隔时间超过频率,把方法执行

clearTimeout(timer); // clearTimeout是从系统中清除定时器,但timer值不会变为null

timer = null; // 后续可以通过判断 timer是否为null,而判断是否有 定时器

// 此时已经执行func 函数,应该将上次触发函数的时间点 = 现在触发的时间点 new Date()

previous = new Date(); // 把上一次操作时间修改为当前时间

func.call(this, …params);

} else if(!timer){

// 两次间隔的事件没有超过频率,说明还没有达到触发标准,设置定时器等待即可(还差多久等多久)

// 假设事件间隔为500ms,第一次执行完之后,20ms后再次点击执行,则剩余 480ms,就能等待480ms

timer = setTimeout( _ => {

clearTimeout(timer)

timer = null // 确保每次执行完的时候,timer 都清 0,回到初始状态

//过了remaining时间后,才去执行func,所以previous不能等于初始时的 now

previous = new Date(); // 把上一次操作时间修改为当前时间

func.call(this, …params);

}, remaining)

}

}

}

function func() {

console. log(‘ok’)

}

btn. onclick = throttle(func, 500)

不带注释版

/**

* 实现函数的节流 (目的是频繁触发中缩减频率)

* @param {*} func 需要执行的函数

* @param {*} wait 检测节流的间隔频率

* @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次

* @return {可被调用执行的函数}

*/

function throttle(func, wait) {

let timer = null;

let previous = 0;

return function anonymous(… params) {

let now = new Date();

remaining = wait - (now - previous);

if (remaining <= 0) {

clearTimeout(timer);

timer = null;

previous = new Date();

func.call(this, …params);

} else if(!timer){

timer = setTimeout( _ => {

clearTimeout(timer);

timer = null;

previous = new Date();

func.call(this, …params);

}, remaining)

}

}

}

function func() {

console. log(‘ok’)

}

btn. onclick = throttle(func, 500);

7.手动实现Object.create


Object.create() = function create(prototype) {

// 排除传入的对象是 null 和 非object的情况

if (prototype === null || typeof prototype !== ‘object’) {

throw new TypeError(Object prototype may only be an Object: ${prototype});

}

// 让空对象的 __proto__指向 传进来的 对象(prototype)

// 目标 {}.proto = prototype

function Temp() {};

Temp.prototype = prototype;

return new Temp;

}

8.手动实现内置new的原理


简化版

  • 步骤1: 创建一个Func的实例对象(实例.proto = 类.prototype)

  • 步骤2:Func 当做普通函数执行,并改变this指向

  • 步骤3: 分析函数的返回值

/**

* Func: 要操作的类(最后要创建这个类的实例)

* args:存储未来传递给Func类的实参

*/

function _new(Func, …args) {

// 创建一个Func的实例对象(实例.proto = 类.prototype)

let obj = {};

obj.proto = Func.prototype;

// 把Func当做普通函数执行,并改变this指向

let result = Func.call(obj, …args);

// 分析函数的返回值

if (result !== null && /^(object|function)$/.test(typeof result)) {

return result;

}

return obj;

}

优化版

__proto__在IE浏览器中不支持

let x = { name: “lsh” };

Object.create(x);

{}.proto = x;

function _new(Func, …args) {

// let obj = {};

// obj.proto = Func.prototype;

// 创建一个Func的实例对象(实例.proto = 类.prototype)

let obj = Object.create(Func.prototype);

// 把Func当做普通函数执行,并改变this指向

let result = Func.call(obj, …args);

// 分析函数的返回值

if (result !== null && /^(object|function)$/.test(typeof result)) {

return result;

}

return obj;

}

9.手动实现call方法


简易版(不考虑context非对象情况,不考虑Symbol\BigInt 不能 new.constructor( context )情况)

/**

* context: 要改变的函数中的this指向,写谁就是谁

* args:传递给函数的实参信息

* this:要处理的函数 fn

*/

Function.prototype.call = function(context, …args) {

//  null,undefined,和不传时,context为 window

context = context == null ? window : context;

let result;

context[‘fn’] = this; // 把函数作为对象的某个成员值

result = context’fn’; // 把函数执行,此时函数中的this就是

delete context[‘fn’]; // 设置完成员属性后,删除

return result;

}

完善版(context必须对象类型,兼容Symbol等情况)

/**

* context: 要改变的函数中的this指向,写谁就是谁

* args:传递给函数的实参信息

* this:要处理的函数 fn

*/

Function.prototype.call = function(context, …args) {

//  null,undefined,和不传时,context为 window

context = context == null ? window : context;

// 必须保证 context 是一个对象类型

let contextType = typeof context;

if (!/^(object|function)$/i.test(contextType)) {

// context = new context.constructor(context); // 不适用于 Symbol/BigInt

context = Object(context);

}

let result;

context[‘fn’] = this; // 把函数作为对象的某个成员值

result = context’fn’; // 把函数执行,此时函数中的this就是

delete context[‘fn’]; // 设置完成员属性后,删除

return result;

}

10.手动实现apply方法


/**

* context: 要改变的函数中的this指向,写谁就是谁

* args:传递给函数的实参信息

* this:要处理的函数 fn

*/

Function.prototype.apply = function(context, args) {

context = context == null ? window : context;

let contextType = typeof context;

if (!/^(object|function)$/i.test(contextType)) {

context = Object(context);

}

let result;

context[‘fn’] = this;

result = context’fn’;

delete context[‘fn’];

return result;

}

11.手动实现bind方法


/**

* this: 要处理的函数 func

* context: 要改变的函数中的this指向 obj

* params:要处理的函数传递的实参 [10, 20]

*/

Function.prototype._bind = function(context, …params) {

let _this = this; // this: 要处理的函数

return function anonymous (…args) {

// args: 可能传递的事件对象等信息 [MouseEvent]

// this:匿名函数中的this是由当初绑定的位置 触发决定的 (总之不是要处理的函数func)

// 所以需要_bind函数 刚进来时,保存要处理的函数 _this = this

_this.call(context, …params.concat(args));

}

}

12.ES5实现数组扁平化flat方法


思路:

  • 循环数组里的每一个元素

  • 判断该元素是否为数组

    • 是数组的话,继续循环遍历这个元素——数组
  • 不是数组的话,把元素添加到新的数组中

let arr = [

[1, 2, 2],

[3, 4, 5, 5],

[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10

];

function myFlat() {

_this = this; // 保存 this:arr

let newArr = [];

// 循环arr中的每一项,把不是数组的元素存储到 newArr中

let cycleArray = (arr) => {

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

let item = arr[i];

if (Array.isArray(item)) { // 元素是数组的话,继续循环遍历该数组

cycleArray(item);

continue;

} else{

newArr.push(item); // 不是数组的话,直接添加到新数组中

}

}

}

cycleArray(_this); // 循环数组里的每个元素

return newArr; // 返回新的数组对象

}

Array.prototype.myFlat = myFlat;

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

13.ES6实现数组扁平化flat方法


const myFlat = (arr) => {

let newArr = [];

let cycleArray = (arr) => {

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

let item = arr[i];

if (Array.isArray(item)) {

cycleArray(item);

continue;

} else {

newArr.push(item);

}

}

}

cycleArray(arr);

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

}

最后

推荐一些系统学习的途径和方法。

路线图

每个Web开发人员必备,很权威很齐全的Web开发文档。作为学习辞典使用,可以查询到每个概念、方法、属性的详细解释,注意使用英文关键字搜索。里面的一些 HTML,CSS,HTTP 技术教程也相当不错。

HTML 和 CSS:

html5知识

css基础知识

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
out(() => {

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

}

最后

推荐一些系统学习的途径和方法。

路线图

每个Web开发人员必备,很权威很齐全的Web开发文档。作为学习辞典使用,可以查询到每个概念、方法、属性的详细解释,注意使用英文关键字搜索。里面的一些 HTML,CSS,HTTP 技术教程也相当不错。

HTML 和 CSS:

html5知识

css基础知识

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-0j4S4z0L-1713025325642)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值