作者: 云峰 github: github.com/ihtml5
一、模拟new
function createNew(constructor, ...args) {
const obj = Object.create(null);
obj.__proto__ = constructor.prototype;
const result = constructor.apply(obj, args);
return typeof result === 'object' && result ? result : obj;
}
实现过程解析
- 创建一个空对象
- 将空对象的原型指向构造函数原型上
- 通过apply方法执行构造函数,并将构造函数中this指向obj,传递参数
- 如果返回值是对象或者数组,返回该返回值,否则返回obj
二、Object.create实现
Object.create = Object.create || function(target) {
const fNop = function() {};
fNop.prototype = target.prototype;
return new fNop();
}
实现过程解析
- 检查是否已经有原生的Object.create方法存在,如果存在直接返回
- 创建一个空函数fNop
- 将空函数fNop的原型指向target的原型
- 通过new返回一个基于target的实例
三、bind/apply/call
Function.prototype.bind = Function.prototype.bind || function(context, ...args) {
if (typeof this !== 'function') {
throw new Error('Function.prototype.bind is not Function');
}
const self = this;
const fNop = function() {};
const fBound = function(...rest) {
return self.apply(this instanceof fBound ? this : context, [...args,...rest]);
};
if (this.prototype) {
fNop.prototype = this.prototype;
}
fBound.prototype = new fNop();
return fBound;
}
Function.prototype.call = Function.prototype.call || function(context, ...args) {
if (typeof this !== 'function') {
throw new Error('Function.prototype.call is not Function');
}
const ctx = context || window;
ctx.fn = this;
const result = ctx.fn(...args);
delete ctx.fn;
return result;
}
Function.prototype.apply = Function.prototype.apply || function(context, ...args) {
if (typeof this !== 'function') {
throw new Error('Function.prototype.apply is not Function');
}
const ctx = context || window;
ctx.fn = this;
let result;
if (args.length > 0) {
result = ctx.fn(...args[0]);
} else {
result = ctx.fn();
}
delete ctx.fn;
return result;
}
四、debounce
防抖的原理就是:频繁触发事件,如果在n秒内继续有事件触发,则会以新触发的时间点为起点,延迟到n秒后执行,常用于搜索关键词查找。总之,就是要等你触发事件,n秒内不再触发事件。类比电梯,如果在n秒内,如果有人一直进来,就会一直等。
function debounce(fn, wait, immediate) {
let timeout;
let result;
function debounced(...args) {
const context = this;
if (timeout) {
clearTimeout(timeout);
}
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) {
result = fn.apply(context, args);
}
return result;
} else {
timeout = setTimeout(() => {
fn.apply(context, args);
}, wait);
}
}
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
}
return debounced;
}
五、throttle
节流的原理就是:每隔n秒触发一次,n秒内如果有频繁事件进入,不触发
function throttle(fn, wait, options = {}) {
let timeout;
let context;
let args;
let previous = 0;
function later() {
previous = options.leading === false ? 0 : new Date.getTime();
timeout = null;
fn.apply(context, args);
if (!timeout) {
context = null;
args = null;
}
}
function throttled(...rest) {
context = this;
args = [...rest];
const now = new Date().getTime();
if (!previous && options.leading === false) {
previous = now;
}
const remaing = wait - (now - previous);
if (remaing <=0 && remaing > wait) {
if (timeout) {
clearTimeout(timeout);
}
fn.apply(context, args);
if (!timeout) {
context = null;
args = null;
}
} else if (!timeout && options.trailing !== false) {
timeout = setTimout(later, remaing);
}
}
throttled.cancel = function() {
clearTimeout(timeout);
timeout = null;
previous = 0;
}
}
六、compose
1.同步版本
const compose = (...fns) => (value) => {
let len = fns.length;
while (len > 0 ) {
const fn = fns.pop();
value = fn(value);
len--;
}
return value;
}
2.异步版本
// promise
const compose = (...fns) => (...args) => {
const initFn = args.pop();
args.reverse().reduce((prev, cur) => {
return prev.then((result) => {
return cur.call(null, result);
});
}, Promise.resolve(initFn.apply(null, args));
}
七、flatten
const flatten = (array, maxDepth = 3) => {
if (maxDepth === 0) {
return array;
}
return array.reduce((a,b) => a.concat(Array.isArray(b)
? flatten(b, maxDepth - 1) : b), []);
}
flatten([1, [2, 3, [5, 6, [7,8]]]], 3); // [1, 2, 3, 5, 6, 7, 8]
八、reduce
Array.prototype.reduce = Array.prototype.reduce || function(fn, initialVal) {
if (!Array.isArray(this)) {
throw new Error('Array.prototype.reduce is used for array');
}
const len = this.length;
const isUsedInitailVal = typeof initialVal !== 'undefined';
let base = isUsedInitailVal ? initialVal : this[0];
const start = isUsedInitailVal ? 0 : 1;
for (let i = start; i < len; i++) {
base = fn(base, this[i]);
}
return base;
}
九、并发控制
常用于请求和逻辑调度
class Scheduler {
constructor(maxLimit = 2) {
this.maxLimit = maxLimit; // 并发控制
this.runingTask = 0; // 正在执行中的任务数
this.tasks = []; // 当前队列中的任务
this.cache = new Map();// 缓存promise回调
}
add(promiseCreator) {
this.tasks.push(promiseCreator); // 追加到队列尾部
return new Promise((resolve, reject) => {
this.cache.set(promiseCreator, resolve); // 缓存resove等真正执行的时候再resovle
this.run(); // 执行任务队列
}).catch((error) => console.error(`add promiseCreator ${error}`));
}
run() {
// 当任务队列不为空,且当前执行队列数不超过最大数据时,依次从队列中取出
while (this.runingTask < this.maxLimit && this.tasks.length > 0) {
const fn = this.tasks.shift(); // 从队列头部取出执行任务
this.runingTask++; // 当前执行任务加一
fn().then(() => {
this.runingTask--; // 当前执行任务减一
if (this.cache.has(fn)) { // 判断当前执行任务是否有回调
const resolve = this.cache.get(fn);
resolve(); // 执行当前回调函数
this.cache.delete(fn); // 从缓存中删除已经执行过的会调
}
this.run(); // 继续轮训
});
}
}
}
测试用例
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));
}