实现JS和Lodash常用方法

653 阅读6分钟

手写Promise

class MyPromise {
  constructor(executor) {
    // Promise 初始状态为 'pending'
    this.state = "pending";
    // 'fulfilled' 状态时的值
    this.value = undefined;
    // 'rejected' 状态时的原因
    this.reason = undefined;
    // 存放所有 'fulfilled' 状态的回调函数
    this.onFulfilledCallbacks = [];
    // 存放所有 'rejected' 状态的回调函数
    this.onRejectedCallbacks = [];

    // 定义 resolve 函数
    const resolve = (value) => {
      // 只有在 'pending' 状态时才能转化状态
      if (this.state === "pending") {
        // 改变状态为 'fulfilled'
        this.state = "fulfilled";
        // 设置 'fulfilled' 状态的值
        this.value = value;
        // 执行所有 'fulfilled' 状态的回调函数
        this.onFulfilledCallbacks.forEach((fn) => fn());
      }
    };

    // 定义 reject 函数
    const reject = (reason) => {
      // 只有在 'pending' 状态时才能转化状态
      if (this.state === "pending") {
        // 改变状态为 'rejected'
        this.state = "rejected";
        // 设置 'rejected' 状态的原因
        this.reason = reason;
        // 执行所有 'rejected' 状态的回调函数
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    // 立即执行 executor 函数,将 resolve 和 reject 函数传入
    try {
      executor(resolve, reject);
    } catch (err) {
      // 如果 executor 函数执行出错,直接 reject
      reject(err);
    }
  }

  // 定义 then 方法
  then(onFulfilled, onRejected) {
    // 如果 Promise 的状态已经是 'fulfilled',直接执行 onFulfilled 函数
    if (this.state === "fulfilled") {
      onFulfilled(this.value);
    }
    // 如果 Promise 的状态已经是 'rejected',直接执行 onRejected 函数
    else if (this.state === "rejected") {
      onRejected(this.reason);
    }
    // 如果 Promise 的状态是 'pending',需要将 onFulfilled 和 onRejected 函数存放起来,等待状态改变时调用
    else {
      this.onFulfilledCallbacks.push(() => onFulfilled(this.value));
      this.onRejectedCallbacks.push(() => onRejected(this.reason));
    }
  }
  
  catch(onRejected) {
    return this.then(null, onRejected);
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      // 遍历 promises 数组
      for (let i = 0; i < promises.length; i++) {
        // 使用 Promise.resolve 将非 Promise 实例转为 Promise 实例
        MyPromise.resolve(promises[i]).then(
          (value) => {
            // 一旦有一个 promise 解决,就解决返回的 promise
            resolve(value);
          },
          (reason) => {
            // 一旦有一个 promise 拒绝,就拒绝返回的 promise
            reject(reason);
          }
        );
      }
    });
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      let result = [];
      let count = 0;

      for (let i = 0; i < promises.length; i++) {
        // 使用 Promise.resolve 将非 Promise 实例转为 Promise 实例
        MyPromise.resolve(promises[i]).then(
          (value) => {
            count++;
            result[i] = value;
            // 所有的 promise 都解决了,就解决返回的 promise
            if (count === promises.length) {
              resolve(result);
            }
          },
          (reason) => {
            // 有一个 promise 拒绝了,就拒绝返回的 promise
            reject(reason);
          }
        );
      }
    });
  }
}

闭包实现once

function once(func) {
  let hasRun = false;
  let result;

  return function() {
    if (!hasRun) {
      hasRun = true;
      result = func.apply(this, arguments);
    }

    return result;
  };
}

节流函数

在间隔时间之内, 只允许执行一次函数; 这样的函数就是函数节流; 方案一:

function throttle(func ,delay = 500) {
  let timer = null;
  return function () {
    let context = this
    let args = arguments;
    if(!timer){
      timer = setTimeout(() => {
        func.apply(context, args)
        timer = null
      }, delay)
    }
  }
}

方案二:

function throttle(func ,delay = 500) {
  let activeTime = 0;
  return function () {
    let context = this
    let current = Date.now();
    if(current - activeTime > delay) {
      func.apply(context, arguments)
      activeTime = Date.now();
    }
  }
}

防抖函数

如果在一定的时间间隔之内去重复执行某个程序,则会把程序执行时间重置;

function debounce(func, delay) {
  let timeout;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(function() {
      func.apply(context, args);
    }, delay);
  };
}

