记录一下准备面试的过程中学习和积累的一些JS手写题,写的过程也是对自己学习的一个输出,在写文章的过程中同时也发现了一些不足,继续努力!🎉全文代码都打上了详细注释,可放心食用!
防抖 防抖是指在一个函数被频繁触发时,只有在最后一次触发后的指定时间内没有再次被触发,才会执行该函数。也就是说,防抖会“等待”一段时间以确保在这段时间内函数没有被再次调用,然后才执行它。
大概步骤如下:
函数被触发时,记录当前的this
判断该函数是否在等待执行,如果正在等待执行,则清除定时器
重新创建定时器
在定时器中执行该函数
1 2 3 4 5 6 7 8 9 10 11 12 function devounce (fn,t ){ let timer = null return function (...args ){ const context = this if (timer) clearTimeout (timer) timer = setTimeout (()=> { fn.apply (context,args) },t) } }
节流 节流是指在一定时间内只执行一次函数,如果在这段时间内再次触发函数,则不会执行。与防抖不同,节流不等待最后一次触发后的时间,而是保证在固定的时间间隔内只执行一次函数。
大致步骤如下:
函数被触发时,记录当前的this
判断该函数在固定时间间隔中是否执行完毕(即timer==null
为执行完毕)
若执行完毕,则重新开启定时器
在定时器中执行该函数,并在函数执行完毕后将timer=null
1 2 3 4 5 6 7 8 9 10 11 function throttle (fn,t ){ let timer = null return function (...arge ){ const context = this if (timer === null ) timer = setTimeout (()=> { fn.apply (context,args) timer = null },t) } }
浅拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const o = {...obj}Object .assign (o1,obj)const o2 = Object .create (obj)const arr = [1 ,2 ,3 ]let a1 = [].concat (arr)let a2 = arr.slice ()
深拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function deepClone (obj ){ if ((obj instanceof Object ) === flase) return let newObj = obj instanceof Array ? [] : {} for (let key in obj){ if (obj.hasOwnProperty (key)){ newObj[key] = deepClone (obj[key]) } } return newObj } function deepClone2 (obj ){ return JSON .parse (JSON .stringify (obj)) }
函数柯里化 1 2 3 4 5 6 7 8 9 10 11 function curry (fn,...args1 ){ if (args1.length >= fn.length ) return fn (...args1) else { return function (...args2 ){ return curry (fn,...args1,...args2) } } }
new new做了哪些事情呢
创建一个新的对象
继承父类原型上的方法
添加父类的属性到新的对象上并初始化,保存方法的返回结果
如果返回结果是一个对象,则返回这个对象;否则返回新创建的对象
1 2 3 4 5 6 function Mynew (constructor,...args ){ let obj = {} obj.__proto__ = constructor.prototype let result = constructor.apply (obj,args) return result instanceof Object ? result : obj }
call、apply和bind call
方法是绑定在Function
的原型对象上的
判断调用call
的对象是否是函数,不是则报错
判断this
所要指向的对象是否存在,不存在则指向window
使用Symbol
创建新的属性
将函数绑定到目标对象的新属性中
传入参数进行函数的调用
删除新增的属性
apply
apply和call十分相似,不同之处在于传入的参数是数组
所以需要对数组进行一个处理
bind
bind
不会马上调用函数,所以需要返回一个函数
在返回的函数中进行改变目标对象后的函数的调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 Function .prototype .myCall = function (target,...args ){ if (typeof this !== 'function' ){ throw new TypeError ("myCall被调用的对象必须是函数" ) } target = target || window const symbolKey = new Symbol () target[symbolKey] = this let result = target[symbolKey](...args) delete target[symbolKey] return result } Function .prototype .myApply = function (target,args ){ if (typeof this !== 'function' ){ throw new TypeError ("myApply被调用的对象必须是函数" ) } if (args && Array .isArray (args) === false ){ throw new TypeError ("第二个参数必须是数组" ) } args = args || [] target = target || window symbolKey = new Symbol () target[symbolKey] = this let result = target[symbolKey](...args) delete target[symbolKey] return result } Function .prototype .myBind = function (target,...args1 ){ if (typeof this !== 'function' ){ throw new TypeError ("myApply被调用的对象必须是函数" ) } target = target || window symbolKey = new Symbol () target[symbolKey] = this return function (...args2 ){ let result = target[symbolKey](...args1,...args2) return result } }
Promise相关 Promise.all 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function myPromiseAll (promiseArray ) { if (!Array .isArray (promiseArray)) { return Promise .reject (new Error ('不是数组' )) } return new Promise ((resolve, reject ) => { let completed = 0 let result = [] for (let promise of promiseArray) { promise = Promise .resolve (promise) promise.then (value => { completed++ result.push (value) if (completed === promiseArray.length ) { resolve (result) } }, reason => { reject (reason) }) } }) } myPromiseAll ([ Promise .resolve (1 ), Promise .resolve (2 ), Promise .resolve (3 ) ]).then (values => { console .log (values); })
Promise.race 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function myPromiseRace (promiseArray ) { if (!Array .isArray (promiseArray)) { return Promise .reject (new Error ('不是数组' )) } return new Promise ((resolve, reject ) => { for (let i = 0 ; i < promiseArray.length ; i++) { let promise = Promise .resolve (promiseArray[i]) promise.then ( value => { resolve (value) }, reason => { reject (reason) }) } }) }
Promise.resolve / Promise.reject 1 2 3 4 5 6 7 8 9 10 11 12 function myPromiseResolve (value ){ if (value instanceof Promise ) return value else return new Promise (resolve => resolve (value)) } function myPromiseReject (reason ){ return new Promise ((resolve,reject )=> reject (reason)) }
类型检测 instanceof 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function myInstanceof (obj,constructor ){ if (!(obj instanceof Object )) return false if (typeof constructor != 'function' || !constructor.prototype ){ return false } let left = obj.__prop__ const right = constructor.prototype while (true ){ if (left === null ) return false if (left === right) return true left = left.__proto__ } return false }
typeof 1 2 3 4 function myTypeof (obj ){ return Object .prototype .toString .call (obj).slice (8 ,-1 ).toLowerCase () }
数组操作 数组分类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Array .prototype .group = function (fn ) { let result = {} for (let i = 0 ; i < this .length ; i++) { let key = fn (this [i]) result[key] ? result[key].push (this [i]) : result[key] = [this [i]] } return result } let arr = [1 , 2 , 3 , 4 , 5 , 6 ]let obj = arr.group ((item ) => { return item % 2 === 0 ? '偶数' : '奇数' }) console .log (obj)
数组扁平化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const arr = [1 , 2 , [1 , 2 , 3 , [4 , 5 , 6 ]]]let arr1 = arr.flat (Infinity )function flatten1 (arr ) { return arr.reduce ((pre, cur ) => { return pre.concat (Array .isArray (cur) ? flatten1 (cur) : cur) }, []) } function flatten2 (arr ) { let result = [] for (let i = 0 ; i < arr.length ; i++) { if (Array .isArray (arr[i])) { result = result.concat (flatten1 (arr[i])) } else { result.push (arr[i]) } } return result }
手写数组方法
方法应该挂载在Array
的prototype
上
方法接收一个回调函数,大多数数组操作方法会接收一个新的this
指向(下面代码中用thisArg
接收)
先判断调用此方法的对象是不是一个数组
再判断callback
参数接收的是不是一个函数
循环遍历数组,在循环中使用call
方法来调用callback
,并传入四个参数(callback
函数中的指向,遍历到的数组元素,遍历到的数组下标,原数组)
根据相应的数组操作决定是否有返回值,返回什么
forEach 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Array .prototype .myForEach = function (callback,thisArg ){ if (this instanceof Array === false ){ throw new Error ("myForEach只能被数组调用" ) } if (typeof callback != 'function' ){ throw new TypeError ("myForEach requires a function as the argument" ) } for (let i = 0 ;i < this .length ;i++){ callback.call (thisArg || this ,this [i],i,this ) } }
map 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Array .prototype .myMap = function (callback,thisArg ){ if (this instanceof Array === false ){ throw new Error ("myMap只能被数组调用" ) } if (typeof callback != 'function' ){ throw new TypeError ("myMap requires a function as the argument" ) } let result = [] for (let i = 0 ;i < this .length ;i++){ result[i] = callback.call (thisArg || this ,this [i],i,this ) } return result }
filter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Array .prototype .myFilter = function (callback,thisArg ){ if (this instanceof Array === false ){ throw new Error ("myFilter只能被数组调用" ) } if (typeof callback != 'function' ){ throw new TypeError ("myFilter requires a function as the argument" ) } let result = [] for (let i = 0 ;i < this .length ;i++){ let flag = callback.call (thisArg || this ,this [i],i,this ) if (flag) result.push (this [i]) } return result }
some 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Array .prototype .mySome = function (callback,thisArg ){ if (this instanceof Array === false ){ throw new Error ("mySome只能被数组调用" ) } if (typeof callback != 'function' ){ throw new TypeError ("mySome requires a function as the argument" ) } for (let i = 0 ;i < this .length ;i++){ let flag = callback.call (thisArg || this ,this [i],i,this ) if (flag) return true } return false }
reduce 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Array .prototype .myReduce = function (callback,initialValue ){ if (!(this instanceof Array )){ throw new Error ("myReduce只能被数组调用" ) } if (typeof callback != 'function' ){ throw new TypeError ("myReduce requires a function as the argument" ) } let startIndex = 0 ,sum = arguments .length > 1 ? initialValue : undefined if (arguments .length === 1 ){ if (this .length === 1 ) throw new Error ("Reduce of empty array with no initial value" ) startIndex = 1 sum = this [0 ] } for (let i = startIndex;i < this .length ;i++){ sum = callback.call (this ,sum,this [i],this ) } return sum }
虚拟DOM转真实DOM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 const vnode = { tag :'div' , attrs :{ id :'myDiv' }, children :[ 123 , "123456" , { tag :'div' ,children :['Hello' ] }, { tag :'span' ,children :['World' ] } ] } function createElementFromVNode (vnode ){ const dom = document .createElement (vnode.tag ) if (vnode.attrs ){ Object .keys (vnode.attrs ).forEach (key => { dom.setAttribute (key,vnode.attrs [key]) }) } if (vnode.children ){ vnode.children .forEach (childVode => { if (typeof childVode === 'number' ){ childVode = String (childVode) } if (typeof childVode === 'string' ){ dom.appendChild (document .createTextNode (childVode)) } else { dom.appendChild (createElementFromVNode (childVode)) } }) } return dom } const realDom = createElementFromVNode (vnode)document .body .appendChild (realDom)