手写发布订阅模式
虽然js已经朝着函数式和数据响应式的方向狂奔而去,但学习过的面向对象思想怎么能轻易抛弃!设计模式要学,要清清楚楚地学。
发布订阅模式是什么?
发布订阅模式就是在一个事件中转站上,用户可以通过发布/订阅去发出或接收特定的事件。一个中转站一般约定有一个on/一个emit/一个off。 任务队列会区分不同的任务,所以我们用哈希map映射的方式去存储 on:把指定事件放进任务队列。
const eventHub = {
map: [],
on: (name, fn) => {
// into queue
eventHub.map[name] = eventHub.map[name] || []
eventHub.map[name].push(fn)
},
emit: (name, data) => {},
off: (name, fn) => {
// remove from queue
if(!eventHub.map[name]) {return}
const index = eventHub.map[name].indexOf(fn)
if(index < 0) {return}
eventHub.map[name].splice(index, 1)
}
}
手写AJAX
四行代码:
- 创建对象
- 设置请求方法和路径
- 监听
onreadystatechange事件,根据状态码调用相应回调 - 发送消息体
const ajax = (method, url, data, success, fail) => {
// 1. 创建对象
var request = new XMLHttpRequest();
// 2. 设置请求方法和路径
request.open(method, url);
// 3. 监听`onreadystatechange`事件,根据状态码调用相应回调
request.onreadystatechange = function () {
if(request.readyState === 4) {
if(request.status >= 200 && request.status < 300 || request.status === success(request)
} else {
fail(request)
}
}
};
// 4. 发送消息体
request.send();
}
手写深拷贝
方法一:JSON
const y = JSON.parse(JSON.stringify(x));
缺点:
- 不支持Date/正则/undefined/函数等数据类型
- 不支持引用(即环状结构 )
方法二:递归
我们需要根据不同数据类型进行不同的拷贝,这里统一用instance of进行判断。
const deepClone(a) {
if (a instanceof Object) {
let result = undefined;
// Data type: Object
if (a instanceof Function) {
if (a.prototype) { // function
result = function(){
return a.apply(this, arguments)
}
}
else { // arrow function
result = (...args) => {
return a.call(undefined, ...args)
}
}
}
else if (a instanceof Array) {
result = []
}
else if (a instanceof Date) {
result = new Date(a-0)
}
else if (a instanceof RegExp) {
result = new RegExp(a.source, a.flags)
}
else {
result = {}
}
for (let key in a) {
result[key] = deepClone(a[key])
}
}
else {
// Data type: String / Number / Bool / Undefined / Null / Symbol / Bigint
return a
}
}
以上代买没有考虑环形结构如a.self = a的情况就会进入死循环。所以我们要用一个map来存放缓存。为什么用map?因为普通Object的key是String或Symbol,而这里需要key是Object。
const cache = new Map();
const deepClone(a) {
// cache
if (cache.get(a)) { return cache.get(a) }
if (a instanceof Object) {
let result = undefined;
// Data type: Object
if (a instanceof Function) {
if (a.prototype) { // function
result = function(){
return a.apply(this, arguments)
}
}
else { // arrow function
result = (...args) => {
return a.call(undefined, ...args)
}
}
}
else if (a instanceof Array) {
result = []
}
else if (a instanceof Date) {
result = new Date(a-0)
}
else if (a instanceof RegExp) {
result = new RegExp(a.source, a.flags)
}
else {
result = {}
}
// cache
cache.set(a, result);
for (let key in a) {
result[key] = deepClone(a[key])
}
}
else {
// Data type: String / Number / Bool / Undefined / Null / Symbol / Bigint
return a
}
}
缺点一:不适用于考虑iframe中的对象。
缺点二:cache没有清空机制。所以我们需要把cache放在参数里,在每次递归都实用它。
const deepClone(a, cache) {
// cache
if (!cache) { cache = new Map(); }
if (cache.get(a)) { return cache.get(a) }
if (a instanceof Object) {
let result = undefined;
// Data type: Object
if (a instanceof Function) {
if (a.prototype) { // function
result = function(){
return a.apply(this, arguments)
}
}
else { // arrow function
result = (...args) => {
return a.call(undefined, ...args)
}
}
}
else if (a instanceof Array) {
result = []
}
else if (a instanceof Date) {
result = new Date(a-0)
}
else if (a instanceof RegExp) {
result = new RegExp(a.source, a.flags)
}
else {
result = {}
}
// cache
cache.set(a, result);
for (let key in a) {
result[key] = deepClone(a[key], cache)
}
}
else {
// Data type: String / Number / Bool / Undefined / Null / Symbol / Bigint
return a
}
}
测试代码:
const a = {
number: 11,
bool: false,
str: 'yeah',
empty: undefined,
emptyObj: null,
array: [
{name: 'chypre', age: 99},
{name: 'pepe', age: 100}
],
date: new Date(2023,0,1,22,33,0),
regex: /\.(j|t)sx/i,
obj: { name:'chypre', age: 99},
f1: (a, b) => a * b,
f2: function(a, b) { return a * b }
}
a.self = a
数组去重
方法一:使用Set()
let array = [1,1,2,2,3,3,3,4];
let unique = function(a) {
return Array.from(new Set(a))
}
let unique2 = function(a) {
return [...new Set(a)]
}
方法二:计数排序
let array = [1,1,2,2,3,3,3,4];
let unique = function(a) {
let map = {}
for (let i=0; i<a.length; i++) {
let number = a[i]
if (mumber in map) {
continue
}
map[number] = true
}
return Object.keys(map)
}
缺点:只支持字符串/如果同时存在字符串和数字,会合二为一。
方法三:使用Map
let uniq = function(a){
let map = new Map()
for(let i=0;i<a.length;i++){
let number = a[i]
if(number === undefined) {continue}
if(map.has(number)){
continue
}
map.set(number, true)
}
return [...map.keys()]
}