• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

手撕前端面试代码题

互联网 diligentman 2周前 (11-22) 5次浏览

  1. 最近我终于找到了一份满意的工作.准备面试的过程中,我整理出了一些有用的笔记,这篇就是其中之一.

    既然写好了,不妨就放在这里分享给大家.

  2. 面试通常都有现场写代码的题目,我基本每次都或多或少的翻车.有意思的是,每次面试结束,自己改不到五分钟就调试出来了.

  3. 所以面试中的写代码的过程,一定不能紧张,要沉住气慢慢来.只要不是系统自动检查结果,只要是面试官看着你写,就有很大的表现的机会,哪怕最后做不出来.

    我参加的最烦人的面试,是那种系统判定结果的面试,只要做不出来,就绝对不可能通过.

  4. 仔细想想,在二三十场面试中,很少有我完整写出毫无瑕疵的答案的题目,但基本也都顺利通过了.

    对比来看,我的同学中,答案能运行成功但面试没通过的也大有人在.

    可以肯定的是,面试官不会只根据是否能运行成功来评价应聘者.

    所以,只需要顺着正确思路稳稳地做就好了,不要怕最后运行不成功.

  5. 如果实在做不出,也一定要和面试官说清你当前的进展和思路,而不是一句"我不会"就想结束问题.

  6. 当然,我指的只是前端这个对算法能力要求不强的岗位…

    有一场百度的面试我甚至直接和面试官说,我是个偏感性的人,喜欢但是不擅长做算法.都给我通过了.

  7. 总的来看,如果能完全掌握这篇文章的内容,就足以应付所有前端面试中的手撕代码环节了.

导读

关于这篇文章,有几点我想先说清楚,方便读者更顺利的学习.

  • 这篇文章不适合前端小白阅读,需要对JSES6有一定了解,否则遇到一些写法可能不太看得懂

  • 因为精力有限,我只加了较为粗略但足以帮助读者理解的注释,因为多数题也只有几行代码而已.

    如果遇到还不懂的地方,我认为读者完全可以自己去查询文档来了解为什么这么做,为什么使用这个函数.

    或者,先查询该问题通常的解决思路,再回来参考我的实现

  • 代码大量使用了ES6的语法

  • 学习手撕代码,不只是理解的过程,更是实践的过程

    我在完全掌握(可以默写出每段代码,并讲清楚每一行的作用)以下代码的过程中,做了以下几件事

    • 参考别人的实现,结合自己的思路,写出一个自己的版本

    • 不断对代码进行优化

      当你尝试去优化一段代码的时候,对它的理解和记忆会异常深刻

    • 不看之前的实现,重新自己实现一次

      再和之前的实现做对比,检查错误

    • 反复阅读和默写,直到可以完全正确的默写为之

  • 作为一个专业的程序员,除了工作中的编码,额外的无实际产出的练习(反复练习解决一个问题,反复默写同一段代码),也是必不可少的.

    这就像歌手不可能到了舞台上才去练习自己的声音.他一定会在平时大量去练声.

    这就是我强调要反复敲代码的原因.别想着平时只要理解,工作中再去熟能生巧.

    工作不是给你练习的地方,工作是你的舞台.

  • 下文中几乎每一段代码,都是我反复优化后的结果,希望可以带给读者新的启发.

  • 我把代码大致分成了几个专题,一共包含了大致30个问题的解决方案

  • 除了文章中的问题,还有些我没有提到的,都是频率较低的问题

    关于算法题,除了排序和查找我也基本没有写.因为算法问题千变万化,需要的是解决问题的思维,而不是固定的实现

  • 重要性与顺序无关

  • 有问题可以问我,我都会回复

