js 常见函数

287 阅读4分钟

目录

1. compose
2. createRearFunc
3. curry
4. debounce 防抖
5. factorial 尾部调用
6. fetchDownload 下载文件
7. 兔子函数 fibonacci
8. findParentDirectory
9. flat 扁平数组
10. flat2 扁平数组
11. Lazyman
12. listToTreeList 列表变树
13. mergeSort 排序
14. mvvm 简单实现
15. observe 观察
16. Observer 观察者
17. person 继承
18. Publish 订阅
19. quickSort 排序
20. throttle 节流
21. Thunk
22. unique
  1. compose
function compose(...fns) {
  return (...result) => {
    let len = fns.length;
    let flag = true;
    while (len--) {
      if (flag) {
        flag = false;
        result = fns[len].apply(this, result);
      } else {
        result = fns[len](result);
      }
    }
    return result;
  };
}

function f1(a, b) {
  return a + 2 + b;
}
function f2(c) {
  return c * 2;
}

function f3(c) {
  return c + ':狗';
}

var v = compose(f3, f2, f1)(3, 4);
/**
 * --- 问题描述 ---
 *
 * 实现一个函数生成器,接收一个原函数和一组 index,生成一个新函数
 * 调用新函数时,按照 index 数组中定义的顺序将参数传入原函数中
 *
 */
const assert = require('assert');
const originalFunc = function (a, b, c) {
  return [a, b, c];
};

const f = createRearFunc(originalFunc, [2, 0, 1]);

function createRearFunc(originalFunc, indexArray) {
  return (...args) => {
    let arr = [];
    args.forEach((item, index) => {
      return (arr[indexArray[index]] = item);
    });

    return originalFunc(...arr);
  };
}
/*******测试部分*******/
(function createRearFuncTest() {
  try {
    const originalFunc = function (a, b, c) {
      return [a, b, c];
    };
    const f = createRearFunc(originalFunc, [2, 0, 1]);
    // 按照 [2, 0, 1] 定义的顺序
    // ['foo', 'bar', 'fiz'] 分别应该作为原函数的第 2/0/1 个参数传入
    assert.deepEqual(f('foo', 'bar', 'fiz'), ['bar', 'fiz', 'foo']);
    console.log('createRearFuncTest 通过');
    return '通过';
  } catch (err) {
    console.log('createRearFuncTest 不通过');
    return '不通过';
  }
})();

  1. curry
function curry(fn) {
  if (fn.length <= 1) {
    return fn;
  }
  return function gen(...args) {
    if (fn.length === args.length) {
      return fn(...args);
    } else {
      return (...args2) => {
        return gen(...args, ...args2);
      };
    }
  };
}

function fn(a, b, c, d) {
  let val = d + a * b * c;
  console.log(val);
  return val;
}

curry(fn)(8, 1, 2, 3);
curry(fn)(8)(1, 2, 3);
curry(fn)(8)(1)(2, 3);
curry(fn)(8)(1)(2)(3);
curry(fn)(8, 1, 2)(3);
curry(fn)(8, 1)(2, 3);

  1. debounce
function debounce(fn, delay, context) {
  let timer = null;
  return (...args) => {
    if (timer) {
      clearTimeout(timer);
      timer = setTimeout(() => {
        fn.apply(context ? context : this, args);
      }, delay);
    } else {
      timer = setTimeout(() => {
        fn.apply(context ? context : this, args);
      }, delay);
    }
  };
}

var i = 0;

function f() {
  console.log(i++);
}
var df = debounce(f, 1000);

let t = setInterval(() => {
  df();
}, 300);

setTimeout(() => {
  clearInterval(t);
}, 3000);
  1. factorial 尾部调用
function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

function factorial(n) {
  return tailFactorial(n, 1);
}

factorial(5); // 120
  1. fetchDownload 下载文件
function fetchDownload(config, data, filename) {
  fetch(config.url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    headers: {
      'Content-Type': 'application/json',
      credentials: 'include',
    },

    body: JSON.stringify(data),
  })
    .then((resp) => resp.blob())
    .then((blob) => {
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.style.display = 'none';
      a.href = url;
      a.download = filename ? `${filename}.csv` : 'file.csv';
      document.body.appendChild(a);
      a.click();
      window.URL.revokeObjectURL(url);
      document.body.removeChild(a);
    });
}
  1. 兔子函数 fibonacci
