前端-JS-手写篇

334 阅读6分钟

1、手写一个push?

  Array.prototype.push = function(...items) { // 可以传入多个值
    let O = Object(this); // 根据草案转换为Object
    let len = this.length >>> 0; 
    // 无符号右移运算符,向右移指定位,并且符号位设为0(代表正数)
    // 这里用于确保len为正
    let argCount = items.length >>> 0;
    if(len + argCount > 2 ** 53 - 1) // 超过最大长度,抛错
      throw new TypeError("The number of array is over the max value restricted!")
    for(let i = 0;i < argCount;i++)
    {
      O[len++] = items[i];
    }
    return len;
  }

image.png

2、手写一个pop?

Array.prototype.pop = function() {
  let O = Object(this);
  let len = this.length >>> 0;
  if(len == 0) return undefined;
  else {
    let element = O[len-1];
    delete O[len-1];
    this.length = len-1;
    return element;
  }
}

image.png

3、手写一个map?

Array.prototype.map = function(func,thisArg) {
  if (this === null || this === undefined) {
    throw new TypeError("Cannot read property 'map' of null or undefined");
  }
  let O = Object(this);
  let len = this.length >>> 0;
  // 判断回调
  if(Object.prototype.toString.call(func) !== '[object Function]')
    throw new TypeError("Cannot read property 'map' of null or undefined");
  
  let A = new Array(len);
  let k = 0;
  while(k < len)
  {
    if(k in O) // *** 这里去除empty,让他保持empty状态,不进入回调
    {
      let kValue = O[k];
      A[k] = func.call(thisArg,kValue,k,O);
    }
    k++;
  }
  return A;
}

image.png

4、手写一个reduce?

Array.prototype.reduce = function(func,initialValue) {
  if(this === undefined || this === null)
  {
    throw new TypeError('...');
  }
  if(Object.prototype.toString.call(func) !== '[object Function]')
  {
    throw new TypeError('...');
  }

  let O = Object(this);
  let len = this.length >>> 0;
  if(len === 0 && initialValue === undefined)
    throw new TypeError('...');
  let k = 0;
  let accumulator = initialValue;
  let kPresent = false;
  while(!kPresent && k < len)
  {
    if(k in O)
    {
      kPresent = true;
      accumulator = O[k];
    }
    k++;
  }
  if(!kPresent) throw new TypeError('...');
  while(k < len)
  {
    if(k in O)
    {
      let kValue = O[k];
      accumulator = func.call(undefined,accumulator,kValue,k,O);
    }
    k++;
  }
  return accumulator;
}

image.png

5、手写一个filter?

    Array.prototype.filter = function(func,thisArg) {
  if(this === null || this === undefined)
  {
    throw new TypeError('...');
  }
  if(Object.prototype.toString.call(func) !== '[object Function]')
  {
    throw new TypeError('...');
  }
  let O = Object(this);
  let len = this.length >>> 0;
  let newArrLen = 0;
  let newArr = [];
  for(let k = 0;k < len;k++)
  {{
      if(func.call(thisArg,O[k],k,O)) newArr[newArrLen++] = O[k];
    }
  }
  return newArr;
}

image.png

6、手写一个splice?