目录

      • 导读
    • DOM
      • 事件代理
    • 数组 对象
      • 扁平化
      • 去重 – `unique()`
      • 拷贝
        • 浅拷贝
        • 深拷贝
          • `copy()`函数实现
          • `JSON.stringify`
    • 字符串
      • 去除空格 – `trim()`
      • 字符串全排列
        • 广度优先实现
        • 深度优先实现
    • 排序查找
    • 功能函数实现
      • `setTimeout`实现`setInterval`
      • 函数柯里化
      • 防抖 节流
        • 防抖
        • 节流
    • 数据结构
      • 单链表
    • 设计模式
      • 发布订阅模式
    • JS原生API实现
      • `bind()` `call()` `apply()`
        • `apply()`
        • `call()`
        • `bind()`
      • `InstanceOf`
      • `new`
      • `reduce()` `forEach()`
        • `reduce()`
        • `forEach()`
      • `Promise`
        • `Promise.all()`
        • ES6所有API完整实现
    • HTTP请求
      • AJAX封装
      • JSONP

DOM

事件代理

document.getElementById("#father-id").onclick=function(event){
    event=ev||window.event
    let target=ev.target||ev.srcElement
    //可以自己打印一下event.target.nodeName,看看是什么
    if (target.nodeName.toLowerCase()==='xxx'){
        //事件内容
    }
}

数组 对象

扁平化

function flatten(arr) {
	let result=[]
	for (let i=0,len=arr.length;i<len;i++) {
		if (Array.isArray(arr[i])) {
			result=result.concat(flatten(arr[i]))
		} else {
			result.push(arr[i])
		}
	}
	return result
}

去重 – unique()

function unique(arr) {
    let appeard=new Set()
    return arr.filter(item=>{
        //创建一个可以唯一标识对象的字符串id
        let id=item+JSON.stringify(item)
        if (appeard.has(id)) {
            return false
        } else {
            appeard.add(id)
            return true
        }
    })
}

拷贝

浅拷贝

function copy(obj) {
	let result=Array.isArray(obj)?[]:{}
	Object.keys(obj).forEach(key=>result[key]=obj[key])
	return result
}
otherStar={...star}
Object.assign({},star)

深拷贝

copy()函数实现

处理了循环引用key为symbol类型的情况

function copy(obj,appeard=new Map()) {
	if (!(obj instanceof Object)) return obj//如果是元素数据类型
    if (appeard.has(obj)) return appeard.get(obj)//如果已经出现过

    let result=Array.isArray(obj)?[]:{}
    appeard.set(obj,result)//将新对象放入map

    //遍历所有属性进行递归拷贝
    ;[...Object.keys(obj),...Object.getOwnPropertySymbols(obj)]
    	.forEach(key=>result[key]=copy(obj[key],appeard))

    return result
}
JSON.stringify
  • 只能处理纯JSON数据
  • 有几种情况会发生错误
    • 包含不能转成 JSON 格式的数据
    • 循环引用
    • undefined,NaN, -Infinity, Infinity 都会被转化成null
    • RegExp/函数不会拷贝
    • new Date()会被转成字符串
new=JSON.parse(JSON.stringify(old))

字符串

去除空格 – trim()

function myTrim(str) {
	return str.replace(/(^s+)|(s+$)/g,'')//将前空格和后空格替换为空
}
function myTrim(str) {//记录前后空格的个数,最后对字符串进行截取
	let first=0,last=str.length
	for (let i in str) {
		if (str[i]===' ') {
			first++
		} else {
			break
		}
	}
	for (let i=last;i>first;i--) {
		if (str[i]===' ') {
			last--
		} else {
			break
		}
	}
	return str.substr(first,last-first)
}

字符串全排列

广度优先实现

function combine(str) {//抽出一个字符s,对其余的进行排列,将s放在每种排列开头
	if (str.length===1) return [str]
	let results=[]
	for (let i in str) {
		for (let s of combine(str.slice(0,i)+str.slice(1+(+i)))) {
			results.push(str[i]+s)
		}
	}
    //可能会出现类似"aa"=>[aa,aa,aa,aa]的情况,需要去重
	return [...new Set(results)]
}

