排序算法
如何实现一个快速排序
时间复杂度:O(nlogN)
快排就是选择一个标准值,一分为二,比它小的往左边数组放,比它大的往右边数组放。return 的时候,再递归调用排序函数,把左数组和右数组排序后合并到一起。有个注意点,选中的标准索引,循环中要跳过。而且对比的是 index,不是 value。因为 sValue 可能有重复的。
记忆点:一刀切两半,真快速!
function quickSort(array) {
if (array.length <= 1) return array;
var sIndex = Math.floor(array.length / 2);//选择一个标准值,从中间找了一个
var sValue = array[sIndex];//选择的标准值
var left = [];
var right = [];
for (let i = 0; i < array.length; i++) {
if (i === sIndex) {//关键点,选中的基准值就不要排序了
continue;
}
if (array[i] < sValue) {
left.push(array[i]);
} else {
right.push(array[i]);
}
}
return [...quickSort(left), sValue, ...quickSort(right)];
}
let arr = [4, 3, 7, 5, 2, 9, 6, 8, 1];
console.log(quickSort(arr));
如何实现一个选择排序
时间复杂度:O(n^2)
选择排序,就是从没排序的数组中找到一个最小(大)的元素,然后把它放到已排序的位置。不断缩小未排序范围,最终就是全部排了。
记忆点:选择选择,挑选最值,扔到一遍,继续挑选,直到最后。
function selectionSort(array) {
let length = array.length;
if (length <= 1) {
return array;
}
// 假设选择第一个为已排序数组中的最小值,那么未排序范围就是[1,length-1]
for (let i = 0; i < length; i++) {
let minIndex = i;//选定这个位置开始为要放置最值的位置,先默认它是最值,后面找到更小的就换位置了
for (let j = i + 1; j < length; j++) {//从i+1开始,每个跟minIndex对比,选出最小的
if (array[j] <= array[minIndex]) {
minIndex = j;
}
}
if (minIndex !== i) {//找到比minIndex小的后,交换位置,i也随着+1
[array[i], array[minIndex]] = [array[minIndex], array[i]]
}
}
return array;
}
let arr = [4, 3, 7, 5, 2, 9, 6, 8, 1];
console.log(selectionSort(arr));
如何实现一个冒泡排序
时间复杂度:O(n^2)
两个相邻的值对比,较大的往右冒泡,直到最后,这样最大的就冒泡的最后面了,然后从头继续冒泡。
记忆点:两两对比,大的往后,冒到最后,压缩空间。
function bubbleSort(array) {
let length = array.length;
if (length <= 1) {
return array;
}
for (let i = length - 1; i > 0; i--) {//不断收缩未排序的范围
for (let j = 0; j < i; j++) {
if (array[j] > array[j + 1]) {
[array[j], array[j + 1]] = [array[j + 1], array[j]]
}
}
}
return array;
}
let arr = [4, 3, 7, 5, 2, 9, 6, 8, 1];
console.log(bubbleSort(arr));
如何实现一个归并排序
归并排序的核心逻辑就是对两个数组的合并,拿到两个数组,怎样把它合成 1 个数组是实现的核心点。最后根据递归,从大数组拆到最小的数组,合并后开始不断往上返回成大数组。
有个注意点:mergetSort 函数的返回值,是 merge(mergeSort(left), mergeSort(right)),也就是说,合并的是排完序的两个数组
记忆点:归并归并,先递归,后合并。递归不断拆数组,合并来个循环添加。
function merge(arr1, arr2) {
if (arr1.length <= 0 || arr2.length <= 0) {
return [...arr1, ...arr2];
}
var result = [];
let i = 0, j = 0;
while (i < arr1.length && j < arr2.length) {
if (arr1[i] < arr2[j]) {
result.push(arr1[i]);
i++;
} else {
result.push(arr2[j]);
j++;
}
}
while (i < arr1.length) {
result.push(arr1[i]);
i++;
}
while (j < arr2.length) {
result.push(arr2[j]);
j++;
}
return result;
}
function mergeSort(array) {
let length = array.length;
if (length <= 1) {
return array;
}
var midIndex = Math.floor(length / 2);
var left = array.slice(0, midIndex);
var right = array.slice(midIndex);
return merge(mergeSort(left), mergeSort(right));
}
let arr = [4, 3, 7, 5, 2, 9, 6, 8, 1];
console.log(mergeSort(arr));
如何实现一个插入排序
插入排序核心的实现逻辑是,[0,i-1]是排好序的,[i,length-1]是未排好序的。拿出第 i 个位置的数,依次和前面的数作对比,如果比前面的小,就让前面的数往右挪一个位置。直到最后比前面的数大了,说明到它的位置了。把这个位置后面的位置改成当初拿出的那个 i 位置的值就好了。至此插入到了它应该呆的位置。
记忆点:找准位置,跟找座位一样,依次对比一下,比人家小,就在往前比一比,比人家大,就在人家后面坐下。
function insertSort(array) {
let length = array.length;
if (length <= 1) {
return array;
}
for (let i = 1; i < array.length; i++) {
let current = array[i];//这个很关键,取出要排的数值,然后用while作对比
let j = i - 1;
while (array[j] > current) {
array[j + 1] = array[j];//往后挪一位
j--;
}
array[j + 1] = current;
}
return array;
}
let arr = [4, 3, 7, 5, 2, 9, 6, 8, 1];
console.log(insertSort(arr));
如何实现一个堆排序
核心是构建一个堆结构,这个必须要背会 Heap 数据结构的写法,尤其是 up 和 down 的逻辑。
up 的逻辑就是往上走,那么只要找到它父亲的值作对比,如果比他父亲小,那就交换,并且更新 pIndex,否则就是找到位置了,break 掉就好了。
down 的逻辑就是往下沉,那么在保证比较的 index<=lastIndex 的情况下,找到作子节点和右子节点,分别对比,看谁小,findIndex 就等于谁。对比完左右,如果 findIndex 变了,说明无论是左还是右都往下沉了,那么要交换元素,并且更新 index 为 findIndex。如果 dindIndex 没变化,说明到位置了,break 掉。
核心点:背住 heap 的写法。
class Heap {
constructor() {
this.data = [];
}
swap(i, j) {
[this.data[i], this.data[j]] = [this.data[j], this.data[i]];
}
// 添加元素
add(v) {
this.data.push(v);
this.up();//从下往上走
}
// 取堆顶
peek() {
return this.size > 0 ? this.data[0] : null;
}
// 删除堆顶
pop() {
if (this.size <= 0) {
return null;
}
this.swap(0, this.size - 1);
this.down(0);//从顶部往下走
return this.data.pop();
}
// 数组大小
get size() {
return this.data.length;
}
// 向上,只需要跟他爸爸比大小
up() {
let index = this.size - 1;
while (index >= 0) {
let pIndex = Math.floor((index - 1) / 2);
if (this.data[pIndex] > this.data[index]) {
this.swap(pIndex, index);
index = pIndex;
} else { //他比他爸爸大,无需再往上走了
break;
}
}
}
down(index) {
let lastIndex = this.size - 1;
while (index <= lastIndex) {
let leftChildIndex = index * 2 + 1;
let rightChildIndex = index * 2 + 2;
let findIndex = index;
if (this.data[findIndex] > this.data[leftChildIndex] && leftChildIndex <= lastIndex) {
findIndex = leftChildIndex;
}
if (this.data[findIndex] > this.data[rightChildIndex] && rightChildIndex <= lastIndex) {
findIndex = rightChildIndex;
}
if (findIndex !== index) {
this.swap(findIndex, index);
index = findIndex;
} else {
break;
}
}
}
}
function heapSort(array) {
let length = array.length;
if (length <= 1) {
return array;
}
let heap = new Heap();
for (let i = 0; i < length; i++) {
heap.add(array[i]);
}
return heap.data;
}
let arr = [4, 3, 7, 5, 2, 9, 6, 8, 1];
console.log(heapSort(arr));
如何实现一个计数排序
这个就是用一个新数组当计数器,从原始数组中找到最大和最小值,然后用数组生成一个 max-min 长度的数组开始记录每个数字出现的次数。最后把数据填回原数组。
记忆点:用新数组计数,适合在一定范围内的数组排序
function countSort(array) {
if (array.length <= 1) {
return array
}
let min = array[0];
let max = array[0];
for (let i = 0; i < array.length; i++) {
if (array[i] < min) {
min = array[i];
}
if (array[i] > max) {
max = array[i];
}
}
const countArray = new Array(max - min + 1).fill(0);
for (let i = 0; i < array.length; i++) {
countArray[array[i] - min]++;
}
let arrayIndex = 0;//原数组准备排序的索引值
for (let i = 0; i < countArray.length; i++) {
let count = countArray[i];//计数器第i个位置的数字,表示min+i的数字出现的次数
while (count > 0) {
array[arrayIndex] = min + i;//原数组对应位置修改数字
arrayIndex++;//原数组往后走一位
count--;//这个数字的数量减少一个
}
}
return array;
}
let arr = [4, 3, 7, 5, 2, 9, 6, 8, 1];
console.log(countSort(arr));
TODO:如何实现一个桶排序
就是分了若干个桶,每个桶的容量可以自己定,然后将数字放到对应的桶里,然后对桶内的进行排序,最后合并。比如桶的容量是 10,0~10 放到 1 个桶,11~20 放到 1 个桶,依次类推。最后桶之间进行排序。这个方法和计数排序类似,但是增加了桶的概念。
TODO:如何实现一个基数排序
这个也类似于桶排序,基数的含义就是将桶的容量换成了 10,100,1000 这种基数,并且层层往上。
TODO:如何实现一个希尔排序
这个也是动态调整间隔来划分数据,然后间隔不断缩小,不断对内部排序。
JS 标准实现
实现一个 debounce 函数
就是不断重置 timer,直到最后一次执行
function myDebounce(fn, delay) {
let timer = null;
return function (...args) {
const context = this;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args)
}, delay);
}
}
实现一个 throttle 函数
// 基于时间戳,并考虑在间隔中触发的函数也能触发
function throttle(func, wait) {
let timer = null;
// Not that this question is slightly different where you have to save the arguments of the
// last throttled call
let lastArgs = [];
return function throttledFunc(...args) {
// Initial case timer would be null, so this would get invoked
if(timer == null) {
// Call the underlying function, then setup the timer
func.apply(this, args);
timer = setTimeout(() => {
// If there were throttled calls, run the function post timer
// with the saved arguments
if(lastArgs.length) {
func.apply(this, lastArgs);
}
// Back to initial condition
timer = null;
lastArgs = [];
}, wait);
}
else {
// Function is throttled, no call, just save the arguments and do nothing else.
lastArgs = args;
return;
}
}
}
//基于定时器
function myThrottle(fn, delay) {
let timer;
let result;
return function (...args) {
const context = this;
if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer);
result = fn.apply(context, args);
}, delay);
}
return result;
}
}
实现一个 call 函数
Function.prototype.myCall = function (context, ...args) {
context = context || window;
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return context;
}
实现一个 apply 函数
跟 call 一样,只是入参区别
Function.prototype.myApply = function (context, args) {
context = context || window;
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return context;
}
实现一个 bind 函数
Function.prototype.myBind = function (context) {
const fn = this;
return function (...args) {
return fn.apply(context, args);
}
}
实现一个 softBind 函数
softBind 函数与 bind 的区别是,当这个函数被调用时,如果它的调用者提供了不同的上下文(例如通过 call 或 apply),则优先使用调用者提供的上下文。而不是跟 bind 一样,无论调用情况,全部使用提供的 context。
Function.prototype.softBind = function (context) {
var fn = this;
var curried = [].slice.call(arguments, 1);
var bound = function (...args) {
return fn.apply(
this === window || this === global ? context : this,//这个是重点,当默认绑定到window时才会用传入的context,否则还是用原来的this
[...curried, ...args]
)
}
bound.prototype = Object.create(fn.prototype);
return bound;
}
实现一个 LRU 函数
class LRU {
constructor(capacity) {
this.capacity = capacity;
this.data = new Map();
}
get(key) {
if (!this.data.has(key)) {
return null;
}
const value = this.data.get(key);
this.data.delete(key);
this.data.set(key, value);
return value;
}
put(key, value) {
if (this.data.has(key)) {
this.data.delete(key);
}
if (this.data.size >= this.capacity) {
//重点,获取最早的key
this.data.delete(this.data.keys().next().value);
}
this.data.set(key, value);
}
}
实现一个 instanceof 函数
// 模拟 man instanceof Person
function myInstanceOf(instance, constructor) {
if (typeof constructor !== 'function') {
throw TypeError("第二个参数不是函数")
}
let proto = Object.getPrototypeOf(instance);
while (proto !== null) {
// 注意,适合构造函数的原型做对比
if (proto === constructor.prototype) {
return true
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
实现一个 new 操作符
// 模拟 var person = new Person("evin");
function myNew(constructor, ...args) {
// 创建实例对象,并将实例的原型指向构造函数的prototype
var obj = Object.create(constructor.prototype);
// 将参数传入构造函数,获取返回值
const result = constructor.apply(obj, args);
// 返回值是对象直接返回,如果不是就返回原型对象
return typeof result === 'object' && result !== null ? result : obj;
}
实现一个 shuffle 洗牌函数
// 洗牌函数,就是讲元数组打乱
function shuffle(array) {
const shuffleArray = array.slice();
// 从后往前
for (let i = shuffleArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));//生成0-i之间的随机数,也就是包含自己的i位置,也就是有可能i跟i换
[shuffleArray[i], shuffleArray[j]] = [shuffleArray[j], shuffleArray[i]];
}
return shuffleArray;
}
const originalArray = [1, 2, 3, 4, 5];
const shuffledArray = shuffle(originalArray);
console.log('原数组:', originalArray);
console.log('洗牌后的数组:', shuffledArray);
实现一个 deepCopy 函数
就是分别按照数组和对象去处理,然后递归实现。
function deepCopy(obj) {
// 检查是否是 null 或者 不是对象
if (obj === null || typeof obj !== 'object') {
return obj; // 原样返回基本类型或 null
}
// 处理数组
if (Array.isArray(obj)) {
const copy = [];
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i]); // 递归拷贝每个元素
}
return copy;
}
// 处理对象
const copy = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]); // 递归拷贝每个属性
}
}
return copy;
}
//如果需要处理循环引用,可以使用weakmap
function deepCopy(obj, cache = new WeakMap()) {
// 检查是否是 null 或者 不是对象
if (obj === null || typeof obj !== 'object') {
return obj; // 原样返回基本类型或 null
}
// 如果对象已经被拷贝过,直接返回缓存的结果
if (cache.has(obj)) {
return cache.get(obj);
}
// 处理数组
if (Array.isArray(obj)) {
const copy = [];
cache.set(obj, copy); // 将当前数组存入缓存
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i], cache); // 递归拷贝每个元素
}
return copy;
}
// 处理对象
const copy = {};
cache.set(obj, copy); // 将当前对象存入缓存
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key], cache); // 递归拷贝每个属性
}
}
return copy;
}
实现一个 浅拷贝 函数
function shallowCopy(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let clone = Array.isArray(obj) ? [] : {};
for (const key in object) {
if (obj.hasOwnProperty(key)) {
clone[key] = obj[key];
}
}
return clone;
}
实现一个 flatten 函数
//常规调用
function flatten(arr) {
if (!Array.isArray(arr)) {
return arr;
}
const result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result.push(flatten(arr[i]))
} else {
result.push(arr[i])
}
}
return result;
}
//reduce
function flatten2(arr) {
return arr.reduce((pre, cur, i) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
//es6
function flatten3() {
return arr.flat(Infinity);
}
实现一个柯里化 curry 函数
就是将一个接受多个参数的函数,变为一系列可以接受一个或多个参数的函数的技术。
此实现依赖于 fn.length
来判断参数数量,所以需要确保原始函数的参数数量是已知的。
function curry(fn) {
return function curried(...args) {
// 每次返回新函数时,会把之前的参数也给带上
return args.length >= fn.length ? fn(...args) : (...newArgs) => curried(...args, ...newArgs);
}
}
实现一个 unique 数组去重方法
// 用set去重
function unique(arr) {
return Array.from(new Set(arr));
}
// 使用filter
function unique2(arr) {
const cache = [];
return arr.filter(value => {
if (!cache.includes(value)) {
cache.push(value)
return true;
}
return false;
})
}
//使用reduce
function unique3(arr) {
return arr.reduce((pre, cur) => {
if (!pre.includes(cur)) {
pre.push(cur);
}
return pre;
}, [])
}
实现一个 EventBus 发布订阅模式
class EventBus {
constructor() {
this.events = new Map();
}
//触发事件
emit(event, ...args) {
if (this.events.has(event)) {
this.events.get(event).forEach(listener => listener(...args));
}
}
//取消订阅事件
off(event, listener) {
if (!this.events.has(event)) {
return
}
this.events.set(event, this.events.get(event).filter(e => e !== listener));
}
//订阅事件
on(event, listener) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.set(event, this.events.get(event).push(listener));
}
//只执行一次的事件
once(event, listener) {
const wrapper = (...args) => {
listener(...args);
this.off(event, wrapper)
};
this.on(event, wrapper);
}
}
实现一个 ajax,使用 promise 封装 ajax 请求
和下面原生方法差不多,唯一区别就是成功和失败的回调函数是通过 success/error 还是 resolve/reject 实现的。
function myAjaxPromise({ method = 'GET', url, data = null, headers = {} }) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method.toUpperCase(), url, true);
for (const key in object) {
xhr.setRequestHeader(key, headers[key]);
}
xhr.onreadystatechange = function () {
// readyState:存有服务器响应的状态信息。
// 0: 请求未初始化(代理被创建,但尚未调用 open() 方法)
// 1: 服务器连接已建立(open方法已经被调用)
// 2: 请求已接收(send方法已经被调用,并且头部和状态已经可获得)
// 3: 请求处理中(下载中,responseText 属性已经包含部分数据)
// 4: 请求已完成,且响应已就绪(下载操作已完成)
if (xhr.readyState === 4) {//请求完成
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText);
} else {
reject(xhr.status)
}
}
}
if (method.toUpperCase() === 'GET' && data) {
// 如果是 GET 方法,将数据附加到 URL
const queryString = Object.keys(data)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
.join('&');
xhr.send(queryString ? `${url}?${queryString}` : url);
} else {
xhr.send(data);
}
})
}
实现一个 ajax,使用原生方法实现
function myAjax({ method, url, data, headers, success, error }) {
const xhr = new XMLHttpRequest();
xhr.open(method.toUpperCase(), url);
for (const key in object) {
xhr.setRequestHeader(key, headers[key]);
}
xhr.onreadystatechange = function () {
// readyState:存有服务器响应的状态信息。
// 0: 请求未初始化(代理被创建,但尚未调用 open() 方法)
// 1: 服务器连接已建立(open方法已经被调用)
// 2: 请求已接收(send方法已经被调用,并且头部和状态已经可获得)
// 3: 请求处理中(下载中,responseText 属性已经包含部分数据)
// 4: 请求已完成,且响应已就绪(下载操作已完成)
if (xhr.readyState === 4) {//请求完成
if (xhr.status >= 200 && xhr.status < 300) {
success && success(xhr.responseText);
} else {
error && error(xhr)
}
}
}
if (method.toUpperCase() === 'GET' && data) {
const queryString = Object.keys(data).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`).join('&');
xhr.send(queryString ? `${url}?${queryString}` : url);
} else {
xhr.send(data);
}
}
使用 setTimeout 实现 setInterval
// setInterval(()=> {}, 1000);
function mySetInterval(callback, delay) {
let timer;
const loop = () => {
callback();
timer = setTimeout(loop, delay);
}
timer = setTimeout(loop, delay);
return {
clear: () => {
clearTimeout(timer);
}
}
}
Promise 方法实现
实现 Promise.all 方法
function myPromiseAll(arr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(arr)) {
return reject(new TypeError("Argumenst must be iterable"));
}
const promises = arr.map(item => item instanceof Promise ? item : Promise.resolve(item));
let total = promises.length;
if (total === 0) {
resolve([])
}
const result = new Array(total);
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
promise.then(res => {
result[i] = res;
total--;
if (total === 0) {
resolve(result);
}
}).catch(reject)
}
});
}
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 1000));
myPromiseAll([promise1, promise2, promise3])
.then((results) => {
console.log(results); // 输出: [1, 2, 3]
})
.catch((error) => {
console.error('Promise rejected1:', error);
});
// 测试错误情况
const promiseWithError = Promise.reject('Error occurred');
myPromiseAll([promise1, promiseWithError])
.then((results) => {
console.log(results, '===');
})
.catch((error) => {
console.error('Promise rejected2:', error); // 输出: Promise rejected: Error occurred
});
实现 Promise.allSettled 方法
function myPromiseAllSettled(arr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(arr)) {
return reject(new TypeError("Arguments must be iterable"));
}
const promises = arr.map(item => item instanceof Promise ? item : Promise.resolve(item));
let total = promises.length;
const result = new Array(total);
if (total === 0) {
resolve(result);
}
promises.forEach((promise, index) => {
promise.then(value => {
result[index] = { status: 'fulfilled', value }; // 记录成功结果
}).catch(reason => {
result[index] = { status: 'rejected', reason }; // 记录失败原因
}).finally(() => {
total--;
if (total === 0) {
resolve(result);
}
})
})
})
}
const promise1 = Promise.resolve(3);
const promise2 = 1;
const promises = [promise1, promise2];
myPromiseAllSettled(promises)
.then((results) => results.forEach((result) => console.log(result)));
setTimeout(() => {
const p1 = Promise.resolve(3);
const p2 = new Promise((resolve, reject) => setTimeout(reject, 100, "foo"));
const ps = [p1, p2];
myPromiseAllSettled(ps)
.then((results) => results.forEach((result) => console.log(result)));
}, 1000);
myPromiseAllSettled([]).then((results) => console.log(results));
注意:
实现 Promise.any 方法
function myPromiseAny(arr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(arr)) {
return reject(new TypeError("Arguments type error"));
}
const promises = arr.map(item => item instanceof Promise ? item : Promise.resolve(item));
let total = promises.length;
const errors = new Array(total);
if (total === 0) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
promises.forEach((promise, index) => {
promise
.then(value => resolve(value))
.catch(reason => {
errors[index] = reason;
total--;
if (total === 0) {
// * 如果可迭代对象中没有一个 promise 成功,就返回一个失败的 promise 和AggregateError类型的实例,
// * AggregateError是 Error 的一个子类,用于把单一的错误集合在一起。
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
})
})
}
const promise1 = Promise.reject('Error 1');
const promise2 = Promise.reject('Error 2');
const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 1000));
myPromiseAny([promise1, promise2, promise3])
.then((result) => {
console.log(result); // 输出: 3
})
.catch((error) => {
console.error('All promises were rejected:', error);
});
// 测试所有 Promise 被拒绝的情况
const promise4 = Promise.reject('Error 3');
const promise5 = Promise.reject('Error 4');
myPromiseAny([promise4, promise5])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('All promises were rejected:', error);
/*
输出:
All promises were rejected: AggregateError: All promises were rejected
*/
});
注意点:有一个成功就成功,所有失败再返回全部失败。失败返回时要用 AggregateError
实现 Promise.race 方法
function myPromiseRace(arr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(arr)) {
return reject(new TypeError("Arguments must be iterable"));
}
const promises = arr.map(item => item instanceof Promise ? item : Promise.resolve(item));
if (promises.length === 0) {
return resolve();
}
promises.forEach(promise => {
promise.then(value => resolve(value)).catch(reason => reject(reason));
})
})
}
const promise1 = new Promise((resolve) => setTimeout(() => resolve('Promise 1 resolved'), 1000));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('Promise 2 resolved'), 500));
const promise3 = new Promise((resolve, reject) => setTimeout(() => reject('Promise 3 rejected'), 1500));
myPromiseRace([promise1, promise2, promise3])
.then((result) => {
console.log(result); // 输出: "Promise 2 resolved"
})
.catch((error) => {
console.error('Race rejected:', error);
});
// 测试所有 Promise 被拒绝的情况
const promise4 = new Promise((_, reject) => setTimeout(() => reject('Error from Promise 4'), 100));
const promise5 = new Promise((_, reject) => setTimeout(() => reject('Error from Promise 5'), 200));
myPromiseRace([promise4, promise5])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('Race rejected:', error);
// 输出: "Race rejected: Error from Promise 4"
});
很简单,哪个先有结果,哪个先返回。无论是成功还是失败。
实现 Promise.reject 方法
function myPromiseReject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
myPromiseReject('This is an error message')
.then((result) => {
console.log(result); // 不会被调用
})
.catch((error) => {
console.error('Promise rejected:', error); // 输出: Promise rejected: This is an error message
});
// 测试不同类型的拒绝原因
myPromiseReject(new Error('An error occurred'))
.then((result) => {
console.log(result); // 不会被调用
})
.catch((error) => {
console.error('Promise rejected:', error); // 输出: Promise rejected: Error: An error occurred
});
这个就很简单,就是用 new Promise 的 reject 包一下。
实现 Promise.resolve 方法
function myPromiseResolve(value) {
// 如果传入的值是 Promise,直接返回它
if (value instanceof Promise) {
return value;
}
// 否则,返回一个新的 Promise
return new Promise((resolve) => {
resolve(value);
})
}
// 使用基本类型的值
myPromiseResolve(42)
.then((result) => {
console.log(result); // 输出: 42
});
// 使用对象
myPromiseResolve({ message: 'Hello, World!' })
.then((result) => {
console.log(result); // 输出: { message: 'Hello, World!' }
});
// 使用 Promise
const existingPromise = new Promise((resolve) => setTimeout(() => resolve('Resolved after 1 second'), 1000));
myPromiseResolve(existingPromise)
.then((result) => {
console.log(result); // 输出: Resolved after 1 second
});
// 使用数组
myPromiseResolve([1, 2, 3])
.then((result) => {
console.log(result); // 输出: [1, 2, 3]
});
判断一下是否是 promise,是的话返回原来的 promise,否则用 new Promise 中的 resolve 包一下。
实现 Promise.prototype.catch/then/finally 方法
class MyPromise {
static PENDING = "pending";
static FULFILLED = "fulfiled";
static REJECTED = "rejected";
// new Promise((resolve,reject) => {})
constructor(executor) {
this.PromiseState = MyPromise.PENDING;
this.PromiseResult = null;
this.onFulfilledCallbacks = []; // 保存成功回调
this.onRejectedCallbacks = []; // 保存失败回调
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(value) {
if (this.PromiseState === MyPromise.PENDING) {
this.PromiseState = MyPromise.FULFILLED;
this.PromiseResult = value;
this.onFulfilledCallbacks.forEach(cb => {
cb(value);
})
}
}
reject(reason) {
if (this.PromiseState === MyPromise.PENDING) {
this.PromiseState = MyPromise.REJECTED;
this.PromiseResult = reason;
this.onRejectedCallbacks.forEach(cb => {
cb(reason);
})
}
}
// promise.then((value)=>{},(reason)=> {})
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.PromiseState === MyPromise.FULFILLED) {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error)
}
})
} else if (this.PromiseState === MyPromise.REJECTED) {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult)
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error)
}
})
} else if (this.PromiseState === MyPromise.PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error)
}
})
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult)
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error)
}
})
});
}
})
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callBack) {
return this.then(callBack, callBack);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
throw new TypeError("Chaining cycle detected for promise");
}
if (x instanceof MyPromise) {
x.then((y) => {
resolvePromise(promise2, y, resolve, reject);
}, reject);
} else if (x !== null && (typeof x === "object" || typeof x === "function")) {
try {
var then = x.then;
} catch (e) {
return reject(e);
}
if (typeof then === "function") {
let called = false;
try {
then.call(
x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
} else {
return resolve(x);
}
}
作者:圆圆 01 链接:https://juejin.cn/post/7044088065874198536 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
6 种继承方式实现
原型链继承
就是按照原型链的方式,子类的 prototype 设置为父类的实例实现继承。但是只有个问题,如果父类构造函数中的 this.xxx = []这种引用类型的数据,在经过继承到子类实例上时,访问 xxx 其实访问的是引用的地址,原型中包含的引用值会在所有实例间共享。原型链的第二个问题是,子类型在实例化时不能给父类型的构造函数传参。
注意,函数内部的 this.xxx,经过 new 以后,会成为实例对象上的一个属性。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
}
let instance = new SubType();
console.log('getSuperValue-->', instance.getSuperValue());
console.log('getSubValue-->', instance.getSubValue());
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
借用构造函数继承
借用构造函数的意思是子类在构造函数中使用 call 或 apply 的方式将父类构造函数应用于子类实例上。但是这样的话,无法继承父类的原型方法,除非在父类中的方法全使用 this.xxx = function(){}这种方式。但这样会创建无数个函数实例,造成了极大浪费。而且必须在构造函数中定义方法,因此函数不能重用
function SuperType(name) {
console.log('SuperType->', this);
this.name = name;
}
SuperType.prototype.getSuperValue = function () {
return 'super->' + this.name;
}
function SubType(name) {
console.log('SubType->', this);
SuperType.call(this, name)
this.name = name;
}
SubType.prototype.getSubValue = function () {
return 'child->' + this.name;
}
let instance = new SubType('小李');
console.log('getSubValue-->', instance.getSubValue());
console.log('getSuperValue-->', instance.getSuperValue());
//SubType-> SubType {}
//SuperType-> SubType {}
//getSubValue--> child->小李
//TypeError: instance.getSuperValue is not a function
组合继承
结合原型链继承和借用构造函数继承的方式。调用父类的构造函数来继承属性,将子类的原型设置为父类的实例可以继承属性和方法。这样可以避免原型链上的属性共享问题。
但缺点也可以看到,父类的构造函数被调用了 2 次。
function SuperType(name) {
console.log('SuperType->', this);
this.name = name;
}
SuperType.prototype.getSuperValue = function () {
return 'super->' + this.name;
}
function SubType(name) {
console.log('SubType->', this);
SuperType.call(this, name)
this.name = name;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.getSubValue = function () {
return 'child->' + this.name;
}
let instance = new SubType('小李');
console.log('getSubValue-->', instance.getSubValue());
console.log('getSuperValue-->', instance.getSuperValue());
//SuperType-> SuperType {}
//SubType-> SubType {}
//SuperType-> SubType {}
//getSubValue--> child->小李
//getSuperValue--> super->小李
原型式继承
就是类似于 Object.create 的方法创建继承。原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。
function createObject(proto) {
function fn() { }
fn.prototype = proto;
return new fn();
}
var instance = createObject({ name: '小李' });
console.log(instance);
console.log('getName-->', instance.name);
//fn {}[[Prototype]]: Objectname: "小李"[[Prototype]]: Object
//getName--> 小李
寄生式继承
与原型式继承类似,有点像工厂模式。说白了就是 Object.create 再加上点方法,然后返回。这样的缺点也一样,函数不能复用。
function createAnother(proto) {
let clone = Object.create(proto);
clone.sayHello = function () {
console.log("hello");
}
return clone;
}
寄生式组合继承
与组合式继承相比,优化了父类构造函数实例化时重复调用父类够高函数,避免了重复创建不必要的属性。
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
// SubType.prototype = new SuperType();
// SubType.prototype.constructor = SubType;
function inheritPrototype(subType, superType) {
let prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
console.log(this.age);
}
场景笔试
红绿灯问题,红灯 3s 一次,黄灯 1s 一次,绿灯 2s 一次。如何让三个灯不断交替重复亮灯
//使用回调函数
function task(light, time, next) {
setTimeout(() => {
if (light === 'red') {
red();
} else if (light === 'yellow') {
yellow();
} else if (light === 'green') {
green();
}
next();
}, time);
}
function run() {
task('red', 3000, () => {
task('yellow', 1000, () => {
task('green', 2000, run);
})
})
}
run()
//使用promise
function red() {
console.log("red light->", new Date().toLocaleString());
}
function yellow() {
console.log("yellow light->", new Date().toLocaleString());
}
function green() {
console.log("green light->", new Date().toLocaleString());
}
function task(light, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red();
} else if (light === 'yellow') {
yellow();
} else if (light === 'green') {
green();
}
resolve();
}, time);
})
}
function run() {
task('red', 3000)
.then(() => task('yellow', 1000))
.then(() => task('green', 2000))
.then(run)
}
run()
//使用async和await
function red() {
console.log("red light->", new Date().toLocaleString());
}
function yellow() {
console.log("yellow light->", new Date().toLocaleString());
}
function green() {
console.log("green light->", new Date().toLocaleString());
}
function task(light, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red();
} else if (light === 'yellow') {
yellow();
} else if (light === 'green') {
green();
}
resolve();
}, time);
})
}
async function run() {
await task('red', 3000)
await task('yellow', 1000)
await task('green', 2000)
run()
}
run()
实现一个简单的路由
class SimpleRouter {
constructor() {
this.routes = {}; // 存储路由
this.currentPath = ''; // 当前路径
this.init(); // 初始化路由
}
// 初始化路由
init() {
window.addEventListener('popstate', () => {
this.handleRoute(window.location.pathname);
});
this.handleRoute(window.location.pathname); // 处理初始路径
}
// 注册路由
register(path, callback) {
this.routes[path] = callback;
}
// 路由处理
handleRoute(path) {
this.currentPath = path;
const callback = this.routes[path] || this.routes['/']; // 默认路由
if (callback) {
callback(); // 执行对应回调
}
}
// 跳转路由
navigate(path) {
window.history.pushState({}, '', path); // 修改浏览器历史
this.handleRoute(path); // 处理新路径
}
}
const router = new SimpleRouter();
// 注册路由和对应的回调
router.register('/', () => {
document.body.innerHTML = '<h1>Home Page</h1>';
});
router.register('/about', () => {
document.body.innerHTML = '<h1>About Page</h1>';
});
router.register('/contact', () => {
document.body.innerHTML = '<h1>Contact Page</h1>';
});
// 添加导航按钮
document.body.innerHTML += `
<nav>
<button onclick="router.navigate('/')">Home</button>
<button onclick="router.navigate('/about')">About</button>
<button onclick="router.navigate('/contact')">Contact</button>
</nav>
`;
手写 Promise 加载图片
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = () => {
resolve(img);
}
img.onerror = () => {
reject(new Error('failed load image'))
}
})
}
loadImage('https://example.com/image.jpg')
.then((img) => {
document.body.appendChild(img); // 将加载的图片添加到文档中
console.log('Image loaded successfully:', img);
})
.catch((error) => {
console.error(error.message); // 处理加载错误
});
解析 URL 参数为对象
利用 new URL 和 new URLSearchParams
function parseUrlParams(url) {
const urlObj = new URL(url);
const params = new URLSearchParams(urlObj.search);
const result = {};
for (const [k, v] of params.entries()) {
if (result[k]) {
if (Array.isArray(result[k])) {
result[k].push(v);
} else {
result[k] = [result[k], v];
}
} else {
result[k] = v;
}
}
return result;
}
const url = 'https://example.com?page=1&sort=asc&filter=blue&filter=red';
const params = parseUrlParams(url);
console.log(params);
// 输出: { page: '1', sort: 'asc', filter: ['blue', 'red'] }
实现一个 jsonp 函数
function encodeObject(obj) {
return Object.entries(obj).map(([k, v]) => `${k}=${v === null || v === undefined || typeof v === "object" ? "" : v}`).join("&");
}
function jsonp({ url, onData, params }) {
const script = document.createElement('script');
const cbName = `JSONP_CALLBACK_${Math.random().toString().slice(2)}`;
console.log({ callback: cbName, ...params });
console.log(encodeObject({ callback: cbName, ...params }));
script.src = `${url}?${encodeObject({ callback: cbName, ...params })}`;
window[cbName] = onData;
document.body.appendChild(script);
}
jsonp({
url: 'http://localhost:10010',
params: { id: 1000 },
onData(data) {
console.log('Data:', data);
}
})
如何找出当前页面出现次数最多的 HTML 标签
function getTopTag() {
const tags = Array.from(document.querySelectorAll("*")).map(dom => dom.tagName.toLowerCase());
const map = {}
tags.forEach(tag => {
if (typeof map[tag] !== 'number') {
map[tag] = 0;
}
map[tag]++;
})
const sortedKeys = Object.keys(map).sort((a, b) => (map[b] - map[a]));
return { key: sortedKeys[0], value: map[sortedKeys[0]] }
}
如何找到当前页面出现次数前三多的 HTML 标签,如过多个标签出现次数同样多,则取多个标签
function getTop3Tags() {
const tags = Array.from(document.querySelectorAll("*")).map(dom => dom.tagName.toLowerCase());
const counter = {}
tags.forEach(tag => {
if (typeof counter[tag] !== 'number') {
counter[tag] = 0;
}
counter[tag]++;
})
const sortedKeys = Object.keys(counter).sort((a, b) => (counter[b] - counter[a]));
const result = [];
let maxNum = 0;
while (maxNum < 3) {
const key = sortedKeys.shift();
if (result.length === 0) {
result.push([key, counter[key]]);
maxNum++;
} else {
const [lastKey, lastCount] = result[result.length - 1];
if (counter[key] !== lastCount) {
maxNum++;
}
result.push([key, counter[key]])
}
}
return result;
}
数组 Array 原型方法
实现 Array.prototype.at()
Array.prototype.at = function(index){
let len = this.length;
if(index < -len || index >= len){
return undefined;
}
return index < 0 ? this[index+len] : this[index]
}
实现 Array.prototype.concat()
Array.prototype.concat = function (...args) {
const result = this.slice();
for (const arg of args) {
if (Array.isArray(arg)) {
for (const item of arg) {
result.push(item);
}
} else {
result.push(arg);
}
}
return result;
}
实现 Array.prototype.copyWithin()
copyWithin() 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。
copyWithin(target, start, end)
,注意,target 是要替换得位置!!!
Array.prototype.copyWithin = function(target, start = 0, end = this.length) {
const len = this.length;
// 处理负数索引
const to = target < 0 ? Math.max(len + target, 0) : Math.min(target, len);
const fromStart = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);
const fromEnd = end < 0 ? Math.max(len + end, 0) : Math.min(end, len);
// 计算要复制的元素数量
const count = fromEnd - fromStart;
// 如果目标位置和源位置相同或无效,直接返回
if (count <= 0 || to === fromStart) {
return this;
}
// 处理重叠情况
if (to < fromStart) {
// 向前移动,先复制元素
for (let i = 0; i < count; i++) {
if (i + fromStart < fromEnd) {
this[to + i] = this[fromStart + i];
}
}
} else {
// 向后移动,先复制到临时数组
const temp = this.slice(fromStart, fromEnd);
for (let i = 0; i < temp.length; i++) {
this[to + i] = temp[i];
}
}
return this; // 返回修改后的数组
};
实现 Array.prototype.entries()
Array.prototype.entries = function() {
let index = 0; // 初始化索引
const array = this; // 保存对当前数组的引用
return {
next: function() {
// 检查索引是否超出数组长度
if (index < array.length) {
const value = [index, array[index]]; // 创建键值对
index++; // 增加索引
return { value, done: false }; // 返回键值对和 done 状态
} else {
return { value: undefined, done: true }; // 返回结束状态
}
},
[Symbol.iterator]: function() {
return this; // 支持迭代
}
};
};
实现 Array.prototype.every()
Array.prototype.every = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 获取数组长度
const len = this.length;
// 遍历数组
for (let i = 0; i < len; i++) {
// 只检查存在的元素
if (i in this) {
// 调用回调函数
const result = callback.call(thisArg, this[i], i, this);
// 如果回调返回 false,立即返回 false
if (!result) {
return false;
}
}
}
// 如果所有元素都满足条件,返回 true
return true;
};
实现 Array.prototype.fill()
Array.prototype.fill = function(value, start = 0, end = this.length) {
// 获取当前数组的引用
const len = this.length;
// 处理负数索引
let to = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);
let from = end < 0 ? Math.max(len + end, 0) : Math.min(end, len);
// 遍历数组并填充值
for (let i = to; i < from; i++) {
this[i] = value;
}
return this; // 返回修改后的数组
};
实现 Array.prototype.filter()
Array.prototype.filter = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const result = []; // 创建一个新数组来存放过滤结果
const len = this.length; // 获取当前数组的长度
// 遍历数组
for (let i = 0; i < len; i++) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数并检查返回值
if (callback.call(thisArg, this[i], i, this)) {
result.push(this[i]); // 如果返回 true,则将元素添加到结果数组
}
}
}
return result; // 返回新数组
};
实现 Array.prototype.find()
Array.prototype.find = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 获取当前数组的长度
const len = this.length;
// 遍历数组
for (let i = 0; i < len; i++) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数,传递当前元素、索引和原数组
const result = callback.call(thisArg, this[i], i, this);
// 如果回调函数返回 true,返回当前元素
if (result) {
return this[i];
}
}
}
// 如果没有找到,返回 undefined
return undefined;
};
实现 Array.prototype.findIndex()
Array.prototype.findIndex = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 获取当前数组的长度
const len = this.length;
// 遍历数组
for (let i = 0; i < len; i++) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数,传递当前元素、索引和原数组
const result = callback.call(thisArg, this[i], i, this);
// 如果回调函数返回 true,返回当前元素
if (result) {
return i;
}
}
}
// 如果没有找到,返回 undefined
return undefined;
};
实现 Array.prototype.findLast()
Array.prototype.findLast = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 获取当前数组的长度
const len = this.length;
// 从数组的最后一个元素开始遍历
for (let i = len - 1; i >= 0; i--) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数,传递当前元素、索引和原数组
const result = callback.call(thisArg, this[i], i, this);
// 如果回调函数返回 true,返回当前元素
if (result) {
return this[i];
}
}
}
// 如果没有找到,返回 undefined
return undefined;
};
实现 Array.prototype.findLastIndex()
Array.prototype.findLast = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 获取当前数组的长度
const len = this.length;
// 从数组的最后一个元素开始遍历
for (let i = len - 1; i >= 0; i--) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数,传递当前元素、索引和原数组
const result = callback.call(thisArg, this[i], i, this);
// 如果回调函数返回 true,返回当前元素
if (result) {
return i;
}
}
}
// 如果没有找到,返回 undefined
return undefined;
};
实现 Array.prototype.flat()
Array.prototype.flat = function(depth = 1) {
// 确保 depth 是一个非负整数
depth = Math.max(Number(depth), 0);
const result = []; // 创建结果数组
const flatten = (arr, depth) => {
for (const item of arr) {
// 如果 item 是数组并且 depth > 0,递归地展平
if (Array.isArray(item) && depth > 0) {
flatten(item, depth - 1);
} else {
// 否则,直接推入结果数组
result.push(item);
}
}
};
flatten(this, depth); // 从当前数组开始展平
return result; // 返回展平后的数组
};
//考虑depth=Infinity 并且array中有empty稀疏数组
Array.prototype.flat = function(depth = 1) {
// 初始化结果数组
const result = [];
// 递归函数用于扁平化数组
const flatten = (arr, currentDepth) => {
array.forEach(item => {
// 如果当前深度小于指定深度且项是数组,则递归
if(Array.isArray(item) && currentDepth < depth){
flatten(item,currentDepth+1);
}else {
// 只添加非 undefined 的元素
result.push(item);
}
});
};
// 开始递归扁平化
flatten(this, 0);
return result; // 返回结果数组
};
实现 Array.prototype.flatMap()
flatMap() 方法对数组中的每个元素应用给定的回调函数,然后将结果展开一级,返回一个新数组。它等价于在调用 map() 方法后再调用深度为 1 的 flat() 方法(arr.map(...args).flat()),但比分别调用这两个方法稍微更高效一些。
Array.prototype.flatMap = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const result = []; // 创建结果数组
// 遍历数组
for (let i = 0; i < this.length; i++) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数,得到映射结果
const mappedValue = callback.call(thisArg, this[i], i, this);
// 如果映射结果是数组,则将其展平并加入结果数组
result.push(...(Array.isArray(mappedValue) ? mappedValue : [mappedValue]));
}
}
return result; // 返回最终结果数组
};
实现 Array.prototype.forEach()
Array.prototype.forEach = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 获取当前数组的长度
const len = this.length;
// 遍历数组
for (let i = 0; i < len; i++) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数,传递当前元素、索引和原数组
callback.call(thisArg, this[i], i, this);
}
}
};
实现 Array.prototype.includes()
Array.prototype.includes = function(value, fromIndex = 0) {
// 将 fromIndex 转换为整数
const len = this.length;
let startIndex = Math.max(fromIndex >= 0 ? fromIndex : len + fromIndex, 0);
// 遍历数组
for (let i = startIndex; i < len; i++) {
// 使用 Object.is 进行严格比较
if (this[i] === value || (value !== value && this[i] !== this[i])) {
return true; // 找到匹配的值
}
}
return false; // 没有找到匹配的值
};
实现 Array.prototype.indexOf()
Array.prototype.indexOf = function(searchElement, fromIndex = 0) {
// 获取当前数组的长度
const len = this.length;
// 处理 fromIndex 值
let startIndex = Math.max(fromIndex >= 0 ? fromIndex : len + fromIndex, 0);
// 遍历数组
for (let i = startIndex; i < len; i++) {
// 使用严格相等运算符进行比较
if (this[i] === searchElement) {
return i; // 找到匹配的元素,返回索引
}
}
return -1; // 没有找到匹配的元素,返回 -1
};
实现 Array.prototype.join()
Array.prototype.join = function(separator = ',') {
// 获取当前数组的长度
const len = this.length;
let result = '';
// 遍历数组
for (let i = 0; i < len; i++) {
// 处理数组中的空位
if (i > 0) {
result += separator; // 在元素之间添加分隔符
}
// 仅添加存在的元素
if (i in this) {
result += this[i] === null ? 'null' : this[i]; // 处理 null 值
}
}
return result; // 返回连接后的字符串
};
实现 Array.prototype.keys()
Array.prototype.keys = function() {
let index = 0; // 初始化索引
// 创建一个迭代器对象
const iterator = {
next: () => {
// 检查是否到达数组末尾
if (index < this.length) {
return { value: index++, done: false }; // 返回当前索引并递增
} else {
return { value: undefined, done: true }; // 完成迭代
}
},
[Symbol.iterator]: function() {
return this; // 使得对象可迭代
}
};
return iterator; // 返回迭代器对象
};
实现 Array.prototype.lastIndexOf()
Array.prototype.lastIndexOf = function(searchElement, fromIndex = this.length - 1) {
// 获取当前数组的长度
const len = this.length;
// 处理 fromIndex,确保它在有效范围内
let startIndex = Math.min(fromIndex >= 0 ? fromIndex : len + fromIndex, len - 1);
// 从后向前遍历数组
for (let i = startIndex; i >= 0; i--) {
// 使用严格相等运算符进行比较
if (this[i] === searchElement) {
return i; // 找到匹配的元素,返回索引
}
}
return -1; // 没有找到匹配的元素,返回 -1
};
实现 Array.prototype.map()
Array.prototype.map = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const result = []; // 创建一个新数组来存放结果
const len = this.length; // 获取当前数组的长度
// 遍历数组
for (let i = 0; i < len; i++) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数并将结果推入新数组
result.push(callback.call(thisArg, this[i], i, this));
}
}
return result; // 返回新数组
};
实现 Array.prototype.pop()
Array.prototype.pop = function() {
// 获取当前数组的长度
const len = this.length;
// 如果数组为空,返回 undefined
if (len === 0) {
return undefined;
}
// 获取最后一个元素
const lastElement = this[len - 1];
// 删除最后一个元素
this.length = len - 1;
// 返回被移除的元素
return lastElement;
};
实现 Array.prototype.push()
Array.prototype.push = function(...elements) {
// 获取当前数组的长度
const len = this.length;
// 将新元素添加到数组中
for (let i = 0; i < elements.length; i++) {
this[len + i] = elements[i]; // 将元素放入数组的末尾
}
// 更新数组的长度
this.length = len + elements.length;
// 返回新长度
return this.length;
};
实现 Array.prototype.reduce()
// 自定义 reduce 函数实现
Array.prototype.customReduce = function(callback, initialValue) {
// 首先检查数组是否为空并且检查回调函数是否为函数
if (this.length === 0 && !initialValue) {
throw new TypeError('Reduce of empty array with no initial value');
}
let accumulator = initialValue !== undefined ? initialValue : this[0];
for (let i = initialValue !== undefined ? 0 : 1; i < this.length; i++) {
accumulator = callback(accumulator, this[i], i, this);
}
return accumulator;
};
实现 Array.prototype.reduceRight()
// 自定义 reduceRight 方法实现
Array.prototype.customReduceRight = function(callback, initialValue) {
if (this.length === 0 && initialValue === undefined) {
throw new TypeError('Reduce of empty array with no initial value');
}
let accumulator = initialValue !== undefined ? initialValue : this[this.length - 1];
let i = initialValue !== undefined ? this.length - 1 : this.length - 2;
for (; i >= 0; i--) {
accumulator = callback(accumulator, this[i], i, this);
}
return accumulator;
};
实现 Array.prototype.reverse()
Array.prototype.reverse = function() {
const len = this.length; // 获取数组的长度
let temp; // 用于交换元素
// 反转数组
for (let i = 0; i < Math.floor(len / 2); i++) {
// 交换元素
temp = this[i];
this[i] = this[len - 1 - i];
this[len - 1 - i] = temp;
}
return this; // 返回反转后的数组
};
实现 Array.prototype.shift()
Array.prototype.shift = function() {
const len = this.length; // 获取当前数组的长度
// 如果数组为空,返回 undefined
if (len === 0) {
return undefined;
}
const firstElement = this[0]; // 获取第一个元素
// 将数组中的元素向前移动一位
for (let i = 1; i < len; i++) {
this[i - 1] = this[i]; // 将后面的元素移动到前面
}
// 删除最后一个元素(现在是多余的)
this.length = len - 1;
return firstElement; // 返回被移除的第一个元素
};
实现 Array.prototype.slice()
Array.prototype.slice = function(begin = 0, end = this.length) {
const result = []; // 创建一个新数组来存放结果
const len = this.length; // 获取当前数组的长度
// 处理负索引
if (begin < 0) {
begin = Math.max(len + begin, 0); // 计算从末尾开始的索引
}
if (end < 0) {
end = Math.max(len + end, 0); // 计算从末尾开始的结束索引
}
// 确保 begin 和 end 在有效范围内
begin = Math.min(Math.max(begin, 0), len); // 确保 begin 不小于 0 且不大于长度
end = Math.min(Math.max(end, 0), len); // 确保 end 不小于 0 且不大于长度
// 遍历数组并填充结果数组
for (let i = begin; i < end; i++) {
result.push(this[i]); // 将元素添加到新数组中
}
return result; // 返回新数组
};
实现 Array.prototype.some()
Array.prototype.some = function(callback, thisArg) {
// 检查回调函数是否为函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const len = this.length; // 获取当前数组的长度
// 遍历数组
for (let i = 0; i < len; i++) {
// 检查当前索引是否存在于数组中
if (i in this) {
// 调用回调函数,如果返回 true,立即返回 true
if (callback.call(thisArg, this[i], i, this)) {
return true;
}
}
}
return false; // 没有元素满足条件,返回 false
};
实现 Array.prototype.sort()
Array.prototype.sort = function(compareFunction) {
const len = this.length; // 获取当前数组的长度
// 如果数组长度小于 2,直接返回
if (len < 2) {
return this;
}
// 使用冒泡排序算法进行排序
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
let a = this[j];
let b = this[j + 1];
// 如果提供了比较函数,使用它进行比较
if (compareFunction) {
if (compareFunction(a, b) > 0) {
// 交换元素
[this[j], this[j + 1]] = [this[j + 1], this[j]];
}
} else {
// 默认按字符编码顺序排序
if (String(a) > String(b)) {
// 交换元素
[this[j], this[j + 1]] = [this[j + 1], this[j]];
}
}
}
}
return this; // 返回排序后的数组
};
实现 Array.prototype.splice()
Array.prototype.splice = function(start, deleteCount, ...items) {
const len = this.length; // 获取当前数组的长度
const result = []; // 用于存放被删除的元素
// 处理负索引
if (start < 0) {
start = Math.max(len + start, 0); // 从末尾开始计算索引
} else {
start = Math.min(start, len); // 确保起始索引不超过数组长度
}
// 确保 deleteCount 不为负值
deleteCount = Math.min(Math.max(deleteCount, 0), len - start);
// 收集被删除的元素
for (let i = start; i < start + deleteCount; i++) {
if (i in this) {
result.push(this[i]);
}
}
// 从 start 位置开始移除元素
for (let i = start; i < len - deleteCount; i++) {
this[i] = this[i + deleteCount]; // 将后面的元素向前移动
}
// 更新数组长度
this.length = len - deleteCount;
// 在 start 位置插入新元素
for (let i = 0; i < items.length; i++) {
this.spliceHelper(start + i, items[i]); // 插入新元素
}
return result; // 返回被删除的元素
};
// 辅助方法用于插入新元素
Array.prototype.spliceHelper = function(index, item) {
const len = this.length;
// 确保索引在有效范围内
if (index < 0) {
index = Math.max(len + index, 0);
} else {
index = Math.min(index, len);
}
// 将数组元素向后移动一位
this.length++;
for (let i = len; i > index; i--) {
this[i] = this[i - 1];
}
// 插入新元素
this[index] = item;
};
实现 Array.prototype[Symbol.iterator]()
Array.prototype[Symbol.iterator] = function() {
let index = 0; // 当前迭代的索引
const data = this; // 保存当前数组的引用
return {
next: function() {
// 检查当前索引是否超出数组长度
if (index < data.length) {
// 返回当前元素和完成状态
return {
value: data[index++], // 返回当前值并递增索引
done: false // 表示迭代尚未完成
};
} else {
// 迭代完成
return {
value: undefined, // 返回 undefined
done: true // 表示迭代已完成
};
}
}
};
};
实现 Array.prototype.unshift()
Array.prototype.unshift = function(...items) {
const len = this.length; // 获取当前数组的长度
// 增加数组长度以容纳新元素
this.length += items.length;
// 将元素向后移动以空出开头位置
for (let i = len - 1; i >= 0; i--) {
this[i + items.length] = this[i]; // 移动元素
}
// 将新元素插入到开头
for (let i = 0; i < items.length; i++) {
this[i] = items[i]; // 插入新元素
}
return this.length; // 返回新数组的长度
};
实现 Array.prototype.values()
Array.prototype.values = function() {
let index = 0; // 当前迭代的索引
const data = this; // 保存当前数组的引用
return {
next: function() {
// 检查当前索引是否超出数组长度
if (index < data.length) {
// 返回当前元素和完成状态
return {
value: data[index++], // 返回当前值并递增索引
done: false // 表示迭代尚未完成
};
} else {
// 迭代完成
return {
value: undefined, // 返回 undefined
done: true // 表示迭代已完成
};
}
}
};
};
Object 静态方法
实现一个 Object.assign 函数
Object.assign = function (target, ...sources) {
const t = Object(target);
for (let i = 0; i < sources.length; i++) {
const source = sources[i];
if (source) {
for (const key in source) {
t[key] = source[key]
}
}
}
return t;
}
实现一个 Object.create 函数
// Object.create(proto[,propertiesObject])
// var obj = Object.create(prototype);
Object.create = function (proto, propertiesObject) {
// 创建一个新对象
function F() { }
F.prototype = proto; // 将新对象的原型设置为 proto
const obj = new F(); // 创建新对象
// 如果有属性对象,则添加属性
if (propertiesObject !== undefined) {
Object.defineProperties(obj, propertiesObject);
}
return obj; // 返回新创建的对象
};
实现一个 Object.is 函数
function customIs(a, b) {
// 检查是否为同一个引用
if (a === b) {
// 处理 +0 和 -0 的情况
return a !== 0 || 1 / a === 1 / b; // 只有 +0 和 -0 会导致 true
}
// 处理 NaN 的情况
return a !== a && b !== b; // NaN 是唯一不等于自身的值
}
实现一个 Object.seal 函数
创建一个“密封对象”,这个方法会在现有对象上调用 Object.preventExtensions()并把所有属性标记为 configure:false。所以密封后既不能添加新属性,也不能重新配置或删除任何现有的属性。 但是可以修改属性的值。
function mySeal(obj) {
// 判断传入的对象是否是一个对象
if (typeof obj !== 'object' || obj === null) {
return obj; // 如果不是对象,直接返回原始值
}
// 获取对象的所有属性
const props = Object.keys(obj);
// 遍历每个属性
for (let prop of props) {
// 使每个属性的配置为不可配置
Object.defineProperty(obj, prop, {
configurable: false,
writable: true, // 允许属性值可写
});
}
// 返回密封后的对象
return obj;
}
实现一个 Object.freeze 函数
这个和 Object.seal 的区别是,连属性的值都不可以修改了。也就是啥都不能改了。
function myFreeze(obj) {
// 判断传入的对象是否是一个对象
if (typeof obj !== 'object' || obj === null) {
return obj; // 如果不是对象,直接返回原始值
}
// 获取对象的所有属性
const props = Object.keys(obj);
// 遍历每个属性
for (let prop of props) {
// 使每个属性的配置为不可配置和不可写
Object.defineProperty(obj, prop, {
configurable: false, // 不可配置
writable: false, // 不可写
});
}
// 返回被冻结的对象
return obj; //
}
TypeScript 实现
实现 Partial<T>
Partial 部分的,不完全的。就是把 T 所有的属性变成可选
Partial<T>
返回一个包含所有 T 的子集的 type。
请自行实现MyPartial<T>
。
type Foo = {
a: string
b: number
c: boolean
}
// below are all valid
const a: MyPartial<Foo> = {}
const b: MyPartial<Foo> = {
a: 'BFE.dev'
}
const c: MyPartial<Foo> = {
b: 123
}
const d: MyPartial<Foo> = {
b: 123,
c: true
}
const e: MyPartial<Foo> = {
a: 'BFE.dev',
b: 123,
c: true
}
type MyPartial<T> = {
[K in keyof T]?: T[K]; // 将 T 的所有属性变为可选
}
实现 Required<T>
顾名思义,把 T 所有属性变成必需的
和Partial<T>
正好相反, Required<T>
会将所有的属性设为 required。
请自行实现MyRequired<T>
。
// all properties are optional
type Foo = {
a?: string
b?: number
c?: boolean
}
const a: MyRequired<Foo> = {}// Error
const b: MyRequired<Foo> = {
a: 'BFE.dev'
}// Error
const c: MyRequired<Foo> = {
b: 123
}// Error
const d: MyRequired<Foo> = {
b: 123,
c: true
}// Error
const e: MyRequired<Foo> = {
a: 'BFE.dev',
b: 123,
c: true
}// valid
//-? 符号用于移除属性的可选性,强制将 T 中的每个属性设置为必需属性。
type MyRequired<Foo> = {
[K in keyof T]-?: T[K]; // 将 T 的所有属性设为必需
}
实现 Readonly<T>
顾名思义,把 T 所有属性变成只读
Readonly<T>
返回将 T 的全部属性设为 readonly 后的 type。
请自行实现MyReadonly<T>
。
type Foo = {
a: string
}
const a:Foo = {
a: 'BFE.dev',
}
a.a = 'bigfrontend.dev'
// OK
const b:MyReadonly<Foo> = {
a: 'BFE.dev'
}
b.a = 'bigfrontend.dev'
// Error
//readonly 关键字用于将每个属性变为只读,防止在后续代码中修改这些属性。
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]; // 将 T 的所有属性设为 readonly
};
实现 Record<K, V>
就是 key 在 K 的属性之内,value 在 V 的属性之内
Record<K, V
>返回一个 key 是 K 值是 V 的 object type。
请自行实现MyRecord<K, V>
。
注意: 可以用作 object key 的只有 number | string | symbol。
type Key = 'a' | 'b' | 'c'
const a: Record<Key, string> = {
a: 'BFE.dev',
b: 'BFE.dev',
c: 'BFE.dev'
}
a.a = 'bigfrontend.dev' // OK
a.b = 123 // Error
a.d = 'BFE.dev' // Error
type Foo = MyRecord<{a: string}, string> // Error
//[key in K] 用于遍历 K 中的所有键,并为每个键创建一个对应的值,类型为 V。
type MyRecord<K extends keyof any, V> = {
[key in K]: V; // 使用映射类型生成对象类型
};
实现 Pick<T, K>
pick 是选的意思,那就是从 T 中选择包含 K 的属性
Pick<T, K>
,正如其名所示,将从 T 中抽选出 K 中含有的属性然后返回一个新的 type。
请自行实现 MyPick<T, K>
。
type Foo = {
a: string
b: number
c: boolean
}
type A = MyPick<Foo, 'a' | 'b'> // {a: string, b: number}
type B = MyPick<Foo, 'c'> // {c: boolean}
type C = MyPick<Foo, 'd'> // Error
// [P in K] 用于遍历 K 中的每个键,并为每个键创建一个对应的属性,其类型为 T[P],即从 T 中获取该属性的类型。
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]; // 遍历 K 中的键,并选择对应的 T 中的属性
};
实现 Omit<T, K>
omit 是省略;忽略;遗漏;删去的意思。也就是从 T 中去掉包含 K 的属性之后的类型
Omit<T, K>
返回一个从 T 的属性中剔除掉 K 过后的 type。
请自行实现MyOmit<T, K>
。
type Foo = {
a: string
b: number
c: boolean
}
type A = MyOmit<Foo, 'a' | 'b'> // {c: boolean}
type B = MyOmit<Foo, 'c'> // {a: string, b: number}
type C = MyOmit<Foo, 'c' | 'd'> // {a: string, b: number}
//[P in keyof T] 遍历 T 的所有键。
// as P extends K ? never : P 使用条件类型,将 K 中的每个键映射为 never,从而排除它们。其他键保持不变。
type MyOmit<T, K extends keyof any> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
实现 Exclude<T, K>
exclude 是排除(…的可能性);不包括;把…排斥在外;,也就是把 T 中包含 K 的属性去掉之后所有的 type,记住不是 type 和类型,而是一个 type 集合。
Exclude<T, K>
返回一个从 T 中去掉能代入 K 的成员后的 type。
请自行实现MyExclude<T, K>
。
type Foo = 'a' | 'b' | 'c'
type A = MyExclude<Foo, 'a'> // 'b' | 'c'
type B = MyExclude<Foo, 'c'> // 'a' | 'b
type C = MyExclude<Foo, 'c' | 'd'> // 'a' | 'b'
type D = MyExclude<Foo, 'a' | 'b' | 'c'> // never
//T extends E ? never : T 检查 T 的每一个成员是否可以赋值给 E。
//如果可以赋值,返回 never(表示排除这个类型),否则返回 T 自身。
type MyExclude<T, K> = T extends K ? never : T;
实现 Extract<T, U>
extract 是 提取;获得,得到;的意思,也就是从 T 中提取 U 包含的属性,然后所组成的 type
和 Exclude 正好相反, Extract<T, U>
返回 T 中可以代入到 U 的成员所组成的 type。
请自行实现MyExtract<T, U>
。
type Foo = 'a' | 'b' | 'c'
type A = MyExtract<Foo, 'a'> // 'a'
type B = MyExtract<Foo, 'a' | 'b'> // 'a' | 'b'
type C = MyExtract<Foo, 'b' | 'c' | 'd' | 'e'> // 'b' | 'c'
type D = MyExtract<Foo, never> // never
type MyExtract<T, U> = U extends T ? U : never;
实现 NonNullable<T>
顾名思义,去掉所有 null 和 undefined
NonNullable<T>
返回从 T 中排除掉 null 和 undefined 之后的 type。
请自行实现MyNonNullable<T>
。
type Foo = 'a' | 'b' | null | undefined
type A = MyNonNullable<Foo> // 'a' | 'b'
type MyNonNullable<T> = T extends null | undefined ? never : T;
实现 Parameters<T>
对于 function type T, Parameters<T>
返回一个由其参数 type 组成的 tuple type。
请自行实现MyParameters<T>
。
type Foo = (a: string, b: number, c: boolean) => string
type A = MyParameters<Foo> // [a:string, b: number, c:boolean]
type B = A[0] // string
type C = MyParameters<{a: string}> // Error
type MyParameters<T extends Function> = T extends (...args: infer P) => any ? P : never;
条件类型:
T extends (...args: infer P) => any
检查T
是否是一个函数类型。如果是,它将提取参数类型并将其赋值给P
。
infer
关键字:
infer P
用于推断参数类型,P
将成为一个元组类型,包含函数的所有参数类型。
返回类型:
- 如果
T
是函数类型,则返回参数类型的元组P
;否则返回never
。
实现 ConstructorParameters<T>
Parameters 处理的是 function type。 与之类似,ConstructorParameters<T>
针对的是 class,返回 constructor function T 的其参数 type 组成的 tuple type。
请自行实现MyConstructorParameters<T>
。
class Foo {
constructor (a: string, b: number, c: boolean) {}
}
type C = MyConstructorParameters<typeof Foo>
type MyConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any ? P : never;
条件类型:
T extends new (...args: infer P) => any
检查T
是否是一个构造函数类型。如果是,它将提取构造函数的参数类型并将其赋值给P
。
infer
关键字:
infer P
用于推断构造函数的参数类型,P
将成为一个元组类型,包含构造函数的所有参数类型。
返回类型:
- 如果
T
是构造函数类型,则返回参数类型的元组P
;否则返回never
。
实现 ReturnType<T>
Parameters 处理的是参数, ReturnType<T>
,正如起名所述,处理的是 function type T 的返回值 type
请自行实现MyReturnType<T>
。
type Foo = () => {a: string}
type A = MyReturnType<Foo> // {a: string}
type MyReturnType<T extends (...args:any[]) => any > = T extends (...args:any[]) => infer K ? K : never;
实现 InstanceType<T>
对于 constructor function type T,InstanceType<T>
返回其 instance type。
请自行实现MyInstanceType<T>
。
class Foo {}
type A = MyInstanceType<typeof Foo> // Foo
type B = MyInstanceType<() => string> // Error
type MyInstanceType<T extends new (...args:any[]) => any > = T extends new (...args:any[]) => infer K ? K : never;