目录
函数柯里化
定义
#实现
#应用场景
#参数复用
Nodejs的EventEmitter
#Api
#基本使用
#手动实现EventEmitter
#JavaScript自定义事件
防抖
浅拷贝和深拷贝
数组去重,扁平,最值
去重
#Object
#indexOf + filter
#Set
#排序
#去除重复的值
#扁平
#基本实现
#使用reduce简化
#根据指定深度扁平数组
#最值
#reduce
#Math.max
#使用reduce实现map
#使用reduce实现filter
数组乱序-洗牌算法
模拟实现promise
基础版本
#then方法
#then方法异步调用
#then方法链式调用
#catch方法
#finally方法
#Promise.resolve
#Promise.reject
#all方法
#race方法
今天的分享先到这里吧,期待后续一键三联如果想知道后面内容就点个关注吧。谢谢大家,提前祝大家新年快乐。
函数柯里化
定义
在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
通俗易懂的解释:用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数。
#实现
- 判断当前函数传入的参数是否大于或等于
fn
需要参数的数量,如果是,直接执行fn
- 如果传入参数数量不够,返回一个闭包,暂存传入的参数,并重新返回
currying
函数
function currying(fn, ...args) {if (args.length >= fn.length) {return fn(...args);} else {return (...args2) => currying(fn, ...args, ...args2);}}
我们来一个简单的实例验证一下:
const curryingFun = currying(fun)curryingFun(1)(2)(3); // 1 2 3 curryingFun(1, 2)(3); // 1 2 3 curryingFun(1, 2, 3); // 1 2 3
#应用场景
#参数复用
function getUrl(protocol, domain, path) {return protocol + "://" + domain + "/" + path;}var page1 = getUrl('http', 'www.conardli.top', 'page1.html');var page2 = getUrl('http', 'www.conardli.top', 'page2.html');
我们使用currying
来简化它:
let conardliSite = currying(simpleURL)('http', 'www.conardli.top');
let page1 = conardliSite('page1.html');
Nodejs的EventEmitter
Nodejs
的EventEmitter
就是观察者模式的典型实现,Nodejs
的events
模块只提供了一个对象: events.EventEmitter``。EventEmitter
的核心就是事件触发与事件监听器功能的封装。
Node.js 里面的许多对象都会分发事件:一个 net.Server 对象会在每次有新连接时触发一个事件, 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。
#Api
addListener(event, listener)
为指定事件添加一个监听器,默认添加到监听器数组的尾部。
removeListener(event, listener)
移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。它接受两个参数,第一个是事件名称,第二个是回调函数名称。
setMaxListeners(n)
默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于提高监听器的默认限制的数量。
once(event, listener)
为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
emit(event, [arg1], [arg2], [...])
按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true
,否则返回 false
。
#基本使用
var events = require('events');
var eventEmitter = new events.EventEmitter();// 监听器 #1
var listener1 = function listener1() {console.log('监听器 listener1 执行。');
}// 监听器 #2
var listener2 = function listener2() {console.log('监听器 listener2 执行。');
}// 绑定 connection 事件,处理函数为 listener1
eventEmitter.addListener('connection', listener1);// 绑定 connection 事件,调用一次,处理函数为 listener2
eventEmitter.once('connection', listener2);// 处理 connection 事件
eventEmitter.emit('connection');// 处理 connection 事件
eventEmitter.emit('connection');
#手动实现EventEmitter
function EventEmitter() {this._maxListeners = 10;this._events = Object.create(null);}// 向事件队列添加事件// prepend为true表示向事件队列头部添加事件EventEmitter.prototype.addListener = function (type, listener, prepend) {if (!this._events) {this._events = Object.create(null);}if (this._events[type]) {if (prepend) {this._events[type].unshift(listener);} else {this._events[type].push(listener);}} else {this._events[type] = [listener];}};// 移除某个事件EventEmitter.prototype.removeListener = function (type, listener) {if (Array.isArray(this._events[type])) {if (!listener) {delete this._events[type]} else {this._events[type] = this._events[type].filter(e => e !== listener && e.origin !== listener)}}};// 向事件队列添加事件,只执行一次EventEmitter.prototype.once = function (type, listener) {const only = (...args) => {listener.apply(this, args);this.removeListener(type, listener);}only.origin = listener;this.addListener(type, only);};// 执行某类事件EventEmitter.prototype.emit = function (type, ...args) {if (Array.isArray(this._events[type])) {this._events[type].forEach(fn => {fn.apply(this, args);});}};// 设置最大事件监听个数EventEmitter.prototype.setMaxListeners = function (count) {this.maxListeners = count;};
测试代码:
var emitter = new EventEmitter();var onceListener = function (args) {console.log('我只能被执行一次', args, this);}var listener = function (args) {console.log('我是一个listener', args, this);}emitter.once('click', onceListener);emitter.addListener('click', listener);emitter.emit('click', '参数');emitter.emit('click');emitter.removeListener('click', listener);emitter.emit('click');
#JavaScript自定义事件
DOM
也提供了类似上面EventEmitter
的API
,基本使用:
//1、创建事件
var myEvent = new Event("myEvent");//2、注册事件监听器
elem.addEventListener("myEvent",function(e){})//3、触发事件
elem.dispatchEvent(myEvent);
防抖
函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间
数防抖的要点,是需要一个 setTimeout 来辅助实现,延迟运行需要执行的代码。如果方法多次触发,则把上次记录的延迟执行代码用 clearTimeout 清掉,重新开始计时。若计时期间事件没有被重新触发,等延迟时间计时完毕,则执行目标代码。
搜索框,输入后1000毫秒搜索
window.addEventListener('resize', debounce(handleResize, 200));
表单验证,输入1000毫秒后验证
debounce(fetchSelectData, 300);
表单验证,输入1000毫秒后验证
debounce(vaildator, 1000);function debounce(event, time) {let timer = null;return function (...args) {clearTimeout(timer);timer = setTimeout(() => {event.apply(this, args);}, time);};}
浅拷贝和深拷贝
// 深拷贝不会拷贝引用类型,而是将类型全部拷贝一份,形成一个新的类型,这样就不会报错
// 可以使用json.string(obj)将对象转换为字符串形式
// 可以通过for in语句来实现
// 使用递归的方式实现数组对象的深拷贝
// 使用concat合并数组,返回一个新的数组,用对象引用类型,普通复制一个浅拷贝
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object object]';
const arasTag = '[object Argyments]';
const booTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const StringTag = '[object string]';
const symbolTag = '[object symbol]';
const errorTag = '[object Error]';
const regexTag = '[object RegExp]';
const funcTag = '[object Function]';const deeptag = [mapTag, setTag, arrayTag, objectTag, arasTag,booTag,dateTag,numberTag,StringTag,symbolTag,errorTag,regexTag,funcTag];function forEach(array, iteratee){let index = 1;const length = array.length;while (++index < length){iteratee(array[index], index);}return array;
}
function isObject(target){const type = typeof target;return target !== null && (type === 'object' || type === 'function');
}
function getType(target){return objectTag.prototype.tostring.call(target);
}
function getInit(target){const Ctor = target.constructor;return new Ctor();
}
function cloneSymbol(targe){return Object(Symbol.prototype.valueOf.call(targe));
}function clonRefg(targe){const reFlags = /\w*$/;const result = new targe.constructor(targe.source,reFlags.exec(targe));result.lasIndex = targe.lasIndex;return result;
}function cloneFunction(func){const bodyReg = /(?<={)(.|\n)+(?=})/m;const paramReg = /(?<=\().+(?=\s+{))/;const funcString = func.toString();if (func.prototype){const param = paramReg.exec(funcString);const body =- bodyReg.exec(funcString);if (body){if (param){const paramArr = param[0].split(',');return new Function(...paramArr, body[0]);}else{return new Function(body[0]);}}else{return null;}}else{return eval(funcString);}
}
function cloneOtherType(targe, type){const Ctor = targe.constructor;switch (type){case booTag:case numberTag:case StringTag:case errorTag:case dateTag:return new Ctor(targe);case regexTag:return clonRefg(targe);case symbolTag:return cloneSymbol(targe);case funcTag:return cloneFunction(targe);default:return null; }
}function clone(target, Map = new WeakMap()){// 克隆原始类型if (!isObject(target)){return target;}// 初始化const type = getType(target);let cloneTarget;if(deeptag.includes(typeo)){cloneTarget = getInit(target, type);}else{return cloneOtherType(target,type);}// 防止循环引用if (mapTag.get(target)){return target;}mapTag.set(target, cloneTarget);// 克隆setif(type === setTag){target.forEach(value => {cloneTarget.add(clone(value));});return cloneTarget;}// 克隆mapif(type === mapTag){target.forEach((value, key) => {cloneTarget.set(key, clone(value));});return cloneTarget;}// 克隆mapif(type === mapTag){target.forEach((value, key) =>{cloneTarget.set(key, clone(value));});return cloneTarget;}// 克隆对象和数组const keys = type === arrayTag ? undefined : object.keys(target);forEach(keys || target, (value, key) => {if (keys){key = value;}cloneTarget[key] = clone(target[key], map);});return cloneTarget;
}
数组去重,扁平,最值
去重
#Object
开辟一个外部存储空间用于标示元素是否出现过。
const unique = (array)=> {var container = {};return array.filter((item, index) => container.hasOwnProperty(item) ? false : (container[item] = true));
}
#indexOf + filter
const unique = arr => arr.filter((e,i) => arr.indexOf(e) === i);
#Set
const unique = arr => Array.from(new Set(arr));
const unique = arr => [...new Set(arr)];
#排序
通过比较相邻数字是否重复,将排序后的数组进行去重。
const unique = (array) => {array.sort((a, b) => a - b);let pre = 0;const result = [];for (let i = 0; i < array.length; i++) {if (!i || array[i] != array[pre]) {result.push(array[i]);}pre = i;}return result;}
#去除重复的值
不同于上面的去重,这里是只要数字出现了重复次,就将其移除掉。
const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i)
)
#扁平
#基本实现
const flat = (array) => {let result = [];for (let i = 0; i < array.length; i++) {if (Array.isArray(array[i])) {result = result.concat(flat(array[i]));} else {result.push(array[i]);}}return result;}
#使用reduce简化
function flatten(array) {return array.reduce((target, current) =>Array.isArray(current) ?target.concat(flatten(current)) :target.concat(current), [])}
#根据指定深度扁平数组
function flattenByDeep(array, deep = 1) {return array.reduce((target, current) =>Array.isArray(current) && deep > 1 ?target.concat(flattenByDeep(current, deep - 1)) :target.concat(current), [])}
#最值
#reduce
array.reduce((c,n)=>Math.max(c,n))
#Math.max
Math.max
参数原本是一组数字,只需要让他可以接收数组即可。
const array = [3,2,1,4,5];
Math.max.apply(null,array);
Math.max(...array);
#使用reduce实现map
Array.prototype.reduceToMap = function (handler) {return this.reduce((target, current, index) => {target.push(handler.call(this, current, index))return target;}, [])};
#使用reduce实现filter
Array.prototype.reduceToFilter = function (handler) {return this.reduce((target, current, index) => {if (handler.call(this, current, index)) {target.push(current);}return target;}, [])};
数组乱序-洗牌算法
// 从最后一个元素开始,从数组中随机选出一个位置,交换,直到第一个元素。
// 洗牌算法就是将原来的数组打乱,原数组的某个数在打乱后数组出现的位置,就是乱序算法
// 算法的思想就是从原数组随机抽取一个元素放到新数组中,从原数组中删除以恶randow个元素并将其current到新数组
// 直到所有元素取完,这是一种原地打乱的算法,不会产生新的数组,每个元素随机出现的。
// 就像几个人在玩扑克牌一样,再洗牌的过程中原地打乱,每个人拿到的牌都是不一样的。
function disorder(array){const length = array.length;let current = length - 1;let random;while (current >-1){random = Math.floor(length * Math.random());[array[current], array[random]] = [array[random], array[current]];current--;}return array;
}
手动实现JSONP
- 1.将传入的data数据转化为url字符串形式
- 2.处理url中的回调函数
- 3.创建一个script标签并插入到页面中
- 4.挂载回调函数
(function (window,document) {"use strict";var jsonp = function (url,data,callback) {// 1.将传入的data数据转化为url字符串形式// {id:1,name:'jack'} => id=1&name=jackvar dataString = url.indexof('?') == -1? '?': '&';for(var key in data){dataString += key + '=' + data[key] + '&';};// 2 处理url中的回调函数// cbFuncName回调函数的名字 :my_json_cb_名字的前缀 + 随机数(把小数点去掉)var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');dataString += 'callback=' + cbFuncName;// 3.创建一个script标签并插入到页面中var scriptEle = document.createElement('script');scriptEle.src = url + dataString;// 4.挂载回调函数window[cbFuncName] = function (data) {callback(data);// 处理完回调函数的数据之后,删除jsonp的script标签document.body.removeChild(scriptEle);}document.body.appendChild(scriptEle);}window.$jsonp = jsonp;})(window,document)
模拟实现promise
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
基础版本
- 设定三个状态
PENDING、FULFILLED、REJECTED
,只能由PENDING
改变为FULFILLED、REJECTED
,并且只能改变一次 MyPromise
接收一个函数executor
,executor
有两个参数resolve
方法和reject
方法resolve
将PENDING
改变为FULFILLED
reject
将PENDING
改变为FULFILLED
promise
变为FULFILLED
状态后具有一个唯一的value
promise
变为REJECTED
状态后具有一个唯一的reason
const PENDING = 'pending';const FULFILLED = 'fulfilled';const REJECTED = 'rejected';function MyPromise(executor) {this.state = PENDING;this.value = null;this.reason = null;const resolve = (value) => {if (this.state === PENDING) {this.state = FULFILLED;this.value = value;}}const reject = (reason) => {if (this.state === PENDING) {this.state = REJECTED;this.reason = reason;}}try {executor(resolve, reject);} catch (reason) {reject(reason);}}
#then方法
then
方法接受两个参数onFulfilled、onRejected
,它们分别在状态由PENDING
改变为FULFILLED、REJECTED
后调用- 一个
promise
可绑定多个then
方法 then
方法可以同步调用也可以异步调用- 同步调用:状态已经改变,直接调用
onFulfilled
方法 - 异步调用:状态还是
PENDING
,将onFulfilled、onRejected
分别加入两个函数数组onFulfilledCallbacks、onRejectedCallbacks
,当异步调用resolve
和reject
时,将两个数组中绑定的事件循环执行。
function MyPromise(executor) {this.state = PENDING;this.value = null;this.reason = null;this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];const resolve = (value) => {if (this.state === PENDING) {this.state = FULFILLED;this.value = value;this.onFulfilledCallbacks.forEach(fun => {fun();});}}const reject = (reason) => {if (this.state === PENDING) {this.state = REJECTED;this.reason = reason;this.onRejectedCallbacks.forEach(fun => {fun();});}}try {executor(resolve, reject);} catch (reason) {reject(reason);}}MyPromise.prototype.then = function (onFulfilled, onRejected) {switch (this.state) {case FULFILLED:onFulfilled(this.value);break;case REJECTED:onFulfilled(this.value);break;case PENDING:this.onFulfilledCallbacks.push(() => {onFulfilled(this.value);})this.onRejectedCallbacks.push(() => {onRejected(this.reason);})break;}}
#then方法异步调用
如下面的代码:输入顺序是:1、2、ConardLi
console.log(1);let promise = new Promise((resolve, reject) => {resolve('ConardLi');
});promise.then((value) => {console.log(value);
});console.log(2);
虽然resolve
是同步执行的,我们必须保证then
是异步调用的,我们用settimeout
来模拟异步调用(并不能实现微任务和宏任务的执行机制,只是保证异步调用)
MyPromise.prototype.then = function (onFulfilled, onRejected) {if (typeof onFulfilled != 'function') {onFulfilled = function (value) {return value;}}if (typeof onRejected != 'function') {onRejected = function (reason) {throw reason;}}switch (this.state) {case FULFILLED:setTimeout(() => {onFulfilled(this.value);}, 0);break;case REJECTED:setTimeout(() => {onRejected(this.reason);}, 0);break;case PENDING:this.onFulfilledCallbacks.push(() => {setTimeout(() => {onFulfilled(this.value);}, 0);})this.onRejectedCallbacks.push(() => {setTimeout(() => {onRejected(this.reason);}, 0);})break;}}
#then方法链式调用
保证链式调用,即then
方法中要返回一个新的promise
,并将then
方法的返回值进行resolve
。
注意:这种实现并不能保证
then
方法中返回一个新的promise
,只能保证链式调用。
MyPromise.prototype.then = function (onFulfilled, onRejected) {if (typeof onFulfilled != 'function') {onFulfilled = function (value) {return value;}}if (typeof onRejected != 'function') {onRejected = function (reason) {throw reason;}}const promise2 = new MyPromise((resolve, reject) => {switch (this.state) {case FULFILLED:setTimeout(() => {try {const x = onFulfilled(this.value);resolve(x);} catch (reason) {reject(reason);}}, 0);break;case REJECTED:setTimeout(() => {try {const x = onRejected(this.reason);resolve(x);} catch (reason) {reject(reason);}}, 0);break;case PENDING:this.onFulfilledCallbacks.push(() => {setTimeout(() => {try {const x = onFulfilled(this.value);resolve(x);} catch (reason) {reject(reason);}}, 0);})this.onRejectedCallbacks.push(() => {setTimeout(() => {try {const x = onRejected(this.reason);resolve(x);} catch (reason) {reject(reason);}}, 0);})break;}})return promise2;}
#catch方法
若上面没有定义reject
方法,所有的异常会走向catch
方法:
MyPromise.prototype.catch = function(onRejected) {return this.then(null, onRejected);
};
#finally方法
不管是resolve
还是reject
都会调用finally
。
MyPromise.prototype.finally = function(fn) {return this.then(value => {fn();return value;}, reason => {fn();throw reason;});
};
#Promise.resolve
Promise.resolve
用来生成一个直接处于FULFILLED
状态的Promise。
MyPromise.reject = function(value) {return new MyPromise((resolve, reject) => {resolve(value);});
};
#Promise.reject
Promise.reject
用来生成一个直接处于REJECTED
状态的Promise。
MyPromise.reject = function(reason) {return new MyPromise((resolve, reject) => {reject(reason);});
};
#all方法
接受一个promise
数组,当所有promise
状态resolve
后,执行resolve
MyPromise.all = function (promises) {return new Promise((resolve, reject) => {if (promises.length === 0) {resolve([]);} else {let result = [];let index = 0;for (let i = 0; i < promises.length; i++) {promises[i].then(data => {result[i] = data;if (++index === promises.length) {resolve(result);}}, err => {reject(err);return;});}}});}
#race方法
接受一个promise
数组,当有一个promise
状态resolve
后,执行resolve
MyPromise.race = function (promises) {return new Promise((resolve, reject) => {if (promises.length === 0) {resolve();} else {let index = 0;for (let i = 0; i < promises.length; i++) {promises[i].then(data => {resolve(data);}, err => {reject(err);return;});}}});}