深度优先实现

function combine(str) {//记录已经使用过的字符,深度优先访问所有方案
	let result=[]
	;(function _combine(str,path=''){
		if (str.length===0) return result.push(path)
		for (let i in str) {
			_combine(str.slice(0,i)+str.slice((+i)+1,str.length),path+str[i])
		}
	})(str)
    //可能会出现类似"aa"=>[aa,aa,aa,aa]的情况,需要去重
	return [...new Set(result)]
}

排序和查找

插入排序

function sort(arr) {//原地
	for (let i in arr) {//选一个元素
		while (i>0&&arr[i]<arr[i-1]) {//向前移动到合适的位置
			[arr[i],arr[i-1]]=[arr[i-1],arr[i]]
			i--
		}
	}
}

归并排序

function sort(arr) {
	if (arr.length===1) return arr

	//分成两部分
	let mid=Math.floor(arr.length/2)
	let [part1,part2]=[sort(arr.slice(0,mid)),sort(arr.slice(mid))]

	//对比+合并
	let result=[]
	while (part1.length>0&&part2.length>0)
		result.push((part1[0]<part2[0]?part1:part2).shift())
	return [...result,...part1,...part2]
}

快速排序

function sort(arr) {
	if (arr.length<=1) return arr

    //选基准值
	let mid_pos=arr.length>>1
	let mid=arr.splice(mid_pos,1)[0]

	let left=[],right=[]

    //和基准值比较,分别插入left,right数组
	arr.forEach(item=>(item<=mid?left:right).push(item))

	return [...sort(left),mid,...sort(right)]//递归调用排序
}

二分查找

function search(arr,target) {//循环写法,不断移动左右指针,缩小范围
	let left=0,right=arr.length-1

	while (left<=right) {
		const mid_pos=Math.floor((left+right)/2)
		const mid_val=arr[mid_pos]

		if (target===mid_val) {
			return mid_pos
		} else if (target>mid_val) {
			left=mid_pos+1
		} else {
			right=mid_pos-1
		}
	}
	return -1
}

找出出现次数最多的元素 – getMostItem()

function getMost(arr) {
	//计数
	let map=new Map()
	arr.forEach(item=>{
		if (map.has(item)) {
			map.set(item,map.get(item)+1)
		} else {
			map.set(item,1)
		}
	})

	//找出出现最多
	let [max_vals,max_num]=[[arr[0]],map.get(arr[0])]
	map.forEach((count,item)=>{
		if (count>max_num){
			max_vals=[item]
			max_num=count
		} else {
			max_vals.push(item)
		}	
	})
	return max_vals
}

console.log(getMost(['1', '2', '3', '3', '55', '3', '55', '55']))

功能函数实现

setTimeout实现setInterval

function myInterval(fn,interval,...args) {
	let context=this
	setTimeout(()=>{
		fn.apply(context,args)
		myInterval(fn,interval,...args)//别忘了为它传入参数
	},interval)
}


myInterval((num)=>console.log(num),500,10)

函数柯里化

function sum(...args1){
    return function (...args2) {
        return [...args1,...args2].reduce((p,n)=>p+n)
    }
}
console.log(sum(1, 2, 2)(7))

防抖 节流

实现了两个加工方法,返回一个加工后的防抖/节流函数

防抖

function debounce(fn,delay) {
	let timer=null
	return function (){
		if (timer) clearTimeout(timer)
		timer=setTimeout(()=>fn.call(...arguments),delay)//别忘了为它传入参数
	}
}

节流

function throttle(fn,delay) {
	let flag=true
	return function() {
		if (!flag) return

		flag=false
		setTimeout(()=>{
			fn(...arguments)//别忘了为它传入参数
			flag=true
		},delay)
	}
}

数据结构

单链表

function Node(element) {//结点类
	[this.element,this.next]=[element,null]
}