Array.prototype.splice = function(start,deleteCount,...items) {
  let O = Object(this);
  let len = this.length >>> 0;
  if(len - deleteCount + items.length > 2 ** 53 - 1)
  {
    throw new TypeError('...');
  }

  let retArr = []; //用于return的数组容器.

  // 处理start
  if(start < 0)
  {
    start = start+len >= 0 ? start+len : 0;
  }
  else
  {
    start = start >= len ? len : start;
  }
  
  // 处理deleteCount 这里deleteCount依赖start,所以处理顺序不能变
  if(arguments.length == 1) deleteCount = len-start;
  else if(deleteCount < 0) deleteCount = 0;
  else if(deleteCount+start > len) deleteCount = len-start;

  // 判断sealed对象、处理configurable为false的,(不可添加、删除属性,但是可以改变属性值)
  // 判断冻结对象、冻结对象不可以增删改属性值.
  if(Object.isSealed(O) && items.length !== deleteCount)
    throw new TypeError('...');
  else if(Object.isFrozen(O) && (items.length > 0 || deleteCount > 0))
    throw new TypeError('...');

  // 判断是否是需要补满删除的属性值.
  if(deleteCount <= items.length)
  {
    for(let i = start;i < start+deleteCount;i++)
    {
      retArr.push(O[i]);
      O[i] = items[i-start];
    }
  }
  
  // 补满之后还有数据没有添加
  // 注意移动的起始位置,如果放方向开始移动会出现覆盖.
  if(deleteCount < items.length)
  {
    let add = items.length-deleteCount;
    this.length += add;
    for(let i = len-1;i >=start+deleteCount;i--)
    {
      O[i+add] = O[i];
    }
    for(let i = 0;i < add;i++)
    {
      O[start+deleteCount+i] = items[i+deleteCount];
    }
  }
  
  // 删除数据过多 需要将后面的数据前移.
  else if(deleteCount > items.length)
    {
      for(let i = 0;i < deleteCount;i++)
      {
        retArr.push(O[i+start]);
      }
      for(let i = 0; i < items.length;i++)
      {
        O[i+start] = items[i];
      }
      for(let i = 0;i < len - start - deleteCount;i++)
      {
        O[start+items.length+i] = O[i+start+deleteCount];
      }
      this.length -= deleteCount-items.length;
    }
  return retArr;
}

这个就不放源码的,太多了,面试写成这样还不让过,那真就夸张了. 这题主要考察边界值的判断情况.以及对于api整体的了解程度.感觉全写对在算法中应该属于medium偏上了.

7、手撕sort?

 Array.prototype.sort = function(callback) {
  let O = Object(this);
  let len = this.length >>> 0;
  _sort(O,len,callback)

  function _sort(arr,length,compareFn) {
   // 定义交换规则, 
   // a表示是否要交换的数,b表示被选中的数,根据return来确定是否需要交换 这个挺难理解的
    if(Object.prototype.toString.call(compareFn) !== '[object Function]')
    {
      compareFn = function(a,b) {
        a = a.toString();
        b = b.toString();
        if(a === b) return 0;
        else return a < b ? -1 : 1;
      }
    }
    // 插入排序, 在size < 10时, 使用插入排序速度时优于快排的.
    function insertSort(arr,start = 0,end,compareFn) {
      for(let i = start;i <= end;i++)
      {
        let j;
        let val = arr[i];
        for(j = i;j > start && compareFn(arr[j-1],val) > 0;j--)
          arr[j] = arr[j-1];
        arr[j] = val;
      }
    }

    // 选择中位数,防止快速排序为最差情况.
    function getMidIndex(arr,begin,end) {
      let increment = 200 + length & 15;
      let tmpArr = [];
      for(let i = begin;i < end; i += increment)
      {
        tmpArr.push([i,arr[i]]);
      }
      tmpArr.sort((a,b) => {
        return compareFn(a[1],b[1]);
      })
      return tmpArr[tmpArr.length >> 1][0];
    }
    
    function quickSort(arr,begin,end) {
      let midIndex = 0;
      if(end-begin <= 10)
      {
        insertSort(arr,begin,end,compareFn);
        return;
      }
      // 这里不使用(begin+end)/2是为了防止begin+end时溢出,(一般不会,但是要养成好的代码习惯)
      else if(end-begin <= 1000) midIndex = begin + (end-begin) >> 1;
      // 当长度超过1000时,需要选择更多的随机数,优化快速排序
      else midIndex = getMidIndex(arr,start,end);
      // 将两个边界值和拿到的mid排序,让中间的数最左边.优化快速排序
      let tmp = [arr[midIndex],arr[begin],arr[end]];
      tmp.sort(compareFn);
      [arr[begin],arr[midIndex],arr[end]] = [tmp[1],tmp[0],tmp[2]];
      
      // 进行快速排序
      let i = begin;
      let t = begin+1;
      let j = end;
      let pivot = arr[begin];
      while(t <= j)
      {
        // 确定是否符合交换规则
        let order = compareFn(pivot,arr[t]);
        if(order > 0)
          [arr[t++],arr[i++]] = [arr[i],arr[t]];
        else if(order == 0) t++;
        else {
          if(compareFn(pivot,arr[j]) < 0) j--;
          else if(compareFn(arr[j],pivot) == 0) [arr[t++],arr[j--]] = [arr[j],arr[t]];
          else [arr[j--],arr[t++],arr[i++]] = [arr[t],arr[i],arr[j]];
        }
      }
      quickSort(arr,begin,i-1);
      quickSort(arr,j+1,end);
    }

    quickSort(arr,0,length-1);
  }
}

