跳到主要内容

如何实现一个简单的 Promise

https://q.shanyue.tech/fe/js/23

js 中什么是 softbind,与 bind 有什么区别,如何实现

bind 函数多次调用会已第一次绑定的 this 为准,softbind 已最后一次绑定传入的 this 为准;

Function.prototype.softBind = function(context, ...args) {
const originalFunction = this; // 保存原始函数
return function(...newArgs) {
const currentContext = this instanceof originalFunction ? this : context;
return originalFunction.apply(currentContext, args.concat(newArgs));
};
};

https://q.shanyue.tech/fe/js/33

如何实现 promise.map,限制 promise 并发数

首先看 Promise.all 方法,很简单,他会等所有 Promise 执行完毕后,把结果放在数组里,注意这里 Promise 的执行不存在什么顺序还是并发问题,因为 Promise 本身就代表一个已经执行的过程,所以 Promise.all 就是在等所有过程结束,代码:

Promise.all(data.map((v, k) => makePromise(k, v))).then(res => {
console.log(res);
});

如果使用 Promise.map 方法,这个相当于把常见的 Array.map 创建 Promise 的过程和 Promise.all 结合起来,所以用 Promise.map 来实现上例的代码是:

Promise.map(data, (v, k) => makePromise(k, v)).then(res => {
console.log(res)
});

要实现一个 promise.map 函数,限制 Promise 的并发数,可以使用以下步骤:

  1. 创建一个函数,它接收一个数组和一个异步处理函数。
  2. 使用一个队列来管理当前正在执行的 Promise。
  3. 控制并发数,确保一次只执行指定数量的 Promise。

示例代码:

function promiseMap(array, asyncFunc, concurrency) {
const results = new Array(array.length); // 存储结果
let currentIndex = 0; // 当前处理的索引
let activePromises = 0; // 当前活动的 Promise 数量

return new Promise((resolve, reject) => {
function enqueue() {
if (currentIndex === array.length && activePromises === 0) {
// 所有 Promise 完成,返回结果
return resolve(results);
}

while (activePromises < concurrency && currentIndex < array.length) {
const index = currentIndex++; // 获取当前索引并递增
activePromises++; // 增加活动的 Promise 计数

// 调用异步函数并处理结果
asyncFunc(array[index])
.then(result => {
results[index] = result; // 存储结果
})
.catch(reject) // 捕获错误
.finally(() => {
activePromises--; // Promise 完成,减少活动计数
enqueue(); // 继续处理下一个
});
}
}

enqueue(); // 启动初始的 Promise
});
}

// 使用示例
const delay = (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms));

const tasks = [1000, 2000, 1500, 3000, 2500].map(ms => () => delay(ms, ms));

promiseMap(tasks.map(task => task()), (task) => task, 2)
.then(results => {
console.log('Results:', results); // 输出结果
})
.catch(err => {
console.error('Error:', err);
});

详细说明

  1. promiseMap 函数:

    • 接受三个参数:一个数组 array、一个异步处理函数 asyncFunc,和一个限制并发数的 concurrency
  2. 结果数组:

    • results 用于存储每个 Promise 的结果,按照原数组的顺序。
  3. 活动 Promise 计数:

    • activePromises 用于跟踪当前正在执行的 Promise 数量。
  4. enqueue 函数:

    • 这个函数负责在当前并发数未达到限制时,启动新的 Promise。
    • 它会检查是否所有 Promise 都已完成,并在适当的时候递归调用自己,以继续处理下一个 Promise。
  5. 使用示例:

    • 创建一个 delay 函数模拟异步操作,并使用 promiseMap 来处理这些异步操作,限制并发为 2。

总结

通过这种方式,你可以实现一个 promise.map 函数,能够限制 Promise 的并发数。这在处理大量异步操作时非常有用,能够有效控制资源使用。如果有其他问题或需要进一步的帮助,请随时问我!

https://q.shanyue.tech/fe/js/89

如何实现 compose 函数,进行函数合成

在 JavaScript 中,函数合成(function composition)是将多个函数组合成一个函数,使得每个函数的输出可以作为下一个函数的输入。下面是如何实现一个 compose 函数的示例。

实现 compose 函数

这里提供一个简单的实现,同时支持多个函数的组合:

function compose(...fns) {
return function (initialValue) {
return fns.reduceRight((acc, fn) => fn(acc), initialValue);
};
}

// 使用示例
const add = x => x + 1;
const multiply = x => x * 2;
const subtract = x => x - 3;

// 组合函数
const composedFunction = compose(subtract, multiply, add);

// 调用组合函数
const result = composedFunction(5); // ((5 + 1) * 2) - 3
console.log(result); // 输出: 9

