前端四大手写

转载的

手写 bind

bind 用法不难,一句话解释就是把新的 this 绑定到某个函数 func 上,并返回 func 的一个拷贝。使用方法如下:
let boundFunc = func.bind(thisArg[, arg1[, arg2[, …argN]]])
那怎么实现呢?我认为手写 bind 可以分为三个等级:

初级:只用 ES6 新语法
优点:因为可以使用 const 、… 操作符,代码简洁
缺点:不兼容 IE

中级:使用 ES5 语法
优点:兼容 IE
缺点:参数要用Array.prototype.slice 取,复杂且不支持 new

高级:ES5 + 支持 new
优点:支持 new
缺点:最复杂

初级 bind
这种方式的优点是因为可以使用 const 、… 操作符,代码简洁;缺点是不兼容 IE 等一些古老浏览器

// 初级:ES6 新语法 const/...
function bind_1(asThis, ...args) {
  const fn = this; // 这里的 this 就是调用 bind 的函数 func
  return function (...args2) {
    return fn.apply(asThis, ...args, ...args2);
  };
}

中级 bind
优点:兼容 IE
缺点:参数要用Array.prototype.slice 取,复杂且不支持 new

// 中级:兼容 ES5
function bind_2(asThis) {
  var slice = Array.prototype.slice;
  var args = slice.call(arguments, 1);
  var fn = this;
  if (typeof fn !== "function") {
    throw new Error("cannot bind non_function");
  }
  return function () {
    var args2 = slice.call(arguments, 0);
    return fn.apply(asThis, args.concat(args2));
  };
}

高级 bind
优点:支持 new
缺点:最复杂
写之前,我们先来看一看我们应该如何判断 new,

new fn(args) 其实等价于:

const temp = {}
temp.__proto__ = fn.prototype
fn.apply(temp, [...args])
return temp

核心在第二句:temp.proto = fn.prototype,有了这个,我们便知道可以用 fn.prototype 是否为对象原型来判断是否为 new 的情况。

// 高级:支持 new,例如 new (funcA.bind(thisArg, args))
function bind_3(asThis) {
  var slice = Array.prototype.slice;
  var args1 = slice.call(arguments, 1);
  var fn = this;
  if (typeof fn !== "function") {
    throw new Error("Must accept function");
  }
  function resultFn() {
    var args2 = slice.call(arguments, 0);
    return fn.apply(
      resultFn.prototype.isPrototypeOf(this) ? this : asThis, // 用来绑定 this
      args1.concat(args2)
    );
  }
  resultFn.prototype = fn.prototype;
  return resultFn;
}
手写 promise

无疑是要求最高的,如果要硬按照 Promises/A+ 规范来写,可能至少要 2-3 个小时,400+行代码,这种情况是几乎不可能出现在面试中。所以我们只需要完成一个差不多的版本,保留最核心的功能。

核心功能:

new Promise(fn) 其中 fn 只能为函数,且要立即执行
promise.then(success)中的 success 会在 resolve 被调用的时候执行
实现思路:
then(succeed, fail) 先把成功失败回调放到对象实例 callbacks[] 上
resolve() 和 reject() 遍历callbacks
resolve() 读取成功回调 / reject() 读取失败回调(异步等待 then 把回调放好)
执行回调

下面分享我自己根据上述需求及思路实现的模板:

class Promise2 {
  state = "pending";
  callbacks = [];
  constructor(fn) {
    if (typeof fn !== "function") {
      throw new Error("must pass function");
    }
    fn(this.resolve.bind(this), this.reject.bind(this));
  }
  resolve(result) {
    if (this.state !== "pending") return;
    this.state = "fulfilled";
    nextTick(() => {
      this.callbacks.forEach((handle) => {
        if (typeof handle[0] === "function") {
          handle[0].call(undefined, result);
        }
      });
    });
  }
  reject(reason) {
    if (this.state !== "pending") return;
    this.state = "rejected";
    nextTick(() => {
      this.callbacks.forEach((handle) => {
        if (typeof handle[1] === "function") {
          handle[1].call(undefined, reason);
        }
      });
    });
  }
  then(succeed, fail) {
    const handle = [];
    if (typeof succeed === "function") {
      handle[0] = succeed;
    }
    if (typeof fail === "function") {
      handle[1] = fail;
    }
    this.callbacks.push(handle);
  }
}