这里借鉴了一些文章,但是有些文章是有些问题的,有些问题还很难看出来,😭,然后自己思考了一下,基本面试让写sort的就比较过分了,大概有个思路就比一般人强了.我觉得compareFn是比较难理解的.

8、手写一个new?

function newFn(Target,...items)
{
    // 判断函数
    if(Object.prototype.toString.call(Target) !== '[object Function]')
        throw new TypeError('target is not Function');
    // 判断return 是否为引用类型
    let res = Target(...items);
    // 获取原型链
    let obj = Object.create(Target.prototype);
    Target.call(obj,...items);
    return (typeof res === 'object' && res !== null) ? res : obj;
}

9、手写一个bind?

 Function.prototype.bind = function(target,...items) {
  if(Object.prototype.toString.call(this) !== '[object Function]')
      throw new TypeError('...')
  let self = this;
  // 利用闭包
  let resFn = function(...arg) {
      // 要判断 this是否为构造函数,
      //如果是构造函数,this的继承resFn的原型,所以self是在this的原型上的.
      self.apply(this instanceof self ? this : target,[...items,...arg]);
  }
  resFn.prototype = Object.create(self.prototype);
  return resFn;
}

10、手写一个apply和call?

// 这里借鉴了三元大佬的代码
 Function.prototype.call = function(target,...items(直接使用items就是apply)) {
    let context = target || window;
    // 三元的fn是有问题的,通过.fn调用fn会转为字符串.
    // 这里使用Symbol是保证唯一性.不和其他变量冲突
    let fn = Symbol('fn');
    context[fn] = this;
    let res = context[fn](...items);

    delete context[fn];
    return res;
}

11、手写深拷贝?

function getItemType(item) {
    prototypeList = {
        "[object Object]": 'object',
        "[object Array]": 'array',
        "[object Map]": 'map',
        "[object Set]": 'set'
    };
    return prototypeList[Object.prototype.toString.call(item)];
}
function funcRes(target) {
    if(!target.prototype) return target;
    const bodyTag = /(?<={)(.|\n)+(?=})/m;
    const paramsTag = /(?<=\().+(?=\)\s+)/;
    const targetStr = target.toString();
    const params = paramsTag.exec(targetStr);
    const body = bodyTag.exec(targetStr);
    console.log(body);

    if(!body) return null;
    if(params) return new Function(...params[0],body[0]);
    else return new Function(body[0]);
}
function cannotTranverseRes(target)
{
    prototypeList = {
        '[object Error]': 'error',
        '[object Function]': 'function',
        '[object Date]': 'date',
        '[object RegExp]': 'regExp',
        '[object Boolean]': 'boolean',
        '[object Number]': 'number',
        '[object String]': 'string',
        '[object Symbol]': 'symbol',
    }
    let ctor = target.constructor;
    let val = prototypeList[Object.prototype.toString.call(target)];
    switch(val)
    {
        // 获取使用原始类型通过Object创建的原始值
        case 'boolean':
            return new Object(Boolean.prototype.valueOf.call(target));
        case 'number':
            return new Object(Number.prototype.valueOf.call(target));
        case 'string':
            return new Object(String.prototype.valueOf.call(target));
        case 'symbol':
            return new Object(Symbol.prototype.valueOf.call(target));
        case('regExp'):
            return new ctor(target.source,target.flags);
        case('function'):
            return funcRes(target);
        default:
            return new ctor(target);
    }
}
function deepClone(target,map = new Map()) {
    // 处理原始类型
    if((typeof target !== 'object' || target == null) && typeof target !== 'function')
        return target;
    let res;
    // 对于可遍历对象需要递归调用.
    let type = getItemType(target);
    if(!type) return cannotTranverseRes(target);
    else res = new target.constructor();
    // 获取原型上的属性
    
    // 处理循环引用
    if(map.has(target)) return target;
    map.set(target,true);

    // 处理可遍历对象
    if(type === 'map')
    {
        for(let [index,item] of target.entries())
            res.set(deepClone(index,map),deepClone(item,map));
    }
    else if(type === 'set')
    {
        for(let item of target.values())
            res.add(deepClone(item,map));
    }
    else if(type === 'array' || type === 'object')
    {
        for(let k in target)
        {
            if(target.hasOwnProperty(k))
                res[k] = deepClone(target[k],map);
        }
    }
    return res;
}