详细说明

  1. compose 函数:

    • 接收一个或多个函数作为参数(...fns),并返回一个新的函数。
    • 新函数接受一个初始值 initialValue
  2. reduceRight:

    • 使用 reduceRight 方法从右到左应用函数组合,确保最后一个函数的输出作为倒数第二个函数的输入,依此类推。
  3. 函数应用:

    • reduceRight 中,acc 是累积的值,fn 是当前处理的函数。每次调用 fn(acc),将 acc 更新为当前函数的输出。

使用示例

  • 定义了一些简单的函数:addmultiplysubtract
  • 使用 compose 将这些函数组合起来。
  • 调用组合函数 composedFunction(5),计算过程如下:
    • 5 + 1 = 6
    • 6 * 2 = 12
    • 12 - 3 = 9

总结

通过实现 compose 函数,可以轻松地将多个函数组合在一起,从而增强代码的可重用性和可读性。你可以根据需要扩展这个实现,例如支持异步函数或处理错误。如果有其他问题或需要进一步的帮助,请随时问我!

https://q.shanyue.tech/fe/js/182

如何实现类似 lodash.get 函数

function get(obj, path, defaultValue) {
// 将 path 转换为数组
const keys = Array.isArray(path) ? path : path.split('.');

// 使用 reduce 遍历嵌套属性
const result = keys.reduce((acc, key) => {
// 如果 acc 为 undefined,返回 undefined
if (acc === undefined || acc === null) {
return undefined;
}
return acc[key]; // 访问嵌套属性
}, obj);

// 如果 result 为 undefined,则返回 defaultValue
return result === undefined ? defaultValue : result;
}

// 使用示例
const data = {
user: {
name: 'Alice',
address: {
city: 'Wonderland',
zip: 12345
}
}
};

console.log(get(data, 'user.name')); // 输出: Alice
console.log(get(data, 'user.address.city')); // 输出: Wonderland
console.log(get(data, 'user.age', 'N/A')); // 输出: N/A
console.log(get(data, 'user.address.zip')); // 输出: 12345
console.log(get(data, 'user.address.country', 'Unknown')); // 输出: Unknown

https://q.shanyue.tech/fe/js/199

如何实现一个 flatMap 函数 (头条)

function flatMap(arr, fn) {
return arr.reduce((acc, cur) => {
return acc.concat(fn(cur));
}, []);
}

// 使用示例
const arr = [1, 2, 3];
const fn = x => [x, x * 2];

console.log(flatMap(arr, fn)); // 输出: [1, 2, 2, 4, 3, 6]

https://q.shanyue.tech/fe/js/229

如何实现一个 async/await

/**
* async的执行原理
* 其实就是自动执行generator函数
* 暂时不考虑genertor的编译步骤(更复杂)
*/

const getData = () =>
new Promise((resolve) => setTimeout(() => resolve("data"), 1000));

// 这样的一个async函数 应该再1秒后打印data
async function test() {
const data = await getData();
console.log("data: ", data);
const data2 = await getData();
console.log("data2: ", data2);
return "success";
}

// async函数会被编译成generator函数 (babel会编译成更本质的形态,这里我们直接用generator)
function* testG() {
// await被编译成了yield
const data = yield getData();
console.log("data: ", data);
const data2 = yield getData();
console.log("data2: ", data2);
return "success";
}

function asyncToGenerator(generatorFunc) {
return function () {
const gen = generatorFunc.apply(this, arguments);

return new Promise((resolve, reject) => {
function step(key, arg) {
let generatorResult;
try {
generatorResult = gen[key](arg);
} catch (error) {
return reject(error);
}

const { value, done } = generatorResult;

if (done) {
return resolve(value);
} else {
return Promise.resolve(value).then(
function onResolve(val) {
step("next", val);
},
function onReject(err) {
step("throw", err);
},
);
}
}
step("next");
});
};
}

const testGAsync = asyncToGenerator(testG);
testGAsync().then((result) => {
console.log(result);
});

https://q.shanyue.tech/fe/js/241

如何使用 async/await 实现 Promise.all 的效果

const all = (list) => {
const res = new Promise((resolve, reject) => {
let length = list && list.length
let count = 0
let result = []
if(!list || list.length === 0) {
resolve(result)
}
list.forEach(async (item, index) => {
try {
const res = await item
result[index] = res
count ++
if(count === length) {
resolve(result)
}
} catch(err) {
reject(err)
}
});
})
return res
}

https://q.shanyue.tech/fe/js/242

使用 js 实现一个 lru cache

class Node {
constructor(key, value) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}

