高频

一. 柯里化函数(Currying)和反柯里化

简介

柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

个人理解偏函数是一种特殊柯里化,偏函数

核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。

按照Stoyan Stefanov --《JavaScript Pattern》作者 的说法,所谓“柯里化”就是使函数理解并处理部分应用

柯里化有3个常见作用:

  1. 参数复用
  2. 提前返回
  3. 延迟计算/运行
简单实现
function currying(fn, ...rest1) {
  return function(...rest2) {
    return fn.apply(null, rest1.concat(rest2))
  }
}

例:将一个sayHello函数柯里化

function sayHello(name, age, fruit) {
  console.log(console.log(`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`))
}

const curryingShowMsg1 = currying(sayHello, '小明')
curryingShowMsg1(22, '苹果')            // 我叫 小明,我 22 岁了, 我喜欢吃 苹果

const curryingShowMsg2 = currying(sayHello, '小衰', 20)
curryingShowMsg2('西瓜')               // 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜
高阶(递归)柯里化函数
function curryingHelper(fn, len) {
  const length = len || fn.length  // 第一遍运行length是函数fn一共需要的参数个数,以后是剩余所需要的参数个数
  return function(...rest) {
    return rest.length >= length    // 检查是否传入了fn所需足够的参数
        ? fn.apply(this, rest)
        : curryingHelper(currying.apply(this, [fn].concat(rest)), length - rest.length)        // 在通用currying函数基础上
  }
}

例:

function sayHello(name, age, fruit) { console.log(`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`) }    

const betterShowMsg = curryingHelper(sayHello)
betterShowMsg('小衰', 20, '西瓜')      // 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜
betterShowMsg('小猪')(25, '南瓜')      // 我叫 小猪,我 25 岁了, 我喜欢吃 南瓜
betterShowMsg('小明', 22)('倭瓜')      // 我叫 小明,我 22 岁了, 我喜欢吃 倭瓜
betterShowMsg('小拽')(28)('冬瓜')      // 我叫 小拽,我 28 岁了, 我喜欢吃 冬瓜

二. 排序

// 冒泡排序 选出最大
var paixuArr = [34,45,2,4,77,13,0,67,89,3,795,24]
function mao(arr){
let len = arr.length
  for(let i=0; i < len - 1; i++) {
    for(let j=0; j < len - i - 1; j++) {
      if(arr[j] > arr[j+1]){
        let temp = arr[j]
        arr[j] = arr[j+1]
        arr[j+1] = temp
      }
    }
  }
  return arr
}

// 选择排序
function xuanze(arr) {
  let len = arr.length
  for(let i=0; i < len - 1; i++) {
    for(let j=i + 1; j < len; j++) {
      if(arr[i] > arr[j]){
        let temp = arr[i]
        arr[i] = arr[j]
        arr[j] = temp
      }
    }
  }
  return arr
}

// 快速排序
function kuaisu(arr){
  let len = arr.length
  if(len <= 1) {return arr}
  let firstValue = arr[0]
  let leftList = []
  let rightList = []

  for(let i = 1; i < len; i ++){
    if(arr[i] >= arr[0]){
      rightList.push(arr[i])
    } else {
      leftList.push(arr[i])
    }
  }
  return kuaisu(leftList).concat([firstValue], kuaisu(rightList))
}

三. setTimeout 实现 setInterval

使用key 标识当前执行的 setInterval, 存储在 timeWorker 中(对象是为了兼容多个 interval 并存)

值为 当前执行的 setTimeout,

当需要清除时,先找到要清除的 setInterval key,然后 clear 对应的 setTimeout

var timeWorker = {}
var mySetInterval= function(fn, time) {
  // 定义一个key,来标识此定时器
  var key = Symbol();
  // 定义一个递归函数,持续调用定时器
  var execute = function(fn, time) {
     timeWorker[key] = setTimeout(function(){
        fn();
        execute(fn, time);
     }, time)
   }
  execute(fn, time);
  // 返回key
  return key;
}
var myClearInterval = function(key) {
  if (key in timeWorker) {
     clearTimeout(timeWorker[key]);
     delete timeWorker[key];
  }
}

