共计 7241 个字符,预计需要花费 19 分钟才能阅读完成。
前言
记录几段常用的 js 代码实现,掌握了实现原理,就对这些用法会有更加深入的理解。
一、深拷贝
深拷贝,深比较,之前有文章写过,请看:https://www.haorooms.com/post/js_copy_sq
今天的深 copy 考虑了 Symbol 属性,代码如下:
const cloneDeep1 = (target, hash = new WeakMap()) => {
// 对于传入参数处理
if (typeof target !== 'object' || target === null) {return target;}
// 哈希表中存在直接返回
if (hash.has(target)) return hash.get(target);
const cloneTarget = Array.isArray(target) ? [] : {};
hash.set(target, cloneTarget);
// 针对 Symbol 属性
const symKeys = Object.getOwnPropertySymbols(target);
if (symKeys.length) {symKeys.forEach(symKey => {if (typeof target[symKey] === 'object' && target[symKey] !== null) {cloneTarget[symKey] = cloneDeep1(target[symKey]);
} else {cloneTarget[symKey] = target[symKey];
}
})
}
for (const i in target) {if (Object.prototype.hasOwnProperty.call(target, i)) {cloneTarget[i] =
typeof target[i] === 'object' && target[i] !== null
? cloneDeep1(target[i], hash)
: target[i];
}
}
return cloneTarget;
}
二、Promise 实现
// 模拟实现 Promise
// Promise 利用三大手段解决回调地狱:// 1. 回调函数延迟绑定
// 2. 返回值穿透
// 3. 错误冒泡
// 定义三种状态
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已成功
const REJECTED = 'REJECTED'; // 已失败
class Promise {constructor(exector) {
// 初始化状态
this.status = PENDING;
// 将成功、失败结果放在 this 上,便于 then、catch 访问
this.value = undefined;
this.reason = undefined;
// 成功态回调函数队列
this.onFulfilledCallbacks = [];
// 失败态回调函数队列
this.onRejectedCallbacks = [];
const resolve = value => {
// 只有进行中状态才能更改状态
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 成功态函数依次执行
this.onFulfilledCallbacks.forEach(fn => fn(this.value));
}
}
const reject = reason => {
// 只有进行中状态才能更改状态
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 失败态函数依次执行
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
try {
// 立即执行 executor
// 把内部的 resolve 和 reject 传入 executor,用户可调用 resolve 和 reject
exector(resolve, reject);
} catch(e) {
// executor 执行出错,将错误内容 reject 抛出去
reject(e);
}
}
then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected :
reason => {throw new Error(reason instanceof Error ? reason.message : reason) }
// 保存 this
const self = this;
return new Promise((resolve, reject) => {if (self.status === PENDING) {self.onFulfilledCallbacks.push(() => {
// try 捕获错误
try {
// 模拟微任务
setTimeout(() => {const result = onFulfilled(self.value);
// 分两种情况:// 1. 回调函数返回值是 Promise,执行 then 操作
// 2. 如果不是 Promise,调用新 Promise 的 resolve 函数
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {reject(e);
}
});
self.onRejectedCallbacks.push(() => {
// 以下同理
try {setTimeout(() => {const result = onRejected(self.reason);
// 不同点:此时是 reject
result instanceof Promise ? result.then(resolve, reject) : reject(result);
})
} catch(e) {reject(e);
}
})
} else if (self.status === FULFILLED) {
try {setTimeout(() => {const result = onFulfilled(self.value);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
});
} catch(e) {reject(e);
}
} else if (self.status === REJECTED) {
try {setTimeout(() => {const result = onRejected(self.reason);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {reject(e);
}
}
});
}
catch(onRejected) {return this.then(null, onRejected);
}
static resolve(value) {if (value instanceof Promise) {
// 如果是 Promise 实例,直接返回
return value;
} else {
// 如果不是 Promise 实例,返回一个新的 Promise 对象,状态为 FULFILLED
return new Promise((resolve, reject) => resolve(value));
}
}
static reject(reason) {return new Promise((resolve, reject) => {reject(reason);
})
}
static all(promiseArr) {
const len = promiseArr.length;
const values = new Array(len);
// 记录已经成功执行的 promise 个数
let count = 0;
return new Promise((resolve, reject) => {
for (let i = 0; i {values[i] = val;
count++;
// 如果全部执行完,返回 promise 的状态就可以改变了
if (count === len) resolve(values);
},
err => reject(err),
);
}
})
}
static race(promiseArr) {return new Promise((resolve, reject) => {promiseArr.forEach(p => {Promise.resolve(p).then(val => resolve(val),
err => reject(err),
)
})
})
}
}
三、Promise 并行限制
class Scheduler {constructor() {this.queue = [];
this.maxCount = 2;
this.runCounts = 0;
}
add(promiseCreator) {this.queue.push(promiseCreator);
}
taskStart() {for (let i = 0; i = this.maxCount) {return;}
this.runCounts++;
this.queue.shift()().then(() => {
this.runCounts--;
this.request();});
}
}
const timeout = time => new Promise(resolve => {setTimeout(resolve, time);
})
const scheduler = new Scheduler();
const addTask = (time,order) => {scheduler.add(() => timeout(time).then(()=>console.log(order)))
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
scheduler.taskStart()
// 2
// 3
// 1
// 4
四、JSONP 的实现
const jsonp = ({url, params, callbackName}) => {const generateUrl = () => {
let dataSrc = '';
for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`;
}
}
dataSrc += `callback=${callbackName}`;
return `${url}?${dataSrc}`;
}
return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');
scriptEle.src = generateUrl();
document.body.appendChild(scriptEle);
window[callbackName] = data => {resolve(data);
document.removeChild(scriptEle);
}
})
}
五、event 模块实现
实现 node 中回调函数的机制,node 中回调函数其实是内部使用了观察者模式。
function EventEmitter() {this.events = new Map();
}
// 需要实现的一些方法:// addListener、removeListener、once、removeAllListeners、emit
// 模拟实现 addlistener 方法
const wrapCallback = (fn, once = false) => ({callback: fn, once});
EventEmitter.prototype.addListener = function(type, fn, once = false) {const hanlder = this.events.get(type);
if (!hanlder) {
// 没有 type 绑定事件
this.events.set(type, wrapCallback(fn, once));
} else if (hanlder && typeof hanlder.callback === 'function') {
// 目前 type 事件只有一个回调
this.events.set(type, [hanlder, wrapCallback(fn, once)]);
} else {// 目前 type 事件数>=2
hanlder.push(wrapCallback(fn, once));
}
}
// 模拟实现 removeListener
EventEmitter.prototype.removeListener = function(type, listener) {const hanlder = this.events.get(type);
if (!hanlder) return;
if (!Array.isArray(this.events)) {if (hanlder.callback === listener.callback) this.events.delete(type);
else return;
}
for (let i = 0; i {item.callback.apply(this, args);
if (item.once) {this.removeListener(type, item);
}
})
} else {hanlder.callback.apply(this, args);
if (hanlder.once) {this.events.delete(type);
}
}
return true;
}
EventEmitter.prototype.removeAllListeners = function(type) {const hanlder = this.events.get(type);
if (!hanlder) return;
this.events.delete(type);
}
六、一次性渲染几万条数据数据,页面不算很卡的方法
几万条数据,假如不是滚动加载,一次性渲染到页面中,单单是节点,都会很慢。下面方法可以渲染出来。但是节点很多,也会比较慢,但是是一次性渲染几万条数据比较好的方式了。
setTimeout(() => {
// 插入十万条数据
const total = 100000;
// 一次插入的数据
const once = 20;
// 插入数据需要的次数
const loopCount = Math.ceil(total / once);
let countOfRender = 0;
const ul = document.querySelector('ul');
// 添加数据的方法
function add() {const fragment = document.createDocumentFragment();
for(let i = 0; i
这个方法使用 createDocumentFragment 和 requestAnimationFrame,将操作切分为一小段一小段执行。
七、将 VirtualDom 转化为真实 DOM 结构
// vnode 结构:// {
// tag,
// attrs,
// children,
// }
//Virtual DOM => DOM
function render(vnode, container) {container.appendChild(_render(vnode));
}
function _render(vnode) {
// 如果是数字类型转化为字符串
if (typeof vnode === 'number') {vnode = String(vnode);
}
// 字符串类型直接就是文本节点
if (typeof vnode === 'string') {return document.createTextNode(vnode);
}
// 普通 DOM
const dom = document.createElement(vnode.tag);
if (vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach(key => {const value = vnode.attrs[key];
dom.setAttribute(key, value);
})
}
// 子数组进行递归操作
vnode.children.forEach(child => render(child, dom));
return dom;
}
八、字符串解析问题
var a = {
b: 123,
c: '456',
e: '789',
}
var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;
// => 'a123aa456aa {a.d}aaaa'
实现函数使得将 str 字符串中的 {} 内的变量替换,如果属性不存在保持原样(比如{a.d})
类似于模版字符串,但有一点出入,实际上原理大差不差
const fn1 = (str, obj) => {
let res = '';
// 标志位,标志前面是否有{
let flag = false;
let start;
for (let i = 0; i {const keys = str.split('.').slice(1);
let index = 0;
let o = obj;
while (index
九、数组扁平化
数组扁平化是指将一个多维数组变为一个一维数组
const arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]
方法一:使用 flat()
const res1 = arr.flat(Infinity);
方法二:正则
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/[|]/g, '') +']');
正文完