天天看点

面试中的手写系列

手写 bind 函数

知识点: 
	this 指向(作用域), this的指向是在调用时确定的, 由调用当前方法的对象决定;
	不同于自由变量,自由变量的作用域决定于 其本身定义的位置。
分析:bind 函数的特点
1. 改变 this 指向
2. 参数以数组形式表示
3. 返回一个函数
           
Function.prototype.bind1 = function (self, ...rest) {
		// 函数的this指向是在调用时确定的,因此,此处的this实际就是调用bind1函数的函数对应的this
		const _self = this; 
		return function() {
			_self.apply(self, [...rest]);
		}
	}
           

手写深拷贝

知识点:
引用类型和值类型的在内存中的存储方式
引用类型:在栈内存中存储变量名及其值对应的指针,在堆内存中存储其值
深拷贝的目的是新建一个堆内存用来存储变量,且不改变原来的堆内存和指针;
考虑到深拷贝针对非空对象,书写时要注意一下几点
1. 引用类型和值类型的判断
2. null 的 判断
3. 数组 和 对象的区分
4. 排除 原型链 上的属性
5. 递归
           
function deepCopy(obj) {
		if (typeof obj !== 'object' || obj == null) {
			return obj;
		}
		let result;
		if (obj instanceof Array) {
			result = [];
		} else {
			result = {};
		}
		for (key in obj) {
			if (obj.hasOwnProperty(key)) {
				result[key] = deepCopy(obj[key])
			}
		}
		return result;
	}
           

手写防抖函数

1. 防抖是指,在短时间(自定义)多次触发该函数时,只执行最后一次。
	可以与公交车和上车乘客作比较:假如公交车到站后,每上来一个乘客,公交车司机就踩一下油门,踩一脚刹车;
	假如有多个乘客,就会出现,公交车走,停,走,停,哎走,哎停……像是在抖动。
	为了防止这种抖动的情况,公交车司机要等乘客上完之后再踩油门。
	如果乘客没有上完,那就有停止之前准备踩油门的动作。
	关键点:
	1.  准备踩油门 -- 定时器
	2.  后面还有乘客要上车,停止准备踩油门的动作 -- 清除定时器
	3.  乘客全部上车后,踩油门 -- 执行预定的函数
           
function  debounce(fn, delay) {
		let timer = null;
		return function() {
			if (timer) { 
				clearTimeout(timer);
			}
			timer = setTimeout(() => {
				fn.apply(this, arguments);
				timer = null;
			}, delay);
		}
	}
           

手写节流函数

1.  节流是指,短时间只执行第一次。
2.  关键点: 
	a. 定时器,定时器的存在代表将会执行一次目标函数
	b. 如果定时器存在,后续的触发不应做出任何动作
	c. 指定时间段
	d. 在执行过目标函数后,清除定时器
           
function throttle(fn, delay) {
		let timer;
		if (!timer) {
			setTimeout(() => {
				fn.apply(this, arguments);
				timer = null;
			}, delay);
		}
	}
           

手写 简易的ajax请求

1. 初始化xhr实例
2. 激活实例(open)
3. 监听readystate (onreadystatechange)
4. 检查 redaystate 和 status 的值
5. 发送该请求;
           
const xhr = new XMLHttpRequest();
	xhr.open('GET', '/api/path', true); // 方法,路径,是否异步
	xhr.onreadystatechange = function() {
		if (xhr.readyState === 4) {
			if (xhr.status === 200) {
				alert(xhr.responseText);
			} else {
				alert('error');
			}
		}
	}
	xhr.send();
	// 以下供post 请求使用
	// const postData = {
	//	 a: 1,
	//	 b: 2
	//   }
	// xhr.send(JSON.stringify(postData));
	
           

手写 简易ajax请求(promise)

function ajax(path, method = 'GET', data = null) {
		const p = new Promise((resolve, reject) => {
			const xhr = new XMLHttpRequest();
			xhr.open(method,path, true);
			xhr.onreadystatechange = function() {
				if (xhr.readyState === 4 && xhr.status === 200) {
					resolve(xhr.responseText);
				} else {
					reject(xhr.responseText);
				}
			}
			xhr.send(data);
		});
		return p;
	}
	// 使用
	try {
		const res = async ajax('/api/path', 'POST', {id: 1, exp: 2})
	} catch(e) {
		console.log(e)
	}
	
           

手写 通用的事件监听函数

1. 事件绑定的dom节点
2. 事件类型
3. 事件触发后要执行的方法
4. 点击特定元素才触发事件(可选, 一切css支持的选择器,但是要注意 matchs 方法本身的兼容性问题)
           
function bindEvent(elem, type, fn, selector = null) {
		elem.addEventListener(type, event => {
			const target = event.target;
			if (selector) {
				if (target.matchs(selector)) {
					fn.call(target, event);
				}
			} else {
				fn.call(target, event);
			}
		})
		
	}
           

手写 深度比较

const obj1 = {a: 100, b: {x: 100, y:200}}
	const obj2 = {a: 100, b: {x: 100, y:200}}
	console.log(obj1 === obj2) // false
	// 如果 对象或数组 的内容完全相同,如何判断他们相等
	
	// 1. 是否是对象(不包括函数)
	function isObject() {
		return typeof obj === 'object' && obj !== null
	}
	// 2. 比较
	function isEqual(obj1, obj2) {
		// 考虑值类型
		if (!isObject(obj1) || !isObject(obj2)) {
			return obj1 === obj2;
		}
		// 如果 obj1 和 obj2 本身就是同一个对象
		if (obj1 === obj2) {
			return true;
		}
		// 两个都是对象,且不相等
		// 1. 先取出两个对象的 keys, 比较keys的个数
		const obj1Keys = Object.keys(obj1);
		const obj2Keys = Object.keys(obj2);
		if (obj1Keys.length !== obj2Keys.length) {
			return false;
		}
		// 2. 以obj1为基准,和 obj2 依次 递归比较
		for(let key in obj1) {
			// 比较当前 key 的 val --递归
			const res = isEqual(obj1[key], obj2[key]);
			if (!res) {
				return false;
			}
		}
		// 3. 全相等
		return true;
	}
           

继续阅读