目录
函数柯里化
定义
#实现
#应用场景
#参数复用
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
的events
模块只提供了一个对象: events.EventEmitter``。EventEmitter
的核心就是事件触发与事件监听器功能的封装。
Node.js 里面的许多对象都会分发事件:一个 net.Server 对象会在每次有新连接时触发一个事件, 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。
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');
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');
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);
// 克隆set
if(type === setTag){
target.forEach(value => {
cloneTarget.add(clone(value));
});
return cloneTarget;
}
// 克隆map
if(type === mapTag){
target.forEach((value, key) => {
cloneTarget.set(key, clone(value));
});
return cloneTarget;
}
// 克隆map
if(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;
}
开辟一个外部存储空间用于标示元素是否出现过。
const unique = (array)=> {
var container = {};
return array.filter((item, index) => container.hasOwnProperty(item) ? false : (container[item] = true));
}
const unique = arr => arr.filter((e,i) => arr.indexOf(e) === i);
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;
}
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)
, [])
}
array.reduce((c,n)=>Math.max(c,n))
Math.max
参数原本是一组数字,只需要让他可以接收数组即可。
const array = [3,2,1,4,5];
Math.max.apply(null,array);
Math.max(...array);
Array.prototype.reduceToMap = function (handler) {
return this.reduce((target, current, index) => {
target.push(handler.call(this, current, index))
return target;
}, [])
};
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
(function (window,document) {
"use strict";
var jsonp = function (url,data,callback) {
// 1.将传入的data数据转化为url字符串形式
// {id:1,name:'jack'} => id=1&name=jack
var 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对象有以下两个特点。
(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
方法接受两个参数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;
}
}
如下面的代码:输入顺序是: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
方法中要返回一个新的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;
}
若上面没有定义reject
方法,所有的异常会走向catch
方法:
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
};
不管是resolve
还是reject
都会调用finally
。
MyPromise.prototype.finally = function(fn) {
return this.then(value => {
fn();
return value;
}, reason => {
fn();
throw reason;
});
};
Promise.resolve
用来生成一个直接处于FULFILLED
状态的Promise。
MyPromise.reject = function(value) {
return new MyPromise((resolve, reject) => {
resolve(value);
});
};
Promise.reject
用来生成一个直接处于REJECTED
状态的Promise。
MyPromise.reject = function(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
};
接受一个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;
});
}
}
});
}
接受一个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;
});
}
}
});
}