/**
 * --- 问题描述 ---
 *
 * 给出一个数字,找出它是斐波那契数列中的第几个数
 *
 * --- 说明 ---
 *
 * - 斐波那契数列 [1, 1, 2, 3, 5, 8, 13, ...],后一个数字是前两个数字之和
 * - 输入的数字大于等于 2
 * - 如果输入数字不存于斐波那契数列中,返回 -1
 */
const assert = require('assert');
var memoizer = function (func) {
  let memo = [];
  return function (n) {
    if (memo[n] == undefined) {
      memo[n] = func(n);
    }
    return memo[n];
  };
};
var fibonacci = memoizer(function (n) {
  if (n == 1 || n == 2) {
    return 1;
  }
  return fibonacci(n - 2) + fibonacci(n - 1);
});

function findFibonacciIndex(num) {
  let result = -1;
  for (let i = 2; i <= num; i += 1) {
    if (fibonacci(i) + fibonacci(i - 1) === num) {
      result = i;
      break;
    }
  }
  return result;
}
/*******测试部分*******/
(function findFibonacciIndexTest() {
  try {
    assert.strictEqual(findFibonacciIndex(2), 2);
    assert.strictEqual(findFibonacciIndex(13), 6);
    assert.strictEqual(findFibonacciIndex(100), -1);
    console.log('findFibonacciIndexTest 通过');
    return '通过';
  } catch (err) {
    console.log('findFibonacciIndexTest 不通过');
    return '不通过';
  }
})();

  1. findParentDirectory
/**
 * --- 问题描述 ---
 *
 * 给定一组文件路径,找出它们共同的的父级目录
 *
 * --- 说明 ---
 *
 * - 如果不存在共同的父级目录,返回 `null`
 */
const assert = require('assert');

function findParentDirectory(paths) {
  let pathsNum = paths.length;
  let newPath = paths.map((path) => {
    return path.split('/');
  });

  let min = newPath[0].length;
  let index = 0;

  for (let i = 0; i < newPath.length; i += 1) {
    let len = newPath[i].length;
    if (len <= min) {
      min = len;
      index = i;
    }
  }
  let result = null;
  let flag = false;
  for (let i = min; i > 1; i -= 1) {
    result = newPath[index].slice(0, i).join('/');
    flag = newPath.every((item) => {
      return item.slice(0, i).join('/') === result;
    });
    if (flag) {
      break;
    }
  }
  if (flag) {
    return result;
  }
  return null;
}

/*******测试部分*******/
(function findParentDirectoryTest() {
  try {
    assert.strictEqual(
      findParentDirectory(['/home/admin/vue', '/home/admin/react']),
      '/home/admin'
    );
    assert.strictEqual(
      findParentDirectory([
        '/home/admin/react/src',
        '/home/admin/react',
        '/home/admin/react/src/index.js',
      ]),
      '/home/admin/react'
    );
    assert.strictEqual(findParentDirectory(['/usr/bin', '/etc/config']), null);
    console.log('findParentDirectoryTest 通过');
    return '通过';
  } catch (err) {
    console.log('findParentDirectoryTest 不通过');
  }
})();

  1. flat
function flat(arr) {
  return [].concat.apply([], arr);
}

function hasArr(arr) {
  return arr.some((item) => {
    if (Array.isArray(item)) {
      return true;
    } else {
      return false;
    }
  });
}

function flatAll(arr) {
  if (hasArr(flat(arr))) {
    return flatAll(flat(arr));
  } else {
    return flat(arr);
  }
}
  1. flat2
function flat(depth, arr) {
  if (depth === 0) {
    return arr;
  } else {
    return arr.reduce((pre, cur) => {
      if (Array.isArray(cur)) {
        return [...pre, ...flat(depth - 1, cur)];
      } else {
        return [...pre, cur];
      }
    }, []);
  }
}

var arr = [[[3, 4, [6, 7]]], 5];

console.log(flat(Infinity, arr));
console.log(flat(1, arr));
console.log(flat(2, arr));
console.log(flat(3, arr));
  1. Lazyman
function Lazyman(name) {
  this.tasks = []; //设置任务队列
  let task = () => () => {
    console.log(`Hi! This is ${name} !`);
    this.next();
  };
  this.tasks.push(task);
  //通过settimeout的方法,将执行函数放入下一个事件队列中,从而达到先注册事件,后执行的目的

  setTimeout(() => {
    this.next();
  }, 0);
}