四. 二叉树广度、深度(前序、中序、后序)遍历

二叉树的遍历有深度优先和广度优先两种。 前序、中序、后序遍历都属于深度优先遍历。层次遍历属于广度优先遍历,从上到下,从左到右或从右到左一层一层的遍历。

1、二叉树的深度优先遍历-递归
// 前/先序:根左右
var DLR = function(root, res) {
    if(!root)   return ;
    res.push(root.val);
    root.left && DLR(root.left, res);
    root.right && DLR(root.right, res);
}

// 中序:左根右
var LDR = function(root, res) {
    if(!root)   return ;
    root.left && LDR(root.left, res);
    res.push(root.val);
    if(root.right)    LDR(root.right, res);
}

// 后序:左右根
var LRD = function(root, res) {
    if(!root)   return ;
    root.left && LRD(root.left, res);
    root.right && LRD(root.right, res);
    res.push(root.val);
}
2、二叉树的深度优先遍历-非递归-迭代-采用栈(先进后出)
// 前序:根左右
var DLR = function(root) {
    if(!root)   return [];
    let res = [];
    let s = [root];
    while(s.length > 0) {
        let p = s.shift();    // 取第一个
        res.push(p.val);
        p.right && s.unshift(p.right);
        p.left && s.unshift(p.left);
    }
    return res;
}

// 中序:左根右
// 先将根节点入栈,找到所有左节点入栈,直到没有左节点为止
// 然后出栈存入结果数组,每出一个,对比该根节点的右子节点是否有左节点,若有则入栈,否则继续出栈
var LDR = function(root) {
    let res = [];
    let s = [];     // 栈  
    let p = root;   // 指针
    while(p || s.length > 0) { // 直至左节点为空,即没有左节点为止
        while (p) {
            s.push(p);
            p = p.left;
        }
        // 出栈,存放根节点
        p = s.pop();
        res.push(p.val);
        p = p.right;
    }
    return res;
}

// 后序:左右根
// 后序遍历比较复杂,但是看见网上有个比较好记住的办法:
// 按照与前序相似的方法(前序压栈的顺序是先右后左,这里是先左后右),先得到一个结果,然后对结果倒序一下。
var LRD = function(root) {
    if(!root)   return [];
    let res = [];
    let s = [root];
    while(s.length > 0) {
        let p = s.shift();    // 取第一个
        res.push(p.val);
        p.left && s.unshift(p.left);
        p.right && s.unshift(p.right);
    }
    return res.reverse();
}
3、二叉树的广度优先优先-非递归-采用队列
// 二叉树的遍历-层次遍历
// 从上往下,从左到右/从右到左
var levelOrder = function(root) {
   if(!root)   return [];
    let res = [];
    let q = [root];
    while(q.length > 0){
        let tmp = [];
        let temp = [];
        for(let i in q){
            let p = q[i];
            temp.push(p.val);
            p.left && tmp.push(p.left);
            p.right && tmp.push(p.right);
        }
        q = tmp;
        res.push(temp);
    }
    return res;
};

五. 继承

1. 重写原型对象

业务中较少使用,缺点:
(1). 父类属性直接赋值给子类原型,子类会共享属性
(2). 无法向父类传参

缺点:所有对于原型链上的修改,都会变成通用调整。

function Game () { this.name = 'LOL' }
Game.prototype.getName = function () { return this.name }

function LOL () {}
LOL.prototype = new Game()
LOL.prototype.constructor = LOL

2. 构造函数继承

(1)经典继承

优点:解决共享属性 + 向父类传参问题
缺点:原型链上的共享方法无法直接读取继承(getName)

function Game (arg) {this.name = 'LOL'}
Game.prototype.getName = function () { return this.name }

function LOL (arg) {
  Game.call(this, arg)
}

(2)组合继承

解决原型链上的共享方法无法直接读取继承(getName)问题
缺点:性能(两次执行this.name = ‘LOL’)

function Game (arg) {this.name = 'LOL'}
Game.prototype.getName = function () { return this.name }

function LOL (arg) {
  Game.call(this, arg)
}
LOL.prototype = new Game()
LOL.prototype.constructor = LOL

3. 寄生组合继承

