js面试题手写代码
8.手写promise (实现异步调用、链式调用.then .catch、API实现:Promise.resolve、Promise.reject、Promise.all、Promise.race)
function myNew(fn,...args){ if(typeof fn !== 'function){ return TypeError('fn must be a function') } //let obj = {} 创建一个空对象 //obj.__proto__ = fn.prototype 将空对象的原型 设置为构造函数fn的 prototype 原型属性 //创建对象,并指定原型,也可以用下面的代码 let obj = Object.create(fn.prototype) //Object.create(proto) 创建一个对象,并指定其原型 //执行构造函数,并改变this指向 let result = fn.apply(obj,args) //let result = fn.call(obj,...args) return typeof result === 'object' ? result : obj }
/**
* 用法:instanceof 运算符是用检测构造函数的 prototype 属性是否出现在 某个实例对象的原型链上
* 思路:
* 1.通过Object.getPrototypeOf获取 obj 原型
* 2. 循环判断 objPrototype 是否和 构造函数的原型相等;如果相等返回true;
* 3. 如果不相等就继续obj原型上的原型(Object.getPrototypeOf(objPrototype))
* 4. 判断objPrototype是否为 null,如果为null 说明不存在,返回false
*/
function myInstanceof(obj, fn){ if(typeof fn !== 'function'){ return false } //let objPrototype = obj.__proto__; let objPrototype = Object.getPrototypeOf(obj) //Object.getPrototypeOf() 和obj.__proto__可以互换使用,单位了代码的健壮he可维护性,建议使用Object.getPrototypeOf() while(true){ if(objPrototype === null){ return false } if(objPrototype === fn.prototype){ return true } //objPrototype = objPrototype.__proto__ objPrototype = Object.getPrototypeOf(obj) } }
function Ajax(method,url){ return new Promise((resolve,reject) =>{ let xhr = new XMLHttpRequest() xhr.open(method || 'GET',url,true) //async: true (异步) false(同步) xhr.send() //发送 xhr.onreadystatechange = function(res){ if(xhr.readyState == 4){ if(xhr.status == 200){ console.log(xhr.responseText,'xhr.responseText') //成功 resolve(JSON.parse(xhr.responseText)) }else{ //失败 reject(xhr.status) } } } }) }
用法:
Ajax('get','https://testdsmobile.iuoooo.com/api/Products?categoryCode=0&limit=20&page=1&sorting=&roleName=&storeId=&areaCode=110108&t=1725414954615') .then(res=>{ console.log('success',res) }) .catch(res=>{ console.log('err',res) })
1.递归实现深拷贝
function deepCopy(obj){ if(!obj || typeof obj !== 'object'){ return obj } //如果是日期或者正则对象则返回一个新对象 if(obj instanceof Date){ return new Date(obj) } if(obj instanceof RegExp){ return new RegExp(obj) } const newObj = Array.isArray(obj) ? [] : {} for(let key in obj){ if(obj.hasOwnProperty(key)){ newObj[key] = deepCopy(obj[key]) } } return newObj }
上面的实现有缺陷,如果存在循环引用,就会使程序进入无限循环,最后导致堆栈溢出的问题
循环引用:两个对象存在互相引用的关系,形成一个闭环。例如:
var obj2 ={ name:'ss', age:4 } var obj1 ={ ceshi: '123', test: obj2 } obj2.test2 = obj1
如果对上面的obj1做深度拷贝 const newobj = deepCopy(obj1) 就会出现堆栈溢出的问题
2.深度拷贝优化-解决循环引用
/**
* 思路:
* 1.将 obj 放入 map 中,map中key可以是一个对象
* 2.每次递归调用时判断 对象 是否存在 map中,如果存在就不再递归调用了,直接返回数据
*
* */
function deepCopy(obj,map = new Map()){ if(!obj || typeof obj !== 'object'){ return obj } //如果是日期或者正则对象则返回一个新对象 if(obj instanceof Date){ return new Date(obj) } if(obj instanceof RegExp){ return new RegExp(obj) } //判断 obj 是否在map中存在,如果存在就不再递归调用,而是直接返回数据(解决循环引用的问题,导致无限循环) if(map.has(obj)){ return map.get(obj) } const newObj = Array.isArray(obj) ? [] : {} //放入map中,记录当前的对象,避免重复拷贝,循环引用 map.set(obj,newObj) for(let key in obj){ if(obj.hasOwnProperty(key)){ newObj[key] = deepCopy(obj[key],map) } } return newObj }
/** * 手写 apply * 用法: 用于调用一个函数,并指定函数内部this指向,第一个参数是this指向对象,第二个参数是一个数组 * 思路: * 1. 判断 this 是否指向一个函数,只有函数才可以执行 * 2. 获取传入的context 上下文,也就是我们要把this指向的对象。如果不存在就默认指向window * 3. 将当前this 也就是歪不需要执行的函数 绑定到 context 的一个fn属性上 * 4. 执行 fn 函数, 判断参数args 是否有,如果有将参数参入再执行,如果没有就直接执行fn函数 * 5. 删除 context 对象的 fn 属性,并将result 返回 * */ Function.prototype.myApply = function(context,args){ if(typeof this !== 'function'){ return new TypeError('type error') } context = context || window context.fn = this const result = args ? context.fn(...args) : context.fn() // 调用完后需要删除,不然context对象会多一个fn属性,不太合适 delete context.fn return result }
/** * 手写 call * 用法:用于调用一个函数,并指定函数的this指向。第一个参数是this指向对象,从第二个参数开始,每个参数依次传入函数 * 思路: * 1. 判断 this 是否是一个函数 * 2.获取 context 上下文,也就是我们要指向的对象,如果不存在就指向window * 3.将当前 this 也就是外部需要执行的函数 绑定到context上一个fn属性上 * 4.执行fn, 传入...args,确保参数位置正确 * 5. 删除context 的fn属性 * 6. 返回函数执行的结果result * */ Function.prototype.myCall = function(context,...args){ if(typeof this !== 'function'){ return new TypeError('type error') } context = context || window context.fn = this const result = context.fn(...args) delete context.fn return result }
/** * 手写 bind * 用法:bind() 方法创建一个新函数,在bind() 被调用时,这个新函数的 this 被指定为bind()的第一个参数,其余参数做为新函数的参数,供新函数调用时使用 * 思路: * 1. 判断 this 是否指向一个函数 * 2. 获取传入的context 上下文,也就是我们要指向的对象。如果不存在就指向window * 3. 将当前this 也就是外部需要执行的函数,绑定到context 一个fn 属性上 * 4. 返回一个函数,供外部调用。执行新的函数时,仍可以传入新的参数 * 5. 执行在闭包内缓存的 fn 函数,并将两次参数一起传入 * 7. 返回result (注意:不需要删除fn,如果删除了新函数就只能调用一次了,再次调用就找不到方法了) * */ Function.prototype.myBind = function(context,...args1){ if(typeof this !== 'function'){ return new TypeError('type error') } context = context || window context.fn = this return function (...args2){ const result = context.fn(...args1,...args2) delete context.fn return result } }
八、手写promise (实现异步调用、链式调用.then .catch、API实现:Promise.resolve、Promise.reject、Promise.all、Promise.race)
/** * 手写Promise * 1.初始化和异步调用 * 2.实现链式调用 .then .catch * 3. api实现 resolve reject race all * * */ const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise{ //定义两个数组,存储回调 resolveQueue = []; rejectQueue = []; status = PENDING; // 状态 value = null constructor(executor){ const resolve = (val) =>{ if(this.status === PENDING){ //变更状态 this.status = FULFILLED this.value = val //执行所有的回调并清空 while(this.resolveQueue.length){ const callback = this.resolveQueue.shift() //从前面取 callback(val) } } } const reject = (val)=>{ if(this.status === PENDING){ //变更状态 this.status = REJECTED this.value = val //执行所有的回调 while(this.rejectQueue.length){ const callback = this.rejectQueue.shift() callback() } } } //模拟异步执行 const _resolve = (val)=>{ setTimeout(()=>{ resolve(val) },0) } const _reject =(val)=>{ setTimeout(()=>{ reject(val) },0) } // new Promise() 时立即执行 executor,并传入resolve 和 reject 两个参数 try{ executor(_resolve,_reject) }catch(err){ reject(err) } } /** * 实现then 方法 * then 里面的回调方法不是立即执行的,而是异步执行的 * 实现链式调用:返回一个Promise,并且下一个then能去到上一个then的返回值 * */ // 实现then 方法。 存储回调 (then) then(resolveFun,rejectFun){ // 返回全新 promise resolveFun = typeof resolveFun === 'function' ? resolveFun : (val)=>val rejectFun = typeof rejectFun === 'function'? rejectFun : (val)=>val return new MyPromise((resolve,reject)=>{ //定义一个全新的方法 const _resolveFun = (val)=>{ //先执行老方法,获取结果,根据结果的类型进行不同的处理 //如果结果返回普通值,只需要resolve(普通值)就可以了 //如果结果返回的是Promise 对象,就调用.then方法,将resolve和reject传递过去(传递过去后,在外部调用resolve或者reject,就相当于调用里面的resolve 或者 reject) try{ const result = resolveFun(val) if(result instanceof MyPromise){ result.then(resolve,reject) }else{ //返回的是一个值 resolve(result) } }catch(err){ reject(err) } } const _rejectFun = (val)=>{ try{ const result = rejectFun(val) if(result instanceof MyPromise){ result.then(resolve,reject) }else{ resolve(result) } }catch(err){ reject(err) } } //状态判断,不是panding状态立即回调,否则加入队列异步回调 if(this.status === PENDING){ //异步的处理 //将回调添加各自队列里,等待时机触发执行 this.resolveQueue.push(_resolveFun) this.rejectQueue.push(_rejectFun) }else if(this.status === FULFILLED){ _resolveFun(this.value) }else { _rejectFun(this.value) } }) } /** * 静态的 resolve 方法 * */ static resolve(data){ return new MyPromise((resolve,reject)=> resolve(data)) } /** * 静态的 reject 方法 * */ static reject(reason){ return new MyPromise((resolve,reject)=> reject(reason)) } /** * 静态的 race 方法 * */ static all(promiseArr){ if(!Array.isArray(promiseArr)){ new TypeError('you must pass an array to all') } const result = [] const length = promiseArr.length let count = 0 return new MyPromise((resolve,reject)=>{ for(let i = 0; i < length; i++){ promiseArr[i].then(res=>{ result[i] = res count++; if(count === promiseArr.length){ //最后一个promise resolve(result) } }).catch(err=>{ reject(err) }) } }) } /** * 静态的 race 方法 **/ static race(promiseArr){ return new MyPromise((resolve,reject)=>{ if(!Array.isArray(promiseArr)){ throw new TypeError('must pass an array to race') } for(let p of promiseArr){ p.then(res=>{ resolve(res) }).catch(err=>{ reject(err) }) } }) } }
九、手写深度比较isEqual
//判断是否是对象 function isObject(obj){ return typeof obj === 'object' && obj !== null } //全相等 function isEqual(obj1,obj2){ if(!isObject(obj1) || !isObject(obj2)){ return obj1 === obj2 } //两个都是引用类型 // 如果传入是两个相同对象,就没必要再做比较 if(obj1 === obj2){ return true } //两个引用类型不相等 if(Object.keys(obj1).length !== Object.keys(obj2).length){ return false } for(let key in obj1){ let res = isEqual(obj1[key],obj2[key]) if(!res){ return false } } return true }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!