前端笔试面试之方法封装

本篇文章将记录本人面试笔试遇到的一些编程题或自己认为可能会考的封装方法

一、实现深浅克隆

原理:

  1. 判断目标是不是object类型
  2. 判断目标是不是array类型
  3. 判断克隆是深度还是浅度
  4. 若是深度则递归调用原函数,若浅度则直接赋值
  5. 若目标是基本数据类型,则直接返回
function clone(target, deep) {
	if(typeof target === 'object') { //判断目标是不是对象
		var obj = Array.is(target) ? [] : {}; //判断目标是不是数组
		for(var prop in target) {
			if(deep) { //若是深度克隆,则递归调用
				obj[prop] = clone(target[prop], true);
			}else { //若是浅度克隆,则直接赋值
				obj[prop] = target[prop];
			}
		}
		return obj;
	}else { //若是函数或基本数据类型,则直接返回目标
		return target;
	}
}

// 另外,当我们需要深度克隆的对象层级太深,使用递归其实是非常消耗性能的
// 因此需要其他的方法来解决,如JSON、$extend
var newObj = JSON.parse(JSON.stringify(target));
var newObj = $.extend(true, {}, target);

二、实现js的圣杯模式

作用:既实现了父子继承,又实现了父子相互隔离,互不影响
原理:

  1. 创建中间层函数
  2. 将父类的原型赋给中间层的原型
  3. 让子类的原型继承中间层构造函数的实例对象
  4. 改变子类构造函数的指向
  5. 定义子类最终继承自哪里
//1.
var inherit = (function() {
	var F = function(){};
	return function(Father, Son) {
		F.prototype = Father.prototype;
		Son.prototype = new F();
		//Son的构造函数本指向F,这里需要将它指回Son
		Son.prototype.constructor = Son; 
		//Son最终继承自Father
		Son.prototype.uber = Father.prototype;
	}
})();

//2.
var inherit = function(Father, Son) {
	Son.prototype = Object.create(Father.prototype);
	Son.prototype.constructor = Son;
	Son.prototype.uber = Father.prototype;
}

三、实现关键字new的功能

作用:创建构造函数的对象实例;
原理:

  1. 创建了空对象,并作为返回的对象实例
  2. 将这个对象的原型指向构造函数的prototype属性
  3. 将构造函数的this指向这个空对象
  4. 执行构造函数
function _new() {
	// 移除args里的第一个参数并返回,即构造函数
    var Func = [].shift.call(arguments);
    // 创建一个空对象,并继承func的prototype
    var obj = Object.create(Func.prototype);
    // 将func的this指向空对象并执行
    var result = Func.apply(obj, arguments);
    // 若构造函数返回了对象,则直接返回,否则返回创建的对象obj
    return result instanceof Object ? result : obj;
}
function Person (name, age) {
	this.name = name;
    this.age = age;
    return this;
}
var person = _new(Person, 'aa', 10);
console.log(person); //{name: 'aa', age: 10}

四、实现call、apply、bind功能

作用:改变this指向
原理:

  1. 判断this指向的目标是否为对象
  2. 将目标函数存到目标对象的_fn属性中
  3. 执行目标对象的_fn并删除_fn属性
  4. 返回结果
//实现call
//es6写法
Function.prototype.myCall = function(context, ...args) {
	if (!context instanceof Object) return;
	context._fn = this;
	var result = context._fn(...args);
    delete context._fn;
    return result;
}
//es5写法
//尽量不要使用eval,因为安全性不高,容易被注入恶意代码
Function.prototype.myCall = function(context) {
	if(!context instanceof Object) return;
	var args = [];
	for(var i = 1; i < arguments.length; i++) {
		args.push(arguments[i]);
	}
	context._fn = this;
	var result = eval('context._fn(' + args + ')');
	delete context._fn;
	return result;
}
//实现apply
Function.prototype.myApply = function (context, args) {
	if (!context instanceof Object) return;
	context._fn = this;
    var result = context._fn(...args);
    delete context._fn;
	return result;
}
//实现bind
Function.prototype.myBind = function (context, ...args) {
	if (!context instanceof Object) return;
	context._fn = this;
	var result = function (...curArgs) {
		var temp = context._fn(...args, ...curArgs);
        delete context._fn;
        return temp;
    };
	return result;
}

五、实现instanceof功能

作用:判断数据类型
原理:基于原型链进行查询

function myInstanceOf(left, right) {
	if(typeof left !== 'object') return;
	var proto = Object.getPrototypeOf(left); //获取left的原型
	while(true) { //无限循环查找
		if(proto == null) return false;//查找到尽头时,原型为null
		if(proto === right.protoype) return true;//查找到相同原型
		proto = Object.getPrototypeOf(proto);
	}
}