class LinkList {//链表类
	constructor() {
		this.length=0
		this.head=new Node()
		this.tail=new Node()
		this.head.next=this.tail
	}
	get_all() {
		let result=[]
		let now=this.head
		while (now.next!==this.tail) {
			now=now.next
			result.push(now.element)
		}
		return result
	}
	unshift(element) {//开头添加
		let node=new Node(element)
		node.next=this.head.next
		this.head.next=node
	}
	shift(){//开头删除
		let node=this.head.next
		this.head.next=this.head.next.next
		return node.element
	}
}
let list=new LinkList()
list.unshift(15)
list.unshift(16)
list.unshift(17)
console.log(list.shift())//17
console.log(list.get_all())//[ 16, 15 ]

设计模式

发布订阅模式

class Observer {
	constructor() {
		this.events={}//事件中心
	}
	publish(eventName,...args) {//发布=>调用事件中心中对应的函数
		if (this.events[eventName])
			this.events[eventName].forEach(cb=>cb.apply(this,args))
	}
	subscribe(eventName,callback) {//订阅=>向事件中心中添加事件
		if (this.events[eventName]) {
			this.events[eventName].push(callback)
		} else {
			this.events[eventName]=[callback]
		}
	}
	unSubscribe(eventName,callback) {//取消订阅
		if (events[eventName])
			events[eventName]=events[eventName].filter(cb=>cb!==callback)
	}
}

JS原生API实现

bind() call() apply()

apply()

Function.prototype.myApply=function(context,args) {
	context.fn=this//为context设置函数属性
	let result=context.fn(...args)//调用函数
	delete context.fn//删除context的函数属性
	return result
}

call()

//除了...args
//和apply都一样
Function.prototype.myCall=function(context,...args) {
	context.fn=this
	let result=context.fn(...args)
	delete context.fn
	return result
}

bind()

Function.prototype.myBind=function(context,args1) {//使用[闭包+apply]实现
	return (...args2)=>this.apply(context,[...args1,...args2]);
}

InstanceOf

function myInstanceOf(son,father) {//沿着父亲的原型链向上查找是否有儿子的原型
	while (true) {
		son=son.__proto__
		if (!son) return false
		if (son===father.prototype) return true
	}
}

myInstanceOf([], Array)  // true

new

function myNew(constructor_fn,...args) {
	//构造新的空对象
	let new_obj={}
	new_obj.__proto__=constructor_fn.prototype

	let result=constructor_fn.apply(new_obj,args)
	//如果构造函数没有返回一个对象,则返回新创建的对象
	//如果构造函数返回了一个对象,则返回那个对象
	//如果构造函数返回原始值,则当作没有返回对象
	return result instanceof Object?result:new_obj
}



function Animal(name) {
  this.name = name;
}

let animal = myNew(Animal, 'dog');
console.log(animal.name)  // dog

reduce() forEach()

reduce()

api用法:

arr.reduce(function(prev, cur, index, arr){}, initialValue)

实现:

Array.prototype.myReduce=function(fn,init_val){
	let [val,idx]=init_val?[init_val,0]:[this[0],1]//设置初始值
	for (let i=idx,len=this.length;i<len;i++) {
		val=fn(val,this[i],i,this)//循环并迭代结果
	}
	return val
}

console.log([1,2,3,4,5].reduce((pre,item)=>pre+item,0)) // 15

forEach()

api用法:

[1,3,5,7,9].myForEach(function(item,index,arr) {
    console.log(this)
},15)

实现:

Array.prototype.myForEach=function(fn,temp_this) {
    for (let i=0,len=this.length;i<len;i++){
        fn.call(temp_this,this[i],i,this)//循环数组元素,为回调函数传入参数
    }
}

Promise

Promise.all()