这里三元大佬写的边界处理的很到位了, 我看着写的😁

12、手写一个promise(全套promise,过promises-aplus-tests)?

   function Promise(executor) {
   // 保存两种状态下的数据
    this.value = null;
    this.reason = null;
    // 保存异步函数在状态变化后调用的函数
    this.fulfillledArr = [];
    this.rejectedArr = [];
    this.status = 'pending';
    // 保证状态只能改变一次
    this.flag = false;
    const _resolve = (val) => {
        this.value = val;
        this.status = 'resolved';
        this.fulfillledArr.map(cal => {cal()});
    }
    const resolve = (val) => {
        if(this.status === 'pending' && !this.flag)
        {
            this.flag = true;
            // 处理resolve内部含有promise的情况
            if(val !== null && (
               typeof val === 'function' ||
               typeof val === 'object'
                ))
            {
                try {
                    let then = val.then;
                    if(typeof then === 'function')
                    {
                        then.call(val,(val) => {
                            this.flag = false;
                            resolve(val)
                        },(rea) => {
                            this.flag = false;
                            reject(rea);
                        });
                    } else
                    {
                        _resolve(val);
                    }
                }catch(e)
                {
                    this.flag = false;
                    reject(e);
                }
            }
            else {
                _resolve(val);
            }
        }
    }
    const reject = (reason) => {
        if(this.status === 'pending' && !this.flag)
        {
            this.flag = true;
            this.reason = reason;
            this.status = 'rejected';
            this.rejectedArr.map(cal => {cal()});
        }
    }
    executor(resolve,reject);
}


Promise.prototype.then = function(fulfilled,rejected)
{
    // 初始化函数
    if(typeof fulfilled !== 'function') fulfilled = value => value;
    if(typeof rejected !== 'function') rejected = reason => {throw reason};
    let p2 = new Promise((resolve,reject) => {
        if(this.status === 'resolved')
        {
            // 设置微任务
            process.nextTick(() => {
                try {
                    let p = fulfilled(this.value)
                    PromiseAndRelation(p,p2,resolve,reject);
                }catch(e) {
                    reject(e);
                }
            })
        }
        else if(this.status === 'rejected')
        {
            process.nextTick(() => {
                try {
                    let p = rejected(this.reason);
                    PromiseAndRelation(p,p2,resolve,reject);
                }catch(e) {
                    reject(e);
                }
            })
        }
        else if(this.status === 'pending')
        {
            this.fulfillledArr.push(() => {
                process.nextTick(() => {
                    try {
                        let p = fulfilled(this.value)
                        PromiseAndRelation(p,p2,resolve,reject);
                    }catch(e) {
                        reject(e);
                    }
                })
            })
            this.rejectedArr.push(() => {
                process.nextTick(() => {
                    try {
                        let p = rejected(this.reason);
                        PromiseAndRelation(p,p2,resolve,reject);
                    }catch(e) {
                        reject(e);
                    }
                })
            })
        }
    })
    return p2;
}
// 处理then中返回promise的情况
function PromiseAndRelation(p,p2,Toresolve,Toreject)
{
    // 处理循环引用
    if(p2 === p) throw new TypeError('cycling reference');
    let flag = false;
    if(p != null && (
        typeof p === 'function' ||
        typeof p === 'object'
        ))
    {
        try {
            let then = p.then;
            if(typeof then === 'function')
            {
                then.call(p,(val) => {
                    if(flag) return;
                    flag = true;
                    PromiseAndRelation(val,p,Toresolve,Toreject);
                },(rea) => {
                    if(flag) return;
                    flag = true;
                    Toreject(rea);
                });
            } else Toresolve(p);
        }catch(e)
        {
            if(flag) return;
            flag = true;
            Toreject(e);
        }
    }
    else {
        // if(flag) return;
        // flag = true;
        Toresolve(p);
    }
}