function Game (arg) {this.name = 'LOL'}
Game.prototype.getName = function () { return this.name }

function LOL (arg) {
  Game.call(this, arg)
}
LOL.prototype = Object.create(Game.prototype)
LOL.prototype.constructor = LOL

new Object() 和 Object.create() 区别?
后者可以继承传入的 arg 参数

4. 多重继承

function Game (arg) {this.name = 'LOL'}
Game.prototype.getName = function () { return this.name }

function Store () { this.shop = 'Steam' }
Store.prototype.getPlatform = function () { return this.shop }

function LOL (arg) {
  Game.call(this, arg)
}
LOL.prototype = Object.create(Game.prototype)
Objcet.assign(LOL.prototype, Store.prototype)
LOL.prototype.constructor = LOL

六. 经典闭包

函数外部可以获取到函数作用域内的变量值

function mail() {
  let content = '这是内容'
  return function () {
    console.log(content)
  }
}
const getMail = mail()
getMail() // 可以输出

如何实现私有属性? 采用闭包。

Class Mail {
  constructor () {
    let _content = '这是内容'
    this.getContent = () => {
      console.log(_content)
    }
  }
}

箭头函数无 constructor,无法实例化

七. 发布订阅者

class Publisher {
    constructor() {
        this.subList = []
    }

    // 以电话号码作为订阅的标识
    add(phoneNumber,cb) {
        this.subList.push({
            phoneNumber,
            cb
        })
    }

    // 取消订阅
    remove(phoneNumber) {
        const index = this.subList.findIndex(item => item.phoneNumber === phoneNumber)
        this.subList.splice(index, 1)
    }
    
    // 通知
    publish(msg) {
        this.subList.forEach(item => {
            item.cb(msg)
        })
    }

    getSubscriberList() {
        console.log(this.subList)
    }
}

const publisher = new Publisher()

publisher.add(12345678 ,function (msg) {
    console.log('第一个订阅者收到了消息:' + msg)
})

let i = 1

// 通知
setInterval(() => {
    publisher.publish(i++)
    publisher.getSubscriberList()
}, 1000)

setTimeout(() => {
    publisher.add(999888, function (msg) {
        console.log('第二个订阅者收到:' + msg)
    })
}, 1000 * 5)

八. 移动端1px边框

  • 用图片
  • 用阴影模拟
.box-shadow-1px {
	box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}
  • 用伪类
.border-1px:before {
  content: " ";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 1px;
  border-top: 1px solid #D9D9D9;
  color: #D9D9D9;
  -webkit-transform-origin: 0 0;
  transform-origin: 0 0;
  -webkit-transform: scaleY(0.5);
  transform: scaleY(0.5);
}

九. 合并二维有序数组成一维有序数组,归并排序的思路

var arr = [1, 2,  5, 6, [3, 4]]
var arr1 = arr.join().split(',')
var arr2 = arr.flat(Infinity)
function flatten(arr, deep = 1) {
  return deep > 0 ? 
  arr.reduce((acc, val)=> acc.concat(Array.isArray(val) ? flatten(val, deep-1) : val), [])
  : arr.slice()
}
// console.log(flatten(arr, Infinity));

十. 斐波那契数列 1 1 2 3 5 8 13 , arr[n] = arr[n-1] + arr[n-2]

// 循环
function feibona1(n) {
  let n1 = 1;
  let n2 = 1;
  let sum = 1;
  for (let i = 1; i < n; n++){
    sum = n1 + n2
    n1 = n2
    n2 = sum
  }
  return sum
}
// 普通递归
function feibona2(n){
  if(n === 1 || n ===2){
    return 1 
  }
  return feibona2(n-2) + feibona2(n-1)
}
// 改进递归-把前两位数字做成参数避免重复计算
function feibona3(n){
  function fei(m, v1, v2){
    if(m === 1) return v1
    if(m === 2) return v2
    return fei(m-1, v2, v1+v2)
  }
  fei(n, 1, 1)
}

十一. 字符串出现的不重复最长长度

