本篇文章将记录本人面试笔试遇到的一些编程题或自己认为可能会考的封装方法
一、实现深浅克隆
原理:
- 判断目标是不是object类型
- 判断目标是不是array类型
- 判断克隆是深度还是浅度
- 若是深度则递归调用原函数,若浅度则直接赋值
- 若目标是基本数据类型,则直接返回
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.
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的功能
作用:创建构造函数的对象实例;
原理:
- 创建了空对象,并作为返回的对象实例
- 将这个对象的原型指向构造函数的prototype属性
- 将构造函数的this指向这个空对象
- 执行构造函数
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指向
原理:
- 判断this指向的目标是否为对象
- 将目标函数存到目标对象的_fn属性中
- 执行目标对象的_fn并删除_fn属性
- 返回结果
//实现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);
}
}
}