Lazyman.prototype = {
  //尾调用函数,一个任务执行完然后再调用下一个任务
  next() {
    let task = this.tasks.shift();
    task && task();
  },

  eat(food) {
    let task = () => () => {
      console.log(`Eat ${food}`);
      this.next();
    };
    this.tasks.push(task);
    return this;
  },

  sleep(time) {
    let task = () => () => {
      setTimeout(() => {
        console.log(`Wake up after ${time} s!`);
        this.next();
      }, time * 1000);
    };
    this.tasks.push(task);
    return this;
  },

  sleepFirst(time) {
    let task = () => () => {
      setTimeout(() => {
        console.log(`Wake up after ${time} s!`);
        this.next();
      }, time * 1000);
    };
    this.tasks.unshift(task); //sleepFirst函数需要最先执行,所以我们需要在任务队列前面放入,然后再执行后面的任务
    return this;
  },
};

// new Lazyman("Hank")

// new Lazyman("Hank").sleep(4).eat("dinner")

// new Lazyman("Hank").eat("dinner").eat("supper")

new Lazyman('Hank').sleepFirst(5).eat('supper');


  1. listToTreeList

function listToTreeList(list) {
  // 将普通列表转换为树结构的列表
  if (!list || !list.length) {
    return [];
  }
  let treeListMap = {};
  for (let item of list) {
    treeListMap[item.id] = item;
  }
  for (let i = 0; i < list.length; i++) {
    if (list[i].parentId && treeListMap[list[i].parentId]) {
      if (!treeListMap[list[i].parentId].children) {
        treeListMap[list[i].parentId].children = [];
      }
      treeListMap[list[i].parentId].children.push(list[i]);
      list.splice(i, 1);
      i--;
    }
  }
  return list;
}
let list = [
  {
    id: 4,
    parentId: 2,
  },
  {
    id: 2,
    parentId: 1,
  },
  {
    id: 3,
    parentId: 1,
  },
  {
    id: 1,
  },
];
console.log(listToTreeList(list));

  1. mergeSort 排序
// 融合两个有序数组,这里实际上是将数组 arr 分为两个数组
function mergeArray(arr, first, mid, last, temp) {
  let i = first;
  let m = mid;
  let j = mid + 1;
  let n = last;
  let k = 0;
  while (i <= m && j <= n) {
    if (arr[i] < arr[j]) {
      temp[k++] = arr[i++];
    } else {
      temp[k++] = arr[j++];
    }
  }
  while (i <= m) {
    temp[k++] = arr[i++];
  }
  while (j <= n) {
    temp[k++] = arr[j++];
  }
  for (let l = 0; l < k; l++) {
    arr[first + l] = temp[l];
  }
  return arr;
}
// 递归实现归并排序
function mergeSort(arr, first, last, temp) {
  if (first < last) {
    let mid = Math.floor((first + last) / 2);
    mergeSort(arr, first, mid, temp); // 左子数组有序
    mergeSort(arr, mid + 1, last, temp); // 右子数组有序
    arr = mergeArray(arr, first, mid, last, temp);
  }
  return arr;
}

function compare(n) {
  for (let i = 0; i < n; i++) {
    let arr = [];
    let m = parseInt(Math.random() * 100);
    for (let j = 0; j < m; j++) {
      arr.push(parseInt(Math.random() * 1000 - 500));
    }

    let r1 = sort(JSON.parse(JSON.stringify(arr))).reverse();

    let r2 = mergeSort(JSON.parse(JSON.stringify(arr)), 0, arr.length - 1, []);

    if (r1.join('') === r2.join('')) {
      console.log(i);
      console.log(r1.join(','));
    } else {
      console.log('---start--');
      console.log(r1.join(''));
      console.log(r2.join(''));
      console.log(false);
      console.log('---end--');
    }
  }
}

compare(100);

function getMax(arr) {
  let max = arr[0];
  let index = 0;
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] >= max) {
      max = arr[i];
      index = i;
    }
  }
  return {
    max,
    index,
  };
}

function sort(arr) {
  var result = [];
  let len = arr.length;

  for (let i = 0; i < len; i++) {
    var val = getMax(arr);
    result.push(val.max);
    arr.splice(val.index, 1);
  }
  return result;
}
  1. mvvm 简单实现