class LRUCache {
constructor(capacity) {
this.capacity = capacity; // 缓存容量
this.cache = new Map(); // 哈希表存储缓存
this.head = new Node(null, null); // 虚拟头节点
this.tail = new Node(null, null); // 虚拟尾节点
this.head.next = this.tail; // 连接头尾
this.tail.prev = this.head;
}

// 将节点移动到链表的头部
_moveToHead(node) {
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = this.head;
node.next = this.head.next;
this.head.next.prev = node;
this.head.next = node;
}

// 删除链表的尾部节点
_removeTail() {
const node = this.tail.prev;
this.tail.prev = node.prev;
node.prev.next = this.tail;
this.cache.delete(node.key);
}

// 获取缓存
get(key) {
if (!this.cache.has(key)) {
return -1; // 如果不存在,返回 -1
}
const node = this.cache.get(key);
this._moveToHead(node); // 移动到头部
return node.value; // 返回值
}

// 设置缓存
put(key, value) {
if (this.cache.has(key)) {
const node = this.cache.get(key);
node.value = value; // 更新值
this._moveToHead(node); // 移动到头部
} else {
const newNode = new Node(key, value);
this.cache.set(key, newNode); // 添加到哈希表

this._moveToHead(newNode); // 移动到头部

if (this.cache.size > this.capacity) {
this._removeTail(); // 超过容量时移除尾部
}
}
}
}

// 使用示例
const lruCache = new LRUCache(2);
lruCache.put(1, 1); // 缓存是 {1=1}
lruCache.put(2, 2); // 缓存是 {1=1, 2=2}
console.log(lruCache.get(1)); // 返回 1
lruCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
console.log(lruCache.get(2)); // 返回 -1 (未找到)
lruCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
console.log(lruCache.get(1)); // 返回 -1 (未找到)
console.log(lruCache.get(3)); // 返回 3
console.log(lruCache.get(4)); // 返回 4

https://q.shanyue.tech/fe/js/251

实现一个 once 函数,记忆返回结果只执行一次

function once(fn) {
let executed = false; // 标志位,表示函数是否已执行
let result; // 存储返回结果

return function(...args) {
if (!executed) {
executed = true; // 标记为已执行
result = fn(...args); // 执行并存储结果
}
return result; // 返回结果
};
}

// 使用示例
const add = (a, b) => a + b;
const onceAdd = once(add);

console.log(onceAdd(1, 2)); // 输出: 3
console.log(onceAdd(4, 5)); // 仍然输出: 3 (函数未再执行)

https://q.shanyue.tech/fe/js/406

如何实现一个无限累加的 sum 函数

https://q.shanyue.tech/fe/js/428

//更多描述 实现一个 sum 函数如下所示:
sum(1, 2, 3).valueOf(); //6
sum(2, 3)(2).valueOf(); //7
sum(1)(2)(3)(4).valueOf(); //10
sum(2)(4, 1)(2).valueOf(); //9
sum(1)(2)(3)(4)(5)(6).valueOf(); // 21
function sum(initialValue = 0) {
let total = initialValue; // 初始化总值

// 定义一个累加函数
function add(value) {
total += value; // 累加传入的值
return add; // 返回自身,以便链式调用
}

// 添加一个方法来获取总值
add.valueOf = function() {
return total; // 返回当前总值
};

return add; // 返回累加函数
}

// 使用示例
const result = sum(5)(10)(15); // 5 + 10 + 15
console.log(result.valueOf()); // 输出: 30

console.log(sum()(1)(2)(3).valueOf()); // 输出: 6

JS 如何实现一个同步的 sleep 函数

function sleep(milliseconds) {
const start = Date.now();
while (Date.now() - start < milliseconds) {
// 空循环,阻塞线程
}
}

// 使用示例
console.log('Start');
sleep(2000); // 暂停 2 秒
console.log('End');

https://q.shanyue.tech/fe/js/429

JS 如何实现一个 sleep/delay 函数

const sleep = (seconds) =>
new Promise((resolve) => setTimeout(resolve, seconds));

function delay(func, seconds, ...args) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Promise.resolve(func(...args))
.then(resolve)
.catch(reject);
}, seconds);
});
}

https://q.shanyue.tech/fe/js/442

如何实现一个 sample 函数,从数组中随机取一个元素

function sample(array) {
if (array.length === 0) {
return undefined; // 如果数组为空,返回 undefined
}
const randomIndex = Math.floor(Math.random() * array.length); // 生成随机索引
return array[randomIndex]; // 返回随机元素
}

// 使用示例
const numbers = [1, 2, 3, 4, 5];
const randomElement = sample(numbers);
console.log(randomElement); // 输出: 数组中的随机元素

https://q.shanyue.tech/fe/js/443

如何实现一个函数 lodash.merge

