前端常见JS手写代码

742 阅读3分钟

1.手写new关键字

注意:new关键字创建实例对象的时候,返回类型必须为Object,如果返回值不为对象(null一样不行),返回无效。

//原生:
function Person(name) {
	this.name = name;
	return 1;
}
const p = new Person("张三");
console.log(p); // Person { name: '张三' }

//手写代码:
function createObj(...args) {
	const obj = {};
	const Constructor = args.shift();
	obj.__proto__ = Constructor.prototype;
	const ret = Constructor.apply(obj, args);
	return typeof ret === "object" ? ret : obj;
}

2.手写instanceof

function myInstanceof(instance, constructor) {
	let proto = instance.__proto__
	while (proto) { //最终为null结束
		if (proto === constructor.prototype) {
			return true
		} else {
			proto = proto.__proto__
		}
	}
	return false
}

3.数组扁平化

其他方法参考

function flat(arr) { 
        return result = arr.flat(Infinity); 
}

4.节流

简单定义:单位时间内只执行一次函数。
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

// 时间戳版
function throttle(func, delay) {
	let last = 0;
	return function () {
		const now = Date.now();
		if (now >= last + delay) {
			func.apply(this, arguments);
			last = now;
		} else {
			console.log("不执行");
		}
	};
}
// 定时器版
function throttle2(func, delay) {
	let timer;
	return function () {
		if (!timer) {
			func.apply(this, arguments);
			timer = setTimeout(() => {
				timer = null; //记得置空
			}, delay);
		} else {
			console.log("不执行");
		}
	};
}

5.防抖

简单定义:单位时间内执行最后一个函数。
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
防抖节流不同的地方在于,函数在一段时间内的多次调用,仅使得最后一次调用有效。

function debounce(func, delay) {
	let timer;
	return function () {
		clearTimeout(timer);
		timer = setTimeout(() => {
			func.apply(this, arguments);
		}, delay);
	};
}

6.数组随机打乱

//随机性不够大,主要是sort原生函数的原理有关
function shuffle(array) {
    array.sort(() => { Math.random() - 0.5 }); // sort会改变原数组
}
//可行随机函数
function shuffle(array) {
	let m = array.length,
		t,
		i;
	while (m) {
		i = Math.floor(Math.random() * m--);
		t = array[m];
		array[m] = array[i];
		array[i] = t;
	}
	return array;
}

7.数组去重

//利用遍历+Map
function uniqueArr(arr) {
	const map = {};
	const result = [];
	arr.forEach(el => map[el] || ((map[el] = true), result.push(el)));
	return result;
}
//利用Set
function uniqueArr(array) {
	return [...new Set(array)];
}

8.函数柯里化

定义:柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

function add(...args) {
	const _args = Array.prototype.slice.call(args);
	const _adder = function (...args2) {
		_args.push(...args2);
		return _adder;
	};
	_adder.toString = function () {
		return _args.reduce(function (a, b) {
			return a + b;
		}, 0);
	};
	return _adder;
}
//test code
add(1, 2, 3); //6
add(1)(2)(3); //6

//参数复用
function uri_curry(protocol) {
	return function (hostname, pathname) {
		return `${protocol}${hostname}${pathname}`;
	};
}
const uri_base = uri_curry("https://");
const uri_baidu = uri_base("www.baidu.com", "/search");
const uri_ali = uri_base("www.ali.com", "/taobao");
const uri_tencent = uri_base("www.tencent.com", "/qq");

//test code
console.log(uri_baidu); //https://www.baidu.com/search
console.log(uri_ali); //https://www.ali.com/taobao
console.log(uri_tencent); //https://www.tencent.com/qq

函数柯里化的三个特点

  1. 参数复用
  2. 提前返回
  3. 延迟执行 延迟执行实际上就是当我们调用这个方法时,不会立即执行,或者说在参数符合规定的时候才会执行我们真正想执行的内容。
    如上面参数复用中的add(1)(2)(3), 传入1 2 3的时候,函数并没有执行加法运算,只是将这个值推入队列中,仅当不传入参数时,才真正执行。

8.迭代器Iterator

其实一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是 “可遍历的”(iterable)
ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是"可遍历的"(iterable)。

function createIterator(items) {
	let index = 0;
	return {
		next: function () {
			let done = index >= items.length;
			let value = !done ? items[index++] : undefined;
			return {
				done,
				value
			};
		}
	};
}
//test code
const arr = ["a", "b"];
for (let val of arr) {
	console.log(val); // 打印 a b
}
arr[Symbol.iterator] = () => createIterator([1, 2, 3]);
for (let val of arr) {
	console.log(val); //打印 1 2 3
}

9.实现LRU缓存

LRU全称Least Recently Used(最近最少使用),用来淘汰不常用数据,保留热点数据。

class LRU {
	constructor(size) {
		this.size = size;
		this.map = new Map();
	}
	get(key) {
		const val = this.map.get(key);
		if (key === undefined) {
			return -1;
		} else {
			this.map.delete(key);
			this.map.set(key, val);
		}
	}
	put(key, value) {
		const val = this.map.get(key);
		if (val === undefined) {
			this.map.size === this.size && this.map.delete(this.map.keys().next().value);
			this.map.set(key, value);
		} else {
			this.map.delete(key);
			this.map.set(key, value);
		}
	}
}

9.call apply bind

Function.prototype.myCall = function (context, ...args) {
	context = context || window;
	context.fn = this;
	const result = context.fn(...args);
	delete context.fn;
	return result;
};
Function.prototype.myApply = function (context, args) {
	context = context || window;
	context.fn = this;
	const result = context.fn(...args);
	delete context.fn;
	return result;
};
Function.prototype.myBind = function (context, ...args) {
	context = context || window;
	return () => {
		this.apply(context, args);
	};
};

const obj = { name: "jr" };
const fn = function (a, b) {
	console.log(this.name, a, b);
};

// test code
fn.myCall(obj, 1, 2);
fn.call(obj, 1, 2);
fn.myApply(obj, [1, 2]);
fn.apply(obj, [1, 2]);
fn.myBind(obj, 1, 2)();
fn.bind(obj, 1, 2)();