重学JS|一网打尽面试常用的手写函数

下面面试小故事写着玩的🤣🤣🤣

小小的房间,我和他面对面坐着,中间隔着一张大理石材质的桌子,为了这次见面我倒了好几次地铁。明亮的灯光照在我和他的脸上,别说,这样看他还有点让人紧张,毕竟第一次和他见面。简单的寒暄几句之后,为了强压内心的紧张,我先发制人,作了个自我介绍。


于是面试正式开始了。

👨‍🦲: 很会 js?。他死死的盯着我
🤓: “额,会。。。会。。。会一些。。”。心跳💔开始加速,手心已经潮湿,顺势把双手从桌上移到到了发抖的🦵腿🦵上
👨‍🦲: “那你手写一下 call 吧”。或许看出了我的紧张和倔强,他不再看我,目光移到我的简历📄上
🤓: “可以!”。我内心窃喜,嘴角已经已经成 ‘’✔ ‘’ 状😏。


看他还有话说,但我可等不及了,迅速在电脑上敲下了所有我毕生所学。


各种手写

call & bind & apply

老三样了,其实面试基本不问了,思想学习一下

  • Call
Function.prototype.myCall = function (context) {
    let context = context || window;
    let fn = Symbol('fn');
    context.fn = this;

    let args = [];
    for(let i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    let result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}
  • Bind
Function.prototype.myBind= function (thisObj) {
  // 判断是否为函数调用
  if (typeof target !== 'function' || Object.prototype.toString.call(target) !== '[object Function]') {
      throw new TypeError(this + ' must be a function');
  }
  const self = this;
  const args = [...arguments].slice(1);
  var bound = function () {
      var finalArgs = [...args, ...arguments];
      // new.target 用来检测是否是被 new 调用
      if (new.target !== undefined) {
      // 说明是用new来调用的
      var result = self.apply(this, finalArgs);
      if (result instanceof Object) {
          return result;
      }
      return this;
      } else {
      return self.apply(thisArg, finalArgs);
      }
  };
  if (self.prototype) {
      // 为什么使用了 Object.create? 因为我们要防止,bound.prototype 的修改而导致self.prototype 被修改。不要写成 bound.prototype = self.prototype; 这样可能会导致原函数的原型被修改。
      bound.prototype = Object.create(self.prototype);
      bound.prototype.constructor = self;
  }
  return bound;
};
  • Apply
Function.prototype.cusApply = function(thisArg){
   if(typeof this !== 'function'){ throw new TypeError('cusApply 必须被函数调用') }

   const args = arguments[1];
   const fn = Symbol('fn');
   
   thisArg[fn] = this;
   const result = thisArg[fn](args);
   delete thisArg[fn];

   return res;
}

这个 神三元讶羽若川 他们都写过,思路大致都一样。
当然 apply 有一个 eval⇲ 实现的版本,因为 eval 存在安全问题,我就没贴那个 版本⇲

数组原生API

forEach

Array.prototype._forEach = function(callback, thisArg) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0 // 下面有解释
    let k = 0
    while (k < len) {
        if (k in O) {
            callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}

为了保证 len 为正整数,将无符号 右移 0 位, 什么意思⇲

filter

Array.prototype._filter = function(callback, thisArg) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
   let k = 0, res = []
    while (k < len) {
        if (k in O) {
           if (callback.call(thisArg, O[k], k, O)) {
               res.push(O[k])                
           }
        }
        k++;
    }
   return res
}

其实 Array.prototype.someArray.prototype.map 就很简单了,不贴了。

reduce

Array.prototype._reduce = function reduce(callbackfn, initialValue) {
  // 拿到数组
  const O = this,
  len = O.length;
  // 下标值
  let k = 0,
  // 累加器
  accumulator = undefined,
  kPresent = false,

  if (typeof callbackfn !== 'function') {
    throw new TypeError(callbackfn + ' is not a function');
  }
  if (len === 0 && arguments.length < 2) {
    throw new TypeError('Reduce of empty array with no initial value');
  }

  if (initialValue ) accumulator = initialValue
  else {
    accumulator = O[k];
    ++k;
  }

  while (k < len) {
    // 判断是否为 empty [,,,]
    kPresent = O.hasOwnProperty(k);

    if (kPresent) {
      const kValue = O[k];
      // 调用 callbackfn
      accumulator = callbackfn.apply(undefined, [accumulator, kValue, k, O]);
    }
    ++k;
  }
  return accumulator;
};

isArray

Array._isArray = function(o) {
 	return Object.prototype.toString.call(Object(o)) === '[object Array]';
};

事件总线(发布订阅模式)

class EventEmitter {
  constructor() {
    // 为了保证键唯一,用 map 实现
    this.cache = {}
  }
  on(name, fn) {
      if (this.cache[name]) {
          this.cache[name].push(fn)
      } else {
          this.cache[name] = [fn]
      }
  }
  off(name, fn) {
      let tasks = this.cache[name]
      if (tasks) {
          const index = tasks.findIndex(f => f === fn || f.callback === fn)
          if (index >= 0) {
              tasks.splice(index, 1)
          }
      }
  }
  emit(name, once = false, ...args) {
      if (this.cache[name]) {
          // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
          let tasks = this.cache[name].slice()
          for (let fn of tasks) {
              fn(...args)
          }
          if (once) {
              delete this.cache[name]
          }
      }
  }
}
// 测试
let eventBus = new EventEmitter()
let event1 = function(name, age) {
    console.log(`${name} ${age}`)
}
let event2 = function(name, age) {
    console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', event1)
eventBus.on('aaa', event2)
eventBus.emit('aaa', false, 'ethan', 12)
// ethan 12
// hello, ethan 12

想要了解更多请参考 **观察者模式与发布/订阅模式⇲**

New

new 运算符用来创建用户自定义的对象类型的实例或者具有构造函数的内置对象的实例。实现要点:

  • new 会产生一个新对象;
  • 新对象需要能够访问到构造函数的属性,所以需要重新指定它的原型;
  • 构造函数可能会显示返回;
function newOperator(ctor) {
   if (typeof ctor !== 'function') {
       throw 'newOperator function the first param must be a function';
   }
   newOperator.target = ctor; //目标函数
   var newObj = Object.create(ctor.prototype); // newObj.__proto__ = ctor.prototype
   var argsArr = [].slice.call(arguments, 1); // 取非第一位的参数
   var ctorReturnResult = ctor.apply(newObj, argsArr); // 运行;改变this指向;传参
   var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
   var isFunction = typeof ctorReturnResult === 'function';
   if (isObject || isFunction) {
       return ctorReturnResult;
   }
   return newObj;
}

Object.create

if (typeof Object.create !== "function") {
  Object.create = function (prototype, properties) {
    if (typeof prototype !== "object") { throw TypeError(); }
    function Ctor() {}
    Ctor.prototype = prototype;
    var o = new Ctor();// o.__proto__ = Ctor
    if (prototype) { o.constructor = Ctor; }
    if (properties !== undefined) {
      if (properties !== Object(properties)) { throw TypeError(); }
      Object.defineProperties(o, properties);
    }
    return o;
  };
}

Object.assign

Object._assign = function(target, ...source) {
    if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object')
    }
    let ret = Object(target) 
    source.forEach(function(obj) {
        if (obj != null) {
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    ret[key] = obj[key]
                }
            }
        }
    })
    return ret
}

