手写系列
本文最后更新于:2022年5月31日 早上
事件中心 EventCenter
class EventCenter {
constructor() {
this.events = new Map();
}
register(event, isOnce, fn, ...args1) {
const curEvent = this.events.has(event) ? this.events.get(event) : this.events.set(event, new Map()).get(event);
curEvent.set(fn, (...args2) => {
const res = fn(...args1, ...args2);
if (isOnce) {
this.off(event, fn);
}
return res;
});
}
on(event, fn, ...args) {
register(event, false, fn, ...args);
}
once(event, fn, ...args) {
register(event, true, fn, ...args);
}
off(event, fn) {
if (!event || !this.events.has(event)) return false;
if (!fn) {
// 删除整个事件
return this.events.delete(event);
} else {
// 删除单个订阅
return this.events.get(event).delete(fn);
}
}
fire(event, ...args) {
const curEvents = this.events.get(event);
if (!curEvents) return false;
for (const cb of curEvents.values()) {
cb(...args);
}
}
}
// 请使用原生代码实现一个Events模块,可以实现自定义事件的订阅、触发、移除功能
const fn1 = (...args) => console.log("I want sleep1", ...args);
const fn2 = (...args) => console.log("I want sleep2", ...args);
const event = new Events();
event.on("sleep", fn1, 1, 2, 3);
event.on("sleep", fn2, 1, 2, 3);
event.fire("sleep", 4, 5, 6);
// I want sleep1 1 2 3 4 5 6
// I want sleep2 1 2 3 4 5 6
event.off("sleep", fn1);
event.once("sleep", () => console.log("I want sleep"));
event.fire("sleep");
// I want sleep2 1 2 3
// I want sleep
event.fire("sleep");
// I want sleep2 1 2 3
new
操作符
new(constructor)
过程中发生了什么?
- 创建一个空对象
- 将这个空对象链接到构造函数的原型
- 将
this
指向创建出来的空对象并执行构造函数 - 如果执行结果是对象则返回这个结果
- 否则返回
this
const isObj = obj => obj !== null && typeof obj === 'object'
function _new(_constructor,...args){
if (typeof _constructor !== "function") throw new TypeError("constructor must new a function");
const newObj = Object.create(_constructor_.prototype)
const res = func.call(newObj...args)
return isObj(res) ? res : newObj
}
Object.create
Object._create = (proto, propertiesObject) => {
if (typeof proto !== "function" || typeof proto !== "object") {
throw new TypeError("instance can only be object or function");
}
function F() {}
F.prototype = proto;
const res = new F();
if (propertiesObject) {
Reflect.ownKeys(propertiesObject).forEach((key) => {
Reflect.defineProperty(res, key, propertiesObject[key]);
});
}
return res;
};
instanceof
检测右侧的原型是否在左侧的原型链上游,但它不能检测null
和 undefined
function _instanceof(L, R) {
if (typeof L !== "object" || !L) {
throw new TypeError("need object data");
}
L = Reflect.getPrototypeOf(L); // L = L.__proto__
R = R.prototype;
while (true) {
if (L === null) return false; // 原型连到头
if (L === R) return true;
L = Reflect.getPrototypeOf(L);
}
}
防抖函数
- 将高频调用函数更改为固定时间不再调用才执行回调
- 输入框联想,提交按钮
/**
* @param callback 回调
* @param wait 间隔时间
* @param {boolean} immediate 立即执行?
*/
function debounce(callback, wait = 300, immediate = true, ...args1) {
let timer = null;
function _debounce(...args2) {
if (timer) clearTimeout(timer);
const isInit = immediate && !timer;
timer = setTimeout(() => {
timer = null;
!immediate && callback.apply(this, [...args1, ...args2]);
}, wait);
isInit && callback.apply(this, [...args1, ...args2]);
}
_debounce.clear = () => {
clearTimeout(timer);
timer = null;
};
return _debounce;
}
节流函数
- 将高频调用函数更改为固定间隔执行回调
- 窗口滚动,窗口缩放
/**
* @param callback 回调
* @param wait 间隔时间
*/
function throttle(callback, wait = 300, ...args1) {
let previous = 0;
let timer = null;
function _throttle(...args2) {
const now = +Date.now();
const interval = wait - (now - previous);
if (interval <= 0) {
// 凑巧和设定频率相同,两个if都会满足,则先执行这里
// 也要清理定时器
clearTimeout(timer);
timer = null;
// 操作频率慢于设定间隔
callback.apply(this, [...args1, ...args2]);
previous = now;
} else if (!timer) {
// 如果操作频率过快
// 并且还没有定时器
// 设置一个达到规定间隔后执行回调的定时器
// 如果有定时器则return
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
callback.apply(this, [...args1, ...args2]);
previous = +Date.now();
}, wait);
}
}
_throttle.clear = () => {
clearTimeout(timer);
timer = null;
};
return _throttle;
}
重试函数
实现一个重试函数,按一定间隔不断尝试执行一个 promise
function retry(callback, wait, limit) {
return new Promise((resolve,reject) => {
function attempt() {
Promise.resolve(callback())
.then(resolve)
.catch((r) => {
console.log(limit, r);
// 本次执行失败
if (limit-- <= 1) {
reject("reach maximum retry times");
} else {
let timer = setTimeout(() => {
attempt();
timer = null;
}, wait);
}
});
}
attempt();
});
}
const cb = () => Promise.reject("nonono");
retry(cb, 300, 10).then(r=>console.log(r)).catch(e=>console.log(e))
// 10 nonono
// 9 nonono
// 8 nonono
// 7 nonono
// 6 nonono
// 5 nonono
// 4 nonono
// 3 nonono
// 2 nonono
// 1 nonono
// reach maximum retry times
并发线程池
同时执行几个函数(or 不同参数的同一个函数
/**
* @params limit 并行限制
* @params args 回调参数数组
* @params func 函数
*/
async function asyncPool(executingLimit, argsArray, callback) {
const ret = [];
const executingPool = [];
for (const arg of argsArray) {
const p = Promise.resolve(callback(arg));
ret.push(p);
if (limit <= argsArray) {
// 运行完后从并发池中删除
const e = p.then(() => executingPool.splice(executingPool.indexOf(e), 1));
executingPool.push(e);
if(executingPool.length >= limit){
// 当并发池的大小达到限制 等待一个race
await Promise.race(executingPool)
}
}
}
return Promise.all(ret)
}
// Promise.all 在所有promise fulfilled后才会输出
// 同时executing又保证了同时在改变中的promise不会超出限制
const cb = (str) => setTimeout(() => console.log(str), 2000);
const arr = [1, 12, 123, 124, 666, 777, 888];
console.log(asyncPool(2, arr, cb));
深拷贝
- 浅拷贝
Object.assign/{...originObj}
面对引用类型只拷贝了指针 -
JSON.parse(JSON.stringify(originObj))
面对函数和Symbol会报错
const isObj = (o) => 0 !== null && typeof o === "object";
function deepClone(source, cache = new WeakSet()) {
// cache处理循环引用
if (!isObj(source) || cache.has(source)) return source;
const _constructor = source.constructor;
const newObj = _constructor === Object ? {} : new _constructor(source);
// 不只是对象或者数组,也处理其他的类型像日期和正则
cache.add(newObj);
Reflect.ownKeys(source).forEach((key) => {
Reflect.set(resObj,key,deepClone(Reflect.get(source,key)))
});
return newObj;
}
Object.assign
Object._assign = (target, ...properties) => {
if (target === null || typeof target !== "object") {
throw new TypeError("can only assign an object");
}
properties.reduce((pre, acc) => {
if (acc === null) return pre;
// 不可遍历属性也会被拷贝
Reflect.ownKeys(acc).forEach((key) => {
Reflect.set(pre,key,acc[key])
});
return pre;
}, target);
};
typeof
const _typeof = (o) => Object.prototype.call(o).slice(8, -1).toLowerCase();
call/apply/bind
非严格模式下,则 context
指定为 null
或 undefined
时会自动替换为指向全局对象,原始值会被包装。
Function.prototype._call = function (context, ...args) {
const func = this;
context = context || window;
const caller = Symbol("caller");
context[caller] = func;
const res = context[caller](...args);
Reflect.deleteProperty(context, caller);
return res;
};
Function.prototype._apply = function (context, args) {
const func = this;
context = context || window;
const caller = Symbol("caller");
context[caller] = func;
const res = context[caller](...args);
Reflect.delete(context, caller);
return res;
};
Function.prototype._bind = function (context, ...args1) {
const func = this;
context = context || window;
function boundFn(...args2) {
return func.apply(this instanceof func ? this : context, [...args1, ...args2]);
}
boundFn.prototype = Object.create(func.prototype);
return boundFn;
};
实现一个迭代器
function createIterator(arrayLike) {
let i = 0;
return {
next() {
return {
value: arrayLike[i],
done: i++ >= arrayLike.length,
};
},
[Symbol.Iterator]() {
return this;
},
};
}
封装 ajax
使用 promise 封装 ajax,async javascript xml
const request = (method, url, params = {}) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return;
if (this.status === 200) {
resolve(JSON.parse(this.response));
} else {
reject(this.statusText);
}
};
xhr.onerror = reject;
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
if (/^(get)$|^(GET)$/.test(method)) {
let paramsStr = "";
Object.entries(params).forEach((item) => {
const [k, v] = item;
paramsStr += `${k}=${v}&`;
});
xhr.open("GET", url + `?${paramsStr}`);
xhr.send();
} else if (/^(post)$|^(POST)$/.test(method)) {
xhr.setRequestHeader("Content-type", "application/x-www-urlencode");
xhr.open("POST", url);
xhr.send(encodeURIComponent(params));
}
}).catch((e) => {
throw new Error(e);
});
};
sleep 函数
function sleep(wait) {
return new Promise((resolve) => {
setTimeout(resolve, wait);
});
}
fill 方法
如果 start 是个负数, 则开始索引会被自动计算成为 length+start, 其中 length 是 this 对象的 length 属性值。如果 end 是个负数, 则结束索引会被自动计算成为 length+end。
Array.prototype._fill = function (value, startIndex, endIndex) {
let i = startIndex >= 0 ? startIndex : startIndex + this.length;
endIndex = endIndex >= 0 ? endIndex : endIndex + this.length;
while (i < endIndex) {
this[i++] = value;
}
return this;
};
LRU 缓存
function LRU(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
LRU.prototype.get = function (key) {
if (this.cache.has(key)) {
// 如果有这个缓存,取出并调整在map中的位置
const val = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, val);
return val;
} else {
return false;
}
};
LRU.prototype.add = function (key, val) {
if (this.cache.has(key)) {
this.cache.delete(key);
this.cache.set(key, val);
} else {
const size = this.cache.size;
if (size === this.capacity) {
// 缓存满了,删除最旧的
const _key = this.cache.keys().next().value;
this.cache.delete(_key);
this.cache.set(key, val);
} else {
this.cache.set(key, val);
}
}
};
co 自执行 generator 函数
function co(generator) {
const gen = generator();
let ret;
try {
ret = gen.next();
} catch (e) {
ret = gen.throw(e);
}
return step(ret);
function step(res) {
if (res.done) {
return Promise.resolve(res.value);
}
// promise化
if (!(res.value instanceof Promise)) {
res.value = Promise.resolve(res.value);
}
return res.value.then(
(v) => step(gen.next(v)),
(e) => step(gen.throw(e))
);
}
}
// test
const gen1 = function* () {
const res = yield Promise.resolve(1);
const ret = yield Promise.resolve(res);
return ret;
};
co(gen1).then((v) => console.log(v)); // 1
手写系列
http://yoursite.com/2022/03/16/手写/