❌ API 工程师

想想你会吗?

实现 call

Function.prototype.myCall = function(context = window, ...args) {
  if (typeof this !== 'function') {
    throw new TypeError(`expect function, but got a ${typeof this}`);
  }
  const fn = Symbol('fn');
  context[fn] = this;
  const result = context[fn](...args);
  delete context[fn];
  return result;
};

// 测试一下
function Person(name, age) {
  this.name = name;
  this.age = age;
}

function Student(...args) {
  Person.myCall(this, ...args);
  this.eat = 'eating';
}

const kk = new Student('kk', 16);
console.log(kk);

另一种写法

Function.prototype.myCall = function(context) {
  context = context || window;
  context.fn = this;
  let arr = [],
    args = arguments;
  for (var i = 1; i < args.length; i++) {
    if (typeof args[i] === 'string') {
      args[i] = '"' + args[i] + '"'; // 将字符串添加"" 塞进数组里变成 ["1"]
    }
    arr.push(args[i]);
  }
  let _args = arr.toString();
  let result = eval('context.fn(' + _args + ')');
  delete context.fn;
  return result;
};

实现 apply

注意,apply 传进来的是是数组,在进行扩展运算符的时候要小心!

Function.prototype.myApply = function(context = window) {
  if (typeof this !== 'function') {
    throw new TypeError(`expect function, but got a ${typeof this}`);
  }

  const fn = Symbol('fn');
  context[fn] = this;
  let result;
  if (arguments[1]) {
    result = context[fn](...arguments[1]);
  } else {
    result = context[fn]();
  }
  delete context[fn];
  return result;
};

function Person(name, age) {
  this.name = name;
  this.age = age;
}

function Student(...args) {
  Person.myApply(this, args);
  this.eat = 'eat';
}

const kk = new Student('kk', 16);
console.log(kk);

实现 bind

Function.prototype.myBind = function() {
  if (typeof this !== 'function') {
    throw new TypeError(`expect function, but got ${typeof this}`);
  }
  const slice = Array.prototype.slice,
    thatFunc = this,
    thatArgs = arguments[0],
    args = slice.call(arguments, 1);
  return function() {
    const _args = args.concat(slice.call(arguments));
    return thatFunc.apply(thatArgs, _args);
  };
};

const a = {
  x: 1,
  getX: function() {
    return this.x;
  },
};
const b = a.getX;
const c = {
  x: 2,
};
const d = b.myBind(c, 3);
d();

实现 map

Array.prototype.map = function(callback) {
  if (typeof callback !== 'function') {
    throw new TypeError(`expect got function, but got ${typeof callback}`);
  }

  if (!Array.isArray(this)) {
    throw new TypeError(`expect got array, but got ${typeof this}`);
  }

  const arr = this;
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    result.push(callback(arr[i], i, arr));
  }
  return result;
};

[1, 2, 3].map(i => i + 1);

实现 filter

Array.prototype.filter = function(callback) {
  if (typeof callback !== 'function') {
    throw new TypeError(`expect got function, but got ${typeof callback}`);
  }

  if (!Array.isArray(this)) {
    throw new TypeError(`expect got array, but got ${typeof callback}`);
  }

  const arr = this;
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i, arr) ? result.push(arr[i]) : '';
  }
  return result;
}


[0, 1, 2, 3].filter(i => i > 0);

实现 reduce

Array.prototype.reduce = function(callback, initValue) {
  if (typeof callback !== 'function') {
    throw new TypeError(`expect got function, but got ${typeof callback}`);
  }

  if (!Array.isArray(this)) {
    throw new TypeError(`expect got array, but got ${typeof callback}`);
  }

  const arr = this;
  let index = 0,
    accumulator = initValue;
  if (!accumulator) {
    index = 1;
    accumulator = arr[0];
  }

  for (; index < arr.length; index++) {
    let sum = callback(accumulator, arr[index], index, arr);
    accumulator = sum;
  }
  return accumulator;
}

[1, 2, 3].reduce((x, y) => x + y, 10);

实现 new

new 实现原理: new 是用来实例化一个类,从而在内存中分配一个实例对象。

会做如下操作:

  1. 创建一个空的 Javascript 对象,即{};

  2. 将函数的 prototype 赋值给对象的 __proto__ 属性;

  3. 调用函数,并将步骤 1 新建的对象作为函数的this上下文;