function nextTick(fn) {
  if (process !== undefined && typeof process.nextTick === "function") {
    return process.nextTick(fn);
  } else {
    // 实现浏览器上的nextTick
    var counter = 1;
    var observer = new MutationObserver(fn);
    var textNode = document.createTextNode(String(counter));

    observer.observe(textNode, {
      characterData: true,
    });
    counter += 1;
    textNode.data = String(counter);
  }
}
手写深拷贝

先问这么几个问题,

首先为什么要深拷贝?不希望数据被修改或者只需要部分修改数据。
怎么实现深拷贝?简单需求用 JSON 反序列化,复杂需求用递归克隆。
手写深拷贝的优点?体现扎实的 JS 基础
至于缺点以及如何解决缺点稍后再回答
简单需求
最简单的手写深拷贝就一行,通过 JSON 反序列化来实现

const B = JSON.parse(JSON.stringify(A))

缺点也是显而易见的,JSON value不支持的数据类型,都拷贝不了

不支持函数
不支持undefined(支持null)
不支持循环引用,比如 a = {name: ‘a’}; a.self = a; a2 = JSON.parse(JSON.stringify(a))
不支持Date,会变成 ISO8601 格式的字符串
不支持正则表达式
不支持Symbol
如何支持这些复杂需求,就需要用到递归克隆了。

复杂需求
核心有三点:

递归
对象分类型讨论
解决循环引用(环)
下面给出我的模板

class DeepClone {
  constructor() {
    this.cacheList = [];
  }
  clone(source) {
    if (source instanceof Object) {
      const cache = this.findCache(source);
      if (cache) return cache; // 如果找到缓存,直接返回
      else {
        let target;
        if (source instanceof Array) {
          target = new Array();
        } else if (source instanceof Function) {
          target = function () {
            return source.apply(this, arguments);
          };
        } else if (source instanceof Date) {
          target = new Date(source);
        } else if (source instanceof RegExp) {
          target = new RegExp(source.source, source.flags);
        }
        this.cacheList.push([source, target]); // 把源对象和新对象放进缓存列表
        for (let key in source) {
          if (source.hasOwnProperty(key)) { // 不拷贝原型上的属性,太浪费内存
            target[key] = this.clone(source[key]); // 递归克隆
          }
        }
        return target;
      }
    }
    return source;
  }
  findCache(source) {
    for (let i = 0; i < this.cacheList.length; ++i) {
      if (this.cacheList[i][0] === source) {
        return this.cacheList[i][1]; // 如果有环,返回对应的新对象
      }
    }
    return undefined;
  }
}

这个方案是完美无缺的吗?其实还有不小的距离:

对象类型支持不够多(Buffer,Map,Set等都不支持)
存在递归爆栈的风险
怎么解决?用别人封装好的第三方库。。。

手写 EventHub(发布-订阅)

核心思路是:

使用一个对象作为缓存
on 负责把方法注册到缓存的 EventName 对应的数组
emit 负责遍历触发 EventName 底下的方法数组
off 找方法的索引,并删除

class EventHub {
  cache = {};
  on(eventName, fn) {
    this.cache[eventName] = this.cache[eventName] || [];
    this.cache[eventName].push(fn);
  }
  emit(eventName) {
    this.cache[eventName].forEach((fn) => fn());
  }
  off(eventName, fn) {
    const index = indexOf(this.cache[eventName], fn);
    if (index === -1) return;
    this.cache[eventName].splice(index, 1);
  }
}

function indexOf(arr, item) {
  if (arr === undefined) return -1;
  let index = -1;
  for (let i = 0; i < arr.length; ++i) {
    if (arr[i] === item) {
      index = i;
      break;
    }
  }
  return index;
}

https://zhuanlan.zhihu.com/p/160315811?utm_source=wechat_session&utm_medium=social&utm_oi=642493669156982784&utm_content=sec

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值