var Target = null;
  class Dep {
    //目标
    constructor() {
      this.subs = [];
    }

    add(watcher) {
      let state = true;
      for (let item of this.subs) {
        if (this.subs._uid == watcher._uid) {
          state = false;
          break;
        }
      }
      if (state) this.subs.push(watcher); //防止观察者重复
    }

    notify() {
      this.subs.forEach((sub) => {
        sub.update();
      });
    }
  }

  function observe(data) {
    if (typeof data !== 'object') {
      //如果不是对象
      return;
    }
    Object.keys(data).forEach((key) => {
      //遍历对象键值
      defineReactive(data, key, data[key]);
    });
  }

  function defineReactive(data, key, val) {
    observe(val);
    let dep = new Dep();
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get() {
        Target && dep.add(Target); //添加观察者了
        return val;
      },
      set(newval) {
        val = newval;
        dep.notify(); //通知所有观察者去更新
      },
    });
  }

  var uId = 0;
  class Watcher {
    constructor(vm, exp, cb) {
      //初始化的时候把对象和键值传进来
      this._cb = cb;
      this._vm = vm;
      this._exp = exp; //保存键值
      this._uid = uId;
      uId++; //每个观察者配个ID,防止重复添加
      Target = this;
      this._value = vm[exp]; //看到没,这里触发getter了
      Target = null; //用完就删
    }

    update() {
      let value = this._vm[this._exp];
      if (value != this._value) {
        this._value = value;
        this._cb.call(this.vm, value);
      }
    }
  }

  class Compile {
    constructor(el, vm) {
      this._el = el;
      this._vm = vm;
      this._compileElement(el);
    }

    _compileElement(el) {
      //遍历节点
      let childs = el.childNodes;
      Array.from(childs).forEach((node) => {
        if (node.childNodes && node.childNodes.length) {
          this._compileElement(node);
        } else {
          this._compile(node);
        }
      });
    }

    _compile(node) {
      if (node.nodeType == 3) {
        //文本节点
        let reg = /\{\{(.*)\}\}/;
        let text = node.textContent;
        if (reg.test(text)) {
          //如果这个元素是{{}}这种格式
          let key = RegExp.$1;
          node.textContent = this._vm[key];
          new Watcher(this._vm, key, (val) => {
            node.textContent = val;
          });
        }
      } else if (node.nodeType == 1) {
        //元素节点
        let nodeAttr = node.attributes;
        Array.from(nodeAttr).forEach((attr) => {
          if (attr.nodeName == 'v-model') {
            node.value = this._vm[attr.nodeValue]; //初始化赋值
            //如果这个元素有v-model属性,那么得做点事情了
            node.addEventListener('input', () => {
              this._vm[attr.nodeValue] = node.value;
            });
            new Watcher(this._vm, attr.nodeValue, (val) => {
              node.value = val;
            });
          }
        });
      }
    }
  }

  class MVVM {
    constructor(options) {
      this._options = options;
      let data = (this._data = options.data());
      Object.keys(data).forEach((key) => {
        this._proxy(key);
      });

      observe(data); //给数据的所有键值加上get set
      let dom = document.getElementById(options.el);
      new Compile(dom, this);
    }

    _proxy(key) {
      //这时是为了vm[key] 能够直接访问到data的数据
      Object.defineProperty(this, key, {
        configurable: false,
        enumerable: true,
        get: function proxyGetter() {
          return this._data[key];
        },
        set: function proxySetter(newVal) {
          this._data[key] = newVal;
        },
      });
    }
  }

  var vm = new MVVM({
    el: 'ele',
    data() {
      return {
        test: '123',
      };
    },
  });
  1. observe
//遍历传入实例的data对象的属性,将其设置为Vue对象的访问器属性
function observe(obj, vm) {
Object.keys(obj).forEach(function (key) {
  defineReactive(vm, key, obj[key]);
});
}
//设置为访问器属性,并在其getter和setter函数中,使用订阅发布模式。互相监听。
function defineReactive(obj, key, val) {
//这里用到了观察者(订阅/发布)模式,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
//实例化一个主题对象,对象中有空的观察者列表
var dep = new Dep();
//将data的每一个属性都设置为Vue对象的访问器属性,属性名和data中相同
//所以每次修改Vue.data的时候,都会调用下边的get和set方法。然后会监听v-model的input事件,当改变了input的值,就相应的改变Vue.data的数据,然后触发这里的set方法
Object.defineProperty(obj, key, {
  get: function () {
    //Dep.target指针指向watcher,增加订阅者watcher到主体对象Dep
    if (Dep.target) {
      dep.addSub(Dep.target);
    }
    return val;
  },
  set: function (newVal) {
    if (newVal === val) {
      return;
    }
    val = newVal;
    //console.log(val);
    //给订阅者列表中的watchers发出通知
    dep.notify();
  },
});
}

