万网网站模板购买,网站建设方案书一定要交,山大优秀网站建设2018年度,wordpress无法跳转正确的404文章目录 一、前言二、题目1. 防抖节流解读 2.一个正则题3. 不使用a标签#xff0c;如何实现a标签的功能4. 不使用循环API 来删除数组中指定位置的元素#xff08;如#xff1a;删除第三位#xff09; 写越多越好5. 深拷贝解读 6. 手写call bind applycall 解读apply 解读 … 文章目录 一、前言二、题目1. 防抖节流解读 2.一个正则题3. 不使用a标签如何实现a标签的功能4. 不使用循环API 来删除数组中指定位置的元素如删除第三位 写越多越好5. 深拷贝解读 6. 手写call bind applycall 解读apply 解读 7. 手写实现继承8. 手写 new 操作符9. js执行机制 说出结果并说出why10. 如何拦截全局Promise reject但并没有设定 reject处理器 时候的错误11. 手写实现sleep12. 实现add(1)(2) 313. 两个数组中完全独立的数据14. 判断完全平方数15. 函数执行 说出结果并说出why16. 原型调用面试题 说出结果并说出 why17. 数组分组改成减法运算18. 手写数组的 flat19. 数组转为tree20. 合并数组并排序去重21、Function.prototype使用 三、结语 一、前言
话说跳槽好时节金三银四金九银十。 我在上周也成功跳槽并入职了新公司跳槽的路途坎坷遇到的面试也有奇奇怪怪的。
在上一篇文章中有答应大家整理出部分手写面试题给大家。这不 现在就来了!
本文的所有题目大部分是我自己去面试的时候遇到的。如有错误或者更好的答案欢迎大家评论区留言~
二、题目
1. 防抖节流
这也是一个经典题目了首先要知道什么是防抖什么是节流。 防抖 在一段时间内事件只会最后触发一次。 节流 事件按照一段时间的间隔来进行触发。 实在不懂的话可以去这个大佬的Demo地址玩玩防抖节流DEMO
// 防抖
function debounce(fn) {let timeout null; return function () {// 如果事件再次触发就清除定时器重新计时clearTimeout(timeout);timeout setTimeout(() {fn.apply(this, arguments);}, 500);};
}// 节流
function throttle(fn) {let flag null; // 通过闭包保存一个标记return function () {if (flag) return; // 当定时器没有执行的时候标记永远是nullflag setTimeout(() {fn.apply(this, arguments);// 最后在setTimeout执行完毕后再把标记设置为null(关键)// 表示可以执行下一次循环了。flag null;}, 500);};
} 这道题主要还是考查对 防抖 节流 的理解吧千万别记反了
解读
这段代码包含了两个函数debounce和throttle它们都是用来控制另一个函数的执行频率的。
debounce函数 debounce函数的作用是防止一个函数在短时间内被频繁调用。它通过设置一个定时器来实现如果在设定的时间间隔内再次触发事件则清除之前的定时器并重新设置一个新的定时器。只有在最后一次触发事件后的一段时间内没有新的触发时才会执行原函数。
function debounce(fn) {let timeout null; // 定义一个变量来保存定时器的引用return function () {// 如果事件再次触发就清除定时器重新计时clearTimeout(timeout);timeout setTimeout(() {fn.apply(this, arguments); // 在延迟后执行原函数并保持this指向和参数不变}, 500); // 延迟时间设置为500毫秒};
}throttle函数 throttle函数的作用是确保一个函数在单位时间内只被调用一次即使在这段时间内多次触发事件。它通过设置一个标记来实现如果标记为真非null则不执行函数如果标记为假null则执行函数并设置标记为真然后在一段时间后将标记重置为假。
function throttle(fn) {let flag null; // 通过闭包保存一个标记return function () {if (flag) return; // 当定时器没有执行的时候标记永远是null此时允许执行flag setTimeout(() {fn.apply(this, arguments); // 执行原函数并保持this指向和参数不变flag null; // 最后在setTimeout执行完毕后再把标记设置为null表示可以执行下一次循环了}, 500); // 延迟时间设置为500毫秒};
}这两个函数通常用于优化性能例如在处理DOM事件监听器、窗口大小调整或用户输入等场景中避免因为频繁的事件触发导致的性能问题。
2.一个正则题
要求写出 区号8位数字或者区号特殊号码: 10010/110中间用短横线隔开的正则验证。 区号就是三位数字开头。
例如 010-12345678 let reg /^\d{3}-(\d{8}|10010|110)/g这个比较简单熟悉正则的基本用法就可以做出来了。
3. 不使用a标签如何实现a标签的功能 // 通过 window.open 和 location.href 方法其实就可以实现。 // 分别对应了a标签的 blank 和 self 属性4. 不使用循环API 来删除数组中指定位置的元素如删除第三位 写越多越好
这个题的意思就是不能循环的API如 for filter之类的。 var arr [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]// 方法一 splice 操作数组 会改变原数组
arr.splice(2, 1)// 方法二 slice 截取选中元素 返回新数组 不改变原数组
arr.slice(0, 2).concat(arr.slice(3,))// 方法三 delete数组中的元素 再把这个元素给剔除掉
delete arr[2]
arr.join( ).replaceAll(/\s{1,2}/g, ).split( )5. 深拷贝
深拷贝和浅拷贝的区别就在于
浅拷贝 对于复杂数据类型浅拷贝只是把引用地址赋值给了新的对象改变这个新对象的值原对象的值也会一起改变。深拷贝 对于复杂数据类型拷贝后地址引用都是新的改变拷贝后新对象的值不会影响原对象的值。
所以关键点就在于对复杂数据类型的处理这里我写了两种写法第二中比第一种有部分性能提升
const isObj (val) typeof val object val ! null;// 写法1
function deepClone(obj) {// 通过 instanceof 去判断你要拷贝的变量它是否是数组如果不是数组则对象。// 1. 准备你想返回的变量新地址。const newObj obj instanceof Array ? [] : {}; // 核心代码。// 2. 做拷贝简单数据类型只需要赋值如果遇到复杂数据类型就再次进入进行深拷贝直到所找到的数据为简单数据类型为止。for (const key in obj) {const item obj[key];newObj[key] isObj(item) ? deepClone(item) : item;}// 3. 返回拷贝的变量。return newObj;
}// 写法2 利用es6新特性 WeakMap弱引用 性能更好 并且支持 Symbol
function deepClone2(obj, wMap new WeakMap()) {if (isObj(obj)) {// 判断是对象还是数组let target Array.isArray(obj) ? [] : {};// 如果存在这个就直接返回if (wMap.has(obj)) {return wMap.get(obj);}wMap.set(obj, target);// 遍历对象Reflect.ownKeys(obj).forEach((item) {// 拿到数据后判断是复杂数据还是简单数据 如果是复杂数据类型就继续递归调用target[item] isObj(obj[item]) ? deepClone2(obj[item], wMap) : obj[item];});return target;} else {return obj;}
}这道题主要是的方案就是递归加数据类型的判断。
如是复杂数据类型就递归的再次调用你这个拷贝方法 直到是简单数据类型后可以进行直接赋值
解读
这段代码包含了两个函数deepClone 和 deepClone2它们都用于深度克隆一个对象或数组即创建一个新的对象或数组其内容是原对象或数组的深拷贝。 isObj 函数这是一个箭头函数用于检查传入的值 val 是否是一个非空对象即类型为 “object” 且不为 null。这个函数在 deepClone 和 deepClone2 中被用来检测是否需要进行深拷贝。 deepClone 函数这是第一个实现深度克隆的方法。它首先判断传入的对象 obj 是否是数组如果是数组则创建一个空数组 newObj否则创建一个空对象 newObj。然后它遍历 obj 的所有属性对于每个属性值如果它是一个对象或数组递归调用 deepClone 函数进行深拷贝如果不是对象或数组直接将值赋给 newObj 的相应属性。最后返回 newObj。 deepClone2 函数这是第二个实现深度克隆的方法它使用了 WeakMap 来优化性能并支持 Symbol 类型的键。WeakMap 允许垃圾回收器回收其键所对应的对象因为它不会阻止这些对象被垃圾回收。函数首先检查传入的对象 obj 是否是一个对象如果不是直接返回 obj。如果是对象它会检查 WeakMap 中是否已经存在该对象的克隆如果存在直接返回克隆对象。如果不存在它会创建一个新的空对象或数组 target并将其与原始对象关联存储在 WeakMap 中。然后使用 Reflect.ownKeys(obj) 获取对象的所有自有属性包括符号和字符串键并对每个属性进行遍历。对于每个属性值如果它是一个对象或数组递归调用 deepClone2 函数进行深拷贝如果不是对象或数组直接将值赋给 target 的相应属性。最后返回 target。
总结来说这两个函数都是用于深度克隆对象的但 deepClone2 使用了 WeakMap 来提高性能并处理更广泛的键类型。
6. 手写call bind apply
call bind apply的作用都是可以进行修改this指向
call 和 apply的区别在于参数传递的不同bind 区别在于最后会返回一个函数。
// callFunction.prototype.MyCall function (context) {if (typeof this ! function) {throw new Error(type error)}if (context null || context undefined) {// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)context window} else {// 值为原始值数字字符串布尔值的 this 会指向该原始值的实例对象context Object(context)}// 使用Symbol 来确定唯一const fnSym Symbol()//模拟对象的this指向context[fnSym] this// 获取参数const args [...arguments].slice(1)//绑定参数 并执行函数const result context[fnSym](...args) //清除定义的thisdelete context[fnSym]// 返回结果 return result} // call 如果能明白的话 apply其实就是改一下参数的问题// applyFunction.prototype.MyApply function (context) {if (typeof this ! function) {throw new Error(type error)}if (context null || context undefined) {// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)context window} else {// 值为原始值数字字符串布尔值的 this 会指向该原始值的实例对象context Object(context) }// 使用Symbol 来确定唯一const fnSym Symbol()//模拟对象的this指向context[fnSym] this// 获取参数const args [...arguments][1]//绑定参数 并执行函数 由于apply 传入的是一个数组 所以需要解构const result arguments.length 1 ? context[fnSym](...args) : context[fnSym]()//清除定义的thisdelete context[fnSym]// 返回结果 //清除定义的thisreturn result}// bindFunction.prototype.MyBind function (context) {if (typeof this ! function) {throw new Error(type error)}if (context null || context undefined) {// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)context window} else {// 值为原始值数字字符串布尔值的 this 会指向该原始值的实例对象context Object(context) }//模拟对象的this指向const self this// 获取参数const args [...arguments].slice(1)// 最后返回一个函数 并绑定 this 要考虑到使用new去调用并且bind是可以传参的return function Fn(...newFnArgs) {if (this instanceof Fn) {return new self(...args, ...newFnArgs)}return self.apply(context, [...args, ...newFnArgs])}}call 解读
这段代码定义了一个名为 MyCall 的方法它被添加到了 JavaScript 的 Function.prototype 上。这意味着所有的函数都可以使用这个方法来模拟调用函数的行为即使这个函数不是作为对象的方法被调用。下面是对代码块中每一部分的解释 Function.prototype.MyCall function (context) { ... }: 这行代码向 Function 原型添加了一个名为 MyCall 的方法。这个方法接受一个参数 context它将用作函数调用时的 this 值。 if (typeof this ! function) { throw new Error(type error) }: 这行代码检查当前的 this即调用 MyCall 方法的函数是否是一个函数。如果不是抛出一个类型错误。 if (context null || context undefined) { context window }: 如果传入的 context 是 null 或 undefined则将 context 设置为全局对象在浏览器环境中通常是 window。 else { context Object(context) }: 如果 context 不是 null 或 undefined则将其转换为一个对象。这样做是为了确保 context 可以作为一个对象来使用特别是当 context 是一个原始值如数字、字符串或布尔值时。 const fnSym Symbol(): 创建一个唯一的符号 fnSym用作将要绑定到 context 上的函数的键。 context[fnSym] this: 将当前函数this赋值给 context 对象的 fnSym 属性。这样context 对象就有了一个与当前函数相同的属性可以用来模拟函数调用的上下文。 const args [...arguments].slice(1): 使用扩展运算符 ... 和 arguments 对象获取除了第一个参数即 context之外的所有参数并将它们存储在 args 数组中。 const result context[fnSym](...args): 通过 context[fnSym] 调用函数并传入之前收集的参数 args。这将模拟函数调用并执行该函数。 delete context[fnSym]: 函数调用完成后从 context 对象中删除之前添加的 fnSym 属性以保持 context 对象的整洁。 return result: 返回函数调用的结果。
总的来说这段代码实现了一个类似于 Function.prototype.call 的功能允许你在任何对象上调用任何函数就像它是那个对象的方法一样。这在需要改变函数执行上下文的场景中非常有用例如事件处理程序或者构造函数中。
apply 解读 这段代码定义了一个名为 MyApply 的方法它被添加到了 Function.prototype 上这意味着所有的函数都可以使用这个方法。MyApply 方法模拟了原生 JavaScript 中的 apply 方法的行为允许你指定一个上下文对象context并在这个上下文中调用当前函数。 以下是对代码块的逐行解释 Function.prototype.MyApply function (context) {: 这行代码开始定义一个新的方法 MyApply并将其添加到 Function.prototype 上使其成为所有函数的一个属性。 if (typeof this ! function) {: 这行代码检查当前的 this 是否是一个函数。如果不是抛出一个类型错误异常。 throw new Error(type error): 如果 this 不是一个函数则抛出一个错误。 if (context null || context undefined) {: 这行代码检查传入的 context 参数是否为 null 或 undefined。 context window: 如果 context 是 null 或 undefined则将 context 设置为全局对象在浏览器环境中通常是 window。 } else {: 如果 context 不是 null 或 undefined则执行下面的代码。 context Object(context): 将 context 转换为一个对象。如果 context 是一个原始值如数字、字符串或布尔值则将其转换为对应的包装对象如 Number、String 或 Boolean 的实例。 const fnSym Symbol(): 创建一个唯一的符号用作将要绑定到 context 的属性名。 context[fnSym] this: 将当前函数this赋值给 context 对象的一个新属性属性名为 fnSym。 const args [...arguments][1]: 获取传递给 MyApply 的第二个参数及之后的所有参数这些参数将被用作调用函数时的参数。 const result arguments.length 1 ? context[fnSym](...args) : context[fnSym](): 根据是否有额外的参数来决定如何调用函数。如果有额外的参数则使用扩展运算符 ...args 将这些参数传递给函数如果没有额外的参数则直接调用函数。 delete context[fnSym]: 在函数调用完成后从 context 对象中删除之前添加的属性。 return result: 返回函数调用的结果。
总结来说MyApply 方法允许你将一个函数作为某个对象的方法来调用并且可以传递参数数组。这与原生的 Function.prototype.apply 方法类似但有一些差异例如它不直接接受参数数组而是通过解构剩余参数...arguments来获取参数数组。此外它还处理了 context 为 null 或 undefined 的情况在这种情况下它会默认使用全局对象作为上下文。
7. 手写实现继承
这里我就只实现两种方法了ES6之前的寄生组合式继承 和 ES6之后的class继承方式。 /*** es6之前 寄生组合继承 */{function Parent(name) {this.name namethis.arr [1, 2, 3]}Parent.prototype.say () {console.log(Hi);}function Child(name, age) {Parent.call(this, name)this.age age}// 核心代码 通过Object.create创建新对象 子类 和 父类就会隔离// Object.create创建一个新对象使用现有的对象来提供新创建的对象的__proto__ Child.prototype Object.create(Parent.prototype)Child.prototype.constructor Child}/*** es6继承 使用关键字class*/{class Parent {constructor(name) {this.name namethis.arr [1, 2, 3]}}class Child extends Parent {constructor(name, age) {super(name)this.age age}}}补充一个小知识 ES6的Class继承在通过 Babel 进行转换成ES5代码的时候 使用的就是 寄生组合式继承。
继承的方法有很多记住上面这两种基本就可以了
8. 手写 new 操作符
首先我们要知道 new一个对象的时候他发生了什么。
其实就是在内部生成了一个对象然后把你的属性这些附加到这个对象上最后再返回这个对象。
function myNew(fn, ...args) {// 基于原型链 创建一个新对象let newObj Object.create(fn.prototype)// 添加属性到新对象上 并获取obj函数的结果let res fn.call(newObj, ...args)// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象return res typeof res object ? res : newObj;
}9. js执行机制 说出结果并说出why
这道题考察的是js的任务执行流程对宏任务和微任务的理解
console.log(start);setTimeout(() {console.log(setTimeout1);
}, 0);(async function foo() {console.log(async 1);await asyncFunction();console.log(async2);})().then(console.log(foo.then));async function asyncFunction() {console.log(asyncFunction);setTimeout(() {console.log(setTimeout2);}, 0);new Promise((res) {console.log(promise1);res(promise2);}).then(console.log);
}console.log(end);提示
script标签算一个宏任务所以最开始就执行了async await 在await之后的 Promise 都会被放到微任务队列中去
开始执行
最开始碰到 console.log(“start”); 直接执行并打印出 start往下走遇到一个 setTimeout1 就放到宏任务队列碰到立即执行函数 foo 打印出 async 1遇到 await 堵塞队列先 执行await的函数执行 asyncFunction 函数 打印出 asyncFunction遇到第二个 setTimeout2 放到宏任务队列new Promise 立即执行打印出 promise1执行到 res(“promise2”) 函数调用就是Promise.then。放到微任务队列asyncFunction函数就执行完毕 把后面的打印 async2 会放到微任务队列然后打印出立即执行函数的then方法 foo.then最后执行打印 end开始执行微任务的队列 打印出第一个 promise2然后打印第二个 async2微任务执行完毕执行宏任务 打印第一个 setTimeout1执行第二个宏任务 打印 setTimeout2、就此函数执行完毕
画工不好能理解到意思就行。 看看你们的想法和答案是否和这个流程一致
10. 如何拦截全局Promise reject但并没有设定 reject处理器 时候的错误
这道题我是没写出来最开始想着 trycatch 但这个并不是全局的。
后续查了资料才发现 是用一个window上面的方法
// 使用Try catch 只能拦截try语句块里面的
try {new Promise((resolve, reject) {reject(WTF 123);});
} catch (e) {console.log(e, e);throw e;
}// 使用 unhandledrejection 来拦截全局错误 这个是对的
window.addEventListener(unhandledrejection, (event) {event event.preventDefault();console.log(event, event);
});11. 手写实现sleep
这个我只通过了一种方法实现就是刚刚我们在上面js执行流程中我有提过。 await 会有异步堵塞的意思
还有一个方法是我在网上找到的方法通过完全堵塞进程的方法来实现 这个有点吊 // 使用 promise 配合await的异步方法来实现 sleep{(async () {console.log(start);await sleep(3000)console.log(end);function sleep(timer) {return new Promise(res {setTimeout(() {res()}, timer);})}})();}// 方法二 这是完全堵塞进程来达到sleep{(async () {console.log(start);await sleep(3000)console.log(end);function sleep(delay) {let t Date.now();while (Date.now() - t delay) {continue;}};})()}12. 实现add(1)(2) 3
光这个的话可以通过闭包的方式实现了
我给这个加了一个难度如何才能实现一直调用 // 题意的答案const add (num1) (num2) num2 num1;// 我自己整了一个加强版 可以无限链式调用 add(1)(2)(3)(4)(5)....function add(x) {// 存储和let sum x;// 函数调用会相加然后每次都会返回这个函数本身let tmp function (y) {sum sum y;return tmp;};// 对象的toString必须是一个方法 在方法中返回了这个和tmp.toString () sumreturn tmp;}alert(add(1)(2)(3)(4)(5))无限链式调用实现的关键在于 对象的 toString 方法: 每个对象都有一个 toString() 方法当该对象被表示为一个文本值时或者一个对象以预期的字符串方式引用时自动调用。
也就是我在调用很多次后他们的结果会存在add函数中的sum变量上当我alert的时候 add会自动调用 toString方法 打印出 sum, 也就是最终的结果
13. 两个数组中完全独立的数据
就是找到仅在两个数组中出现过一次的数据
var a [1, 2, 4], b [1, 3, 8, 4]
const newArr a.concat(b).filter((item, _, arr) {return arr.indexOf(item) arr.lastIndexOf(item)
})最终出来的结果是 [2,3,8] 原理其实很简单 合并两个数组然后查找数组的第一个出现的索引和最后一个出现的索引是否一致就可以判断是否是独立的数据了。
14. 判断完全平方数
就是判断一个数字能不能被开平方 比如9的开平方是3 是对的。 5没法开平方就是错的。
var fn function (num) {return num ** 0.5 % 1 0
};原理就是开平方后判断是否是正整数就行了
15. 函数执行 说出结果并说出why
function Foo() {getName function () {console.log(1);};return this;
}Foo.getName function () {console.log(2);
}Foo.prototype.getName function () {console.log(3);
}var getName function () { console.log(4);
}function getName() {console.log(5)
}Foo.getName();getName();Foo().getName()getName();new Foo.getName(); new Foo().getName()new new Foo().getName()这道题其实就是看你对作用域的关系的理解吧
执行结果 执行 Foo.getName(), 执行Foo函数对象上的的静态方法。打印出 2 执行 getName() 就是执行的getName变量的函数。打印 4 为什么这里是 执行的 变量getName而不是函数getName呢。这得归功于js的预编译js在执行之前进行预编译会进行 函数提升 和 变量提升所以函数和变量都进行提升了但是函数声明的优先级最高会被提升至当前作用域最顶端当在执行到后面的时候会导致getName被重新赋值就会把执行结果为 4 的这个函数赋值给变量 执行 Foo().getName() 调用Foo执行后返回值上的getName方法。 Foo函数执行了里面会给外面的getName函数重新赋值并返回了this。 也就是执行了this.getName。所以打印出了 1 执行 getName() 由于上一步函数被重新赋值。所以这次的结果和上次的结果是一样的还是为1 执行 new Foo.getName() 这个 new 其实就是new了Foo上面的静态方法getName 所以是2。 当然如果你们在这个函数里面打印this的话会发现指向的是一个新对象 也就是new出来的一个新对象 可以把 Foo.getName()看成一个整体因为这里 . 的优先级比 new 高 执行 new Foo().getName()这里函数执行 new Foo() 会返回一个对象然后调用这个对象原型上的getName方法 所以结果是 3 执行 new new Foo().getName(), 这个和上一次的结果是一样上一个函数调用后并咩有返回值所以在进行new的时候也没有意义了。 最终结果也是3
16. 原型调用面试题 说出结果并说出 why
function Foo() {Foo.a function () {console.log(1);};this.a function () {console.log(2);};
}Foo.prototype.a function () {console.log(4);
};Function.prototype.a function () {console.log(3);
};Foo.a();let obj new Foo();
obj.a();
Foo.a();执行结果 执行Foo.a()Foo本身目前并没有a这个值就会通过 __proto__ 进行查找但是 所以输出是 3 new 实例化了 Foo 生成对象 obj然后调用 obj.a()但是在Foo函数内部给这个obj对象附上了a函数。 所以结果是2。 如果在内部没有给这个对象赋值a的话就会去到原型链查找a函数就会打印4. 执行Foo.a() 在上一步中Foo函数执行内部给Foo本身赋值函数a所以这次就打印1
17. 数组分组改成减法运算
这个题的意思就是 [5, [[4, 3], 2, 1]] 变成 (5 - ((4 - 3) - 2 - 1)) 并执行。 且不能使用eval()
方法一 既然不能用 eval 那我们就用new Function吧
方法二 当然方法一有点违背了题意所以还有第二种方法
var newArr [5, [[4, 3], 2, 1]]// 1. 取巧// 转为字符串let newStringArr ${JSON.stringify(newArr)}// 循环修改括号和减号let fn newStringArr.split().map((el) {switch (el) {case [:return (case ]:return )case ,:return -default:return el}}).join()// 最终通过new Function 调用可以了new Function(return fn)()// 2. 方法二 function run(arr) {return arr.reduce((pre, cur) {let first Array.isArray(pre) ? run(pre) : prelet last Array.isArray(cur) ? run(cur) : curreturn first - last})}run(newArr)方法一的原理就很简单转成字符串循环修改括号和减号在进行拼接。最终通过 new Function 调用就可以了 方法二的意思就是通过 reduce 进行一个递归调用 的意思。 如果左边不是数组就可以减去右边的但如果右边是数组的话就要把右边的数组先进行减法运算。也是就减法括号运算的的优先级.
18. 手写数组的 flat const flat function (arr, deep 1) {// 声明一个新数组let result []arr.forEach(item {if (Array.isArray(item) deep 0) {// 层级递减// deep-- 来自评论区的大佬指正deep - 1// 使用concat链接数组 result result.concat(flat(item, deep - 1))} else {result.push(item)}})return result}原理就是先在内部生成一个新数组遍历原来的数组 当原数组内 存在数组并且层级deep大于等于1时进行递归, 如果不满足这个条件就可以直接push数据到新数组去 递归同时要先把层级减少 然后通过 concat 链接递归出来的数组 最终返回这个数组就可以了
19. 数组转为tree
最顶层的parent 为 -1 其余的 parent都是为 上一层节点的id let arr [{ id: 0, name: 1, parent: -1, childNode: [] },{ id: 1, name: 1, parent: 0, childNode: [] },{ id: 99, name: 1-1, parent: 1, childNode: [] },{ id: 111, name: 1-1-1, parent: 99, childNode: [] },{ id: 66, name: 1-1-2, parent: 99, childNode: [] },{ id: 1121, name: 1-1-2-1, parent: 112, childNode: [] },{ id: 12, name: 1-2, parent: 1, childNode: [] },{ id: 2, name: 2, parent: 0, childNode: [] },{ id: 21, name: 2-1, parent: 2, childNode: [] },{ id: 22, name: 2-2, parent: 2, childNode: [] },{ id: 221, name: 2-2-1, parent: 22, childNode: [] },{ id: 3, name: 3, parent: 0, childNode: [] },{ id: 31, name: 3-1, parent: 3, childNode: [] },{ id: 32, name: 3-2, parent: 3, childNode: [] }]function arrToTree(arr, parentId) {// 判断是否是顶层节点如果是就返回。不是的话就判断是不是自己要找的子节点const filterArr arr.filter(item {return parentId undefined ? item.parent -1 : item.parent parentId})// 进行递归调用把子节点加到父节点的 childNode里面去filterArr.map(item {item.childNode arrToTree(arr, item.id)return item})return filterArr}arrToTree(arr)这道题也是利用递归来进行的在最开始会进行是否是顶层节点的判断 如果是就直接返回如果不是则判断是不是自己要添加到父节点的子节点 然后再一层一层把节点加入进去 最后返回这个对象
20. 合并数组并排序去重
题意就是 我有两个数组把他们两个合并。然后并去重去重的逻辑是哪儿边的重复次数更多我就留下哪儿边的。
比如下面的数组中一边有两个数字5另一半有三个数字5 。则我需要留下三个数字5去掉两个数字5。 循环往复最后得到的结果在进行排序。 数组一 [1, 100, 0, 5, 1, 5] 数组二 [2, 5, 5, 5, 1, 3] 最终的结果 [0, 1, 1, 2, 3, 5, 5, 5, 100] // 判断出现次数最多的次数function maxNum(item, arr) {let num 0;arr.forEach(val {item val num})return num}function fn(arr1, arr2) {// 使用Map数据类型来记录次数let obj new Map();// 合并数组并找出最多的次数, 并以键值对存放到Map数据类型[...arr1, ...arr2].forEach(item {let hasNum obj.get(item)let num 1if (hasNum) {num hasNum 1}obj.set(item, num)})// 存放合并并去重之后的数组let arr []// 遍历Map数据类型 然后把次数最多的直接push到新数组for (const key of obj.keys()) {if (obj.get(key) 1) {for (let index 0; index Math.max(maxNum(key, arr1), maxNum(key, arr2)); index) {arr.push(key)}} else {arr.push(key)}}// 最后进行排序return arr.sort((a, b) a - b)}这个题的思路其实就是我先把两个数组合并起来 并以键值对的方式存放到Map数据类型, 键就是数据而值就是这个数据出现的次数 生成一个新数组用来存放合并之后的数组 遍历这个Map数据类型, 如果这个数据出现的次数大于一那么就去寻找两个数组中谁出现的次数更多把出现次数更多的这个数据循环push到新数组中。 如果出现次数等于一那就直接push到新数组中即可。 最后再把数组进行排序然后返回新数组就可。
21、Function.prototype使用
Function.prototype 是 JavaScript 中的一个属性它允许你向所有函数对象添加新的属性和方法。通过修改 Function.prototype你可以为所有的函数实例添加自定义的行为或功能。
例如如果你想给所有的函数添加一个名为 myCustomMethod 的方法你可以这样做
Function.prototype.myCustomMethod function() {console.log(这是一个自定义方法);
};// 现在所有的函数都可以调用这个方法
function exampleFunction() {console.log(这是一个示例函数);
}exampleFunction.myCustomMethod(); // 输出 这是一个自定义方法需要注意的是直接修改 Function.prototype 可能会导致与其他库或框架的冲突因此在实际开发中要谨慎使用。
三、结语
最后希望大家能理解这些题并知晓why有些题并不是单单只是用来考你。而是变相的让你理解这其中深藏的意义。
以上的题目我的答案并非就是最优答案。若你有更好的解决方法请不要吝啬大胆的敲到评论区 我会把你的解决方法补充到文章里面。
如果文中内容对你有帮助 记得三连~ 如文中有错误也欢迎大家指正修改