function myNew(func) {
  if (typeof func !== 'function') {
    throw new TypeError(`expect function, but got ${typeof func}`);
  }
  return function() {
    const obj = {
      __proto__: func.prototype,
    };
    func.call(obj, ...arguments);
    return obj;
  };
}

myNew(Person)('kk', 16);

另一种写法

function myNew2(func, ...args) {
  if (typeof func !== 'function') {
    throw new TypeError(`expect function, but got ${typeof func}`);
  }

  let obj = {};
  if (func.prototype !== null) {
    obj.__proto__ = func.prototype;
  }

  // 也可以写成
  // const obj = Object.create(func.prototype); // 以func为原型创造对象

  let res = func.apply(obj, args);
  if (res !== null && (typeof res === 'function' || typeof res === 'object')) {
    return res;
  }
  return obj;
}

myNew2(Person, 'kk', 16);

实现 instanceof

通过new关键字实现原理,我们可以知道,当构造函数在执行的时候,会讲构造函数的prototype 赋值给实例对象的__proto__属性,因此可以通过判断实例对象或者其原型链中的__proto__ 是否等于构造函数的prototype

function instanceOf(left, right) {
  let leftVal = left.__proto__;
  const rightVal = right.prototype;
  while (leftVal) {
    if (leftVal === rightVal) {
      return true;
    }
    leftVal = leftVal.__proto__;
  }
  return false;
}

const kk = new Person('kk', 16);
const bb = new Student('bb', 3);

instanceOf(kk, Person); // true
instanceOf(kk, Student); // false
instanceOf(bb, Student); // true

实现 Object.create 的基本原理

function create(obj) {
  function F() {}
  F.prototype = obj;
  let func = new F();
  if (obj === null) {
    func.__proto__ = null;
  }
  return func;
}

create(null);
create({});
create({ a: 2 });

实现节流 throttle

节流,单位时间内只会触发一次,稀释函数的执行频率。高频调用,就需要用到节流。

一般使用场景:鼠标不断移动、监听滚动事件。

function throttle(fn, delay) {
  let prev = Date.now();
  return function() {
    const context = this;
    const now = Date.now();
    if (now - prev >= delay) {
      fn.apply(context, arguments);
      prev = Date.now();
    }
  };
}

function print() {
  console.log('节流。鼠标移动,1秒内只会触发一次。减少请求的频率');
}

document.onmousemove = throttle(print, 1000);

实现防抖 debounce

防抖,只有当停止操作时才会触发。

一般使用场景:用于模糊查询输入框 "减少请求次数" 这一类的场景

function debounce(fn, delay) {
  let timer = null;
  return function() {
    const context = this;
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, arguments);
    }, delay);
  };
}

function print() {
  console.log('防抖,鼠标停止操作后,等待1s后,触发函数。减少执行次数');
}

document.onmousemove = debounce(print, 1000);

🤔 throttle & debounce

throttle:一直触发,触发每隔一段时间,还是可以被调用

debounce:一直触发,就一直不能调用。只有等到一段时间,才能被调用

实现 setTimeout 模拟 setInterval

setTimeout(function() {
  // do something
  setTimeout(arguments.callee, 1000);
}, 500);

温馨提示

argument.callee 指的是该函数的函数体内当前正在执行的函数。

argument.callee,已经在 ES5 严格模式中删除了。

另一种写法 👇

function selfSetTimeout(callback, delay) {
  let timer = null;
  function fn() {
    callback && callback();
    timer && clearTimeout(timer);
    timer = setTimeout(fn, delay);
  }
  return setTimeout(fn, delay);
}

// 测试一下
const timer = selfSetTimeout(() => {
  console.log(1);
}, 100);

实现深拷贝

// 乞丐版
JSON.parse(JSON.stringify());

// 基础版
function clone(target) {
  let cloneTarget = {};
  for (const key in target) {
    cloneTarget[key] = target[key];
  }
  return cloneTarget;
}

// 基础版 -> 对象引用, 递归调用
function clone(target) {
  if (typeof target === 'object') {
    let cloneTarget = {};
    for (const key in target) {
      cloneTarget[key] = clone(target[key]);
    }
    return cloneTarget;
  }
  return target;
}

//  基础版 -> 考虑数组
function clone(target) {
  if (target !== null && typeof target === 'object') {
    let cloneTarget = Array.isArray(target) ? [] : {};
    for (const key in target) {
      cloneTarget[key] = clone(target[key]);
    }
    return cloneTarget;
  }
  return target;
}