//主题对象Dep构造函数
function Dep() {
this.subs = [];
}
//Dep有两个方法,增加订阅者  和  发布消息
Dep.prototype = {
addSub: function (sub) {
  this.subs.push(sub);
},
notify: function () {
  this.subs.forEach(function (sub) {
    sub.update();
  });
},
};

  1. Observer
class Subject {
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  notify() {
    this.subs.forEach((sub) => {
      sub.update();
    });
  }
}

class Observer {
  update() {
    console.log('Observer update ...');
  }
}

let subject = new Subject();
let ob = new Observer();
//目标添加观察者了
subject.addSub(ob);
//目标发布消息调用观察者的更新方法了
subject.notify(); //update
  1. person 继承
function Person(name) {
  this.name = name;
}

function fn(Conb, args) {
  var o = Object.create(Conb.prototype);
  Conb.apply(o, args);
  return o;
}

var p = fn(Person, ['丽丽']);

console.log(p);
  1. Publish 订阅
function Publish() {
  //存放订阅者信息
  this.subscribers = [];

  //添加订阅者
  this.subscribe = function (subscriber) {
    //保证一个订阅者只能订阅一次
    let isExist = this.subscribers.some(function (item) {
      return item == subscriber;
    });

    if (!isExist) {
      this.subscribers.push(subscriber);
    }

    return this;
  };
  this.unSubscribe = function (subscriber) {
    for (let i = 0; i < this.subscribers.length; i++) {
      if (this.subscribers[i] === subscriber) {
        this.subscribers.splice(i, 1);
        break;
      }
    }
    return this;
  };

  //发布消息
  this.deliver = function (data) {
    this.subscribers.forEach(function (fn) {
      fn(data);
    });

    return this;
  };
}

//订阅者
let a = function (data) {
  console.log(`订阅者a收到订阅信息:${data}`);
};
let b = function (data) {
  console.log(`订阅者b收到订阅信息:${data}`);
};
let c = function (data) {
  console.log(`订阅者c收到订阅信息:${data}`);
};

//初始化
let publisher = new Publish();

//添加订阅者
publisher.subscribe(a);
publisher.subscribe(b).subscribe(c);

//公众号发布消息
publisher.deliver('这是公众号推送的第1条新信息!');
publisher
  .deliver('这是公众号推送的第2条新信息!')
  .deliver('这是公众号推送的第3条新信息!');

publisher.unSubscribe(b);

publisher.deliver('消息1');
  1. quickSort 排序
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = []; //存放比基准点小的数组
  var right = []; //存放比基准点大的数组
  for (var i = 0; i < arr.length; i++) {
    //遍历数组,进行判断分配
    if (arr[i] < pivot) {
      left.push(arr[i]); //比基准点小的放在左边数组
    } else {
      right.push(arr[i]); //比基准点大的放在右边数组
    }
  }
  //递归执行以上操作,对左右两个数组进行操作,直到数组长度为<=1;
  return quickSort(left).concat([pivot], quickSort(right));
}

console.log(quickSort([2, 3, 6, 0, 1]));

  1. throttle 节流
function throttle(fn, delay) {
  let pre = Date.now();

  return (...args) => {
    let du = Date.now() - pre;
    if (du >= delay) {
      fn.apply(this, args);
      pre = Date.now();
    }
  };
}
var i = 0;
function fn() {
  console.log(i++);
}

var tf = throttle(fn, 2000);

setInterval(() => {
  tf();
}, 200);

  1. Thunk
function Thunk(fn) {
  return function () {
    var args = Array.prototype.slice.call(arguments);
    return function (callback) {
      args.push(callback);
      return fn.apply(this, args);
    };
  };
}

function fn(a, b, cb) {
  console.log(a + b);
  cb(33);
}

Thunk(fn)(100, 2)((v) => {
  console.log(v);
});

  1. unique
function unique(arr) {
  return [...new Set(arr)];
}