目录
2618. 检查是否是类的对象实例
2619. 数组原型对象的最后一个元素
2620. 计数器
2621. 睡眠函数
2622. 有时间限制的缓存
2623. 记忆函数
2625. 扁平化嵌套数组
2626. 数组归约运算
2627. 函数防抖
2618. 检查是否是类的对象实例
请你编写一个函数,检查给定的值是否是给定类或超类的实例。
可以传递给函数的数据类型没有限制。例如,值或类可能是 undefined
。
示例 :
输入:func = () => checkIfInstance(new Date(), Date)
输出:true
解释:根据定义,Date 构造函数返回的对象是 Date 的一个实例。
题解
var checkIfInstanceOf = function(obj, classFunction) {
if(obj === null || classFunction === null || obj === undefined || classFunction === undefined) {
return false;
}
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
if (proto.constructor === classFunction) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
};
这段代码定义了一个名为checkIfInstanceOf的函数,该函数的作用是判断一个对象是否是指定类的实例。
函数接受两个参数:obj和classFunction,分别表示待判断的对象和指定类的构造函数。
首先,函数会检查obj和classFunction是否为null或undefined,如果是的话,函数直接返回false,表示对象不是指定类的实例。
接下来,函数会通过Object.getPrototypeOf(obj)获取obj的原型。然后,进入一个while循环,不断获取原型的原型,直到原型为null为止。
在每一次循环中,函数会判断当前原型的构造函数是否等于传入的classFunction。如果是的话,表示obj是classFunction的实例,函数会返回true。
如果循环结束后仍未找到符合条件的原型,函数会返回false,表示obj不是指定类的实例。
最后,整个函数的定义结束。
2619. 数组原型对象的最后一个元素
请你编写一段代码实现一个数组方法,使任何数组都可以调用 array.last()
方法,这个方法将返回数组最后一个元素。如果数组中没有元素,则返回 -1
。
你可以假设数组是 JSON.parse
的输出结果。
示例 :
输入:nums = [null, {}, 3]
输出:3
解释:调用 nums.last() 后返回最后一个元素: 3。
题解:
Array.prototype.last = function() {
let res=this[this.length-1]
return res===undefined?-1:res
};
这段代码定义了一个名为last的函数,该函数是Array原型的一个方法。该方法用于获取数组中的最后一个元素。
函数内部首先通过this关键字获取调用该方法的数组对象。然后通过this.length - 1获取数组中最后一个元素的索引,并将其赋值给变量res。
接下来使用三元运算符判断变量res的值是否为undefined。如果是undefined,则说明数组为空,函数返回-1;否则,函数返回变量res的值,即数组中的最后一个元素。
这段代码的作用是为数组对象添加一个方法,方便获取数组的最后一个元素。如果数组为空,则返回-1。
2620. 计数器
请你编写并返回一个 计数器 函数,它接收一个整型参数 n 。这个 计数器 函数最初返回 n,每次调用它时返回前一个值加 1 的值 ( n
, n + 1
, n + 2
,等等)。
示例 :
输入:
n = 10
["call","call","call"]
输出:[10,11,12]
解释:
counter() = 10 // 第一次调用 counter(),返回 n。
counter() = 11 // 返回上次调用的值加 1。
counter() = 12 // 返回上次调用的值加 1。
题解:
var createCounter = function (n) {
return function () {
return n++;
};
};
var createCounter
定义了一个变量createCounter
,它将引用这个函数。function (n)
是一个匿名函数,它接受一个参数n
。return function () { ... }
是匿名函数的返回值,它也是一个函数。return n++
是返回了变量n
的值,并将n
自增 1。
因此,当调用 createCounter
函数时,它会返回一个新的函数。每次调用这个新函数时,它会返回变量 n
的当前值,并将 n
自增 1。这样就创建了一个简单的计数器,每次调用计数器函数时,计数器的值都会增加。
2621. 睡眠函数
请你编写一个异步函数,它接收一个正整数参数 millis
,并休眠这么多毫秒。要求此函数可以解析任何值。
示例
输入:millis = 100
输出:100
解释:
在 100ms 后此异步函数执行完时返回一个 Promise 对象
let t = Date.now();
sleep(100).then(() => {
console.log(Date.now() - t); // 100
});
题解
async function sleep(millis) {
return new Promise((reslove, reject) => {
setTimeout(() => {
reslove()
}, millis)
})
}
这是一个异步 JavaScript 函数,名称为 sleep
。这个函数接受一个参数 millis
,用来标明延迟的毫秒数。
这个函数的功能是创建一个新的 Promise 对象,这个 Promise 对象在经过指定的毫秒数 millis
后解决(即执行 resolve
方法)。由于 JavaScript 的 Timer 函数(例如 setTimeout
)是非阻塞的,sleep
函数使用 Promise
和 setTimeout
一起来实现一个"暂停"功能。
当你在一个 async
函数中使用 await sleep(millis)
形式调用此函数时,该函数会“暂停”或者说“睡眠”指定的毫秒数,然后继续执行。这样的函数在需要延迟一段时间然后执行的场景中特别有用。
2622. 有时间限制的缓存
编写一个类,它允许获取和设置键-值对,并且每个键都有一个 过期时间 。
该类有三个公共方法:
set(key, value, duration)
:接收参数为整型键 key
、整型值 value
和以毫秒为单位的持续时间 duration
。一旦 duration
到期后,这个键就无法访问。如果相同的未过期键已经存在,该方法将返回 true
,否则返回 false
。如果该键已经存在,则它的值和持续时间都应该被覆盖。
get(key)
:如果存在一个未过期的键,它应该返回这个键相关的值。否则返回 -1
。
count()
:返回未过期键的总数。
示例
["TimeLimitedCache", "set", "get", "count", "get"] [[], [1, 42, 100], [1], [], [1]] [0, 0, 50, 50, 150] 输出: [null, false, 42, 1, -1] 解释: 在 t=0 时,缓存被构造。 在 t=0 时,添加一个键值对 (1: 42) ,过期时间为 100ms 。因为该值不存在,因此返回false。 在 t=50 时,请求 key=1 并返回值 42。 在 t=50 时,调用 count() ,缓存中有一个未过期的键。 在 t=100 时,key=1 到期。 在 t=150 时,调用 get(1) ,返回 -1,因为缓存是空的。
var TimeLimitedCache = function () {
this.map = new Map();
this.timeoutMap = new Map();
};
TimeLimitedCache.prototype.set = function (key, value, duration) {
if (this.timeoutMap.has(key)) {
clearTimeout(this.timeoutMap.get(key));
}
let isKeyExists = this.map.has(key);
this.map.set(key, value);
this.timeoutMap.set(key, setTimeout(() => {
this.map.delete(key);
this.timeoutMap.delete(key);
}, duration));
return isKeyExists;
};
TimeLimitedCache.prototype.get = function (key) {
if (this.map.has(key)) {
return this.map.get(key);
} else {
return -1;
}
};
TimeLimitedCache.prototype.count = function () {
return this.map.size;
};
这段代码定义了一个名为TimeLimitedCache的构造函数。构造函数内部有两个实例属性,map和timeoutMap,分别使用JavaScript内置的Map类来存储数据。
构造函数之后定义了三个方法。
-
set方法用于向缓存中设置一个键值对,并指定过期时间。如果指定键已存在,且仍未过期,set方法会清除之前的过期计时器。然后,它会将键值对存储到map中,并使用setTimeout方法设置过期计时器,计时器到期后会从map和timeoutMap中删除该键值对。返回值表示之前是否已存在未过期的键。
-
get方法用于根据键获取对应的值。如果键存在于map中,则返回对应的值;否则,返回-1。
-
count方法用于返回当前未过期的键的数量,即map的大小。
这段代码实现了一个有时限的缓存机制,可以设置过期时间,并能够根据键获取对应的值以及获取未过期键的数量。
2623. 记忆函数
请你编写一个函数,它接收另一个函数作为输入,并返回该函数的 记忆化 后的结果。
记忆函数 是一个对于相同的输入永远不会被调用两次的函数。相反,它将返回一个缓存值。
你可以假设有 3 个可能的输入函数:sum
、fib
和 factorial
。
-
sum
接收两个整型参数a
和b
,并返回a + b
。 -
fib
接收一个整型参数n
,如果n <= 1
则返回1
,否则返回fib (n - 1) + fib (n - 2)
。 -
factorial
接收一个整型参数n
,如果n <= 1
则返回1
,否则返回factorial(n - 1) * n
。
示例
输入:
"sum"
["call","call","getCallCount","call","getCallCount"]
[[2,2],[2,2],[],[1,2],[]]
输出:
[4,4,1,3,2]
解释:
const sum = (a, b) => a + b;
const memoizedSum = memoize(sum);
memoizedSum (2, 2);// 返回 4。sum() 被调用,因为之前没有使用参数 (2, 2) 调用过。
memoizedSum (2, 2);// 返回 4。没有调用 sum(),因为前面有相同的输入。
//总调用数: 1
memoizedSum(1、2);// 返回 3。sum() 被调用,因为之前没有使用参数 (1, 2) 调用过。
//总调用数: 2
题解
function memoize(func) {
var cache = {};
return function(...args) {
var key = JSON.stringify(args);
if (cache.hasOwnProperty(key)) {
return cache[key];
}
var result = func(...args);
cache[key] = result;
return result;
};
}
这段代码定义了一个名为memoize的函数,它接受一个参数func。memoize函数返回一个新的函数,并利用闭包在返回的函数中创建一个缓存对象cache。
返回的函数接受任意数量的参数,并将这些参数使用JSON.stringify方法转换成一个字符串作为缓存字典cache的键。如果缓存对象已经存在该键,则直接返回缓存中的值。
如果缓存对象中不存在该键,那么调用传入的原始函数func,将其返回值存储在result变量中。并将结果存储在缓存对象的对应键中。最后,返回结果。
这段代码的作用是:在调用原始函数之前,先检查是否已经存在相同参数的结果。如果存在,则直接返回缓存中的结果,避免重复计算。如果不存在,则计算参数对应的结果,并将结果存储在缓存中,以备将来使用。
2625. 扁平化嵌套数组
请你编写一个函数,它接收一个 多维数组 arr
和它的深度 n
,并返回该数组的 扁平化 后的结果。
多维数组 是一种包含整数或其他 多维数组 的递归数据结构。
数组 扁平化 是对数组的一种操作,定义是将原数组部分或全部子数组删除,并替换为该子数组中的实际元素。只有当嵌套的数组深度大于 n
时,才应该执行扁平化操作。第一层数组中元素的深度被认为是 0。
请在没有使用内置方法 Array.flat
的前提下解决这个问题。
示例
输入 arr = [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]] n = 0 输出 [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]] 解释 传递深度 n=0 的多维数组将始终得到原始数组。这是因为 子数组(0) 的最小可能的深度不小于 n=0 。因此,任何子数组都不应该被平面化。
题解
var flat = function (arr, n) {
if (n === 0) {
return arr;
}
const res = flat(arr, n - 1);
return [].concat(...res);
};
这段代码的作用是将一个嵌套的数组(arr)展开成一个一维数组,展开的层数由参数n决定。
代码的流程是:
-
定义一个名为flat的函数,接受两个参数,一个是要展开的数组arr,另一个是展开的层数n。
-
如果n等于0,表示已经展开完毕,直接返回原始数组arr。
-
如果n不等于0,则递归调用flat函数,将展开的层数减1,得到一个新的数组res。
-
使用数组的concat方法将新的数组res拼接成一个一维数组,并返回该数组。
这个函数的实现原理是通过递归的方式,每次递归都将展开的层数减1,直到达到指定的展开层数,然后通过concat方法将递归得到的中间展开结果合并成最终结果。
2626. 数组归约运算
你编写一个函数,它的参数为一个整数数组 nums
、一个计算函数 fn
和初始值 init 。返回一个数组 归约后 的值。
你可以定义一个数组 归约后 的值,然后应用以下操作: val = fn(init, nums[0])
, val = fn(val, nums[1])
, val = fn(val, nums[2])
,...
直到数组中的每个元素都被处理完毕。返回 val
的最终值。
如果数组的长度为 0,它应该返回 init
的值。
请你在不使用内置数组方法的 Array.reduce
前提下解决这个问题。
示例
输入: nums = [1,2,3,4] fn = function sum(accum, curr) { return accum + curr; } init = 0 输出:10 解释: 初始值为 init=0 。 (0) + nums[0] = 1 (1) + nums[1] = 3 (3) + nums[2] = 6 (6) + nums[3] = 10 Val 最终值为 10。
题解
var reduce = function (nums, fn, init) {
if (nums.length === 0) { return init; }
let val = init;
for (let i = 0; i < nums.length; i++) {
val = fn(val, nums[i]);
}
return val;
};
这段代码定义了一个名为reduce的函数,该函数接受三个参数:nums(数组)、fn(回调函数)和init(初始值)。
函数的作用是将数组中的每个元素都传递给回调函数进行处理,并返回最终的计算结果。
首先,函数会判断数组的长度是否为0,若是则直接返回初始值。
接下来,定义一个变量val并将其初始化为初始值。
然后,通过一个for循环遍历数组中的每个元素,每次循环都将当前元素和val作为参数传递给回调函数fn进行处理,得到的结果赋值给val。
最后,返回最终计算得到的val。
2627. 函数防抖
请你编写一个函数,接收参数为另一个函数和一个以毫秒为单位的时间 t
,并返回该函数的 函数防抖 后的结果。
函数防抖 方法是一个函数,它的执行被延迟了 t
毫秒,如果在这个时间窗口内再次调用它,它的执行将被取消。你编写的防抖函数也应该接收传递的参数。
例如,假设 t = 50ms
,函数分别在 30ms
、 60ms
和 100ms
时调用。前两个函数调用将被取消,第三个函数调用将在 150ms
执行。如果改为 t = 35ms
,则第一个调用将被取消,第二个调用将在 95ms
执行,第三个调用将在 135ms
执行。
上图展示了了防抖函数是如何转换事件的。其中,每个矩形表示 100ms,反弹时间为 400ms。每种颜色代表一组不同的输入。
请在不使用 lodash 的 _.debounce()
函数的前提下解决该问题。
示例
输入: t = 50 calls = [ {"t": 50, inputs: [1]}, {"t": 75, inputs: [2]} ] 输出:[{"t": 125, inputs: [2]}] 解释: let start = Date.now(); function log(...inputs) { console.log([Date.now() - start, inputs ]) } const dlog = debounce(log, 50); setTimeout(() => dlog(1), 50); setTimeout(() => dlog(2), 75); 第一次调用被第二次调用取消,因为第二次调用发生在 100ms 之前 第二次调用延迟 50ms,在 125ms 执行。输入为 (2)。
题解
function debounce(func, t) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, t);
};
}
这个函数接受两个参数:func
表示要进行函数防抖的函数,t
表示延迟的时间窗口。
在内部,我们定义了一个变量 timeout
用于存储定时器的标识。然后,返回一个立即执行函数,这个函数接受参数 args
作为传递给被防抖的函数 func
的参数。
每当调用该函数时,我们首先清除之前的定时器,然后设置一个新的定时器,在延迟时间 t
后调用函数 func
。这样,如果在延迟时间窗口内再次调用该函数,之前的定时器将被清除,然后重新设置一个新的定时器。
这样就实现了函数防抖:只有当在延迟时间窗口内没有再次调用该函数时,才会真正执行函数 func
。如果在延迟时间窗口内再次调用该函数,上一个定时器会被取消,因此函数不会被执行。
❤❤❤❤❤❤------this------❤❤❤❤❤❤
在函数防抖的实现中,使用func.apply(this, args)
来调用被防抖的函数func
,其中this
的值是由调用防抖函数的上下文确定的。
当使用箭头函数创建防抖函数时,箭头函数没有独立的执行上下文,它会继承上一级作用域的this
值,因此在防抖函数中,this
的值将与上一级作用域中的this
相同。
例如,在全局作用域中调用防抖函数时,防抖函数中的this
将指向全局对象(浏览器中是window
对象):
function debounce(func, t) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, t);
};
}
function example() {
console.log(this); // global object (e.g. window)
}
const debouncedExample = debounce(example, 500);
debouncedExample();
而当在对象的方法中调用防抖函数时,防抖函数中的this
将指向该对象:
const obj = {
name: 'example',
debouncedMethod: debounce(function () {
console.log(this.name); // example
}, 500)
};
obj.debouncedMethod();
总的来说,防抖函数内部的this
指向是由调用防抖函数的上下文决定的,可以通过箭头函数继承上一级作用域的this
值,或者使用apply
、call
等方法来显式指定this
的值。