Object.getOwnPropertyNames

if (typeof Object.getOwnPropertyNames !== 'function') {
  Object._getOwnPropertyNames = function(o) {
    if (o !== Object(o)) {
      throw TypeError('Object.getOwnPropertyNames called on non-object');
    }
    var props = [],
      p;
    for (p in o) {
      if (Object.prototype.hasOwnProperty.call(o, p)) {
        props.push(p);
      }
    }
    return props;
  };
}

instanceof 实现

L instanceof R, 检测 L 是否是 R 的实例, 即 L.prototype 是否在 R 的原型链上。

function instanceOf(left, right) {
    let proto = left.__proto__
    while (true) {
        if (proto === null) return false
        if (proto === right.prototype) {
            return true
        }
        proto = proto.__proto__
    }
}

深/浅拷贝

浅拷贝只是复制地址,深拷贝复制值。

function shallowCopy(obj) {
    if (typeof obj !== 'object') return
    
    let newObj = obj instanceof Array ? [] : {}
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key]
        }
    }
    return newObj
}

其实浅拷贝意义不大,

如果数据无嵌套(对象属性或数组项为基本类型),那么这种方式也不错

function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}

后面有 JSON.stringifyJSON.parse 重写。

递归深拷贝,考虑到正则和日期

const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

function deepClone(target, map = new WeakMap()) {
    if (map.get(target)) {
        return target;
    }
    // 获取当前值的构造函数:获取它的类型
    let constructor = target.constructor;
    // 检测当前对象target是否与正则、日期格式对象匹配
    if (/^(RegExp|Date)$/i.test(constructor.name)) {
        // 创建一个新的特殊对象(正则类/日期类)的实例
        return new constructor(target);  
    }
    if (isObject(target)) {
        map.set(target, true);  // 为循环引用的对象做标记
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {
            if (target.hasOwnProperty(prop)) {
                cloneTarget[prop] = deepClone(target[prop], map);
            }
        }
        return cloneTarget;
    } else {
        return target;
    }
}

