柯里化实现类型判断
// 实现utils工具集,带有判断类型的函数const utils = {}const isType = type => checkVal => Object.prototype.toString.call(checkVal) === `[object ${type}]`const typeMapping = ['String', 'Number', 'Function']typeMapping.forEach(type => {utils[`is${type}`] = isType(type)})// utils.isString('123') true
柯里化
//柯里化加法const sum = (a, b, c, d, e) => {return a + b + c + d + e}// curring sum function// 这里使用的是递归的方法// 注意: 函数的length属性,返回其参数的个数const curring = (executer, arr = []) => {const len = executer.lengthreturn function (...args) {let argsAssembles = [...arr, ...args]// 当递归的参数数组和本身调用函数参数相等时if (argsAssembles.length === len) {return executer(...argsAssembles)} else {return curring(executer, argsAssembles)}}}const sumExecuter = curring(sum)console.log(sumExecuter(1)(2)(3)(4)(5))//15// partial function callconsole.log(sumExecuter(1,2)(3)(4)(5))//15
手写Promise方法
思路:
- 需要三种状态:PENDING、FULFILLED、REJECTED、
- 对于同步,直接根据状态判断执行回调
- 对于异步,利用发布订阅模式,将异步回调的
onResolve、onReject推入队列,在resolve函数中执行 - 对于链式调用,利用递归,返回自身实例,将
onResolve、onReject结果保存,在递归的resolve中执行。利用了promise立即执行的特点,将返回值保存,达成传入下一个then作为函数参数的目的。这里注意核心逻辑。我们在promise递归中,当then返回了一个promise时,我们需要传递一个异步调用的处理函数来处理新返回的(then方法返回)promise,因为我们在调用外部的解析函数resolvePromise时,递归的新实例并没有生成。则根据这个思路,我们利用宏任务或微任务,例子中我们使用的是宏任务setTimeout来异步执行,这样做就完成了处理函数会在创建实例后执行。综上,我们的处理函数需要既处理then中的同步返回,也要处理promise返回。- 如果传入的返回值(即参数
t)是promise,则采用promise的返回值,继续递归 - 如果传入的返回值(即参数
t)不是promise,则直接调用resolve,参考下面的链式调用规则 - 判断是不是promise的关键,是判断是不是对象或函数,其次看是不是有
then方法。
- 如果传入的返回值(即参数
promise链式调用的规则
- 如果then方法中(不管成功还是失败),返回值的不是一个promise,则这个值会传递给外层的下一个then的成功回调中
- 如果then方法中的调用方法出错了,则会走到下一个then的失败回调中
如果then方法返回的是一个promise,则会用这个promise的结果作为下一次then的成功或者失败 ```javascript // 异步调用then方法的核心逻辑 function resolvePromise(t, promiseIns, resolve, reject) { // 以下内容参考promise A+规范实现 // https://promisesaplus.com/#terminology if (t === promiseIns) { return reject(TypeError(‘类型出错了,不能调用自己’)) } // 查看 t 是不是promise,如果是promise,则采用promise的状态 // https://promisesaplus.com/#point-53 if ((typeof t === ‘object’ && t !== null) || typeof t === ‘function’) { try { // t 可以是一个对象或者是函数 // 如何区分是不是Promise呢? 看他是不是有then方法 let then = t.then // 如果有then方法,则认为是promise if (typeof then === ‘function’) {
// 采用这个promise的状态then.call(x,function (y) {resolve(y)},function (r) {reject(r)})
} else {
// 没有then方法,则是一个普通对象或函数resolve(t)
} } catch (error) { reject(t) } } else { // 既不是对象,也不是函数,那么t肯定不是promise,是一个原始数据类型,所以直接resolve resolve(t) } } class Promise { constructor(executor) { // promise的三种状态 this.STATUS = { PENDING: ‘PENDING’, FULFILLED: ‘FULFILLED’, REJECTED: ‘REJECTED’, } // 当前的状态 this.STATE = this.STATUS.PENDING this.val = undefined this.reason = undefined // 保存异步回调的事件订阅队列 this.onResolveQueue = [] this.onRejectQueue = [] const resolve = val => { if (this.STATE === ‘PENDING’) {
this.STATE = this.STATUS.FULFILLEDthis.val = val// 成功后,发布事件,将存入的所有函数执行this.onResolveQueue.forEach(resolve => resolve())
} } const reject = reason => { if (this.STATE === ‘PENDING’) {
this.STATE = this.STATUS.REJECTEDthis.reason = reasonthis.onRejectQueue.forEach(reject => reject())
} } // promise创建之后马上执行 // 执行中的错误会将状态改为 REJECTED //new Promise ((resolve, reject) => { // …some code here // }) try { executor(resolve, reject) } catch (error) { reject(error) } } // then 方法 // promise.then( response => {}, err => {}) // 链式的then方法调用,采用递归的方式,返回新的实例 // 核心逻辑 // 这里注意,当我们返回一个promise时,我们需要一个异步处理函数,但是这个处理函数 // 无法获取新的实例,因为实例还没有生成,则我们利用异步方法(setTimeout)来后执行,这样就可以 // 在获取实例后,将其放入处理函数中执行 then(onResolve, onReject) { const thenableCall = new Promise((resolve, reject) => { if (this.STATE === ‘FULFILLED’) {
setTimeout(() => {try {const t = onResolve(this.val)resolvePromise(t, thenableCall, resolve, reject)} catch (error) {reject(t)}}, 0)
} if (this.STATE === ‘REJECTED’) {
setTimeout(() => {try {const t = onReject(this.reason)resolvePromise(t, thenableCall, resolve, reject)} catch (error) {reject(t)}}, 0)
} // 如果当前状态是PENDING,则为异步调用,这里使用发布订阅模式 // 将回调函数推入队列,结合装饰器的切片函数的目的,是方便在回调函数中加入逻辑 if (this.STATE === ‘PENDING’) {
this.onResolveQueue.push(() => {setTimeout(() => {try {const t = onResolve(this.val)resolvePromise(t, thenableCall, resolve, reject)} catch (error) {reject(t)}}, 0)})if (onReject) {this.onRejectQueue.push(() => {setTimeout(() => {try {const t = onReject(this.reason)resolvePromise(t, thenableCall, resolve, reject)} catch (error) {reject(t)}}, 0)})}
} })
return thenableCall } }
const promiseDemo = new Promise((resolve, reject) => { console.log(‘run code’) setTimeout(resolve, 1000, ‘done’) }) promiseDemo .then( res => (console.log(res), ‘thenable done!’), err => console.log(err) ) .then(res => { console.log(res) })
---<a name="aCpkN"></a>## 手写promisify将需要执行的函数`promise`化,使得我们可以`then````javascriptconst fs = require('fs')// 函数形式const promisify = function (caller) {return function (...args) {return new Promise((resolve, reject) => {caller(...args, (err, data) => {if (err) reject(err)resolve(data)})})}}//箭头函数形式const promisifyArrowFunction = caller => (...args) =>new Promise((resolve, reject) => {caller(...args, (err, data) => (err ? reject(err) : resolve(data)))})const readFile = promisify(fs.stat)readFile('./test.text', 'utf-8').then(res => {console.log(res)})
深拷贝
这是一个不完全版,需要优化,思路是对的,如果只利用JSON.stringify对于一些特殊类型无法做到处理,例如Date、RegExp,思路是先处理这些特殊内容,针对数组和对象,
利用Object.prototype.constructor来拷贝一个新对象,hash参数的作用是防止循环引用的出现,做缓存处理。还有一个比较需要注意的点是 null == undefined双等是返回true的,这里就对null、undefined这两个基本类型做了处理
function deepClone(target, hash = new WeakMap()) {if (target instanceof RegExp) return new RegExp(target);if (target instanceof Date) return new Date(target);if (target == null) return target;if (typeof target !== "object") return target;if (hash.has(target)) return hash.get(target);const copy = new target.constructor();hash.set(target, copy);for (const key in target) {if (Reflect.ownKeys(target).includes(key)) {copy[key] = deepClone(target[key]);}}return copy;}const demo = {name: "szh",department: {development: ["FE", "BE", "DS"]},employees: ["100"]};const clone = deepClone(demo);console.log(clone === demo); //false
组合函数
将多个函数组合执行,实现套娃效果,形如compose(one(two(three)))前一个函数,以后一个函数的返回值作为参数。
思路(reduceRight方法):
1.返回一个新函数,参数为需要执行的最后一个函数的参数
2.利用闭包,将所有的函数利用Array.prototype.reduceRight从最后一个向前依次执行。
3.需要注意的是,我们需要先执行一次最后一个函数拿到返回值,然后进行收敛
// 实现组合函数// 函数式编程实现组合函数// 如const final = compose(one(two(three())))//reduceRight方式实现function composeBaseOnReduceRight(...fns) {return (...args) => {//获取最后一个函数句柄const lastOneHandle = fns[fns.length - 1]// 执行最后一个函数const entryFn = lastOneHandle(...args)// 排除最后一个函数const executer = fns.slice(0, -1)// 序列化执行所有函数return executer.reduceRight((acc, curr) => {return curr(acc)}, entryFn)}}const one = str => '$' + strconst two = str => '#' + strconst three = (a, b) => a + bconst final = composeBaseOnReduceRight(one, two, three)console.log(final('1', '2'))console.log(final('3', '4'))console.log(final('5', '6'))
思路(reduce方法):
1.利用reduce返回一个函数,函数的返回值是将后一个函数的执行结果作为参数传递给前一个函数,但是不执行
2.利用reduce特性,迭代所有的函数,并将所有执行的函数体返回。
3.传入参数并执行函数,将触发所有函数的执行
//reduce实现组合函数//这里我们先只用两个函数传入的方式做个理解// a,b//第一次传入的时候 acc:a, curr:b//返回一个函数,const final = compose(a,b)//此时执行这个函数,传入参数,正常执行//然后我们理解传多个参数的情况// a,b,c//第一次执行,返回的结果为// function (...args) {// return acc(curr())// }// 进入第二次执行会将第一次执行的结果,作为第二次的acc传入,依此类推// 在最后调用的时候,会将最后一个函数的参数传入,整个函数执行function compose(...fns) {return fns.reduce(function(acc, curr) {return function(...args) {return acc(curr(...args));};});}//arrow function implementfunction compose = (...fns) => fns.reduce((acc, curr) => (...args) => acc(curr(...args)))const one = str => "#" + str;const two = str => "$" + str;const three = (a, b) => a + b;const final = compose(one,two,three);console.log(final(1,2))//#$12
实现红绿灯需求
红灯3秒亮一次,绿灯2秒亮一次,黄灯1秒亮一次,需求为红绿灯连续反复亮。
思路:利用async/await不停回调实现函数实现需求
const red = () => console.log('red');const green = () => console.log('green');const yellow = () => console.log('yellow');const blinkLight = (light, time) => {return new Promise((resolve, reject) => {setTimeout(() => {const mapping = {red,green,yellow};mapping[light]();resolve();}, time);});};const mission = async () => {await blinkLight('red', 3000);await blinkLight('green', 2000);await blinkLight('yellow', 1000);mission();};mission();
简单实现一个断言函数
断言函数我们在测试当中会经常使用,原理是为断言对象设置拦截代理,在断言错误时抛出错误信息
const assert = new Proxy({},{set(target, key, value, receiver) {if (!value) {throw new Error(key);}}});assert['this is testing assert!'] = 1 === 0;// 'this is testing assert!'
分解参数辅助函数
对于接受固定个数参数的函数,有时候我们在包裹函数调用的时候无法拆分实参,造成调用失败,这里我们可以采用拆分实参的方法切片函数的方法,通过函数参数的解构赋值,使包裹函数和调用函数之间参数匹配。
function foo(x, y) {console.log(x + y);return x + y;}function bar(fn) {fn([3, 9]);}//此时调用 bar(foo)将会失败,因为入参将会对应到参数x的位置,//所以并不会得到我们需要的返回值//使用spreadArgs作为切片辅助函数,可以把接受的单一数组扩展成各自独立的实参,即 fn(args) => fn(...args)function speardArgs(fn) {return function speardFn(args) {return fn(...args);};}const speard = speardArgs(foo);bar(speard);
