前端手写代码系列

273 阅读2分钟

1、手写一个EventBus

// 手写一个事件触发器
class EventEmeitter {
	constructor() {
		this._events = this._events || {};
		this._maxListeners = this._maxListeners || 10;
	}
	// 监听名为event的事件  
	on(event, fn) {
		if (!this._events[event]) {
			(this._events[event] = []).push(fn);
		} else {
			this._events[event].push(fn);
		}
	}
	// 取消监听某个事件的事件处理回调  
	off(event, fn) {
		let fns = this._events[event];
		if (!fns) return false;
		if (!fn) {
			//如果没有传 fn 的话,就会将event值对应缓存列表中的 fn 都清空     
			fns && (fns.length = 0);
		} else {
			for (let i = 0, fnLen = fns.length; i < fnLen; i++) {
				let curFn = fns[i];
				if (curFn === fn) {
					fns.splice(i, 1);
					break;
				}
			}
		}
		return this;
	}
	emit(event) {
		let fns = this._events[event];
		if (!fns || fns.length === 0) {
			return false
		}
		let args = [].slice.call(arguments, 1) fns.forEach(fn => {
			fn.apply(this, args)
		});
		return this;
	} // 只监听一次  
	once(event, fn) {
		function onceWrapper(...args) {
			fn.apply(this, args);
			this.off(event, onceWrapper)
		}
		this.on(event, onceWrapper) return this;
	}
}

EventBus可以参照以下node.js源码

// Copyright Joyent, Inc. and other Node contributors.//// Permission is hereby granted, free of charge, to any person obtaining a// copy of this software and associated documentation files (the// "Software"), to deal in the Software without restriction, including// without limitation the rights to use, copy, modify, merge, publish,// distribute, sublicense, and/or sell copies of the Software, and to permit// persons to whom the Software is furnished to do so, subject to the// following conditions://// The above copyright notice and this permission notice shall be included// in all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE// USE OR OTHER DEALINGS IN THE SOFTWARE.function EventEmitter() {  this._events = this._events || {};  this._maxListeners = this._maxListeners || undefined;}module.exports = EventEmitter;// Backwards-compat with node 0.10.xEventEmitter.EventEmitter = EventEmitter;EventEmitter.prototype._events = undefined;EventEmitter.prototype._maxListeners = undefined;// By default EventEmitters will print a warning if more than 10 listeners are// added to it. This is a useful default which helps finding memory leaks.EventEmitter.defaultMaxListeners = 10;// Obviously not all Emitters should be limited to 10. This function allows// that to be increased. Set to zero for unlimited.EventEmitter.prototype.setMaxListeners = function(n) {  if (!isNumber(n) || n < 0 || isNaN(n))    throw TypeError('n must be a positive number');  this._maxListeners = n;  return this;};EventEmitter.prototype.emit = function(type) {  var er, handler, len, args, i, listeners;  if (!this._events)    this._events = {};  // If there is no 'error' event listener then throw.  if (type === 'error') {    if (!this._events.error ||        (isObject(this._events.error) && !this._events.error.length)) {      er = arguments[1];      if (er instanceof Error) {        throw er; // Unhandled 'error' event      } else {        // At least give some kind of context to the user        var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');        err.context = er;        throw err;      }    }  }  handler = this._events[type];  if (isUndefined(handler))    return false;  if (isFunction(handler)) {    switch (arguments.length) {      // fast cases      case 1:        handler.call(this);        break;      case 2:        handler.call(this, arguments[1]);        break;      case 3:        handler.call(this, arguments[1], arguments[2]);        break;      // slower      default:        args = Array.prototype.slice.call(arguments, 1);        handler.apply(this, args);    }  } else if (isObject(handler)) {    args = Array.prototype.slice.call(arguments, 1);    listeners = handler.slice();    len = listeners.length;    for (i = 0; i < len; i++)      listeners[i].apply(this, args);  }  return true;};EventEmitter.prototype.addListener = function(type, listener) {  var m;  if (!isFunction(listener))    throw TypeError('listener must be a function');  if (!this._events)    this._events = {};  // To avoid recursion in the case that type === "newListener"! Before  // adding it to the listeners, first emit "newListener".  if (this._events.newListener)    this.emit('newListener', type,              isFunction(listener.listener) ?              listener.listener : listener);  if (!this._events[type])    // Optimize the case of one listener. Don't need the extra array object.    this._events[type] = listener;  else if (isObject(this._events[type]))    // If we've already got an array, just append.    this._events[type].push(listener);  else    // Adding the second element, need to change to array.    this._events[type] = [this._events[type], listener];  // Check for listener leak  if (isObject(this._events[type]) && !this._events[type].warned) {    if (!isUndefined(this._maxListeners)) {      m = this._maxListeners;    } else {      m = EventEmitter.defaultMaxListeners;    }    if (m && m > 0 && this._events[type].length > m) {      this._events[type].warned = true;      console.error('(node) warning: possible EventEmitter memory ' +                    'leak detected. %d listeners added. ' +                    'Use emitter.setMaxListeners() to increase limit.',                    this._events[type].length);      if (typeof console.trace === 'function') {        // not supported in IE 10        console.trace();      }    }  }  return this;};EventEmitter.prototype.on = EventEmitter.prototype.addListener;EventEmitter.prototype.once = function(type, listener) {  if (!isFunction(listener))    throw TypeError('listener must be a function');  var fired = false;  function g() {    this.removeListener(type, g);    if (!fired) {      fired = true;      listener.apply(this, arguments);    }  }  g.listener = listener;  this.on(type, g);  return this;};// emits a 'removeListener' event iff the listener was removedEventEmitter.prototype.removeListener = function(type, listener) {  var list, position, length, i;  if (!isFunction(listener))    throw TypeError('listener must be a function');  if (!this._events || !this._events[type])    return this;  list = this._events[type];  length = list.length;  position = -1;  if (list === listener ||      (isFunction(list.listener) && list.listener === listener)) {    delete this._events[type];    if (this._events.removeListener)      this.emit('removeListener', type, listener);  } else if (isObject(list)) {    for (i = length; i-- > 0;) {      if (list[i] === listener ||          (list[i].listener && list[i].listener === listener)) {        position = i;        break;      }    }    if (position < 0)      return this;    if (list.length === 1) {      list.length = 0;      delete this._events[type];    } else {      list.splice(position, 1);    }    if (this._events.removeListener)      this.emit('removeListener', type, listener);  }  return this;};EventEmitter.prototype.removeAllListeners = function(type) {  var key, listeners;  if (!this._events)    return this;  // not listening for removeListener, no need to emit  if (!this._events.removeListener) {    if (arguments.length === 0)      this._events = {};    else if (this._events[type])      delete this._events[type];    return this;  }  // emit removeListener for all listeners on all events  if (arguments.length === 0) {    for (key in this._events) {      if (key === 'removeListener') continue;      this.removeAllListeners(key);    }    this.removeAllListeners('removeListener');    this._events = {};    return this;  }  listeners = this._events[type];  if (isFunction(listeners)) {    this.removeListener(type, listeners);  } else if (listeners) {    // LIFO order    while (listeners.length)      this.removeListener(type, listeners[listeners.length - 1]);  }  delete this._events[type];  return this;};EventEmitter.prototype.listeners = function(type) {  var ret;  if (!this._events || !this._events[type])    ret = [];  else if (isFunction(this._events[type]))    ret = [this._events[type]];  else    ret = this._events[type].slice();  return ret;};EventEmitter.prototype.listenerCount = function(type) {  if (this._events) {    var evlistener = this._events[type];    if (isFunction(evlistener))      return 1;    else if (evlistener)      return evlistener.length;  }  return 0;};EventEmitter.listenerCount = function(emitter, type) {  return emitter.listenerCount(type);};function isFunction(arg) {  return typeof arg === 'function';}function isNumber(arg) {  return typeof arg === 'number';}function isObject(arg) {  return typeof arg === 'object' && arg !== null;}function isUndefined(arg) {  return arg === void 0;}