Promise.prototype.all=function(promiseList) {
    return new Promise((resolve,reject)=>{
        if (promiseList.length===0) return resolve([])
        let result=[],count=0

        promiseList.forEach((promise,index)=>{
            Promise.resolve(promise).then(value=>{
                result[index]=value
                if (++count===promiseList.length) resolve(result)
            },reason=>reject(reason))
        })
    })
}

ES6所有API完整实现

通过Promise/A+ test测试

实现细节过多,还请参照Promise/A+规范阅读

也可以直接参考我关于promise的笔记

深入理解promise

https://blog.csdn.net/weixin_43758603/article/details/109641486

class Promise {
	constructor(task) {
		this.status="pending"
		this.value=undefined
		this.reason=undefined
		this.fulfilled_callbacks=[]
		this.rejected_callbacks=[]

		try {
			task(this._resolve,this._reject)
		} catch (error) {
			this._reject(error)
		}
	}
	then(onFulfilled,onRejected){
		if (this.status==='fulfilled') {
			let promise2=new Promise((resolve,reject)=>{
				setTimeout(()=>{
					try {
						if (!this._isFunction(onFulfilled)) {
							resolve(this.value)
						} else {
							this._resolvePromise(promise2,onFulfilled(this.value))
						}
					} catch (error) {
						reject(error)
					}
				},0)
			})
			return promise2
		} else if (this.status==='rejected') {
			let promise2=new Promise((resolve,reject)=>{
				setTimeout(()=>{
					try {
						if (!this._isFunction(onRejected)) {
							reject(this.reason)
						} else {
							this._resolvePromise(promise2,onRejected(this.reason))
						}
					} catch (error) {
						reject(error)
					}
				},0)
			})
			return promise2
		} else if (this.status==='pending')  {
			let promise2=new Promise((resolve,reject)=>{
				this.fulfilled_callbacks.push(()=>{
					try {
						if (!this._isFunction(onFulfilled)) {
							resolve(this.value)
						} else {
							this._resolvePromise(promise2,onFulfilled(this.value))
						}
					} catch (error) {
						reject(error)
					}
				})
				this.rejected_callbacks.push(()=>{
					try {
						if (!this._isFunction(onRejected)) {
							reject(this.reason)
						} else {
							this._resolvePromise(promise2,onRejected(this.reason))
						}
					} catch (error) {
						reject(error)
					}
				})
			})
			return promise2
		}
	}
	catch=onRejected=>this.then(null,onRejected)

	finally=onFinished=>this.then(onFinished,onFinished)

	static deferred(){
		let deferred={}
		deferred.promise=new Promise((resolve,reject)=>{
			deferred.resolve=resolve
			deferred.reject=reject
		})
		return deferred
	}
	static resolve(value) {
		if (value instanceof Promise) return value
		return new Promise(resolve=>resolve(value))
	}
	static reject=reason=>{return new Promise((resolve, reject)=>reject(reason))}

	static all(promiseList) {
		return new Promise((resolve,reject)=>{
			if (promiseList.length===0) return resolve([])
			let result=[],count=0

			promiseList.forEach((promise,index)=>{
				Promise.resolve(promise).then(value=>{
					result[index]=value
					if (++count===promiseList.length) resolve(result)
				},reason=>reject(reason))
			})
		})
	}
	static race(promiseList) {
		return new Promise((resolve,reject)=>{
			if (promiseList.length===0) return resolve()
			promiseList.forEach(promise=>{
				Promise.resolve(promise)
					.then(value=>resolve(value),reason=>reject(reason))
			})
		})
	}
	static allSettled(promiseList) {
		return new Promise(resolve=>{
			let result=[],count=0
			if (len===0) return resolve(result)

			promiseList.forEach((promise,i)=>{
				Promise.resolve(promise).then(value=>{
					result[i]={
						status:'fulfilled',
						value:value
					}
					if (++count===promiseList.length) resolve(result)
				},reason=>{
					result[i]={
						status:'rejected',
						reason:reason
					}
					if (++count===promiseList.length) resolve(result)
				})
			})
		})
	}
	_resolve=value=>{
		if (this.status!=='pending') return
		setTimeout(()=>{
			this.status ='fulfilled'
			this.value = value
			this.fulfilled_callbacks.forEach(cb=>cb(this.value))
		},0)
	}
	_reject=reason=>{
		if (this.status!=='pending') return
		setTimeout(()=>{
			this.reason = reason
			this.status ='rejected'
			this.rejected_callbacks.forEach(cb=>cb(this.reason))
		},0)
	}
	_isFunction=f=>Object.prototype.toString.call(f).toLocaleLowerCase()==='[object function]'
	