对于校验更多类型的版本 参考⇲

async/await 实现

async/await 这两个关键字是 让异步变同步的的 语法糖。

知识点很大 ,请参考我以前的文章。

手搓Promise

知识点很大 ,请参考我以前的文章。

防抖 & 节流

知识点很大 ,请参考我以前的文章。

JSON.stringify

JSON.stringify([, replacer [, space]) 方法是将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串。此处模拟实现,不考虑可选的第二个参数 replacer 和第三个参数 space,可参考 ecma标准⇲

1.基本数据类型:

  • undefined 转换之后仍是 undefined(类型也是 undefined)
  • boolean 值转换之后是字符串 “false”/“true”
  • number 类型(除了 NaN 和 Infinity)转换之后是字符串类型的数值
  • symbol 转换之后是 undefined
  • null 转换之后是字符串 “null”
  • string 转换之后仍是string
  • NaN 和 Infinity 转换之后是字符串 “null”

2.函数类型:转换之后是 undefined

3.如果是对象类型(非函数)

  • 如果有 toJSON() 方法,那么序列化 toJSON() 的返回值。
  • 如果属性值中出现了 undefined、任意的函数以及 symbol 值,忽略。
  • 所有以 symbol 为属性键的属性都会被完全忽略掉。
  • 如果是一个数组:如果属性值中出现了 undefined、任意的函数以及 symbol,转换成字符串 “null” ;
  • 如果是 RegExp 对象:返回 {} (类型是 string);
  • 如果是 Date 对象,返回 Date 的 toJSON 字符串值;
  • 如果是普通对象;
    4.对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

代码:

function jsonStringify(data) {
    let dataType = typeof data;
    
    if (dataType !== 'object') {
        let result = data;
        //data 可能是 string/number/null/undefined/boolean
        if (Number.isNaN(data) || data === Infinity) {
            //NaN 和 Infinity 序列化返回 "null"
            result = "null";
        } else if (dataType === 'function' || dataType === 'undefined' || dataType === 'symbol') {
            //function 、undefined 、symbol 序列化返回 undefined
            return undefined;
        } else if (dataType === 'string') {
            result = '"' + data + '"';
        }
        //boolean 返回 String()
        return String(result);
    } else if (dataType === 'object') {
        if (data === null) {
            return "null"
        } else if (data.toJSON && typeof data.toJSON === 'function') {
            return jsonStringify(data.toJSON());
        } else if (data instanceof Array) {
            let result = [];
            //如果是数组
            //toJSON 方法可以存在于原型链中
            data.forEach((item, index) => {
                if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {
                    result[index] = "null";
                } else {
                    result[index] = jsonStringify(item);
                }
            });
            result = "[" + result + "]";
            return result.replace(/'/g, '"');
            
        } else {
            //普通对象
            /**
             * 循环引用抛错(暂未检测,循环引用时,堆栈溢出)
             * symbol key 忽略
             * undefined、函数、symbol 为属性值,被忽略
             */
            let result = [];
            Object.keys(data).forEach((item, index) => {
                if (typeof item !== 'symbol') {
                    //key 如果是symbol对象,忽略
                    if (data[item] !== undefined && typeof data[item] !== 'function'
                        && typeof data[item] !== 'symbol') {
                        //键值如果是 undefined、函数、symbol 为属性值,忽略
                        result.push('"' + item + '"' + ":" + jsonStringify(data[item]));
                    }
                }
            });
            return ("{" + result + "}").replace(/'/g, '"');
        }
    }
}

JSON.parse

  • eval 实现,注意使用 eval⇲ 是危险的!!;
var json = '{"a":"1", "b":2}';
var obj = eval("(" + json + ")"); 
  • new Function 实现;
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;

if (
    rx_one.test(
        json.replace(rx_two, "@")
            .replace(rx_three, "]")
            .replace(rx_four, "")
    )
) {
    var obj = eval("(" +json + ")");
}

虽然他几次想打断我的 show ,但我心思早已在代码上,必然会顾不上他的感受,就这样,一个小时过去了。

👨‍🦲: “可以基础还不错,回去等通知吧”。他看我写完,眉头紧锁,无心看我代码也没再问其他问题,想要离开。
🤓: “嗯”。说完我起身便离开了。我知道,这是我们第一次见面,也是最后一次见面。。。


参考

站在别人肩膀上能看的更远,感谢💖💖💖以下文章作者

【公众号】| JS兵法36 计,你会多少?
【掘金】| (建议精读)原生JS灵魂之问(中),检验自己是否真的熟悉JavaScript?
【掘金】 | 各种源码实现,你想要的这里都有

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值