1、手写一个push?
Array.prototype.push = function(...items) { // 可以传入多个值
let O = Object(this); // 根据草案转换为Object
let len = this.length >>> 0;
// 无符号右移运算符,向右移指定位,并且符号位设为0(代表正数)
// 这里用于确保len为正
let argCount = items.length >>> 0;
if(len + argCount > 2 ** 53 - 1) // 超过最大长度,抛错
throw new TypeError("The number of array is over the max value restricted!")
for(let i = 0;i < argCount;i++)
{
O[len++] = items[i];
}
return len;
}
2、手写一个pop?
Array.prototype.pop = function() {
let O = Object(this);
let len = this.length >>> 0;
if(len == 0) return undefined;
else {
let element = O[len-1];
delete O[len-1];
this.length = len-1;
return element;
}
}
3、手写一个map?
Array.prototype.map = function(func,thisArg) {
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'map' of null or undefined");
}
let O = Object(this);
let len = this.length >>> 0;
// 判断回调
if(Object.prototype.toString.call(func) !== '[object Function]')
throw new TypeError("Cannot read property 'map' of null or undefined");
let A = new Array(len);
let k = 0;
while(k < len)
{
if(k in O) // *** 这里去除empty,让他保持empty状态,不进入回调
{
let kValue = O[k];
A[k] = func.call(thisArg,kValue,k,O);
}
k++;
}
return A;
}
4、手写一个reduce?
Array.prototype.reduce = function(func,initialValue) {
if(this === undefined || this === null)
{
throw new TypeError('...');
}
if(Object.prototype.toString.call(func) !== '[object Function]')
{
throw new TypeError('...');
}
let O = Object(this);
let len = this.length >>> 0;
if(len === 0 && initialValue === undefined)
throw new TypeError('...');
let k = 0;
let accumulator = initialValue;
let kPresent = false;
while(!kPresent && k < len)
{
if(k in O)
{
kPresent = true;
accumulator = O[k];
}
k++;
}
if(!kPresent) throw new TypeError('...');
while(k < len)
{
if(k in O)
{
let kValue = O[k];
accumulator = func.call(undefined,accumulator,kValue,k,O);
}
k++;
}
return accumulator;
}
5、手写一个filter?
Array.prototype.filter = function(func,thisArg) {
if(this === null || this === undefined)
{
throw new TypeError('...');
}
if(Object.prototype.toString.call(func) !== '[object Function]')
{
throw new TypeError('...');
}
let O = Object(this);
let len = this.length >>> 0;
let newArrLen = 0;
let newArr = [];
for(let k = 0;k < len;k++)
{{
if(func.call(thisArg,O[k],k,O)) newArr[newArrLen++] = O[k];
}
}
return newArr;
}
6、手写一个splice?
Array.prototype.splice = function(start,deleteCount,...items) {
let O = Object(this);
let len = this.length >>> 0;
if(len - deleteCount + items.length > 2 ** 53 - 1)
{
throw new TypeError('...');
}
let retArr = []; //用于return的数组容器.
// 处理start
if(start < 0)
{
start = start+len >= 0 ? start+len : 0;
}
else
{
start = start >= len ? len : start;
}
// 处理deleteCount 这里deleteCount依赖start,所以处理顺序不能变
if(arguments.length == 1) deleteCount = len-start;
else if(deleteCount < 0) deleteCount = 0;
else if(deleteCount+start > len) deleteCount = len-start;
// 判断sealed对象、处理configurable为false的,(不可添加、删除属性,但是可以改变属性值)
// 判断冻结对象、冻结对象不可以增删改属性值.
if(Object.isSealed(O) && items.length !== deleteCount)
throw new TypeError('...');
else if(Object.isFrozen(O) && (items.length > 0 || deleteCount > 0))
throw new TypeError('...');
// 判断是否是需要补满删除的属性值.
if(deleteCount <= items.length)
{
for(let i = start;i < start+deleteCount;i++)
{
retArr.push(O[i]);
O[i] = items[i-start];
}
}
// 补满之后还有数据没有添加
// 注意移动的起始位置,如果放方向开始移动会出现覆盖.
if(deleteCount < items.length)
{
let add = items.length-deleteCount;
this.length += add;
for(let i = len-1;i >=start+deleteCount;i--)
{
O[i+add] = O[i];
}
for(let i = 0;i < add;i++)
{
O[start+deleteCount+i] = items[i+deleteCount];
}
}
// 删除数据过多 需要将后面的数据前移.
else if(deleteCount > items.length)
{
for(let i = 0;i < deleteCount;i++)
{
retArr.push(O[i+start]);
}
for(let i = 0; i < items.length;i++)
{
O[i+start] = items[i];
}
for(let i = 0;i < len - start - deleteCount;i++)
{
O[start+items.length+i] = O[i+start+deleteCount];
}
this.length -= deleteCount-items.length;
}
return retArr;
}
这个就不放源码的,太多了,面试写成这样还不让过,那真就夸张了. 这题主要考察边界值的判断情况.以及对于api整体的了解程度.感觉全写对在算法中应该属于medium偏上了.
7、手撕sort?
Array.prototype.sort = function(callback) {
let O = Object(this);
let len = this.length >>> 0;
_sort(O,len,callback)
function _sort(arr,length,compareFn) {
// 定义交换规则,
// a表示是否要交换的数,b表示被选中的数,根据return来确定是否需要交换 这个挺难理解的
if(Object.prototype.toString.call(compareFn) !== '[object Function]')
{
compareFn = function(a,b) {
a = a.toString();
b = b.toString();
if(a === b) return 0;
else return a < b ? -1 : 1;
}
}
// 插入排序, 在size < 10时, 使用插入排序速度时优于快排的.
function insertSort(arr,start = 0,end,compareFn) {
for(let i = start;i <= end;i++)
{
let j;
let val = arr[i];
for(j = i;j > start && compareFn(arr[j-1],val) > 0;j--)
arr[j] = arr[j-1];
arr[j] = val;
}
}
// 选择中位数,防止快速排序为最差情况.
function getMidIndex(arr,begin,end) {
let increment = 200 + length & 15;
let tmpArr = [];
for(let i = begin;i < end; i += increment)
{
tmpArr.push([i,arr[i]]);
}
tmpArr.sort((a,b) => {
return compareFn(a[1],b[1]);
})
return tmpArr[tmpArr.length >> 1][0];
}
function quickSort(arr,begin,end) {
let midIndex = 0;
if(end-begin <= 10)
{
insertSort(arr,begin,end,compareFn);
return;
}
// 这里不使用(begin+end)/2是为了防止begin+end时溢出,(一般不会,但是要养成好的代码习惯)
else if(end-begin <= 1000) midIndex = begin + (end-begin) >> 1;
// 当长度超过1000时,需要选择更多的随机数,优化快速排序
else midIndex = getMidIndex(arr,start,end);
// 将两个边界值和拿到的mid排序,让中间的数最左边.优化快速排序
let tmp = [arr[midIndex],arr[begin],arr[end]];
tmp.sort(compareFn);
[arr[begin],arr[midIndex],arr[end]] = [tmp[1],tmp[0],tmp[2]];
// 进行快速排序
let i = begin;
let t = begin+1;
let j = end;
let pivot = arr[begin];
while(t <= j)
{
// 确定是否符合交换规则
let order = compareFn(pivot,arr[t]);
if(order > 0)
[arr[t++],arr[i++]] = [arr[i],arr[t]];
else if(order == 0) t++;
else {
if(compareFn(pivot,arr[j]) < 0) j--;
else if(compareFn(arr[j],pivot) == 0) [arr[t++],arr[j--]] = [arr[j],arr[t]];
else [arr[j--],arr[t++],arr[i++]] = [arr[t],arr[i],arr[j]];
}
}
quickSort(arr,begin,i-1);
quickSort(arr,j+1,end);
}
quickSort(arr,0,length-1);
}
}
这里借鉴了一些文章,但是有些文章是有些问题的,有些问题还很难看出来,😭,然后自己思考了一下,基本面试让写sort的就比较过分了,大概有个思路就比一般人强了.我觉得compareFn是比较难理解的.
8、手写一个new?
function newFn(Target,...items)
{
// 判断函数
if(Object.prototype.toString.call(Target) !== '[object Function]')
throw new TypeError('target is not Function');
// 判断return 是否为引用类型
let res = Target(...items);
// 获取原型链
let obj = Object.create(Target.prototype);
Target.call(obj,...items);
return (typeof res === 'object' && res !== null) ? res : obj;
}
9、手写一个bind?
Function.prototype.bind = function(target,...items) {
if(Object.prototype.toString.call(this) !== '[object Function]')
throw new TypeError('...')
let self = this;
// 利用闭包
let resFn = function(...arg) {
// 要判断 this是否为构造函数,
//如果是构造函数,this的继承resFn的原型,所以self是在this的原型上的.
self.apply(this instanceof self ? this : target,[...items,...arg]);
}
resFn.prototype = Object.create(self.prototype);
return resFn;
}
10、手写一个apply和call?
// 这里借鉴了三元大佬的代码
Function.prototype.call = function(target,...items(直接使用items就是apply)) {
let context = target || window;
// 三元的fn是有问题的,通过.fn调用fn会转为字符串.
// 这里使用Symbol是保证唯一性.不和其他变量冲突
let fn = Symbol('fn');
context[fn] = this;
let res = context[fn](...items);
delete context[fn];
return res;
}
11、手写深拷贝?
function getItemType(item) {
prototypeList = {
"[object Object]": 'object',
"[object Array]": 'array',
"[object Map]": 'map',
"[object Set]": 'set'
};
return prototypeList[Object.prototype.toString.call(item)];
}
function funcRes(target) {
if(!target.prototype) return target;
const bodyTag = /(?<={)(.|\n)+(?=})/m;
const paramsTag = /(?<=\().+(?=\)\s+)/;
const targetStr = target.toString();
const params = paramsTag.exec(targetStr);
const body = bodyTag.exec(targetStr);
console.log(body);
if(!body) return null;
if(params) return new Function(...params[0],body[0]);
else return new Function(body[0]);
}
function cannotTranverseRes(target)
{
prototypeList = {
'[object Error]': 'error',
'[object Function]': 'function',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Symbol]': 'symbol',
}
let ctor = target.constructor;
let val = prototypeList[Object.prototype.toString.call(target)];
switch(val)
{
// 获取使用原始类型通过Object创建的原始值
case 'boolean':
return new Object(Boolean.prototype.valueOf.call(target));
case 'number':
return new Object(Number.prototype.valueOf.call(target));
case 'string':
return new Object(String.prototype.valueOf.call(target));
case 'symbol':
return new Object(Symbol.prototype.valueOf.call(target));
case('regExp'):
return new ctor(target.source,target.flags);
case('function'):
return funcRes(target);
default:
return new ctor(target);
}
}
function deepClone(target,map = new Map()) {
// 处理原始类型
if((typeof target !== 'object' || target == null) && typeof target !== 'function')
return target;
let res;
// 对于可遍历对象需要递归调用.
let type = getItemType(target);
if(!type) return cannotTranverseRes(target);
else res = new target.constructor();
// 获取原型上的属性
// 处理循环引用
if(map.has(target)) return target;
map.set(target,true);
// 处理可遍历对象
if(type === 'map')
{
for(let [index,item] of target.entries())
res.set(deepClone(index,map),deepClone(item,map));
}
else if(type === 'set')
{
for(let item of target.values())
res.add(deepClone(item,map));
}
else if(type === 'array' || type === 'object')
{
for(let k in target)
{
if(target.hasOwnProperty(k))
res[k] = deepClone(target[k],map);
}
}
return res;
}
这里三元大佬写的边界处理的很到位了, 我看着写的😁
12、手写一个promise(全套promise,过promises-aplus-tests)?
function Promise(executor) {
// 保存两种状态下的数据
this.value = null;
this.reason = null;
// 保存异步函数在状态变化后调用的函数
this.fulfillledArr = [];
this.rejectedArr = [];
this.status = 'pending';
// 保证状态只能改变一次
this.flag = false;
const _resolve = (val) => {
this.value = val;
this.status = 'resolved';
this.fulfillledArr.map(cal => {cal()});
}
const resolve = (val) => {
if(this.status === 'pending' && !this.flag)
{
this.flag = true;
// 处理resolve内部含有promise的情况
if(val !== null && (
typeof val === 'function' ||
typeof val === 'object'
))
{
try {
let then = val.then;
if(typeof then === 'function')
{
then.call(val,(val) => {
this.flag = false;
resolve(val)
},(rea) => {
this.flag = false;
reject(rea);
});
} else
{
_resolve(val);
}
}catch(e)
{
this.flag = false;
reject(e);
}
}
else {
_resolve(val);
}
}
}
const reject = (reason) => {
if(this.status === 'pending' && !this.flag)
{
this.flag = true;
this.reason = reason;
this.status = 'rejected';
this.rejectedArr.map(cal => {cal()});
}
}
executor(resolve,reject);
}
Promise.prototype.then = function(fulfilled,rejected)
{
// 初始化函数
if(typeof fulfilled !== 'function') fulfilled = value => value;
if(typeof rejected !== 'function') rejected = reason => {throw reason};
let p2 = new Promise((resolve,reject) => {
if(this.status === 'resolved')
{
// 设置微任务
process.nextTick(() => {
try {
let p = fulfilled(this.value)
PromiseAndRelation(p,p2,resolve,reject);
}catch(e) {
reject(e);
}
})
}
else if(this.status === 'rejected')
{
process.nextTick(() => {
try {
let p = rejected(this.reason);
PromiseAndRelation(p,p2,resolve,reject);
}catch(e) {
reject(e);
}
})
}
else if(this.status === 'pending')
{
this.fulfillledArr.push(() => {
process.nextTick(() => {
try {
let p = fulfilled(this.value)
PromiseAndRelation(p,p2,resolve,reject);
}catch(e) {
reject(e);
}
})
})
this.rejectedArr.push(() => {
process.nextTick(() => {
try {
let p = rejected(this.reason);
PromiseAndRelation(p,p2,resolve,reject);
}catch(e) {
reject(e);
}
})
})
}
})
return p2;
}
// 处理then中返回promise的情况
function PromiseAndRelation(p,p2,Toresolve,Toreject)
{
// 处理循环引用
if(p2 === p) throw new TypeError('cycling reference');
let flag = false;
if(p != null && (
typeof p === 'function' ||
typeof p === 'object'
))
{
try {
let then = p.then;
if(typeof then === 'function')
{
then.call(p,(val) => {
if(flag) return;
flag = true;
PromiseAndRelation(val,p,Toresolve,Toreject);
},(rea) => {
if(flag) return;
flag = true;
Toreject(rea);
});
} else Toresolve(p);
}catch(e)
{
if(flag) return;
flag = true;
Toreject(e);
}
}
else {
// if(flag) return;
// flag = true;
Toresolve(p);
}
}
Promise.resolve = function(val) {
return new Promise((resolve,reject) => {
resolve(val);
})
}
Promise.reject = function(val) {
return new Promise((resolve,reject) => {
reject(val);
})
}
Promise.prototype.finally = function(fn) {
this.then(() => {
fn();
},() => {
fn();
})
return this;
}
Promise.prototype.catch = (rej) => {
return this.then(null,rej);
}
// promise.all
// 1、保证全部为resolved才返回fulfilled.
// 2、函数必须传入拥有迭代器的变量.
// 3、一旦有一个错误里吗抛出
Promise.all = function(arr) {
return new Promise((resolve,reject) => {
let res = [];
function count(val) {
res.push(val);
if(res.length === arr.length || arr.size) resolve(res);
}
if(arr[Symbol.iterator])
{
for(let item of arr.values())
{
Promise.resolve(item).then((val) => {
count(val)
},reject);
}
}
else reject(new TypeError(`${typeof arr} ${arr} is not iterable (cannot read property Symbol(Symbol.iterator))`));
})
}
Promise.race = function(arr) {
return new Promise((resolve,reject) => {
if(arr[Symbol.iterator])
{
for(let item of arr.values())
{
Promise.resolve(item).then((val) => {
resolve(val)
},reject);
}
}
else reject(new TypeError(`${typeof arr} ${arr} is not iterable (cannot read property Symbol(Symbol.iterator))`));
})
}
// 创建一个延迟对象
Promise.defer = Promise.deferred = function() {
let dfd = {};
dfd.promise = new Promise((resolve,reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise;
13、手写防抖和节流?
防抖: 在规定时间那再次触发则重新计时.
function debounse(fn,delay = 1000) {
let timer = null;
return (...arg) => {
timer && clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this,arg);
timer = null;
}, delay);
}
}
节流: 在一个定时器被触发时,在时间未到的情况下不能被再次出发.
function throttle(fn,delay = 1000) {
let flag = true;
return (...arg) => {
if(!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this,arg)
flag = true;;
}, 1000);
}
}
14、实现数组去重的几种方式?
1、使用set数据结构,再转换为数组.
let a = new Set();
for(let item of arr.values()) a.add(item);
a = [...a];
2、使用hash去重
let obj = {};
for(let item of arr.values()) obj[item] = item;
let res = [];
for(let item of Object.values(obj)) res.push(item);
3、使用reduce+sort
// 排序过后,相同值的被放在一起,这时根据和上一个值的比较来去重复
arr.sort();
let res = arr.reduce((pre,item,index) => {
// Object.is是处理+-1和NaN的情况
(index === 0 || !Object.is(item,pre[pre.length-1])) && pre.push(item);
return pre;
},[])
4、常规比较
// 额外的空间复杂度为O[1].
for(let i = 0;i < arr.length;i++)
{
let t;
for(t = i+1;t < arr.length;t++)
{
if(Object.is(arr[i],arr[t])) break;
}
t != arr.length && arr.splice(t,1);
}
15、观察者模式和发布者订阅者模式?
观察者模式:
class Subscribe {
constructor() {
this.observers = [];
}
on(callback) {
if(typeof callback === 'function')
{
this.observers.push(callback);
}
}
notify(val) {
this.observers.map(cbk => {cbk(val)});
}
}
发布者订阅者模式?
class Subscribe {
constructor() {
this.eventChannel = {}
}
// 订阅 并且根据once来表示是否执行一次
on(type, cbk, once = false) {
if(!this.eventChannel[type]) this.eventChannel[type] = [{cbk,once}];
else this.eventChannel[type].push({cbk,once});
}
// 分发
emit(type,val) {
let events = this.eventChannel[type];
if(events)
{
// 这里为了once做处理
// 如果使用map的话 在删除元素时,索引无法控制.
for(let i = 0;i < events.length;i++)
{
events[i].cbk(val);
events[i].once && events.splice(i--,1)
}
}
}
// 移除事件中心的type类型的cbk回调
off(type,cbk) {
let events = this.eventChannel[type];
if(events.length === 0) delete this.eventChannel[type];
else {
let index = events.findIndex(item => item.cbk === cbk);
events.splice(index,1);
}
}
// 移除type类型的回调.
removeAll(type) {
this.eventChannel[type] && delete this.eventChannel[type];
}
}
16、手写一个JSONP?
client:
function jsonp(url,data)
{
// 构造query
let params = new URLSearchParams();
for(let [index,item] of Object.entries(data))
{
params.set(index,item);
}
// 创建在server端用于响应的函数
params.set('cbk','_cbk');
url += '?' + params.toString();
let script = document.createElement('script');
script.setAttribute('src', url);
document.body.appendChild(script);
return new Promise((resolve,reject) => {
window['_cbk'] = (val) => {
resolve(val);
document.body.removeChild(script);
delete window['_cbk']
}
})
}
jsonp('http://localhost:8082',{name: 1,val: 2}).then((val) => {
console.log(val);
})
server:
let express = require('express');
let server = express();
server.use('/',(req,res) => {
res.end(`${req.query.cbk}(数据))`);
})
server.listen(8082);
17、手写一个await?
await是genarator的语法糖,就是在yield上套一层promise,再自动执行
// ** 测试代码
function f1() {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
})
}
function* f(a) {
let data = yield f1();
console.log(data,a);
throw new Error(123);
let data1 = yield f1();
console.log(data1,a);
return 1
}
async function f0(a) {
let da = await f1();
console.log(da,a);
throw new Error(123);
let da1 = await f1();
console.log(da1,a);
return 1
}
测试代码 ** //
// 升级genarator
function asyncGanerator(func) {
return function(...arg) {
// 获取迭代对象
let gen = func.apply(this,arg);
return new Promise((resolve,reject) => {
let genarator;
// 这里传type是为了应对Error,需要考虑两种情况
// 1、在执行过程有thrpw
// 2、在执行promise时,reject了
function next(type,data) {
// 捕捉错误
try {
genarator = gen[type](data);
}catch(e) {
return reject(e);
}
// 结束 return;
if(genarator.done === true)
{
resolve(genarator.value);
return;
}
// 迭代.
Promise.resolve(genarator.value).then((data) => {
next('next',data);
},(err) => {
next('throw',data);
});
}
next('next');
})
}
}
let val = asyncGanerator(f);
let val1 = f0(123);
val = val(123);
setTimeout(() => {
console.log(val);
console.log(val1);
}, 3000);
18、手写一个Instanceof?
通过查找原型链
function myInstanceof(val,target) {
// 基本类型直接return
if(typeof val !== 'object' || val === null) return false;
while(Object.getPrototypeOf(val))
{
if(Object.getPrototypeOf(val) === target.prototype) return true;
val = Object.getPrototypeOf(val);
}
return false;
}
19、手写一个Object.is?
function myObjectIs(val1,val2) {
// 处理+0和-0。 // 处理NaN
return val1 === val2 ? 1/val1 === 1/val2 : (isNaN(val1) && isNaN(val2));
}
20、手写一个函数柯里化?
柯里化主要是把多个参数转换为单个参数进行链式调用的技术.
function curry()
{
let args = [...arguments];
let _curry =function () {
args.push(...arguments);
return _curry;
}
_curry.toString = function() {
return args.reduce((pre,val) => pre+val);
}
return _curry;
}
console.log(curry(1,2,3,4) == curry(1)(2)(3)(4)) true;