Promise.resolve = function(val) {
    return new Promise((resolve,reject) => {
        resolve(val);
    })
}

Promise.reject = function(val) {
    return new Promise((resolve,reject) => {
        reject(val);
    })
}

Promise.prototype.finally = function(fn) {
    this.then(() => {
        fn();
    },() => {
        fn();
    })
    return this;
}

Promise.prototype.catch = (rej) => {
    return this.then(null,rej);
}

// promise.all 
// 1、保证全部为resolved才返回fulfilled.
// 2、函数必须传入拥有迭代器的变量.
// 3、一旦有一个错误里吗抛出
Promise.all = function(arr) {
    return new Promise((resolve,reject) => {
        let res = [];
        function count(val) {
            res.push(val);
            if(res.length === arr.length || arr.size) resolve(res);
        }
        if(arr[Symbol.iterator])
        {
            for(let item of arr.values())
            {
                Promise.resolve(item).then((val) => {
                    count(val)
                },reject);
            }
        }
        else reject(new TypeError(`${typeof arr} ${arr} is not iterable (cannot read property Symbol(Symbol.iterator))`));
    })
}

Promise.race = function(arr) {
    return new Promise((resolve,reject) => {
        if(arr[Symbol.iterator])
        {
            for(let item of arr.values())
            {
                Promise.resolve(item).then((val) => {
                    resolve(val)
                },reject);
            }
        }
        else reject(new TypeError(`${typeof arr} ${arr} is not iterable (cannot read property Symbol(Symbol.iterator))`));
    })
}
// 创建一个延迟对象
Promise.defer = Promise.deferred = function() {
    let dfd = {};
    dfd.promise = new Promise((resolve,reject) => {
      dfd.resolve = resolve;
      dfd.reject = reject;
    });
    return dfd;
  }

module.exports = Promise;

image.png

13、手写防抖和节流?

防抖: 在规定时间那再次触发则重新计时.
    function debounse(fn,delay = 1000) {
      let timer = null;
      return (...arg) => {
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(this,arg);
          timer = null;
        }, delay);
      }
    }
节流: 在一个定时器被触发时,在时间未到的情况下不能被再次出发.
    function throttle(fn,delay = 1000) {
      let flag = true;
      return (...arg) => {
        if(!flag) return;
        flag = false;
        setTimeout(() => {
          fn.apply(this,arg)
          flag = true;;
        }, 1000);
      }
    }

14、实现数组去重的几种方式?

1、使用set数据结构,再转换为数组.
    let a = new Set();
    for(let item of arr.values()) a.add(item);
    a = [...a];
2、使用hash去重 
    let obj = {};
    for(let item of arr.values()) obj[item] = item;
    let res = [];
    for(let item of Object.values(obj)) res.push(item);
3、使用reduce+sort
    // 排序过后,相同值的被放在一起,这时根据和上一个值的比较来去重复
    arr.sort();
    let res = arr.reduce((pre,item,index) => {
     // Object.is是处理+-1和NaN的情况
      (index === 0 || !Object.is(item,pre[pre.length-1])) && pre.push(item);
      return pre;
    },[])
4、常规比较
    // 额外的空间复杂度为O[1].
    for(let i = 0;i < arr.length;i++)
    {
      let t;
      for(t = i+1;t < arr.length;t++)
      {
        if(Object.is(arr[i],arr[t])) break;
      }
      t != arr.length && arr.splice(t,1);
    }

15、观察者模式和发布者订阅者模式?

观察者模式:
    class Subscribe {
        constructor() {
            this.observers = [];
        }
        on(callback) {
            if(typeof callback === 'function')
            {
                this.observers.push(callback);
            }
        }
        notify(val) {
            this.observers.map(cbk => {cbk(val)});
        }
    }