六、常用数组方法的封装

1. forEach

作用:遍历数组
第一个参数(必填):函数,有三个参数分别为数组元素,数组索引,数组本身
第二个参数(选填):对象,callback函数的this指向,若没有传或为null、undefined,则this指向window

Array.prototype.myForEach = function(callback, thisArgs) {
	var _arr = this, 		//数组本身
		len = _arr.length, 	//数组的长度
		thisArgs = thisArgs || window;  //forEach第二个参数
	for(var i = 0; i < len; i++) {
		if(!i in _arr) continue;//已删除或未初始化的项(empty)将被跳过
		callback.apply(thisArgs, [_arr[i], i, _arr]);
    }
}

2. filter

作用:过滤数组
第一个参数(必填):函数,有三个参数分别为数组元素,数组索引,数组本身
第二个参数(选填):对象,callback函数的this指向,若没有传或为null、undefined,则this指向window
该方法返回一个新数组

Array.prototype.myFilter = function(callback, thisArgs) {
	var _arr = this, 
		len = _arr.length, 
		thisArgs = thisArgs || window, 
		newArr = [];
	for(var i = 0; i < len; i++) {
		if(!i in _arr) continue;//已删除或未初始化的项(empty)将被跳过
		//返回false,将元素过滤
        //返回true,将元素加入新数组
		if(callback.apply(thisArgs, [_arr[i], i, _arr])) {
			newArr.push(_arr[i]);
		}
	}
	return newArr;
}

3. map

作用:映射
第一个参数(必填):函数,有三个参数分别为数组元素,数组索引,数组本身
第二个参数(选填):对象,callback函数的this指向,若没有传或为null、undefined,则this指向window
该方法返回一个新数组

Array.prototype.myMap = function(callback, thisArgs) {
	var _arr = this, 
		len = _arr.length, 
		thisArgs = thisArgs || window, 
		newArr = [];
	for(var i = 0; i < len; i++) {
		if(!i in _arr) continue;//已删除或未初始化的项(empty)将被跳过
		//因为map方法具有映射关系,所以不能用push
		newArr[i] = callback.apply(thisArgs, [_arr[i], i, _arr]);
	}
	return newArr;
}

4. every

作用:只要有一个元素不符合条件就停止遍历,返回false,若全部符合就返回true
第一个参数(必填):函数,有三个参数分别为数组元素,数组索引,数组本身
第二个参数(选填):对象,callback函数的this指向,若没有传或为null、undefined,则this指向window
该方法返回一个新数组

Array.prototype.myEvery = function(callback, thisArgs) {
	var _arr = this, 
		len = _arr.length, 
		thisArgs = thisArgs || window;
	for(var i = 0; i < len; i++) {
		if(!i in _arr) continue;//已删除或未初始化的项(empty)将被跳过
		if(!callback.apply(thisArgs, [_arr[i], i, _arr])) {
			return false;
		}
	}
	return true;
}

5. some

作用:只要有一个元素符合条件就停止遍历,返回true,若全部都不符合就返回false
第一个参数(必填):函数,有三个参数分别为数组元素,数组索引,数组本身
第二个参数(选填):对象,callback函数的this指向,若没有传或为null、undefined,则this指向window
该方法返回一个新数组

Array.prototype.mySome = function(callback, thisArgs) {
	var _arr = this, 
		len = _arr.length, 
		thisArgs = thisArgs || window;
    for(var i = 0; i < len; i++) {
    	if(!i in _arr) continue;//已删除或未初始化的项(empty)将被跳过
    	if(callback.apply(thisArgs, [_arr[i], i, _arr])) {
        	return true;
		}
	}
       return false;
}

6. reduce

作用:对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值
第一个参数(必填):函数,有四个参数分别为累加器,数组元素,数组索引,数组本身
第二个参数(选填):作为第一次调用func函数时的第一个参数值,如果没有传该值,则使用数组的第一个值作为初始值,并且func函数的index从1开始
第三个参数(选填):对象,callback函数的this指向,若没有传或为null、undefined,则this指向window
该方法返回一个新数组

Array.prototype.myReduce = function (callback, initialVal, thisArgs) {
	var _arr = this,
		len = _arr.length,
        thisArgs = thisArgs || window,
        initialVal = initialVal || _arr[0];
	for (var i = 0; i < len; i++) {
		//已删除或未初始化的项(empty)将被跳过
		if (!i in _arr || (i == 0 && initialVal == _arr[i])) continue;
		initialVal = callback.apply(thisArgs, [initialVal, _arr[i], i, _arr]);
	}
	return initialVal;
};

