Skip to content
On this page

手写函数源码

各种面试常见手写源码 😏😏😏😏

对象深拷贝

js
const obj1 = {
  name: 'wozien',
  age: 23,
  have: ['house', 'car', 'money', function() {}]
};

function getType(val) {
  return Object.prototype.toString.call(val).slice(8, -1);
}

function deepCopy(obj) {
  let res,
    type = getType(obj);

  if (type === 'Object') {
    res = {};
  } else if (type === 'Array') {
    res = [];
  } else {
    return obj;
  }

  // 对象每个属性的复制
  for (let key in obj) {
    let value = obj[key];
    if (getType(value) === 'Object' || getType(value) === 'Array') {
      // 递归深拷贝子对象
      res[key] = deepCopy(value);
    } else {
      res[key] = value;
    }
  }

  return res;
}

const obj2 = deepCopy(obj1);
obj2.name = 'marry';
obj2.have[0] = 'aasds';

console.log(obj1);
console.log(obj2);

浅拷贝可以用 Object.assign[].slice 实现

数组去重

对象属性标记

js
const unique = arr => {
  const temp = {};
  return arr.filter(item =>
    temp.hasOwnProperty(typeof item + JSON.stringify(item))
      ? false
      : (temp[typeof item + JSON.stringify(item)] = true)
  );
};

在循环中利用 indexOf 求出的索引和当前循环索引比较进行过滤,只返回相等的元素。

js
const unique = arr => arr.filter((e, i) => arr.indexOf(e) === i);

es6方法:

js
const unique = arr => Array.from(new Set(arr));
const unique = arr => [...new Set(arr)];

//利用映射map,该方法的对象标记一个意思。
const unique = arr => {
  const seen = new Map();
  return arr.filter(e => !seen.has(e) && seen.set(e, 1));
};

数组扁平

通过循环每个元素,如果元素是数组,递归调用扁平函数,并把返回结果 concat 到当前的结果数组中。

js
const flatten = arr => {
  let res = [];

  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      res = res.concat(flatten(arr[i]));
    } else {
      res.push(arr[i]);
    }
  }

  return res;
};

toString: 因为多层嵌套的数组调用 toString 方法会转换成逗号拼接的字符串。如[1,[2,[3,4]]] 会变成1,2,3,4。然后利用 split 分割下丢进返回结果数组。改方法只适用于都是数字的数组。

js
const flatten = arr => {
  return arr
    .toString()
    .split(',')
    .map(item => +item);
};

reduce: 数组的 reduce 会循环每个元素,并存储上次的计算结果,最终返回一个值。所以可以利用该函数进行递归的优化。

js
const flatten = arr => {
  return arr.reduce((pre, e) => {
    return pre.concat(Array.isArray(e) ? flatten(e) : e);
  }, []);
};

es6扩展运算符: 因为[].concat([1,[2,[3,4]]]) 会返回 [1,2,[3,4]]。可见,每次调用会扁平一层数组,所以可以循环调用,直到数组不包含数组元素。

js
const flatten = arr => {
  while (arr.some(e => Array.isArray(e))) {
    arr = [].concat(...arr);
  }
  return arr;
};

实现 instanceof

js
function myInstancceOf(obj, Constr) {
  let proto = obj.__proto__

  while(proto) {
    if(proto === Constr.prototype) return true
    proto = proto.__proto__
  }

  return false
}

实现 new 运算符

js
function Foo(name) {
  this.name = name
}
 
function myNew(Contr, ...args) {
  const obj = Object.create(Contr.prototype)
  const res = Foo.apply(obj, args) 

  return typeof res === 'object' ? res : obj
}

var foo = myNew(Foo, 'wozien')
console.log(foo)

实现 call, apply 和 bind

实现 call

js
Function.prototype.myCall = function(context) {
  context = context || window;
  // 这里的属性名应该利用唯一的key,防止覆盖原属性
  // 可以用es6的symbol
  context.fn = this;

  // 获取参数
  var args = [];
  for (var i = 1; i < arguments.length; i++) {
    args.push('arguments[' + i + ']');
  }

  // args自动调用Array.toString方法
  var result = eval('context.fn(' + args + ')');
  delete context.fn;

  return result;
}

实现 apply:

js
Function.prototype.myApply = function(context, arr) {
  context = context || window;
  context.fn = this;

  var result;
  if (Object.prototype.toString.call(arr).slice(8, -1) !== 'Array') {
    // 传的不是数组直接调用
    result = context.fn();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push('arr[' + i + ']');
    }

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

  delete context.fn;
  return result;
};

实现 bind:

js
Function.prototype.myBind = function(context) {
  if (typeof this !== 'function') {
    throw new Error('Function.prototype.bind - what is trying to be bound is not callable');
  }

  var fn = this;
  var arg1 = Array.prototype.slice.call(arguments, 1);
  var Fnp = function() {};

  var bindfn = function() {
    var arg2 = Array.prototype.slice.call(arguments);

    // 执行原函数,修改this
    fn.apply(this instanceof fn ? this : context, arg1.concat(arg2));
  };

  // 新函数和原函数指向同一个原型
  Fnp.prototype = fn.prototype;
  bindfn.prototype = new Fnp();
  return bindfn;
};

实现 EventEmitter

js
function EventEmitter() {
  // 回调函数对象
  this.cbs = {};
}

EventEmitter.prototype.addListener = function(type, listener) {
  if (this.cbs[type]) {
    this.cbs[type].push(listener);
  } else {
    this.cbs[type] = [listener];
  }
};

EventEmitter.prototype.once = function(type, listener) {
  // 处理监听函数,再调用后立即removeListener
  const only = (...arg) => {
    listener.apply(this, arg);
    this.removeListener(type, listener);
  };

  // 这一步是为了能够在移除的时候找到对应的监听函数
  only.origin = listener;
  this.addListener(type, only);
};

EventEmitter.prototype.removeListener = function(type, listener) {
  if (Array.isArray(this.cbs[type])) {
    if (!listener) {
      // 清空所有监听
      delete this.cbs[type];
    } else {
      // 移除通过addListener或者once添加的回调
      this.cbs[type] = this.cbs[type].filter(e => e !== listener && e.origin !== listener);
    }
  }
};

EventEmitter.prototype.emit = function(type, ...args) {
  if (Array.isArray(this.cbs[type])) {
    this.cbs[type].forEach(cb => {
      cb.apply(this, args);
    });
  }
};