var lengthOfLongestSubstring = function(s) {
     var res = 0; // 用于存放当前最长无重复子串的长度
     var str = ""; // 用于存放无重复子串
     var len = s.length;
     for(var i = 0; i < len; i++) {
        var char = s.charAt(i);
        var index = str.indexOf(char);
        if(index === -1) {
          str += char;
          res = res < str.length ? str.length : res;
        } else {
          str = str.substr(index + 1) + char;
        }
     }
     return res;
};
// console.log(longStr('abcdaouritbjhlfcldfkmdf'));

十二. 简单实现loadsh的_get

function get(obj, keys, det){
  return keys.split(/\./).reduce((o, v) => {
    return (o || {})[v]}, 
  obj) || det
}
get({ 'a': [{ 'b': { 'c': 3 } }] }, 'a.0.b.c', '1111')

十三. 实现add(1)(2)(3)

function add(...args){
    return args.reduce(
        (a,b)=> a+b
    )
}

function currying(fn){
  let args=[]
  return function temp(...newArgs){
    if(newArgs.length){
      args = [...args, newArgs]
      return temp
    } else {
      let val = fn.apply(this, args)
      args = []
      return val
    }
  }
}
let addCurry = currying(add)
// console.log(addCurry(1)(2)(3)(4, 5)())
// console.log(addCurry(1)(2)(3, 4, 5)())
// console.log(addCurry(1)(2, 3, 4, 5)())
// console.log(add(1)(2)(3).toString())

十四. 手写数组转树 & 树转数组

数组->树

const arrtree = [
  {id:1, parentId: null, name: 'a'},
  {id:2, parentId: null, name: 'b'},
  {id:6, parentId: 3, name: 'f'},
  {id:7, parentId: 4, name: 'g'},
  {id:8, parentId: 7, name: 'h'},
  {id:3, parentId: 1, name: 'c'},
  {id:4, parentId: 2, name: 'd'},
  {id:5, parentId: 1, name: 'e'}
]

function arrayTree(arr){
  if(!Array.isArray(arr) || !arr.length) return
  let map = {}
  arr.forEach(v => map[v.id] = v)

  let list = []
  arr.forEach(v => {
    const item = map[v.parentId]
    if(item){
      ( item.children || (item.children = [])).push(v)
    } else {
      list.push(v)
    }
  })
  return list
}

// console.log(arrayTree(arrtree))

树->数组

// 将树数据转化为平铺数据
flatTreeData(treeData: any[], childKey = 'children') {
  const arr: any[] = [];
  const expanded = (data: any) => {
    if (data && data.length > 0) {
      data
        .filter((d: any) => d)
        .forEach((e: any) => {
          arr.push(e);
          expanded(e[childKey] || []);
        });
    }
  };
  expanded(treeData);
  return arr;
}

十五. 手写一个数据双向绑定

let input = document.getElementById('value')
let text = document.getElementById('text')

let data = { value: '' }
input.value = data.value
text.innerHTML = data.value
// 数据劫持
Object.defineProperty(data, 'value', {
  set: function (val) {
    input.value = val
    text.innerHTML = val
  },
  get: function () {
    return input.value
  }
})
input.addEventListener('keyup', function (){
  data.value = input.value
  console.log(data);
})

十六. 手写 new

const customNew = (Func, ...args) => {
    // 创建一个空的简单JavaScript对象
    const obj = Object.create({})
    // 链接该对象到prototype
    obj.__proto__ = Func.prototype
    // 将创建的对象作为this的上下文 
    const res = Func.apply(obj, args)
    // 如果该函数没有返回对象,则返回this
    return res instanceof Object ? res : obj
}

十七. 判断一个单词是否回文

function checkPalindrom(str) {
  return str.split('').reverse().join() === str
}

十八. 实现call、apply、bind

Function.prototype.customCall = function (context, ...args) {
    if (this === Function.prototype) {
        return undefined; // 用于防止 Function.prototype.customCall() 直接调用
    }
    const _this = context || window;
    _this.fn = this

    const res = _this.fn(...args)

    delete _this.fn

    return res
}
Function.prototype.customApply = function (context, args) {
    if (this === Function.prototype) {
        return undefined; // 用于防止 Function.prototype.customApply() 直接调用
    }
    const _this = context || window;
    _this.fn = this

    let res
    if (Array.isArray(args)) {
        res = _this.fn(...args)
    } else {
        res = _this.fn()
    }

    delete _this.fn

    return res
}
Function.prototype.customBind = function (context) {
    if (this === Function.prototype) {
        throw new TypeError('Error')
    }
    const _this = this
    return function F(...args) {
        if (this instanceof F) {
            return new _this(...args)
        } else {
            return _this.apply(context, args)
        }
    }
}