7. flat

作用:将数组扁平化,移除数组中的空项
唯一参数(选填):指定要提取嵌套数组的结构深度,默认值为 1,若传入Infinity,则可以展开任意深度的嵌套数组
该方法返回一个新数组

// 以下两种方法均不完善,无法移除数组空项,只适用将数组完全展开
// 1. 使用正则表达式
function flatten(array) {
	return JSON.parse('[' + JSON.stringfy(array).replace(/\[|\]/g, '') + ']')
}

// 2. 使用generator(生成器)
function* flatten(array) {
	for(var iterator of array) {
		if(Array.isArray(iterator)) {
			yield* flatten(iterator);
		}else {
			yield iterator;
		}
	}
}

七、封装函数将对象数组去重

var arr = [
	{a: 1, b: 2, c: 3}, 
	{a: 3, b: 1, c: 2}, 
	{c: 3, a: 1, b: 2}, 
	{a: 3, b: 1, c: 2}
]; // 去重后应是[{a:1, b:2, c:3},{a:3, b:1, c:2}]

function unique(arr) {
	// 辅助函数,用来判断两个对象的key/value是否相同
	function isSame(obj1, obj2) {
		for (const prop in obj1) {//forin遍历拿出obj1的属性名
			//若obj2没有obj1的属性,则返回false
			//若obj1的属性值与obj2的属性值不同,则返回false
			if (!(prop in obj2) || obj1[prop] != obj2[prop]) {
				return false;
			}
		}
		return true;
	}
	
	for (var i = 0; i < arr.length - 1; i++) {
		for (var j = i + 1; j < arr.length; j++) {
  			if (isSame(arr[i], arr[j])) { //若相同
				arr.splice(j, 1);//移除当前下标的元素,此时数组的length-=1
                j--; //需要把下标往前移一位,否则最后一位无法拿出来比较
            }
		}
	}
	return arr;
}

八、手写Promise API

const MyPromise = (() => {
	const PENDING = 'pending',
		RESOLVED = 'resolved',
		REJECTED = 'rejected',
		PromiseStatus = Symbol('PromiseStatus'), // 当前状态
		PromiseValue = Symbol('PromsieValue'), // 状态数据
		changeStatus = Symbol('changeStatus'), // 改变当前状态
		thenables = Symbol('thenables'), // resolved作业队列
		catchables = Symbol('catchables'), // rejected作业队列
		settleHandler = Symbol('settleHandler'), //处理thenable/catchable函数
		linkPromise = Symbol('linkPromise'); // 链式调用
	
	return class MyPromise {
		// 构造函数
		constructor(executor) {
			// 初始化
			this[PromiseStatus] = PENDING;
			this[PromiseValue] = undefined;
			this[thenables] = [];
			this[catchables] = [];
			const resolve = data => {
				this[changeStatus](RESOLVED, data, this[thenables]);
			}
			const reject = reason => {
				this[changeStatus](REJECTED, reason, this[catchables]);
			}
			// 使用try/catch捕获promise执行函数的错误
			try{
				executor(resolve, reject);
			}catch(err) {
				reject(err);
			}
		}
		// 改变当前状态
		[changeStatus](newStatus, newValue, queue) {
			if(this[PromiseStatus] !== PENDING) return;
			this[PromsieStatus] = newStatus;
			this[PromiseValue] = newValue;
			queue.forEach(handler => {
				handler(this[PromiseValue]);
			})
		}
		then(thenable, catchable) {
			return this[linkPromise](thenable, catchable);
		}
		catch(catchable) {
			return this[linkPromise](null, catchable);
		}
		//处理thenable/catchable函数
		[settleHanlder](handler, status, queue) {
			if(typeof handler !== 'function') return;
			if(this[PromiseStatus] == status) {
				setTimeout(() => { handler(this[PromiseValue]) }); // 模拟异步操作
			}else {
				queue.push(handler);
			}
		}
		// 链式调用
		[linkPromise](thenale, catchable) {
			function exec(data, handler, resolve, reject) {
				try{
					const result = handler(data);
					result instanceof MyPromise ? 
						result.then(res => { resolve(res) }, err => { reject(err) }) :
						resolve(result);
				}catch(reason) {
					reject(reason);
				}
			}
			thenable = typeof thenable === 'function' ? thenable : val => val;
			catchable = typeof catchable === 'function' ? catchable : err => { throw err };
			return new MyPromise((resolve, reject) => {
				this[settleHanler](data => {
					exec(data, thenable, resolve, reject);
				}, RESOLVED, this[thenables]);
				this[settleHander](reason => {
					exec(reason, catchable, resolve, reject);
				}, REJECTED, this[catchables]);
			})
		}
		static resolve (data) {
			// 返回一个promise对象,状态为resolved,状态数据为data
			return data instanceof MyPromise ? data : new MyPromise(resolve => {
				resolve(data);
			})
		}
		static reject(reason) {
			// 返回一个promise对象,状态为rejected,状态数据为reason
			return new Promise((resolve, reject) => {
				reject(reason);
			})
		}
		static all(promiseArr) {
			// 将多个promsie对象包装成一个promimse对象
			// resolved的状态数据为一个数组
			// rejected的状态数据为最先抛出异常的错误信息
			let times = promiseArr.length;
			let newArr = [];
			return new MyPromise((resolve, reject) => {
				promiseArr.forEach(promise => {
					promise.then(data => {
						newArr.push(result);
						--times;
						if(times === 0) {
							resolve(newArr);
						}
					}, reason => {
						reject(reason);
					})
				})
			})
		}
		static race(promiseArr) {
			// 将多个promsie对象包装成一个promimse对象
			// 哪个promise对象获得的结果最快,就返回哪个结果,不论resolve或reject
			return new MyPromise((resolve, reject) => {
				promiseArr.forEach(promise => {
					promise.then(res => { resolve(res) }, err => { reject(err) });
				})
			})
		}
	}
})();

