金三银四:20道前端手写面试题
文章目录
- 一、前言
- 二、题目
- 1. 防抖节流
- 解读
- 2.一个正则题
- 3. 不使用a标签,如何实现a标签的功能
- 4. 不使用循环API 来删除数组中指定位置的元素(如:删除第三位) 写越多越好
- 5. 深拷贝
- 解读
- 6. 手写call bind apply
- call 解读
- apply 解读
- 7. 手写实现继承
- 8. 手写 new 操作符
- 9. js执行机制 说出结果并说出why
- 10. 如何拦截全局Promise reject,但并没有设定 reject处理器 时候的错误
- 11. 手写实现sleep
- 12. 实现add(1)(2) =3
- 13. 两个数组中完全独立的数据
- 14. 判断完全平方数
- 15. 函数执行 说出结果并说出why
- 16. 原型调用面试题 说出结果并说出 why
- 17. 数组分组改成减法运算
- 18. 手写数组的 flat
- 19. 数组转为tree
- 20. 合并数组并排序去重
- 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
的这个函数赋值给变量
- 为什么这里是 执行的 变量getName,而不是函数getName呢。这得归功于
-
执行 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 高
- 可以把 Foo.getName()看成一个整体,因为
-
执行 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
,有些题,并不是单单只是用来考你。而是变相的让你理解这其中深藏的意义。
以上的题目,我的答案并非就是最优答案。若你有更好的解决方法
,请不要吝啬,大胆的敲到评论区! 我会把你的解决方法补充到文章里面。
如果文中内容对你有帮助, 记得三连~ 🎉🎉🎉 如文中有错误,也欢迎大家指正修改!
相关文章:
金三银四:20道前端手写面试题
文章目录 一、前言二、题目1. 防抖节流解读 2.一个正则题3. 不使用a标签,如何实现a标签的功能4. 不使用循环API 来删除数组中指定位置的元素(如:删除第三位) 写越多越好5. 深拷贝解读 6. 手写call bind applycall 解读apply 解读 …...
RAC被修改权限及相关问题
RDBMS : 19.19 修改RAC权限及相关问题 修改RAC权限,参考文档: How to check and fix file permissions on Grid Infrastructure environment (Doc ID 1931142.1) Script to capture and restore file permission in a directory (for eg. O…...
Golang | Leetcode Golang题解之第441题排列硬币
题目: 题解: func arrangeCoins(n int) int {return sort.Search(n, func(k int) bool { k; return k*(k1) > 2*n }) }...
数学建模--什么是数学建模?数学建模应该怎么准备?
前言 这是去年底学数学建模老哥的建模课程笔记;未来本人将陆陆续续的更新数学建模相关的一些基础算法,大家可以持续关注一下;提示:数学建模只有实战才能提升,光学算法没有啥意义,也很难学的很懂。 文章目录…...
Java项目实战II基于Java+Spring Boot+MySQL的智能物流管理系统(源码+数据库+文档)
目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者 一、前言 随着电商行业的蓬勃发展,物流行业迎来了前所未有的机遇与挑战。面对日益增长的订单量和复…...
【数据分享】2000—2023年我国省市县三级逐月植被覆盖度(FVC)数值(Shp/Excel格式)
之前我们分享过2000—2023年我国250米分辨率逐月植被覆盖度(FVC)栅格数据(可查看之前的文章获悉详情),该数据来源于高吉喜等学者在国家青藏高原科学数据中心平台上分享的数据,合成方式采用月最大值合成&…...
《Linux从小白到高手》理论篇(十一):Linux的系统环境管理
值此国庆佳节,深宅家中,闲来无事,就多写几篇博文。本篇详细深入介绍Linux的系统环境管理。 环境变量 linux系统下,如果你下载并安装了应用程序,很有可能在键入它的名称时出现“command not found”的提示内容。如果每…...
Qt/C++开源控件 自定义雷达控件
使用Qt框架创建一个简单的雷达图,包含动态扫描、目标点生成、刻度和方向标识。代码实现使用C编写,适合用作学习和扩展的基础。 1. 头文件与基本设置 #include "RadarWidget.h" #include <QPainter> #include <QPen> #include &…...
什么是IDE(集成开发环境)?
集成开发环境(IDE)详解 在软件开发的世界中,集成开发环境(IDE,Integrated Development Environment)扮演着至关重要的角色。它是一个综合性的软件应用程序,旨在为软件开发者提供一整套的、易于使用的工具集,以便他们能够更高效地编写、调试、测试和部署代码。简而言之…...
【Linux】用虚拟机配置Ubuntu 24.04.1 LTS环境
目录 1.虚拟机安装Ubuntu系统 2.Ubuntu系统的网络配置 3.特别声明 首先我们先要下载VMware软件,大家自己去下啊! 1.虚拟机安装Ubuntu系统 我们进去之后点击创建新的虚拟机,然后选择自定义 接着点下一步 再点下一步 进入这个界面之后&…...
MacOS升级Ruby版本详解:步骤、挑战与解决方案
MacOS升级Ruby版本详解:步骤、挑战与解决方案 在MacOS上升级Ruby版本是一个涉及多个步骤和考虑因素的过程。Ruby作为一种广泛使用的编程语言,其新版本通常会引入一系列改进,包括性能优化、安全修复和新特性。因此,升级Ruby版本不…...
Log4j的配置与使用详解
Log4j的配置与使用详解 Log4j介绍 Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,我们可以控制每条日志的输出格式;只需要通过一个配置文件就可以灵活的配置,…...
docker 的目录有那些,分别存放什么东西
Docker 的目录结构和文件存放位置取决于你所使用的操作系统和Docker的版本。以下是一些常见的目录和它们通常存放的内容: 通用目录 /var/lib/docker (Linux) 这是Docker在Linux系统上的主要数据目录。存放了镜像、容器、数据卷、网络等的元数据和状态信息。具体结构…...
开源模型应用落地-模型微调-语料采集-数据格式化(四)
一、前言 在自然语言处理(NLP)的快速发展中,语料采集作为基础性的步骤显得尤为重要。它不仅为机器学习模型提供了所需的训练数据,还直接影响模型的性能和泛化能力。随着数据驱动技术的不断进步,如何有效并高效地收集、清洗和整理丰富多样的语料,已成为研究者和工程师们亟…...
C语言+单片机
今天内容有点水哈哈(忙着练焊铁技术了嘻嘻) C语言 简单学习了while语言以及其与for语言的区别和适用方法 .循环结构: 初始化语句条件判断句条件控制句 for语句 for(int1;i<100;i){执行条件} for (int i 1; i < 100; i) {printf(&quo…...
vmvare虚拟机centos 忘记超级管理员密码怎么办?
vmvare虚拟机centos 忘记超级管理员密码怎么办?如何重置密码呢? 一、前置操作 重启vmvare虚拟机的过程中,长按住Shift键 选择第一个的时候,按下按键 e 进入编辑状态。 然后就会进入到类似这个界面中。 在下方界面 添加 init=/bin/sh,然后按下Ctrl+x进行保存退出。 init=/bi…...
使用 Vue3 和 Axios 实现 CRUD 操作
文章目录 1、准备工作2、创建 Vue 3 项目3、项目结构4、实现 CRUD 操作5、运行项目6、小结在当今的前端开发中,Vue.js 作为一款流行的 JavaScript 框架,正在被越来越多的开发者所青睐。尤其是 Vue 3 引入了 Composition API 和更优雅的响应式处理,使得模板编写和状态管理变得…...
.NET MAUI(.NET Multi-platform App UI)下拉选框控件
MAUI下拉选框控件详解: 在开发跨平台应用程序时,下拉选框(ComboBox)是一个极为常见且实用的控件,它允许用户从一组预定义的选项中选择一个。在.NET MAUI(.NET Multi-platform App UI)框架中&am…...
C++平台跳跃游戏
目录 开头程序Game.cpp源文件Player.h头文件Player.cpp源文件 程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好,我叫这是我58。 程序 Game.cpp源文件 #include <iostream> #include "Player.h" using namespace std; void printma…...
多系统萎缩患者必看!这些维生素助你对抗病魔
亲爱的朋友们,今天我们来聊聊一个相对陌生但重要的健康话题——多系统萎缩(MSA)。这是一种罕见的神经系统疾病,影响着患者的自主神经系统、运动系统和平衡功能。面对这样的挑战,科学合理的饮食和营养补充显得尤为重要。…...
深度学习模型性能优化实战之从评估到提升的全流程解析
1. 概述 在构建和使用机器学习模型的过程中,模型的效果评估和优化是两个至关重要的环节。无论模型是用于分类、回归还是其他任务,评估其表现以及持续优化模型性能,都是确保模型在实际应用中取得成功的关键。本节将重点介绍模型效果评估的定义…...
C++ | Leetcode C++题解之第446题等差数列划分II-子序列
题目: 题解: class Solution { public:int numberOfArithmeticSlices(vector<int> &nums) {int ans 0;int n nums.size();vector<unordered_map<long long, int>> f(n);for (int i 0; i < n; i) {for (int j 0; j < i;…...
【解密 Kotlin 扩展函数】扩展属性与扩展函数类似(十九)
导读大纲 1.1.1 扩展属性的创建和使用 1.1.1 扩展属性的创建和使用 之前, 我们已经了解声明 Kotlin 属性的语法 Kotlin中的顶级属性–传送门就像扩展函数一样,我们也可以指定扩展属性就像之前所说,属性和函数的区别在于前者是特征,后者是行为 相比扩展函…...
【Spring Boot 入门二】Spring Boot中的配置文件 - 掌控你的应用设置
一、引言 在上一篇文章中,我们开启了Spring Boot的入门之旅,成功构建了第一个Spring Boot应用。我们从环境搭建开始,详细介绍了JDK的安装以及IDE的选择与配置,然后利用Spring Initializr创建了项目,分析了项目结构&am…...
OpenCV第十二章——人脸识别
1.人脸跟踪 1.1 级联分类器 OpenCV中的级联分类器是一种基于AdaBoost算法的多级分类器,主要用于在图像中检测目标对象。以下是对其简单而全面的解释: 一、基本概念 级联分类器:是一种由多个简单分类器(弱分类器)级联组…...
深入Volatile
深入Volatile 1、变量不可见性: 1.1多线程下变量的不可见性 直接上代码 /*** author yourkin666* date 2024/08/12/16:12* description*/ public class h1 {public static void main(String[] args) {MyClass myClass new MyClass();myClass.start();while (tr…...
数据结构 ——— 顺序表oj题:编写函数,合并两个有序数组
目录 题目要求 代码实现 题目要求 nums1 和 nums2 是两个升序的整型数组,另外有两个整数 m 和 n 分别代表 nums1 和 nums2 中的元素个数 要求合并 nusm2 到nums1 中,使合并后的 nums1 同样按升序顺序排列 最终,合并后的数组不应由函数返…...
Proto文件相关知识
百度Apollo的数据结构常用proto文件来定义, proto文件允许你以类似于C结构体或类的方式定义数据结构。你可以在这个文件中定义简单数据类型、枚举、消息类型等。 基于proto文件,Protocol Buffers编译器(protoc)可以自动生成对应的…...
k8s的控制节点不能访问node节点容器的ip地址
master控制node服务器添加容器后,访问不了该node服务器容器的ip,只能在node服务器访问 排查后发现是k8s的master服务器和node节点的网址网段和k8s初始化时提示的ip网段不一致 我之前是192.168.137.50, 实际上master主机期望的是192.168.1.50 解决方案: 1.删除服务器后重建ma…...
鸿蒙OpenHarmony
开源鸿蒙系统编译指南 Ubuntu编译环境配置第一步:Shell 改 Bash第二步:安装Git和安装pip3工具第三步:远程仓配置第四步:拉取代码第五步:安装编译环境第六步:本地编译源码 Windows开发环境配置第一步&#x…...
配置wordpress七牛/竞价推广外包托管
1、C11用实例化 std::mutex 创建互斥量,通过调用成员函数lock()进行上锁,unlock()进行解锁。不过,不推荐实践中直接去调用成员函数,因为调用成员函数就意味着,必须记住在每个函数出口都要去调用unlock(),也…...
厦门软件园多客宝网站开发/壹起航网络推广的目标
一、HTTP 1.1 超文本传输协议(HTTP)是用于分布式,协作式和超媒体信息系统的应用协议。它是Web上数据交换的基础,是一种client-server协议,也就是说请求通常是由像浏览器这样的接受方发起的。 1.2 HTTP被设计于上20世纪…...
多种手机网站建设/信息流广告公司排名
南京2018年9月4日电 /美通社/ -- 随着水污染问题日益受到消费者的关注,家庭净水机的安装量可谓稳步上升。不过,毕竟国人的饮水习惯和欧美国家不同,往往需要将其烧开之后才会饮用;更不要说给孩子冲奶粉、给自己泡杯茶等,…...
呼市做引产z首大网站/安徽网络推广和优化
导语:本文中,阿里云智能运营专家斯麦跟大家分享了宜搭的产品生态及生态伙伴政策,并介绍了宜搭产品的应用场景、产品优势、产品价值和具体实践。 今天是我们距离3月21号宜搭正式发布公测以来刚好是第216天。今天会向大家介绍宜搭产品的生态伙伴…...
thinkphp3.2 企业网站源码/百度品牌广告多少钱一个月
问题 如题, 用pyinstaller打包, 打包正常, 启动闪退. 用命令行终端shell执行, 报错no module named xxx. 我这里是openpyxl 按网上说的加了 pyinstaller -F excel_handle.py --hidden-importopenpyxl无效 又看了说是openpyxl版本问题, 降级到2.3.4, 无效 解决 实际上是命…...
网站 用cms 侵权/必应搜索引擎网址
一、getdate()作用:得到服务器当前的时间二、datepart(interval,date)参数:interval:时间的某一个部分(如时、分、秒、天、周、月、季、年)date:当前日期或指定日期作用:取指定时间的某一个部分,年月天时分秒示例:select datepart…...