2、手写new

function create(){ // 创建一个控空象 let obj = new Object(); // 获得构造函数, arguments里面的第一个参数 let Con = Array.prototype.shift.call(arguments); // 链接到原型,obj 可以访问到构造函数原型中的属性 obj.prototype = Con.prototype; let result = Con.apply(obj, arguments); // 优先返回构造函数返回的对象 return result instanceof Object ? result : obj; }

3、手写JS柯里化函数

// ES5版本
function curry(fn, args) {
	var length = fn.length;
	var args = args || [];
	return function() {
		newArgs = args.concat(Array.prototype.slice.call(arguments));
		if (newArgs.length < length) {
			return curry.call(this, fn, newArgs);
		} else {
			return fn.apply(this, newArgs);
		}
	}
}


// ES6 版本
const curry = (fn, ...arr) => (...args) => (
	arg => arg.length === fn.length ?
	fn(...arg) : curry(fn, ...arg)
)([...arr, ...args])

4、手写instanceof

function myInstanceof(x, y) {
	while (x.__proto__ != null) {
		if (x.__proto__ === y.prototype) {
			return true
		}
		x.__proto__ = x.__proto__.__proto__
	}
	if (x.__proto__ == null) {
		return false
	}
}

5、模拟实现Object.create()

function create(proto) {
	function F() {}
	F.prototype = proto;
	return new F();
}

6、实现一个简单的深克隆-支持数组、对象

function deepCopy(obj) {
	if (typeof obj === 'object') {
		let result = obj instanceof Array ? [] : {};
		for (let i in obj) {
			result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i]
		}
		return result;
	} else {
		return obj;
	}
}

7、求多个数组的交集

const intersection = (...args) => args.reduce((result, cur, index) => {
  return index === 0? cur : cur.filter(item => result.includes(item));
},[])