实现map

const map = (array, fn) => {
  let results = []
  for (let item of array) {
    results.push(fn(item))
  }
  return results
}

实现every

const every = (array, fn) => {
  let result = true
  for (let value of array) {
    result = fn(value)
    if (!result) {
      break
    }
  }
  return result
}

实现some

const some = (array, fn) => {
  let result = false
  for (let value of array) {
    result = fn(value)
    if (result) {
      break
    }
  }
  return result
}

模拟lodash的memoize方法的实现

function memoize(func) {
  let cache = {};

  return function(...args) {
    let key = JSON.stringify(args);
    if (cache[key] === undefined) {
      cache[key] = func(...args);
    }
    return cache[key];
  };
}

试一下是ok的

function getArea (r) {
  console.log(r);
  return Math.PI**r
}
let getAreaWithMemory = memoize(getArea)
console.log(getAreaWithMemory(4)) // 只打印了一句 4 , 缓存了下来
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))

实现函数组合compose

模拟 lodash 中的 flow 方法

const reverse = arr => arr.reverse();
const first = arr => arr[0];
const toUpper = s => s.toUpperCase();

const compose = (...fns) => {
  return function (value) {
    return fns.reduce((acc, fn) => {
      return fn(acc)
    }, value)
  }
}

const a = compose(reverse, first, toUpper)
console.log(a(['one', 'two', 'three']));

使用箭头函数简写

const compose = (...fns) => value => fns.reduce((acc, fn) => fn(acc), value);

模拟实现 lodash 中的 curry 方法

  • 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
  • 这是一种对函数参数的「缓存」,运用了闭包
  • 让函数变得更灵活,让函数的粒度更小
  • 可以把多元函数转换为一元函数,可以组合使用函数产生强大的功能
function getSum(a, b, c) {
  return a + b + c
}
function curry (func) {
  return function curriedFn (...args) {
    // 判断实参和形参的个数
    if (args.length < func.length) { // 方法名.length => 得到参数的个数
      return function () { // 参数为 arguments
        // 递归调用
        return curriedFn(...args.concat(Array.from(arguments))) 
      }
    }
    // 实参和形参个数相等时执行
    return func(...args)
  }
}
const curried = curry(getSum)
// console.log(curried(1, 2, 3))
// console.log(curried(1, 2)(3))
console.log(curried(1)(2)(3))

Object.create()

// Object.create() 是一个 JavaScript 方法,它创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__。
function createObject(proto) {
  function F() {};
  F.prototype = proto;
  return new F();
}

手写一个new操作

  1. 第一种
function _new(fn, ...args) {
  let obj = Object.create(fn.prototype)
  fn.apply(obj, args)
  return obj
}

  1. 第二种写法
function New(Fn, ...args) {
  // 1. 创建一个空对象obj
  const obj = {};
  // 2. 将该对象的__proto__属性指向该构造函数的原型
  obj.__proto__ = Fn.prototype; 
  // 3. 将执行上下文(this)绑定到新创建的对象中
  const result = Fn.apply(obj, args); 
  // 4. 如果构造函数返回「引用类型」的值,那么将其返回。否则返回上面创建的对象
  return result instanceof Object ? result : obj; 
}

bind,apply,call实现

Function.prototype.myBind = function(context, ...args) {
  let self = this;
  return function() {
    return self.apply(context, args.concat(Array.prototype.slice.call(arguments)))
  }
}

Function.prototype.myApply = function(context, args) {
  context = context || window;
  // JavaScript 中,当一个函数作为一个对象的方法被调用时,函数内部的 this 值将被设置为那个对象。
  // 所以,通过将函数赋值给 context.fn,我们可以在 context 的上下文中调用这个函数。
  context.fn = this;
  let result;
  if (!args) {
    result = context.fn()
  } else {
    result = context.fn(...args);
  }
  delete context.fn;
  return result
}

Function.prototype.myCall = function(context, ...args) {
  context = context || window;
  context.fn = this;
  let result = context.fn(...args);
  delete context.fn;
  return result;
}

var实现let块级作用域特性

由于函数参数是按值传递的,所以就会将变量index的当前值复制给参数i,而在timer1函数内部又创建了一个访问变量i的闭包,因此可以返回各自不同的值

for (var index = 0; index < 5; index++) {
  (function timer1(i) {
     console.log(i);
  })(index);
}

利用函数创建新的作用域