// 循环引用,如何解决循环引用造成栈溢出的问题?
// 我们可以额外开辟一个存储空间,
// 来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,
// 先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,
// 如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题
function clone(target, map = new Map()) {
  if (target !== null && typeof target === 'object') {
    if (map.has(target)) {
      return map.get(target);
    }

    let cloneTarget = Array.isArray(target) ? [] : {};
    map.set(target, cloneTarget);

    for (const key in target) {
      cloneTarget[key] = clone(target[key], map);
    }
    return cloneTarget;
  }
  return target;
}

let target = {
  a: 1,
  b: 2,
  c: [3, 4, 5],
  d: {
    d1: 6,
  },
};

target.target = target;
clone(target);

// 2. 性能优化 -> 变成弱引用 new WeakMap()

// 2. 性能优化 -> 改掉for in, 使用 while
function forEach(array, fn) {
  let index = -1;
  const length = array.length;
  while (++index < length) {
    fn(array[index], index);
  }
  console.log(array);
  return array;
}

function clone(target, map = new WeakMap()) {
  if (typeof target === 'object') {
    if (map.get(target)) {
      return map.get(target);
    }

    let isArray = Array.isArray(target);
    let cloneTarget = isArray ? [] : {};
    map.set(target, cloneTarget);

    let keys = isArray ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
      if (keys) {
        key = value;
      }
      cloneTarget[key] = clone(target[key], map);
    });
    return cloneTarget;
  }
  return target;
}

实现 eventEmitter,拥有四个方法 on, off, once 和 trigger

class EventEmitter {
  constructor() {
    this.listeners = Object.create(null);
  }

  on(eventName, callback) {
    if (typeof callback !== 'function') {
      throw TypeError(`expect for a function, but got a ${typeof callback}`);
    }
    let eventArray = this.listeners[eventName];
    if (!eventArray) {
      eventArray = this.listeners[eventName] = [];
    }
    this.listeners[eventName].push(callback);
    return this;
  }

  once(eventName, callback) {
    if (typeof callback !== 'function') {
      throw TypeError(`expect for a function, but got a ${typeof callback}`);
    }
    let eventArray = this.listeners[eventName];
    if (!eventArray) {
      eventArray = this.listeners[eventName] = [];
    }

    let isOnce = false;
    var fn = function() {
      if (isOnce) return;
      callback();
      isOnce = true;
    };
    this.listeners[eventName].push(fn);
    return this;
  }

  off(eventName, callback) {
    if (!eventName) return (this.listeners = {});
    let eventArr = this.listeners[eventName];
    if (!eventArr) return;

    if (!callback) {
      this.listeners[eventName] && delete this.listeners[eventName];
      return;
    }
    const idx = eventArr.indexOf(callback);
    if (idx !== -1) this.listeners[eventName].splice(idx, 1);
  }

  trigger(eventName, ...args) {
    let eventArray = this.listeners[eventName];
    if (!eventArray) {
      throw TypeError(`not found function, trigger name is ${eventName}`);
    }
    if (!eventArray) return null;
    eventArray.forEach(cb => {
      typeof cb === 'function' && cb(...args);
    });
  }
}

测试一下 👇

const test = new EventEmitter();
test.once('click', () => {
  console.log(1);
});
test.once('click', () => {
  console.log(2);
});
test.once('click', () => {
  console.log(3);
});

test.on('click', () => {
  console.log(4);
});
test.on('click', () => {
  console.log(5);
});
test.on('click', () => {
  console.log(6);
});

function f7() {
  console.log(7);
}
test.on('click', f7);

test.on('login', name => {
  console.log(`${name} 已登录`);
});

test.trigger('click');
test.trigger('login', 'kk');

// 所有事件都移除
test.off();

// 移除 login
test.off('login');

// 移除 click中 f7
test.off('click', f7);

console.log(test.listeners);

也可以用 Map 实现,其实换汤不换药!读取的方式有差别以外,其他都一样。自己实现看看吧

class Observer {
  static events = new Map();

  static on(name, fn) {
    this.events.set(name, { isOnce: false, fn });
  }

  static once(name, fn) {
    this.events.set(name, { isOnce: true, fn });
  }

  static off(name) {
    this.events.delete(name);
  }

  static emit(name, data) {
    const cache = this.events.get(name);
    if (!cache) throw Error('没有找到此方法');
    if (cache.isOnce) this.events.delete(name);
    cache.fn(data);
  }
}
Last Updated:
Contributors: kk