手写系列

本文最后更新于: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)过程中发生了什么?

  1. 创建一个空对象
  2. 将这个空对象链接到构造函数的原型
  3. this指向创建出来的空对象并执行构造函数
  4. 如果执行结果是对象则返回这个结果
  5. 否则返回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

检测右侧的原型是否在左侧的原型链上游,但它不能检测nullundefined

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 指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

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/手写/
作者
tatekii
发布于
2022年3月16日
许可协议