(function() {
  var x = 1;
  console.log(x);
})();
console.log(x); // x is not defind

实现 reduce

function reduce(array, callback, initialValue) {
  let acc = initialValue;

  for (let i = 0; i < array.length; i++) {
    if (i === 0 && acc === undefined) {
      acc = array[0];
    } else {
      acc = callback(acc, array[i], i, array);
    }
  }

  return acc;
}

let array = [1, 2, 3, 4, 5];
let sum = reduce(array, function(acc, val) {
  return acc + val;
});

console.log(sum); // 输出 15

reduce 实现 map

function mapWithReduce(array, callback) {
  return array.reduce(function(accArr, cur, index, array) {
    accArr.push(callback(cur, index, array))
    return accArr;
  }, [])
}
if (!Array.prototype.mapUsingReduce) {
    Array.prototype.mapUsingReduce = function (callback, thisArg) {
        return this.reduce(function (accumulator, currentValue, currentIndex, sourceArray) {
            accumulator[currentIndex] = callback.call(thisArg, currentValue, currentIndex, sourceArray)
            return accumulator;
        }, [])
    }
}
const resp = [1, 2, 3].mapUsingReduce((currentValue, index, array) => currentValue + index)

reduce 实现 groupBy

function groupBy(arr, property) {
  return arr.reduce((data, item) => {
    const key = item[property];
    if (!data[key]) {
      data[key] = [];
    }
    data[key].push(item);
    return data;
  }, {});
}

实现domToJson

  function domToJson(dom) {
  const obj = {
    tag: dom.tag,
    type: dom.type,
    attributes: {},
    // 递归处理children;
    children: [],
  };

  for (let i = 0; i < dom.attributes.length; i++) {
    obj.attributes[dom.attributes[i].name] = dom.attributes[i].value;
  } 
  
  for (let i = 0; i < dom.children.length; i++) {
    obj.children.push(domToJson(dom.children[i]));  
  }
  return obj;
}

实现优先队列 PriorityQueue

元素属性:priority

方法:add,out

function PriorityQueue() {
  let queue = []
  this.add = (item) => {
    if (queue.length === 0) {
      queue.push(item)
    } else {
      for (let i = 0; i < queue.length; i++) {
        const element = queue[i];
        if (element < item) {
          queue.splice(i, 0, item) // 执行插队操作
          return; // 终止函数
        }
      }
      queue.push(item);
    }
  };
  this.out = () => {
    return queue.shift()
  }
  this.print = () => {
    return queue;
  }
}
const queue = new PriorityQueue()

const arr = [1,0,0,1,0,0,0,0,0,1,0,0];

思路很重要!

题意:把 0 的地方替换成 1,但是 1 和 1 之前不能挨着。

const arr = [1, 0, 0, 1, 0, 0, 0, 1, 0, 0];
const foo = (arr, n = 3) => {
  let count = 0;
  const tempArr = arr.join('').split(1);
  tempArr.forEach((item) => {
    if (item.length >= 3) {
      const arrLen = arr.length;
      count += Math.trunc(item.length / 2);
      // 单独处理数组两端的情况
      if (arr[0] === 0 && arr[1] === 0 || arr[arrLen - 1] === 0 && arr[arrLen - 2] === 0) {
        count += 1;
      }
    }
  })
  return count === n
};

number数组按照末尾数字大小排序

const sort = (arr) => {
    arr.sort((a, b) => a % 10 - b % 10)
}

arrayToTree

let arr = [
  {id: 1, name: '部门1', pid: 0},
  {id: 2, name: '部门2', pid: 1},
  {id: 3, name: '部门3', pid: 1},
  {id: 4, name: '部门4', pid: 3},
  {id: 5, name: '部门5', pid: 4},
];

[
  {
      "id": 1,
      "name": "部门1",
      "pid": 0,
      "children": [
          {
              "id": 2,
              "name": "部门2",
              "pid": 1,
              "children": []
          },
          {
              "id": 3,
              "name": "部门3",
              "pid": 1,
              "children": [
                  // 结果 ,,,
              ]
          }
      ]
  }
]
function getChildren(arr, result, pid) {
  arr.forEach(item => {
    if (item.pid === pid) {
      const newItem = {...item, children: []};
      result.push(newItem);
      getChildren(arr, newItem.children, item.id);
    }
  });
}

function arrayToTree(arr, pid = 0) {
  let result = [];
  getChildren(arr, result, pid);
  return result;
}