以下是前端面试经常问到的手写代码题:
1. 实现一个new
function _new(fn,...args){
var obj = {} //1.创建一个新对象
obj.__proto__ = fn.prototype //2.绑定其原型到新对象上
fn.apply(obj,args) //3.改变其构造函数this的指向为一个新对象
return obj //4.返回一个新对象
}
// 调用
var test = function(a,b) {
this.a = a ;
this.b = b;
}
text.prototype.add = function() {
return this.a+ this.b
}
var a = _new(test,1,2)
console.log(a.add()) // 3
2. 实现一个call、(apply也同理)
Object.prototype._call = function(content = window) {
content.fn = this // 1.获取需要改变this指向的函数,并且绑定到指向对象上
var params = [...arguments].slice(1) //2.获取call传入参数
var result = content.fn(...params) // 3.在指向对象上调用其方法
delete content.fn // 4.删除临时指向对象上临时创建的方法
return result // 5.返回执行结果
}
//调用
var fn = function(...args) {
console.log(this.a); // ok
console.log(args) // [1,2,3]
}
var obj = { a: 'ok' }
fn._call(obj,1,2,3)
3.实现一个bind
Object.prototype._bind = function(content = window) {
content.fn = this
var params = [...arguments].slice(1)
return function() {
params = params.concat([...arguments])
var result = content.fn(...params)
return result
}
}
var fn = function(...args) {
console.log(this.a); // ok
console.log(args) // [1,2,3,4]
}
var obj = { a: 'ok' }
fn._bind(obj,1,2)(3,4)
3.实现一个instanceof
function _instanceof(A,fn) {
var l = A.__proto__
var r = fn.prototype
while(true) {
console.log(l,r)
if(l == r) {
return true
}else {
l = l.__proto__
}
if(l == null) { return false }
}
}
function fn() {}
function fn1() {}
var a = new fn()
console.log(_instanceof(a,fn)) // true
console.log(_instanceof(a,fn1)) // false
4.实现一个JSONP
function jsonp(obj) {
if(Object.prototype.toString.call(obj) !== '[object Object]') return console.error('this don\'t is Object');
// 1.创建script标签、设置超时时间
var script = document.createElement('script')
var head = document.getElementsByTagName('head')[0]
var waitTime = obj.setTimeOut || 3000;
script.type = "text/javascript"
// 2.设置回调名
var callbackName =( obj.callback ? obj.callback : 'callback') + '_' + Math.floor(new Date().getTime()/1000)
// 3.地址拼接
script.src = obj.url.indexOf('?') != -1 ? obj.url + "&callback=" + callbackName : obj.url + "?callback=" + callbackName
// 4.添加到文档中
head.appendChild(script)
// 5.全局挂载回调
window[callbackName] = function(json){
clear()
obj.success && obj.success(json)
}
// 6.错误监听
script.onerror = function(error) {
clear()
obj.failed && obj.failed('Request failed')
}
// 7.超时清除
setTimeout(() => {
clear()
},waitTime)
function clear() {
head.removeChild(script)
window[callbackName] = null
}
}
// 调用
jsonp({
url: 'http://baidu.com?a=1&b=2',
setTimeOut: 10000,
success: (res) => {},
failed: (res) => {}
})
5.使用promise实现一个promise.all
Promise._all = arr => {
// 1.新建一个状态数组
let res = []
// 2.返回一个promise
return new Promise((resolve, reject) => {
let i = 0;
next();
// 状态计数并添加状态
function computed(val,fn) {
res.push(val)
if (++i === arr.length) {
resolve(res)
} else {
fn()
}
}
// 执行判断是否是promise,是则使用then方法(如果发生错误会直接执行reject)、不是直接添加数据
function next() {
if (arr[i] instanceof Promise) {
arr[i].then((s) => {
computed(s,next)
}, reject)
} else {
computed(arr[i],next)
}
}
})
}
// 调用
Promise._all([1,2,3]).then(res => {
console.log(res) // [1,2,3]
})
6.实现一个柯里化
柯里化过程相当于是把函数可以把参数进行分隔,然后再分别传入参数使函数执行也能达到原函数的效果,常用方法是判断原函数参数长度,在调用函数时,若参数长度不够则将其参数通过闭包形式进行拼接,并返回当前函数,直到参数达到函数所需要参数长度才会借助call或者apply返回原函数结果。下面实现没有判断原函数长度,通过判断最后一次调用参入参数长度为0表示结束。
// 这里通过判断最后一次执行参数的个数来判断结束,目前可以使用function的length属性判断原函数的参数长度来进行
Object.prototype.curry = function () {
var fn = this;
var args = [...arguments]
var _fn = function () {
if (arguments.length === 0) {
return fn.apply(this, args)
} else {
args = args.concat([...arguments])
return _fn
}
}
return _fn
}
// 调用
function add() {
return [].reduce.call(arguments, (a, b) => a + b)
}
var fn = add.curry()
console.log(fn(1,2,3)(4,5)(6)()) // 1+2+3+4+5+6 = 21
7.实现一个偏应用函数
偏应用函数是通过提前设置已知参数,在函数调用过程中只需要提供部分参数而达到实现原函数的一样效果的目的。下面主要使用undefined进行参数占位,表明未知参数。
Object.prototype.partial = function () {
var fn = this
var args = [...arguments]
var _fn = function () {
var otherArgs = [...arguments]
for (let i = 0; i < args.length && otherArgs.length > 0; i++) {
args[i] = args[i] == undefined ? otherArgs.shift() : args[i]
}
return fn.apply(null, args)
}
return _fn
}
// 调用
function test(fn,time) {
setTimeout(fn,time)
}
var timer4s = test.partial(undefined,4000)
timer4s(() => { console.log(1111111)})
8.组合compose和管道pipe实现(绑定在数组上)
// 组合(从右向左执行)
Array.prototype.compose = function () {
var args = [...arguments]
return this.reduceRight(function (arg, fn) {
return fn(arg)
}, args)
}
// 管道(从左向右执行)
Array.prototype.pipe = function () {
var args = [...arguments]
return this.reduce(function (arg, fn) {
return fn(arg)
}, args)
}
function start(a) {
return a + ' 2'
}
function end(data) {
return data + ' 3'
}
console.log([start,end].compose(1)) // 1 3 2
console.log([start,end].pipe(1)) // 1 2 3
9.深度优先、广度优先
// 测试数据
const data = [
{
name: 'a',
children: [
{ name: 'b', children: [{ name: 'e' }] },
{ name: 'c', children: [{ name: 'f' }] },
{ name: 'd', children: [{ name: 'g' }] },
],
},
{
name: 'a2',
children: [
{ name: 'b2', children: [{ name: 'e2' }] },
{ name: 'c2', children: [{ name: 'f2' }] },
{ name: 'd2', children: [{ name: 'g2' }] },
],
}
]
// 深度优先
Object.prototype.DFS= function() {
const result = [],data = this;
data.forEach(v => {
const map = data => {
result.push(data.name);
data.children && data.children.forEach(child => map(child))
}
map(v)
})
return result.join(',')
}
// 广度优先
Object.prototype.BFS = function() {
const result = [],data = JSON.parse(JSON.stringify(this));
while(data.length > 0) {
[...data].forEach((child,index) => {
data.shift() // 1.取出当前遍历节点
result.push(child.name) //2.插入当前节点数据
child.children && data.push(...child.children) //3.合并附近子集,并再次遍历
})
}
return result.join(',')
}
// 调用
console.log(data.DFS()) //a,b,e,c,f,d,g,a2,b2,e2,c2,f2,d2,g2
console.log(data.BFS()) //a,a2,b,c,d,b2,c2,d2,e,f,g,e2,f2,g2
10.二叉树
function BT() {
this.root = null;
// 1.创建节点构造函数(包括参数,val、left、right)
function Node(e) {
this.value = e;
this.left = null;
this.right = null;
}
//2.添加元素
this.insert = function (e) {
e = new Node(e);
if (this.root == null) {
this.root = e;
} else {
_add(this.root, e);
}
return this.root;
// 递归从根节点遍历所以节点的左右节点(左小右大)
function _add(tem, e) {
if (e.value < tem.value) {
if (tem.left == null) {
tem.left = e;
} else {
tem = tem.left;
return _add(tem, e);
}
} else {
if (tem.right == null) {
tem.right = e;
} else {
tem = tem.right;
return _add(tem, e);
}
}
}
}
//3.查找元素
this.find = function (e) {
e = new Node(e);
if (this.root == null) {
return;
} else {
return _find(this.root, e);
}
// 通过递归从根节点向下寻找
function _find(tem, e) {
if (tem.value == e.value) {
return tem;
} else {
if (e.value < tem.value) {
tem = tem.left;
_find(tem, e)
} else {
if (e.value > tem.value) {
tem = tem.right;
_find(tem, e)
}
}
}
}
}
//4.删除元素
this.delete = function (e) {
const _this = this
e = new Node(e);
if (this.root == e) {
this.root = null;
} else {
_delete(this.root, e);
}
function _delete(tem, e) {
if (e.value == tem.value) {
_this.root = null
return ;
} else if (e.value < tem.value) {
if (tem.left == null) {
return;
} else {
if (tem.left.value == e.value) {
tem.left = null;
return;
} else {
tem = tem.left;
_delete(tem, e)
}
}
} else {
if (e.value > tem.value) {
if (tem.right == null) {
return;
} else {
if (tem.right.value == e.value) {
tem.right = null;
return;
} else {
tem = tem.right;
_delete(tem, e)
}
}
}
}
}
}
}
// 调用
var bt = new BT();
bt.insert(5)
bt.insert(4)
bt.insert(8)
bt.insert(7)
bt.insert(10)
bt.find(5)
bt.delete(4)
11.二分查找 (log2N)
Array.prototype.BinarySearch = function(target) {
var from = 0, to = this.length,mid;
while(from <= to) {
mid = Math.floor((from + to) / 2)
if (this[mid] > target) {
to = mid - 1
} else if (this[mid] < target) {
from = mid + 1
} else {
return mid
}
}
}
// 调用
console.log([1,2,4,6,7,8,12,14,15].BinarySearch(4)) // 2
//过程分析
//1. from = 0 ,to = 8;
//2. mid = 4,7 > 4, from = 0,to = 3
//3. mid = 1,2 < 4,from = 1,to = 3
//4. mid = 2, 4 == 4, 返回 mid
11.实现一个链表
// 1.构造函数
function LinkedList() {
this.head = null;
this.length = 0;
this.current = null
}
// 2.创建节点构造函数
var Node =function (e) {
this.ele = e
this.next = null
}
//3.新增数据(步骤:新建节点->判断头指针(头不存在,当前节点为头节点,存在则添加在尾指针上)->记录尾指针->长度+1)
LinkedList.prototype.add = function (e) {
var node = new Node(e)
if(!this.head) {
this.head = node
} else{
this.current.next = node
}
this.current = node
length ++
}
// 4.查询数据,从头指针向下寻找
LinkedList.prototype.find = function (target) {
var data = this.head
while (data.ele){
if(data.ele == target) return true
if(!data.next) break ;
data = data.next
}
return false
}
// 5.打印数据
LinkedList.prototype.print = function () {
var data = this.head
while(data.ele) {
console.log(data.ele)
if(!data.next) return ;
data = data.next
}
}
12.模拟实现一个Hash表(不太准确哈)
function hashList() {
this.list = {}
this.size = 0
}
hashList.prototype.add = function () {
var [ key, val] = [...arguments]
this.list[key] = val
this.size ++
}
hashList.prototype.remove = function (key) {
if (this.hasKey(key) && (delete res[key])) {
this.size --
}
}
hashList.prototype.hasKey = function (target) {
return (target in this.list)
}
hashList.prototype.hasValue = function (target) {
return Object.values(this.list).indexOf(target) != -1 ? true : false
}
hashList.prototype.getKeyValue = function (target) {
return this.list[target]
}
hashList.prototype.getValueKey = function (target) {
return Object.entries(this.list).find(o => target === o[1])[0]
}
// 调用
var a = new hashList()
a.add('a',1111)
a.add('b',2222)
console.log(a.hasKey('a'),a.hasKey('c')) // true false
console.log(a.hasValue(1111),a.hasKey(33333)) // true false
console.log(a.getKeyValue('a'),a.getValueKey(2222)) // 1111 b
13.实现一个栈和队列,并且用两个栈实现一个队列,两个队列实现一个栈
// 栈
function Stack() {
this.data = []
this.length = 0
}
Stack.prototype.set= function (val) {
this.data.push(val)
this.length ++
}
Stack.prototype.get = function () {
if(this.length == 0) return console.error('stack data is empty')
this.length --;
return this.data.pop()
}
// 队列
function Queue() {
this.data = []
this.length = 0
}
Queue.prototype.get = function () {
if(this.length == 0) return console.error('queue data is empty')
this.length --;
return this.data.shift()
}
Queue.prototype.set = function (val) {
this.data.push(val)
this.length ++
}
// 2个栈实现队列(栈1负责插入数据、栈2负责获取数据,当栈2数据为空时,栈1为栈2补充新数据)
function StackToQueue() {
this.s1 = new Stack()
this.s2 = new Stack()
}
StackToQueue.prototype.set = function (val) {
this.s1.set(val)
}
StackToQueue.prototype.get = function (val) {
if(this.s2.length === 0 && this.s1.length !== 0) {
while(this.s1.length) {
this.s2.set(this.s1.get())
}
console.log(this.s2)
return this.s2.get()
}
if(this.s2.length !==0) {
return this.s2.get()
}
return console.error('queue data is empty')
}
// 2个队列实现栈(把数据从一个队列移动到另外一个队列,取出最后移动的一个数)
function QueueToStack() {
this.q1 = new Queue()
this.q2 = new Queue()
}
QueueToStack.prototype.set = function (val) {
if(this.q2.length === 0){
this.q1.set(val)
}else if(this.q1.length === 0){
this.q2.set(val)
}
}
QueueToStack.prototype.get = function () {
if(this.q1.length !== 0 && this.q2.length ===0) {
while(this.q1.length) {
if(this.q1.length === 1) return this.q1.get();
this.q2.set(this.q1.get())
}
}
if(this.q2.length !== 0 && this.q1.length === 0) {
while(this.q2.length) {
if(this.q2.length === 1) return this.q2.get();
this.q1.set(this.q2.get())
}
}
return console.error('stack data is empty')
}
14.防抖和节流函数
// 防抖
function debounce(fn, delay) {
let timer = null;
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 节流
function throttle(fn, delay) {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args)
flag = true
}, delay)
}
}
15.实现一个深克隆
function deepClone(oldObj) {
let result;
let isType = (arg, type) => {
let typeString = Object.prototype.toString.call(arg)
let flag
switch (type) {
case "Array":
flag = typeString === "[object Array]";
break;
case "Date":
flag = typeString === "[object Date]";
break;
case "RegExp":
flag = typeString === "[object RegExp]";
break;
case "Object":
flag = typeString === "[object Object]";
break;
default:
flag = false;
}
return flag
}
if (isType(oldObj, 'Array')) {
result = []
for (let i in oldObj) {
result.push(deepClone(oldObj[i]))
}
} else if (isType(oldObj, 'Object')) {
result = {}
for(let i in oldObj) {
result[i] = deepClone(oldObj[i])
}
} else if(isType(oldObj,'Date')) {
result = new Date(oldObj.getTime())
}
else {
result = oldObj
}
return result
}
16. 要求输入一个地址url,传入一个数据对象将可以修改地址的参数
执行:deal(‘http://baidu.com?a=222&b=333’)({a:1,b:2,c:3})
输出:http://baidu.com?a=1&b=2&c=3
function deal(url) {
var arr = url.split("?"), res = arr[0] + "?", params = {}
return function (obj) {
if(Object.prototype.toString.call(obj) !== '[object Object]') {
return url
}
if (arr.length > 1) {
var paramsArr = arr[1].split("&&")
for (let i = 0; i < paramsArr.length; i++) {
var val = paramsArr[i].split("=")
params[val[0]] = val[1]
}
params = {...params, ...obj }
} else {
params = obj
}
var dealArr = Object.entries(params)
for (let i = 0; i < dealArr.length; i++) {
res += dealArr[i][0] + '=' + dealArr[i][1]
if (dealArr.length - 1 > i) res += "&"
}
return res
}
}
console.log(deal("http://baidu.com?a=222&b=333")({a:1,b:2,c:3 }))
//http://baidu.com?a=1&b=2&c=3