前端并发请求控制
async function controlAsync(maxLimit, dataArray, doFn) {
let finish = [] // 完成
let doing = [] // 执行
for(let data of dataArray) {
let p = doFn(data)
finish.push(p)
if(maxLimit <= dataArray.length) {
// 如果没达到限制数就直接只往finish里添加就行
p = p.then(() => {
doing.splice(doing.indexOf(p), 1)
// 执行完成后把自己从执行列表中去除
})
doing.push(p)
if(maxLimit <= doing.length) {
await Promise.race(doing)
// 等待执行列表有完成的就可以开始下一次循环
}
}
}
return Promise.all(finish)
}
// 使用
controlAsync(
2, //参数一,同时并发的格式
[1000, 3000, 2000, 5000, 6000], //参数二,每个请求传入的参数组成的数组
(param) => //参数三,返回值被Promise包裹的异步执行函数
new Promise((reslove) => {
setTimeout(() => {
console.log(param);
reslove(param);
}, param);
})
).then((res) => {
console.log("res:", res);
});
订阅发布模式
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, Fn) {
if(this.events[eventName]) {
this.events[eventName].push(Fn);
} else {
this.events[eventName] = [];
this.events[eventName].push(Fn);
}
}
// 交牛客测试时候要去掉emit的第二个参数😂
emit(eventName, ...args) {
if(this.events[eventName]) {
this.events[eventName].forEach(item => { item(...args) });
}
}
}
手写快排
- 简单来说就是选出一个基准数,把比这个数大的放一边,比这个数小的放一边
- 然后把上述两边的部分再做同样的操作
- 一直大的一边,小的一边,到最后就能排序了
function quickSort(arr, left, right) {
if (left < right) {
// 确认两边的分界在哪
let part = sortPart(arr, left, right)
// 给两边的部分再分大小
quickSort(arr, left, part - 1)
quickSort(arr, part + 1, right)
}
return arr
}
function sortPart(arr, left, right) {
let standardNum = arr[left]
while (left < right) {
while (right > left && arr[right] > standardNum) {
right--
}
arr[left] = arr[right]
while (right > left && arr[left] <= standardNum) {
left++
}
arr[right] = arr[left]
}
arr[left] = standardNum
return left
}
观察者模式
// 被观察
class Subject {
constructor(name) {
this.name = name
this.message = ""
this.watchers = []
}
addWatcher(watcher) {
this.watchers.push(watcher)
}
setMessage(msg) {
this.message = msg
this.sendNotice()
}
sendNotice() {
this.watchers.forEach((e) => {
e.notice(this.name, this.message)
})
}
}
// 观察者
class Observer {
constructor() {
}
notice(name, message) {
console.log(`${name} changed, message:${message}`)
}
}
// 使用
const subject = new Subject("subject1");
const observerA = new Observer();
const observerB = new Observer();
subject.addWatcher(observerB);
subject.setMessage('123');
subject.addWatcher(observerA);
subject.setMessage('456');
防抖节流
防抖:n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
节流:n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
// 防抖:
function debounce(fn, wait) {
let timer;
return function (...args) {
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
// 防抖:立即执行
function debounce(fn, wait, immediate) {
let timer;
return function () {
let context = this;
let args = arguments;
if (timer) clearTimeout(timer); // timer 不为null
if (immediate) {
let callNow = !timer;
// 无定时器的时候就立刻执行,有的话,正常走防抖逻辑
timer = setTimeout(function () {
timer = null;
}, wait)
if (callNow) {
fn.apply(context, args)
}
}
else {
timer = setTimeout(function () {
fn.apply(context, args)
}, wait);
}
}
}
// 节流:时间戳写法
function throttled(fn, delay) {
let oldTime = Date.now()
return function() {
let context = this;
let args = arguments;
let nowTime = Date.now()
// 使用时间戳写法,事件会立即执行,停止触发后没有办法再次执行
if(nowTime - oldTime >= delay) {
fn.apply(context, args)
oldTime = Date.now()
}
}
}
// 节流:定时器写法
function throttled(fn, delay) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay);
}
}
}
// 节流:前两个结合(弥补时间戳不能停止触发后不能延迟执行)
function throttled(fn, delay) {
let timer = null
let starttime = Date.now()
return function () {
let curTime = Date.now() // 当前时间
let remaining = delay - (curTime - starttime)
// 从上一次到现在,还剩下多少多余时间
let context = this
let args = arguments
clearTimeout(timer)
if (remaining <= 0) {
fn.apply(context, args)
starttime = Date.now()
} else {
timer = setTimeout(fn, remaining);
}
}
}
深拷贝
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
// JSON.stringify()缺点: 会忽略undefined、symbol 和 函数
const obj2=JSON.parse(JSON.stringify(obj1));
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
function deepClone(obj) {
if (obj === null) return obj;// 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== 'object') return obj;
let cloneObj = new obj.constructor(); //保持继承链
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
};
// 考虑symbol的话,要加一下这段
let symKeys = Object.getOwnPropertySymbols(obj); // 查找symbol属性
if (symKeys.length) { // 查找成功
symKeys.forEach(symKey => {
if (typeof (obj[symKey]) === 'object' && (obj[symKey]) != null) {
cloneObj[symKey] = deepClone(obj[symKey]);
} else {
cloneObj[symKey] = obj[symKey];
}
});
}
// 再考虑循环引用的话,完整版大概这样
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj;// 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
let cloneObj = new obj.constructor(); //保持继承链
hash.set(obj, cloneObj);
let symKeys = Object.getOwnPropertySymbols(obj); // 查找symbol属性
if (symKeys.length) { // 查找成功
symKeys.forEach(symKey => {
if (typeof (obj[symKey]) === 'object' && (obj[symKey]) != null) {
cloneObj[symKey] = deepClone(obj[symKey], hash);
} else {
cloneObj[symKey] = obj[symKey];
}
});
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
};
解决循环引用:(Lodash)
当然了,感觉用WeakMap也挺好,到时候结束了不影响垃圾回收
浅拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
let oldobj = {
// ....
}
let newobj = {}
for (let k in oldobj) {
newobj[k] = oldobj[k]
}
let newobj = Object.assign({},oldobj);
promise.all
function myPromiseAll(pro) {
return new Promise((resolve, reject) => {
// 要执行的promise们
const promises = Array.from(pro)
// 结果
const result = []
let count = 0
for(let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(
res => {
// 注意保持顺序
result[i] = res;
count++;
// 都完成了就可以返回了
if(count == promises.length) {
resolve(result)
}
}
).catch(e => {
// 有一个reject就立刻返回
return reject(e)
})
}
})
}
// 测试
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(88);
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(99);
}, 3000);
});
let arr = [p1, p2, 3, 4];
myPromiseAll(arr).then(res => {
console.log(res)
})
Promise.race
function myPromiseRace(promisesList) {
return new Promise((resolve, reject) => {
// 直接循环同时执行传进来的promise
for (const promise of promisesList) {
// 直接返回出去了,所以只有一个,就看哪个快
promise.then(resolve, reject)
}
})
}
// 测试
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(88);
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(99);
}, 3000);
});
let arr = [p1, p2];
myPromiseRace(arr).then(res => {
console.log(res)
})
再来个寄生组合继承吧
function Father(name) {
this.name = name
this.say = function () {
console.log("hello,world");
}
}
Father.prototype.showName = function () {
// 这个是测试输出用的
console.log(this.name)
}
function Son() {
Father.call(this, name)
this.age = age
}
Son.prototype = object.create(Father.prototype)
Son.prototype.constructor = Son
手写一个 new
function myNew (Func, ...arg){
let obj = {} //定义了一个对象。
obj.__proto__ = Func.prototype //将Func.prototype赋值为对象的__proto__属性,即原型链的概念
let res = Func.call(obj, ...arg) //更改Func的this指向
return res instanceof Object ? res : obj
}
myNew(RegExp)
手写个bind, apply, call
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 函数绑定到context时候,可能有重复属性,可以用symbol优化,但是缺点是这是ES6的(话说我下面的
??也是ES6的😂)
// call函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context ?? window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
// apply
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context ?? window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
}
// bind 函数实现
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
Object.freeze
developer.mozilla.org/zh-CN/docs/…
let myFreeze = function (object) {
let freeze = (item) => {
Object.defineProperty(object, item, {
writable: false,
configurable: false
})
}
for (let item in object) {
item instanceof Object && myFreeze(item) //递归
!(item instanceof Object) && freeze(item)
}
Object.seal(object)
}
函数柯里化
原理:
当传入的参数数 不足 所需要的参数数时,返回一个可以传入剩余参数的高阶函数
function fnToCurrying(fn, ...items) {
if (fn.length == items.length) {
let res=fn(...items)
return res;
}
return (...rests) => {
return fnToCurrying(fn, ...items, ...rests);
};
}
// 使用:
let add = function (a, b, c) {
return a + b + c;
};
let addCurry = fnToCurrying(add);
console.log(
addCurry(1)(2,4), //7
addCurry(9)(10)(10) //29
);
Promise
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const PENDING = 'pending';
function Promise(executor){
const self = this;
this.status = PENDING;
this.value = '';
this.FulfilledFn = [];
this.RejectedFn = [];
function reslove(res) {
self.value = res;
self.status = FULFILLED;
self.FulfilledFn.map(fn => fn(res));
}
function reject(err) {
self.value = err;
self.status = REJECTED;
self.RejectedFn.map(fn => fn(res));
}
executor(reslove, reject);
}
Promise.prototype.then = function(onFulfilled, onRejected) {
return new Promise((reslove, reject) => {
if (this.status === PENDING) {
this.FulfilledFn.push(v => {
let p = onFulfilled(v);
if (p instanceof Promise && p !== this) {
p.then(res => {
reslove(res);
}, err => {
reject(err);
});
} else {
reslove(p);
}
});
this.RejectedFn.push(v => {
setTimeout(() => {
let p = onRejected(v);
});
});
} else if (this.status === FULFILLED) {
onFulfilled(this.value);
} else if (this.status === REJECTED) {
onRejected(this.value);
}
});
}