学习一下JS手写

74 阅读7分钟

前端并发请求控制

async function controlAsync(maxLimit, dataArray, doFn) {
  let finish = [] // 完成
  let doing = [] // 执行
  for(let data of dataArray) {
    let p = doFn(data)
    finish.push(p)
    if(maxLimit <= dataArray.length) {
    // 如果没达到限制数就直接只往finish里添加就行
      p = p.then(() => {
          doing.splice(doing.indexOf(p), 1)
          // 执行完成后把自己从执行列表中去除
      })       
      doing.push(p)
      
      if(maxLimit <= doing.length) {
          await Promise.race(doing)
          // 等待执行列表有完成的就可以开始下一次循环
      }
    }
  }
  return Promise.all(finish)
}

// 使用
controlAsync(
  2,   //参数一,同时并发的格式
  [1000, 3000, 2000, 5000, 6000],  //参数二,每个请求传入的参数组成的数组
  (param) => 	//参数三,返回值被Promise包裹的异步执行函数
    new Promise((reslove) => {
      setTimeout(() => {
        console.log(param);
        reslove(param);
      }, param);
    })
).then((res) => {
  console.log("res:", res);
});

订阅发布模式

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(eventName, Fn) {
    if(this.events[eventName]) {
       this.events[eventName].push(Fn);
    }  else {
       this.events[eventName] = [];
       this.events[eventName].push(Fn);
    }
  }
  // 交牛客测试时候要去掉emit的第二个参数😂
  emit(eventName, ...args) {
     if(this.events[eventName]) {
       this.events[eventName].forEach(item => { item(...args) });
    }
  }
}

手写快排

  • 简单来说就是选出一个基准数,把比这个数大的放一边,比这个数小的放一边
  • 然后把上述两边的部分再做同样的操作
  • 一直大的一边,小的一边,到最后就能排序了
function quickSort(arr, left, right) {
    if (left < right) {
        // 确认两边的分界在哪
        let part = sortPart(arr, left, right)
        // 给两边的部分再分大小
        quickSort(arr, left, part - 1)
        quickSort(arr, part + 1, right)
    }
    return arr
}
function sortPart(arr, left, right) {
    let standardNum = arr[left]
    while (left < right) {
        while (right > left && arr[right] > standardNum) {
            right--
        }
        arr[left] = arr[right]
        while (right > left && arr[left] <= standardNum) {
            left++
        }
        arr[right] = arr[left]
    }
    arr[left] = standardNum
    return left
}

观察者模式

// 被观察
class Subject {
    constructor(name) {
        this.name = name
        this.message = ""
        this.watchers = []
    }

    addWatcher(watcher) {
        this.watchers.push(watcher)
    }

    setMessage(msg) {
        this.message = msg
        this.sendNotice()
    }

    sendNotice() {
        this.watchers.forEach((e) => {
            e.notice(this.name, this.message)
        })
    }
}

// 观察者
class Observer {
    constructor() {

    }

    notice(name, message) {
        console.log(`${name} changed, message:${message}`)
    }
}

// 使用
const subject = new Subject("subject1");
const observerA = new Observer();
const observerB = new Observer();
subject.addWatcher(observerB);
subject.setMessage('123');
subject.addWatcher(observerA);
subject.setMessage('456');

防抖节流

防抖:n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
节流:n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

// 防抖:
function debounce(fn, wait) {
    let timer;
    return function (...args) {
        if(timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, wait)
    }
}

// 防抖:立即执行
function debounce(fn, wait, immediate) {
    let timer;
    return function () {
        let context = this;
        let args = arguments;
        if (timer) clearTimeout(timer); // timer 不为null
        if (immediate) {
            let callNow = !timer;
            // 无定时器的时候就立刻执行,有的话,正常走防抖逻辑
            timer = setTimeout(function () {
                timer = null;
            }, wait)
            if (callNow) {
                fn.apply(context, args)
            }
        }
        else {
            timer = setTimeout(function () {
                fn.apply(context, args)
            }, wait);
        }
    }
}
// 节流:时间戳写法
function throttled(fn, delay) {
    let oldTime = Date.now()
    return function() {
        let context = this;
        let args = arguments;
        let nowTime = Date.now()
        // 使用时间戳写法,事件会立即执行,停止触发后没有办法再次执行
        if(nowTime - oldTime >= delay) {
            fn.apply(context, args)
            oldTime = Date.now()
        }
    }
}

