1. 单例模式的经典实现
首先,来看一道关于单例模式的经典面试题:
const getSingleton = (function() {
let instance; // 闭包中的私有变量,保存唯一实例
class Singleton {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
// 返回获取实例的函数
return function(name) {
if (!instance) {
instance = new Singleton(name);
}
return instance;
};
})();
// 测试:多次调用,得到同一个实例
const s1 = getSingleton('实例1');
const s2 = getSingleton('实例2');
console.log(s1 === s2); // true(证明是同一个实例)
s1.sayName(); // 输出"实例1"(第二次调用的参数不生效,因为实例已存在)
解析:
通过闭包将 instance 变量私有化,确保无论调用多少次 getSingleton,都只会返回同一个实例。这是前端面试中常见的单例模式实现方式。
2. 商铺价格与预算的高效查找
题目描述
给定一个商铺价格数组
prices和一个预算金额budget,请编写一个函数,返回有多少个商铺的价格 ≤ budget。
假设prices数组可能非常大(长度超过 10 万),需要考虑性能优化。
例如:
const prices = [1, 4, 2, 3, 5];
console.log(findAffordableShops(prices, 3)); // 输出3(价格≤3的商铺有3家)
朴素解法
最直接的思路是遍历数组,统计价格小于等于 budget 的商铺数量,时间复杂度为 O(n):
function findAffordableShops(prices, budget) {
let count = 0;
for (let price of prices) {
if (price <= budget) count++;
}
return count;
}
优化思路
如果多次查询不同的 budget,每次都遍历数组效率较低。可以先对数组排序,然后用二分查找,时间复杂度降为 O(n log n)(排序)+ O(log n)(查找)。
二分查找实现:
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
排序放在哪里最合适?
-
每次查找都排序?
不划算,O(n log n) > O(n),还不如直接遍历。 -
全局排序?
虽然高效,但会污染全局变量,不推荐。 -
闭包+单例模式?
用闭包缓存排序结果,既不污染全局,又能保证只排序一次,后续查找直接用缓存,效率高。
最优实现
function findAffordableShops(arr, target) {
let sortedArr = null;
return function() {
if (!sortedArr) {
sortedArr = [...arr].sort((a, b) => a - b);
}
// 找到第一个大于 target 的位置,该位置即为价格 ≤ target 的商铺数量
return binarySearch(sortedArr, target + 1);
}
}
const prices = [1, 4, 2, 3, 5];
const findShops = findAffordableShops(prices, 3);
console.log(findShops()); // 输出3
总结:
- 单例模式和闭包结合,可以优雅地缓存数据,避免重复计算和全局污染。
- 面对大数据量和多次查询场景,排序+二分查找+闭包缓存是高效且优雅的解法。