九、手写发布订阅模式

class EventEmitter {
	constructor() {
		this.events = {}
	}
	// 订阅
	on(type, handler) {
		if(!this.events[type]) {
			this.events[type] = [handler];
		}else {
			this.events[type].push(handler);	
		}
	}
	// 取消订阅
	off(type, handler) {
		if(!this.events[type]) return
		this.events[type] = this.events[type].filter(item => item !== handler);
	}
	// 触发事件
	emit(type, ...args) {
		this.events[type] && this.events[type].forEach(item => item.apply(this, args));
	}
}

十、防抖

作用:该函数会从上一次被调用后,延迟 delay 毫秒后调用 handler 方法
原理:闭包

function debounce(handler, delay) {
    var timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            handler.apply(this, args);
        }, delay);
    }
}

十一、节流

作用:在 wait 毫秒内最多执行 handler 一次的函数
原理:闭包

function throttle(handler, wait) {
    var lastTime = 0;
    return function(...args) {
        var newTime = Date.now();
        if(newTime - lastTime > wait) {
            handler.apply(this, args);
            lastTime = newTime;
        }
    }
}

十二、柯里化

作用:固定 func 函数的参数,返回一个新函数接收 func 函数的剩余参数,如果没有剩余参数,则调用
原理:闭包

function curry(func, ...args) {
    // 得到func后面的参数
    var _self = this;
    return function(...curArgs) {
        var totalArgs = args.concat(curArgs);
        if(totalArgs.length >= func.length) {
            return func.apply(null, totalArgs);
        }else {
        	// 1.
            totalArgs = [func, ...totalArgs];
            // 2.
            //totalArgs.unshift(func);
            
            return curry.apply(_self, totalArgs);
        }
    }
}
  • 3
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
前端程序员面试笔试宝典PDF》是一本帮助前端程序员备战面试笔试的电子书籍。该书内容包含了面试题目、常见笔试题以及解析和答案。这本宝典对于想要进入前端开发行业或者进阶的人来说,是一本非常实用的参考资料。 首先,该宝典提供了大量的面试题目,从基础的HTML、CSS和JavaScript知识到高级的前端框架和工具,涵盖了前端开发技术的各个方面。这些面试题目旨在考察面试者对于前端技术的理解和应用能力,帮助面试者系统性地学习和掌握前端相关知识。 其次,宝典还包含了常见的笔试题目及其解析和答案。笔试题目的设计更注重考察面试者的编程能力和解决题的能力。通过详细的解析和答案,读者可了解题目的解题思路和方法,提高自己的解题效率和技巧。 此外,宝典还提供了面试过程中需要注意的事项和策略,如面试前的准备、面试时的表现技巧,以及常见的面试题和回答技巧等。这些内容帮助读者在面试过程中更加自信和从容,提高自己的竞争力。 总之,《前端程序员面试笔试宝典PDF》是一本帮助前端程序员备战面试笔试的实用电子书籍。它提供了丰富的面试题目和笔试题目,配备详细的解析和答案,在帮助读者学习和巩固前端知识的同时,提高了面试笔试的准备水平。无论是入门初学者还是有一定经验的前端开发者,都可以从该宝典中获得帮助,提升自己的职业素养。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值