// 节流:定时器写法
function throttled(fn, delay) {
    let timer = null
    return function (...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args)
                timer = null
            }, delay);
        }
    }
}

// 节流:前两个结合(弥补时间戳不能停止触发后不能延迟执行)
function throttled(fn, delay) {
    let timer = null
    let starttime = Date.now()
    return function () {
        let curTime = Date.now() // 当前时间
        let remaining = delay - (curTime - starttime)  
        // 从上一次到现在,还剩下多少多余时间
        let context = this
        let args = arguments
        clearTimeout(timer)
        if (remaining <= 0) {
            fn.apply(context, args)
            starttime = Date.now()
        } else {
            timer = setTimeout(fn, remaining);
        }
    }
}

深拷贝

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

// JSON.stringify()缺点: 会忽略undefined、symbol 和 函数
const obj2=JSON.parse(JSON.stringify(obj1));

深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

function deepClone(obj) {
    if (obj === null) return obj;// 如果是null或者undefined我就不进行拷贝操作
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== 'object') return obj;
    let cloneObj = new obj.constructor();  //保持继承链
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            // 实现一个递归拷贝
            cloneObj[key] = deepClone(obj[key]);
        }
    }
    return cloneObj;
}; 
// 考虑symbol的话,要加一下这段
    let symKeys = Object.getOwnPropertySymbols(obj); // 查找symbol属性
    if (symKeys.length) { // 查找成功	
        symKeys.forEach(symKey => {
            if (typeof (obj[symKey]) === 'object' && (obj[symKey]) != null) {
                cloneObj[symKey] = deepClone(obj[symKey]);
            } else {
                cloneObj[symKey] = obj[symKey];
            }
        });
    }
// 再考虑循环引用的话,完整版大概这样
function deepClone(obj, hash = new WeakMap()) {
    if (obj === null) return obj;// 如果是null或者undefined我就不进行拷贝操作
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== 'object') return obj;
    if (hash.has(obj)) return hash.get(obj);
    let cloneObj = new obj.constructor();  //保持继承链
    hash.set(obj, cloneObj);
    let symKeys = Object.getOwnPropertySymbols(obj); // 查找symbol属性
    if (symKeys.length) { // 查找成功	
        symKeys.forEach(symKey => {
            if (typeof (obj[symKey]) === 'object' && (obj[symKey]) != null) {
                cloneObj[symKey] = deepClone(obj[symKey], hash);
            } else {
                cloneObj[symKey] = obj[symKey];
            }
        });
    }
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            // 实现一个递归拷贝
            cloneObj[key] = deepClone(obj[key], hash);
        }
    }
    return cloneObj;
};

解决循环引用:(Lodash)
当然了,感觉用WeakMap也挺好,到时候结束了不影响垃圾回收

浅拷贝

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

let oldobj = {
    // ....
}

let newobj = {}

for (let k in oldobj) {
    newobj[k] = oldobj[k]
}
let newobj = Object.assign({},oldobj);

promise.all

function myPromiseAll(pro) {
    return new Promise((resolve, reject)  => {
        // 要执行的promise们
        const promises = Array.from(pro)
        // 结果
        const result = []
        let count = 0
        for(let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]).then(
                res => {
                    // 注意保持顺序
                    result[i] = res;
                    count++;
                    // 都完成了就可以返回了
                    if(count == promises.length) {
                        resolve(result)
                    }
                }
            ).catch(e => {
                // 有一个reject就立刻返回
                return reject(e)
            })
        }
    })
}


// 测试
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(88);
  }, 1000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(99);
  }, 3000);
});

let arr = [p1, p2, 3, 4];

