在 leetcode 看到 JavaScript 14 天编程挑战 的题目。
这里分享下自己的做题思路,期望同学面对同类型问题时,有所启发。
下面是以解决问题依赖的知识做的分类。
闭包
闭包中的变量是封闭的,对外暴露的接口相对可控,有利于储存一些私有的内容。
涉及的题目
计数器
计数器 函数最初返回 n,每次调用它时返回前一个值加 1 的值 ( n , n + 1 , n + 2 ,等等)。
思路
- 利用闭包去储存
计数
return
的函数修改闭包中的计数
,并返回出去
代码
var createCounter = function (n) {
/* 闭包区域 */
let preN = n;
return function () {
return preN++; // return 是优先返回 preN 再做 ++
};
};
记忆函数
记忆函数 是一个对于相同的输入永远不会被调用两次的函数。相反,它将返回一个缓存值。
思路
- 使用闭包去储存之前的调用结果
- 使用
args.join(',')
计算出需要缓存的key
代码
function memoize(fn) {
/* 闭包区域 */
const cacheMap = new Map();
const getCacheKey = (...args) => args.join(",");
return function (...args) {
const key = getCacheKey(...args);
return cacheMap.has(key)
? cacheMap.get(key)
: cacheMap.set(key, fn(...args)).get(key);
};
}
基本数组转换
对于数组数据的转换,已经提供了很多 API
。例如map
、filter
等。使用 循环即可模拟这些方法。
其它
分享一些关于 callback
使用技巧,避免手动传递参数。
以 filter
使用为例。
// 有些同学在这里将 callback 的参数做了二次传递,可以简化成另一种方式
[].filter((v) => Boolean(v))
// 简化版
[].filter(Boolean)
Reduce
根据 reduce
自身的特点。只要是 下一步依赖上一步
的场景,就都可以尝试使用reduce
。
例如累加、斐波那契数列等。中间件模式,也可以使用reduce
实现。
涉及的题目
复合函数
请你编写一个函数,它接收一个函数数组 [f1, f2, f3,…, fn] ,并返回一个新的函数 fn ,它是函数数组的 复合函数 。
[f(x), g(x), h(x)] 的 复合函数 为 fn(x) = f(g(h(x))) 。
思路
- 将
上一个函数
的返回结果传递给下一个函数
,可以直接使用reduceRight
。
代码
var compose = function (functions) {
return function (x) {
return functions.reduceRight((acc, fn) => fn(acc), x);
};
};
分块数组
给定一个数组 arr 和一个块大小 size ,返回一个 分块 的数组。分块的数组包含了 arr 中的原始元素,但是每个子数组的长度都是 size 。
思路
- 判断分块数组最后一个元素是否小于给定的 size,
- 如果小于则 push
item
到分块数组最后的数组中 - 如果大于等于则 push
[item]
到分块数组中
- 如果小于则 push
代码
var chunk = function (arr, size) {
if (!arr.length) return [];
return arr.reduce(
(acc, item) => {
acc[acc.length - 1].length < size
? acc[acc.length - 1].push(item)
: acc.push([item]);
return acc;
},
[[]]
);
};
Promise
Promise 提供了6 种静态方法去满足不同场景。
涉及的题目
Promise 对象池
请你编写一个异步函数 promisePool ,它接收一个异步函数数组 functions 和 池限制 n。它应该返回一个 promise 对象,当所有输入函数都执行完毕后,promise 对象就执行完毕。
思路
下面是具体思路:
- 声明两个数组,分别用于储存正在执行的函数
execPools
和 等待执行的函数waitPools
- 取出
waitPools
的函数,并用warperFunction
包裹一下,添加到execPools
中 - 在函数执行完时,将执行完的函数,从
execPools
剔除,执行步骤 2
- 当
execPools
和execPools
都为空时,则表示所有任务都执行结束
warperFunction
使用了 装饰器模式
。 它适用于对原有方法的增强,在原函数增加了调整储存 执行中 和 待执行 函数的数组的逻辑。
代码
var promisePool = async function (functions, n) {
return new Promise((res) => {
let waitPools = [...functions];
let execPools = [];
const isFinish = () => execPools.length === 0 && waitPools.length === 0;
const removeFinishFunction = (func) => {
execPools = execPools.filter((v) => v !== func);
};
const warperFunction = (fn) => {
const newFunction = () => {
fn().then(() => {
removeFinishFunction(newFunction);
isFinish() ? res() : execNext();
});
};
return newFunction;
};
const execNext = () => {
execPools.push(warperFunction(waitPools.shift()));
};
const init = () => {
let i = 0;
while (i < n && waitPools.length) {
execNext();
i++;
}
};
init();
});
};
其它
看到其他同学的题解,非常精简,这里也分享下。
var promisePool = async function (functions, n) {
return Promise.all(
Array.from({
length: n,
}).map(async () => {
while (functions.length > 0) {
await functions.shift()();
}
})
);
};
哈希表
哈希表一般用于储存关联关系,方便查找。
涉及的题目
递归 + 回溯
递归一般用于重复处理相同的场景。
回溯
就是在递归前记录一下操作,递归后再还原一下操作。 扁平化嵌套数组 就用到了回溯。
涉及的题目
将对象转换为 JSON 字符串
现给定一个对象,返回该对象的有效 JSON 字符串。你可以假设这个对象只包括字符串、整数、数组、对象、布尔值和 null。返回的字符串不能包含额外的空格。键的返回顺序应该与 Object.keys() 的顺序相同。
思路
- 使用原生的
JSON.stringify
转化一下对象,分析转换后字符串的结构- 怎么拼接不同的变量
- 怎么拼接逗号
- 分别处理不同数据类型
- 直接拼接基础数据类型
- 循环
Array
和Object
中的元素,重复(递归)第 2 步操作
代码
var jsonStringify = function (object) {
const stringify = (string, data) => {
const type = Object.prototype.toString
.call(data)
.match(/\[object (.+)\]/)[1];
switch (type) {
case "Array":
return `[${data.reduce((acc, cur) => {
return `${acc}${acc === "" ? "" : ","}${stringify("", cur)}`;
}, string)}]`;
case "Object":
return `{${Object.keys(data).reduce((acc, key) => {
return `${acc}${acc === "" ? "" : ","}"${key}":${stringify(
"",
data[key]
)}`;
}, string)}}`;
case "String":
return `"${data}"`;
default:
return String(data);
}
};
return stringify("", object);
};
扁平化嵌套数组
请你编写一个函数,它接收一个 多维数组 arr 和它的深度 n ,并返回该数组的 扁平化 后的结果。
思路
- 循环处理数组中的元素
- 记录当前递归的深度
- 碰到非数组元素,则直接添加到展开的数组中
- 碰到数组,如果深度小于等于 n, 则重复(递归)第 1 步操作,否则直接添加到展开的数组中
有一个核心问题,就是递归处理时,层级到了第几层,就用到了回溯。下面是回溯的模板代码
递归前 ==> 记录信息
recursion() // 递归处理
递归后 ==> 回撤信息
代码
var flat = function (arr, n) {
if (n === 0) return arr;
const result = [];
let dep = 1; // 记录深度
const flatArray = (source) => {
let i = 0;
const len = source.length;
while (i < len) {
if (Array.isArray(source[i]) && dep <= n) {
dep++; // 记录信息
flatArray(source[i]);
dep--; // 回撤信息
} else {
result.push(source[i]);
}
i++;
}
};
flatArray(arr);
return result;
};
原型链
原型链主要应用的场景是继承。JavaScript 对象有一个指向一个原型对象的链。更多 MDN
涉及的题目
检查是否是类的对象实例
请你编写一个函数,检查给定的对象是否是给定类或超类的实例。
思路
- 判断当前对象的
__proto__
属性, 和类的prototype
是否是同一个对象 - 基于原型链的特性,如果当前对比的结果不是同一个对象,我们可以基于原型链的查找规则(类似
xxx.__proto__.__proto__.__proto__
),继续向上查找对比
代码
var checkIfInstanceOf = function (obj, classFunction) {
while (obj != null) {
if (obj.__proto__ === classFunction?.prototype) return true;
obj = obj.__proto__;
}
return false;
};
生成器
初次调用生成器函数不执行任何代码,而是返回一种称为 Generator 的迭代器。
通过调用生成器的下一个方法消耗值时,Generator 函数将执行,直到遇到 yield 关键字。
斐波那契数列介绍。形如[0, 1, 1, 2, 3 ,5, 8]
涉及的题目
嵌套数组生成器
现给定一个整数的 多维数组 ,请你返回一个生成器对象,按照 中序遍历 的顺序逐个生成整数。中序遍历 是从左到右遍历每个数组。
思路
- 循环处理数组中的元素
- 碰到非数组元素,则在元素前添加
yield
(用于暂停和恢复生成器函数) 关键字 - 碰到数组,则在递归处理当前数组前添加
yield*
(用于委托给另一个 generator 或可迭代对象)关键字
- 碰到非数组元素,则在元素前添加
代码
var inorderTraversal = function* (arr) {
let i = 0;
const len = arr.length;
while (i < len) {
Array.isArray(arr[i]) ? yield* inorderTraversal(arr[i]) : yield arr[i];
i++;
}
};