十九. 手写 instanceof

右边变量的原型在左边变量的原型链上

const customInstanceof = (target, origin) => {
    let proto = target.__proto__, index = 0
    while (proto !== origin.prototype) {
        proto = proto.__proto__
        if (!proto) {
            return false
        }
        index += 1
    }

    console.log(index);
    return true
}

二十. 数组去重

// ES6
Array.from(new Set(arr))

// 遍历
var arrreset = [1,2,3,4,3,4,5,2,7,4]
var arrreset1 = Array.from(new Set(arrreset))

let arrreset2 = []
arrreset.forEach(v => {
  if(arrreset2.indexOf(v) === -1){
    arrreset2.push(v)
  }
})

二十一. 手写object.create

// 将传入的对象作为原型
function create(obj) {
  function F(){}
  F.prototype = obj
  return new F()
}

二十二. 深拷贝deepclone

export function deepClone(source: any) {
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments');
  }
  const targetObj: any = source.constructor === Array ? [] : {};
  Object.keys(source).forEach((keys: any) => {
    if (source[keys] && typeof source[keys] === 'object') {
      targetObj[keys] = deepClone(source[keys]);
    } else {
      targetObj[keys] = source[keys];
    }
  });
  return targetObj;
}

JSON.stringify() 缺点

  • 对象中有时间类型的时候,序列化之后会变成字符串类型。
  • 对象中有undefined和Function类型数据的时候,序列化之后会直接丢失。
  • 对象中有NaN、Infinity和-Infinity的时候,序列化之后会显示 null。
  • 对象循环引用的时候,会直接报错。
const obj = {
    nan:NaN,
    infinityMax:1.7976931348623157E+10308,
    infinityMin:-1.7976931348623157E+10308,
    undef: undefined,
    fun: () => { console.log('叽里呱啦,阿巴阿巴') },
    date:new Date,
}

二十三. 防抖、节流+装饰器

/**
 * 节流
 * @param fn  函数
 * @param delay 延迟时间(毫秒单位),默认200毫秒
 * @returns {Function}
 */
export function throttle(fn: any, delay = 200) {
  let lastCall = 0
  return function (...args: any[]) {
    const now = new Date().getTime()
    if (now - lastCall < delay) return
    lastCall = now
    fn(...args)
  }
}


/**
  * 防抖
  * @param fn {Function}   实际要执行的函数
  * @param delay {Number}  延迟时间,也就是阈值,单位是毫秒(ms)
  * @return {Function}     返回一个“去弹跳”了的函数
  */
  function debounce(fn, delay) {
    let timer = null
    return function(...arg) {
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn(...arg)
        }, delay)
    }
  }


/**
 * 装饰器防抖
 * @param {number} delay
 * @return {*}
 */
export const Debounce = (delay = 500) => {
  let timeoutDeb = null;
  return (target, propertyKey, descriptor) => {
    const original = descriptor.value;
    descriptor.value = function(...args) {
      clearTimeout(timeoutDeb);
      timeoutDeb = setTimeout(() => {
        original.call(this, ...args);
      }, delay);
    };
  };
};

低频

一. rgb转16进制颜色

利用正则 match 方法取出r、g、b 数值,然后分别转换16进制(不足两位补0),最后拼接加 # 转大写

function rgb2hex(rgb) {
    const rgb = rgb.match(/\d+/g); // 分别取出连续数字即为 r/g/b
    const hex = (n) => {
        return ("0" + Number(n).toString(16)).slice(-2);
    }
    return rgb.reduce((acc, cur) => acc + hex, '#').toUpperCase()
}

获取 rgb 还可以通过 split(‘,’)

const rgb = sRGB.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')

二. 千位符