myPromiseAll(arr).then(res => {
    console.log(res)
})

Promise.race

function myPromiseRace(promisesList) {
  return new Promise((resolve, reject) => {
    // 直接循环同时执行传进来的promise
    for (const promise of promisesList) {
      // 直接返回出去了,所以只有一个,就看哪个快
      promise.then(resolve, reject)
    }
  })
}

// 测试
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(88);
  }, 1000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(99);
  }, 3000);
});

let arr = [p1, p2];

myPromiseRace(arr).then(res => {
    console.log(res)
})

再来个寄生组合继承吧

function Father(name) {
  this.name = name
  this.say = function () {
    console.log("hello,world");
  }
}

Father.prototype.showName = function () {
  // 这个是测试输出用的
  console.log(this.name)
}

function Son() {
  Father.call(this, name)
  this.age = age
}

Son.prototype = object.create(Father.prototype) 
Son.prototype.constructor = Son

手写一个 new

function myNew (Func, ...arg){
    let obj = {}  //定义了一个对象。
    obj.__proto__ = Func.prototype  //将Func.prototype赋值为对象的__proto__属性,即原型链的概念
    let res = Func.call(obj, ...arg) //更改Func的this指向
    return res instanceof Object ? res : obj 
}
myNew(RegExp)

手写个bind, apply, call

  • 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  • 函数绑定到context时候,可能有重复属性,可以用symbol优化,但是缺点是这是ES6的(话说我下面的??也是ES6的😂)
// call函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
      result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context ?? window;
  // 将调用函数设为对象的方法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};
// apply
Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  let result = null;
  // 判断 context 是否存在,如果未传入则为 window
  context = context ?? window;
  // 将函数设为对象的方法
  context.fn = this;
  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 将属性删除
  delete context.fn;
  return result;
}
// bind 函数实现
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  var args = [...arguments].slice(1),
  fn = this;
  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};

Object.freeze

developer.mozilla.org/zh-CN/docs/…

let myFreeze = function (object) {
    let freeze = (item) => {
        Object.defineProperty(object, item, {
            writable: false,
            configurable: false
        })
    }
    for (let item in object) {
        item instanceof Object && myFreeze(item) //递归
        !(item instanceof Object) && freeze(item)
    }
    Object.seal(object)
}

函数柯里化

原理:
当传入的参数数 不足 所需要的参数数时,返回一个可以传入剩余参数的高阶函数

function fnToCurrying(fn, ...items) {
 if (fn.length == items.length) { 
    let res=fn(...items)
    return res;
  }
  return (...rests) => {
      return  fnToCurrying(fn, ...items, ...rests);
  };
}

//  使用:
let add = function (a, b, c) {
  return a + b + c;
};
let addCurry = fnToCurrying(add);
console.log(
addCurry(1)(2,4),  //7
addCurry(9)(10)(10) //29
);

Promise

const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const PENDING = 'pending';
function Promise(executor){
 const self = this;
 this.status = PENDING;
 this.value = '';
    this.FulfilledFn = [];
 this.RejectedFn = [];
 function reslove(res) {
        self.value = res;
        self.status = FULFILLED;
        self.FulfilledFn.map(fn => fn(res));
 }
 function reject(err) {
        self.value = err;
        self.status = REJECTED;
        self.RejectedFn.map(fn => fn(res));
 }
 executor(reslove, reject);
}
Promise.prototype.then = function(onFulfilled, onRejected) {
    return new Promise((reslove, reject) => {
        if (this.status === PENDING) {
            this.FulfilledFn.push(v => {
                let p = onFulfilled(v);
                if (p instanceof Promise && p !== this) {
                    p.then(res => {
                        reslove(res);
                    }, err => {
                        reject(err);
                    });
                } else {
                 reslove(p);
                }
            });
            this.RejectedFn.push(v => {
                setTimeout(() => {
                    let p = onRejected(v);
                });
            });
        } else if (this.status === FULFILLED) {
            onFulfilled(this.value);
        } else if (this.status === REJECTED) {
            onRejected(this.value);
        }
    });
}