	_isObject=o=>Object.prototype.toString.call(o).toLocaleLowerCase()==='[object object]'

	_resolvePromise(promise,x){
		if (promise===x) {
    		promise._reject(new TypeError('cant be the same'))
    		return
		}
		if (x instanceof Promise) {
			if (x.status==='fulfilled') {
				promise._resolve(x.value)
			} else if (x.status==='rejected') {
				promise._reject(x.reason)
			} else if (x.status==='pending') {
				x.then(value=>{
					this._resolvePromise(promise,value)
				},reason=>{
					promise._reject(reason)
				})
			}
			return
		}
		if (this._isObject(x)||this._isFunction(x)) {
			let then
			try {
				then=x.then
			} catch (error) {
				promise._reject(error)
				return
			}
			if (this._isFunction(then)) {
				let called=false
				try {
					then.call(x,value=>{
						if (called) return
						called=true
						this._resolvePromise(promise,value)
					},reason=>{
						if (called) return
						called=true
						promise._reject(reason)
					})
				} catch (error) {
					if (called) return
					promise._reject(error)
				}
			} else {
				promise._resolve(x)
			}
		} else {
			promise._resolve(x)
		}
	}
}
module.exports = Promise

HTTP请求

AJAX封装

function ajax(method,url,params,callback) {
	//对参数进行处理
	method=method.toUpperCase()
	let post_params=null
	let get_params=''
	
	if (method==='GET') {
		if (typeof params==='object') {
			let tempArr=[]
			for (let key in params) {
				tempArr.push(`${key}=${params[key]}`)
			}
			params=tempArr.join('&')
		}
		get_params=`?${params}`
	} else {
		post_params=params
	}

	//发请求
	let xhr=new XMLHttpRequest()

	xhr.onreadystatechange=function(){
		if (xhr.readyState!==4) return
		callback(xhr.responseText)
	}

	xhr.open(method,url+get_params,false)
	if (method==='POST')
		xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')

	xhr.send(post_params)	
}

ajax('get','https://www.baidu.com',{id:15},data=>console.log(data))

JSONP

function jsonp(url, params_obj, callback) {
	//创建一个供后端返回数据调用的函数名
	let funcName = 'jsonp_' + Data.now() + Math.random().toString().substr(2, 5)

	//将参数拼接成字符串
	if (typeof params==='object') {
		let temp=[]
		for (let key in params) {
			temp.push(`${key}=${params[key]}`)
		}
		params=temp.join('&')
	}

	//在html中插入<script>资源请求标签
	let script=document.createElement('script')
	script.src=`${url}?${params}&callback=${funcName}`
	document.body.appendChild(script)

	//在本地设置供后端返回数据时调用的函数
	window[funcName]=data=>{
		callback(data)

		delete window[funcName]
		document.body.removeChild(script)
	}
}

//使用方法
jsonp('http://xxxxxxxx',{id:123},data=>{
	//获取数据后的操作
})

js插入html中标签的内容

<script src="https://www.liuzhuocheng.com?callback=funcName"></script>

后端返回的<script>资源的内容

<script src="https://www.liuzhuocheng.com?callback=funcName">
	funcName('datadatadatadatadatadatadatadata')
</script>


喜欢 (0)