发布者订阅者模式?
    class Subscribe {
        constructor() {
            this.eventChannel = {}
        }
        // 订阅 并且根据once来表示是否执行一次
        on(type, cbk, once = false) {
            if(!this.eventChannel[type]) this.eventChannel[type] = [{cbk,once}];
            else this.eventChannel[type].push({cbk,once});
        }
        // 分发
        emit(type,val) {
            let events = this.eventChannel[type];
            if(events)
            {
                // 这里为了once做处理
                // 如果使用map的话 在删除元素时,索引无法控制.
                for(let i = 0;i < events.length;i++)
                {
                    events[i].cbk(val);
                    events[i].once && events.splice(i--,1)
                }
            }
        }
        // 移除事件中心的type类型的cbk回调
        off(type,cbk) {
            let events = this.eventChannel[type];
            if(events.length === 0) delete this.eventChannel[type];
            else {
                let index = events.findIndex(item => item.cbk === cbk);
                events.splice(index,1);
            }
        }
        // 移除type类型的回调.
        removeAll(type) {
            this.eventChannel[type] && delete this.eventChannel[type];
        }
    }

16、手写一个JSONP?

  client: 
    function jsonp(url,data)
    {
      // 构造query
      let params = new URLSearchParams();
      for(let [index,item] of Object.entries(data))
      {
        params.set(index,item);
      }
      // 创建在server端用于响应的函数
      params.set('cbk','_cbk');
      url += '?' + params.toString();
      
      let script =  document.createElement('script');
      script.setAttribute('src', url);
      document.body.appendChild(script);
      
      return new Promise((resolve,reject) => {
        window['_cbk'] = (val) => {
          resolve(val);
          document.body.removeChild(script);
          delete window['_cbk']
        }
      })
    }
    jsonp('http://localhost:8082',{name: 1,val: 2}).then((val) => {
      console.log(val);
    })
  server:
    let express = require('express');
    let server = express();

    server.use('/',(req,res) => {
      res.end(`${req.query.cbk}(数据))`);
    })
    server.listen(8082);

17、手写一个await?

await是genarator的语法糖,就是在yield上套一层promise,再自动执行
    // **  测试代码
    function f1() {
      return new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve(1);
        }, 1000);
      })
    }
    
    function* f(a) {
      let data = yield f1();
      console.log(data,a);
      throw new Error(123);
      let data1 = yield f1();
      console.log(data1,a);
      return 1
    }
    
    async function f0(a) {
      let da = await f1();
      console.log(da,a);
      throw new Error(123);
      let da1 = await f1();
      console.log(da1,a);
      return 1
    }
    
    测试代码  ** // 
    
    // 升级genarator
    function asyncGanerator(func) {
      return function(...arg) {
      // 获取迭代对象
        let gen = func.apply(this,arg);
        
        return new Promise((resolve,reject) => {
          let genarator;
          // 这里传type是为了应对Error,需要考虑两种情况
          // 1、在执行过程有thrpw
          // 2、在执行promise时,reject了
          function next(type,data) {
          // 捕捉错误
            try {
              genarator = gen[type](data);
            }catch(e) {
              return reject(e);
            }
            // 结束 return;
            if(genarator.done === true)
            {
              resolve(genarator.value);
              return;
            }
            // 迭代.
            Promise.resolve(genarator.value).then((data) => {
              next('next',data);
            },(err) => {
              next('throw',data);
            });
          }
          next('next');
        })
      }
    }
    
    
    let val = asyncGanerator(f);
    let val1 = f0(123);
    val = val(123);
    setTimeout(() => {
      console.log(val);
      console.log(val1);
    }, 3000);

18、手写一个Instanceof?

    通过查找原型链
    function myInstanceof(val,target) {
    // 基本类型直接return
      if(typeof val !== 'object' || val === null) return false;
      while(Object.getPrototypeOf(val))
      {
        if(Object.getPrototypeOf(val) === target.prototype) return true;
        val = Object.getPrototypeOf(val);
      }
      return false;
    }

19、手写一个Object.is?

  function myObjectIs(val1,val2) {
                              // 处理+0和-0。        // 处理NaN
      return val1 === val2 ?  1/val1 === 1/val2  : (isNaN(val1) && isNaN(val2));
}

20、手写一个函数柯里化?

柯里化主要是把多个参数转换为单个参数进行链式调用的技术.
function curry()
{
  let args = [...arguments];
  let _curry =function () {
    args.push(...arguments);
    return _curry;
  }
  _curry.toString = function() {
    return args.reduce((pre,val) => pre+val);
  }
  return _curry;
}
console.log(curry(1,2,3,4) == curry(1)(2)(3)(4)) true;