function merge(target, ...sources) {
for (const source of sources) {
for (const key in source) {
// 确保源对象的属性是对象且目标对象的对应属性也是对象
if (isObject(source[key]) && isObject(target[key])) {
// 递归合并
target[key] = merge(target[key], source[key]);
} else {
// 否则直接赋值
target[key] = source[key];
}
}
}
return target;
}

// 辅助函数:判断一个值是否是对象
function isObject(value) {
return value !== null && typeof value === 'object';
}

// 使用示例
const obj1 = { a: 1, b: { x: 1 } };
const obj2 = { b: { y: 2 }, c: 3 };
const obj3 = { a: 2, b: { x: 3 } };

const result = merge({}, obj1, obj2, obj3);
console.log(result);
// 输出: { a: 2, b: { x: 3, y: 2 }, c: 3 }

https://q.shanyue.tech/fe/js/498

实现一个 inherits 函数进行继承

function inherits(Child, Parent) {
// 设置 Child 的原型为 Parent 的实例
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // 确保 constructor 指向 Child
}

// 使用示例
function Parent(name) {
this.name = name;
}

Parent.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};

function Child(name, age) {
Parent.call(this, name); // 调用 Parent 构造函数
this.age = age;
}

// 使用 inherits 实现继承
inherits(Child, Parent);

// 添加 Child 的方法
Child.prototype.sayAge = function() {
console.log(`I am ${this.age} years old`);
};

// 测试
const childInstance = new Child('Alice', 10);
childInstance.sayHello(); // 输出: Hello, my name is Alice
childInstance.sayAge(); // 输出: I am 10 years old

https://q.shanyue.tech/fe/js/576

如何实现一个深比较的函数 deepEqual

function deepEqual(obj1, obj2) {
// 如果两个值是相同的引用,直接返回 true
if (obj1 === obj2) return true;

// 如果任一值为 null 或不是对象,返回 false
if (obj1 == null || obj2 == null || typeof obj1 !== 'object' || typeof obj2 !== 'object') {
return false;
}

// 获取对象的所有键
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);

// 比较键的数量
if (keys1.length !== keys2.length) return false;

// 递归比较每个键的值
for (const key of keys1) {
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
return false;
}
}

return true; // 如果所有键都匹配,返回 true
}

// 使用示例
const objA = { a: 1, b: { c: 2 } };
const objB = { a: 1, b: { c: 2 } };
const objC = { a: 1, b: { c: 3 } };

console.log(deepEqual(objA, objB)); // 输出: true
console.log(deepEqual(objA, objC)); // 输出: false
console.log(deepEqual(objA, null)); // 输出: false
console.log(deepEqual(objA, objA)); // 输出: true

https://q.shanyue.tech/fe/js/614

实现二进制与十进制的互相转化的两个函数

// 十进制转二进制
function decimalToBinary(decimal) {
if (decimal < 0) {
throw new Error("输入必须是非负整数");
}
return decimal.toString(2); // 使用 toString(2) 转换为二进制
}

// 二进制转十进制
function binaryToDecimal(binary) {
if (!/^[01]+$/.test(binary)) {
throw new Error("输入必须是有效的二进制字符串");
}
return parseInt(binary, 2); // 使用 parseInt 转换为十进制
}

// 使用示例
const decimal = 10;
const binary = decimalToBinary(decimal);
console.log(`十进制 ${decimal} 转换为二进制: ${binary}`); // 输出: 10 转换为 1010

const binaryInput = "1010";
const decimalOutput = binaryToDecimal(binaryInput);
console.log(`二进制 ${binaryInput} 转换为十进制: ${decimalOutput}`); // 输出: 1010 转换为 10

https://q.shanyue.tech/fe/js/684

如何取得一个数字的小数部分与整数部分

function getIntegerAndDecimalParts(number) {
const integerPart = Math.floor(number); // 获取整数部分
const decimalPart = number - integerPart; // 计算小数部分

return {
integer: integerPart,
decimal: decimalPart
};
}

// 使用示例
const number = 5.75;
const parts = getIntegerAndDecimalParts(number);
console.log(`整数部分: ${parts.integer}`); // 输出: 整数部分: 5
console.log(`小数部分: ${parts.decimal}`); // 输出: 小数部分: 0.75

https://q.shanyue.tech/fe/js/764

实现 batchFn 函数,可以批量执行函数

function batchFn(functions, ...args) {
// 使用 map 遍历函数数组并调用每个函数
return functions.map(fn => fn(...args));
}

// 使用示例
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
const subtract = (a, b) => a - b;

const functions = [add, multiply, subtract];
const results = batchFn(functions, 5, 3);

console.log(results); // 输出: [8, 15, 2]

https://q.shanyue.tech/fe/js/800