本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
关注 高级前端进阶,回复 “交流”
加入我们一起学习,天天进步
转载自:等不到你. https://blog.csdn.net/weixin_43758603/article/details/109895826
写在前面
最近我终于找到了一份满意的工作. 准备面试的过程中, 我整理出了一些有用的笔记, 这篇就是其中之一.
既然写好了, 不妨就放在这里分享给大家.
面试通常都有现场写代码的题目, 我基本每次都或多或少的翻车. 有意思的是, 每次面试结束, 自己改不到五分钟就调试出来了.
所以面试中的写代码的过程, 一定不能紧张, 要沉住气慢慢来. 只要不是系统自动检查结果, 只要是面试官看着你写, 就有很大的表现的机会, 哪怕最后做不出来.
我参加的最烦人的面试, 是那种系统判定结果的面试, 只要做不出来, 就绝对不可能通过.
仔细想想, 在二三十场面试中, 很少有我完整写出毫无瑕疵的答案的题目, 但基本也都顺利通过了.
对比来看, 我的同学中, 答案能运行成功但面试没通过的也大有人在.
可以肯定的是, 面试官不会只根据是否能运行成功来评价应聘者.
所以, 只需要顺着正确思路稳稳地做就好了, 不要怕最后运行不成功.
如果实在做不出, 也一定要和面试官说清你当前的进展和思路, 而不是一句 "我不会" 就想结束问题.
当然, 我指的只是前端这个对算法能力要求不强的岗位…
有一场百度的面试我甚至直接和面试官说, 我是个偏感性的人, 喜欢但是不擅长做算法. 都给我通过了.
总的来看, 如果能完全掌握这篇文章的内容, 就足以应付所有前端面试中的手撕代码环节了.
来都来了, 点个赞呗
导读
关于这篇文章, 有几点我想先说清楚, 方便读者更顺利的学习.
这篇文章不适合前端小白阅读, 需要对
JS和ES6有一定了解, 否则遇到一些写法可能不太看得懂因为精力有限, 我只加了较为粗略但足以帮助读者理解的注释, 因为多数题也只有几行代码而已.
如果遇到还不懂的地方, 我认为读者完全可以自己去查询文档来了解为什么这么做, 为什么使用这个函数.
或者, 先查询该问题通常的解决思路, 再回来参考我的实现
代码大量使用了
ES6的语法学习手撕代码, 不只是理解的过程, 更是实践的过程
我在完全掌握 (可以默写出每段代码, 并讲清楚每一行的作用) 以下代码的过程中, 做了以下几件事
参考别人的实现, 结合自己的思路, 写出一个自己的版本
不断对代码进行优化
当你尝试去优化一段代码的时候, 对它的理解和记忆会异常深刻
不看之前的实现, 重新自己实现一次
再和之前的实现做对比, 检查错误
反复阅读和默写, 直到可以完全正确的默写为之
作为一个专业的程序员, 除了工作中的编码, 额外的无实际产出的练习 (反复练习解决一个问题, 反复默写同一段代码), 也是必不可少的.
这就像歌手不可能到了舞台上才去练习自己的声音. 他一定会在平时大量去练声.
这就是我强调要反复敲代码的原因. 别想着平时只要理解, 工作中再去熟能生巧.
工作不是给你练习的地方, 工作是你的舞台.
下文中几乎每一段代码, 都是我反复优化后的结果, 希望可以带给读者新的启发.
我把代码大致分成了几个专题, 一共包含了大致 30 个问题的解决方案
除了文章中的问题, 还有些我没有提到的, 都是频率较低的问题
关于算法题, 除了排序和查找我也基本没有写. 因为算法问题千变万化, 需要的是解决问题的思维, 而不是固定的实现
重要性与顺序无关
有问题可以问我, 我都会回复
目录
DOM
- 事件代理
数组 对象
扁平化
去重 -
unique()拷贝
浅拷贝
深拷贝(
copy()函数实现、JSON.stringify)
字符串
去除空格 -
trim()字符串全排列
广度优先实现
深度优先实现
排序和查找
插入排序
归并排序
快速排序
二分查找
找出出现次数最多的元素 -
getMostItem()
功能函数实现
setTimeout实现setInterval函数柯里化
防抖 节流
数据结构
- 单链表
设计模式
- 发布订阅模式
JS 原生 API 实现
bind()call()apply()InstanceOfnewreduce()forEach()Promise
HTTP 请求
AJAX 封装
JSONP
DOM
事件代理
document.getElementById("father-id").onclick=function(event){ event=event||window.event let target=event.target||event.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())//17console.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) // truenew
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) // dogreduce() 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)) // 15forEach()
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 = PromiseHTTP 请求
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>The End
如果你觉得这篇内容对你挺有启发,我想请你帮我三个小忙:
1、点个 「在看」,让更多的人也能看到这篇内容
2、关注官网 https://muyiy.cn,让我们成为长期关系
3、关注公众号「高级前端进阶」,公众号后台回复 「加群」 ,加入我们一起学习并送你精心整理的高级前端面试题。》》面试官都在用的题库,快来看看《《
“在看”吗?在看就点一下吧