function toTousands(num){
  let n = num.toString().split('.')[0],
      decal = num.toString().indexOf('.') !== 1 ? '.' + num.toString().split('.')[1] : '';
  let result = ''
  while(n.length > 3) {
    result = ',' + n.slice(-3) + result
    n = n.silce(0, n.length-3)
  }
  return (n || '') + result + decal
}

三. JS手写最大并发控制实现方式

class LimitResquest {
  constructor(limit){
    this.limit = limit
    this.currentSum = 0
    this.task = []
  }

  request(reqFn) {
    if(!reqFn || !(reqFn instanceof Function)){
      console.error('当前请求不是一个function', reqFn)
      return
    }
    this.task.push(reqFn)
    if(this.currentSum < this.limit){
      this.run()
    }
  }

  async run() {
    try{
      ++this.currentSum
      const fn = this.task.shift()
      await fn()
    } catch(e) {
      console.error(e)
    } finally{
      --this.currentSum
      if(this.task.length > 0){
        this.run()
      }
    }
  }
}

let a = () => new Promise((resolve) => {
  setTimeout(() => {resolve(1)}, 1000)
}).then((data) => console.log(data))
 
 
let b = () => new Promise((resolve) => {
  setTimeout(() => {resolve(2)}, 1000)
}).then((data) => console.log(data))
 
 
let c = () => new Promise((resolve) => {
  setTimeout(() => {resolve(3)}, 1000)
}).then((data) => console.log(data))
 
 
let d = () => new Promise((resolve) => {
  setTimeout(() => {resolve(4)}, 1000)
}).then((data) => console.log(data))

let limitResquest = new LimitResquest(2)
limitResquest.request(a)
limitResquest.request(b)
limitResquest.request(c)
limitResquest.request(d)
limitResquest.request(a)
limitResquest.request(b)
limitResquest.request(c)
limitResquest.request(d)

四. 实现一个简单的路由

class Route{
  constructor(){
    this.route = {} // 整个路由对象
    this.currtHash = '' // 当前路由
    this.freshRoute = this.freshRoute.bind(this)
    window.addEventListener('load', this.freshRoute, false)
    window.addEventListener('hashchange', this.freshRoute, false)
  }

  storeRoute(path, cb){
    this.route[path] = cb || function() {}
  }

  freshRoute(){
    this.currtHash = location.hash.slice(1) || '/'
    this.route[this.currtHash]()
  }
}

五. rem 自适应

function resetFontSize(){
  var baseFontsize = 100
  var designWidth = 750
  var width = window.innerWidth
  var currFonrsize = (width/designWidth) * baseFontsize
  document.getElementsByTagName('html')[0].fontSzie = currFonrsize + 'px'
}

window.onresize = function (){
  resetFontSize()
}

resetFontSize()

六. 懒加载

// 获取所有图片的标签
const imgs = document.getElementsByTagName('img')
// 获取可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight
// num 计算当前显示到哪一张图片,避免每次都从第一张图片开始检查是否需要
let num = 0, len = imgs.length

function lazyLoad() {
  for(let i = num; i < len; i ++) {
    // 用可视区域的高度减去匀速顶部距离可视区域的高度
    let distance = viewHeight - migs[i].getBoundingClientRect().top
    if(distance > 20){
      imgs[i].src = imgs[i].getAttribute('data-src')
    }
  }
}

// 最好添加节流
window.addEventListener('sroll', lazyLoad)

七. 手写fetch拦截器

(function() {
  let interceptor_req = [], interceptor_res = []
  
  function c_fetch(url, init = {}) {

    init.method = init.method || 'GET'

    interceptor_req.forEach(interceptor => {
      init = interceptor(init)
    })

    return new Promise((resolve, reject) => {
      fetch(url, init).then(res => {
        interceptor_res.forEach(interceptor => {
          res = interceptor(res)
        })
        resolve(res)
      }).catch(err => {
        console.error(err)
      })
    })
  }

  c_fetch.interceptor = {
    request: {
      use: function(callback){
        interceptor_req.push(callback)
      }
    },
    response: {
      use: function(callback){
        interceptor_res.push(callback)
      }
    }
  }

  // export default c_fetch

})()

码字不易,觉得有帮助的小伙伴点个赞~