【前端】一网打尽──前端进阶和面试必会的8个手写代码

开发 前端
我们知道在前端进阶和面试的时候,会考察到很多手写源码的问题,这些是通过学习和练习是可以掌握的,下面列举了八个手写的代码系列,希望能够对你有所帮助。

[[401822]]

写在前面

我们知道在前端进阶和面试的时候,会考察到很多手写源码的问题,这些是通过学习和练习是可以掌握的,下面列举了八个手写的代码系列,希望能够对你有所帮助。

1 手写Promise系列

在Promise的学习中,之前也写过相关的分享文章,敬请参见《从小白视角上手Promise、Async/Await和手撕代码》。

1.1 Promise.all

  1. //手写promise.all 
  2. Promise.prototype._all = promiseList => { 
  3.   // 当输入的是一个promise列表 
  4.   const len = promiseList.length; 
  5.   const result = []; 
  6.   let count = 0; 
  7.   //  
  8.   return new Promise((resolve,reject)=>{ 
  9.     // 循环遍历promise列表中的promise事件 
  10.     for(let i = 0; i < len; i++){ 
  11.       // 遍历到第i个promise事件,判断其事件是成功还是失败 
  12.       promiseList[i].then(data=>{ 
  13.         result[i] = data; 
  14.         count++; 
  15.         // 当遍历到最后一个promise时,结果的数组长度和promise列表长度一致,说明成功 
  16.         count === len && resolve(result); 
  17.       },error=>{ 
  18.         return reject(error); 
  19.       }) 
  20.     } 
  21.   }) 

1.2 Promise.race

  1. // 手写promise.race 
  2. Promise.prototype._race = promiseList => { 
  3.   const len = promiseList.length; 
  4.   return new Promise((resolve,reject)=>{ 
  5.     // 循环遍历promise列表中的promise事件 
  6.     for(let i = 0; i < len; i++){ 
  7.       promiseList[i]().then(data=>{ 
  8.         return resolve(data); 
  9.       },error=>{ 
  10.         return reject(error); 
  11.       }) 
  12.     } 
  13.   }) 

1.3 Promise.finally

  1. Promise.prototype._finally = function(promiseFunc){ 
  2.   return this.then(data=>Promise.resolve(promiseFunc()).then(data=>data) 
  3.   ,error=>Promise.reject(promiseFunc()).then(error=>{throw error})) 

2 手写Aysnc/Await

  1. function asyncGenertor(genFunc){ 
  2.   return new Promise((resolve,reject)=>{ 
  3.     // 生成一个迭代器 
  4.     const gen = genFunc(); 
  5.     const step = (type,args)=>{ 
  6.       let next
  7.       try{ 
  8.         next = gen[type](args); 
  9.       }catch(e){ 
  10.         return reject(e); 
  11.       } 
  12.       // 从next中获取done和value的值 
  13.       const {done,value} = next
  14.       // 如果迭代器的状态是true 
  15.       if(done) return resolve(value); 
  16.       Promise.resolve(value).then
  17.         val=>step("next",val), 
  18.         err=>step("throw",err) 
  19.       ) 
  20.     } 
  21.     step("next"); 
  22.   }) 

3 深拷贝

深拷贝:拷贝所有的属性值,以及属性地址指向的值的内存空间。

3.1 丢失引用的深拷贝

当遇到对象时,就再新开一个对象,然后将第二层源对象的属性值,完整地拷贝到这个新开的对象中。

  1. // 丢失引用的深拷贝 
  2. function deepClone(obj){ 
  3.   // 判断obj的类型是否为object类型 
  4.   if(!obj && typeof obj !== "object"return
  5.   // 判断对象是数组类型还是对象类型 
  6.   let newObj = Array.isArray(obj) ? [] : {}; 
  7.   // 遍历obj的键值对 
  8.   for(const [key,value] of Object.entries(obj)){ 
  9.     newObj[key] = typeof value === "string" ? deepClone(value) : value; 
  10.   }; 
  11.   return newObj; 

3.2 终极方案的深拷贝(栈和深度优先的思想)

其思路是:引入一个数组 uniqueList 用来存储已经拷贝的数组,每次循环遍历时,先判断对象是否在 uniqueList 中了,如果在的话就不执行拷贝逻辑了。

  1. function deepCopy(obj){ 
  2.   // 用于去重 
  3.   const uniqueList = []; 
  4.   // 设置根节点 
  5.   let root = {}; 
  6.   // 遍历数组 
  7.   const loopList = [{ 
  8.     parent: root, 
  9.     key: undefined, 
  10.     data: obj 
  11.   }]; 
  12.   // 遍历循环 
  13.   while(loopList.length){ 
  14.     // 深度优先-将数组最后的元素取出 
  15.     const {parent,key,data} = loopList.pop(); 
  16.     // 初始化赋值目标,key--undefined时拷贝到父元素,否则拷贝到子元素 
  17.     let result = parent; 
  18.     if(typeof key !== "undefined") result = parent[key] = {}; 
  19.     // 数据已存在时 
  20.     let uniqueData = uniqueList.find(item=>item.source === data); 
  21.     if(uniqueData){ 
  22.       parent[key] = uniqueData.target; 
  23.       // 中断本次循环 
  24.       continue
  25.     } 
  26.     // 数据不存在时 
  27.     // 保存源数据,在拷贝数据中对应的引用 
  28.     uniqueList.push({ 
  29.       source:data, 
  30.       target:result 
  31.     }); 
  32.     // 遍历数据 
  33.     for(let k in data){ 
  34.       if(data.hasOwnProperty(k)){ 
  35.         typeof data[k] === "object"  
  36.         ? 
  37.           // 下一次循环 
  38.           loopList.push({ 
  39.             parent:result, 
  40.             key:k, 
  41.             data:data[k] 
  42.           }) 
  43.         :  
  44.         result[k] = data[k]; 
  45.          
  46.       } 
  47.     } 
  48.   } 
  49.   return root; 

4 手写一个单例模式

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现方法一般是先判断实例是否存在,如果存在直接返回,如果不存在就先创建再返回。

  1. // 创建单例对象,使用闭包 
  2. const getSingle = function(func){ 
  3.   let result; 
  4.   return function(){ 
  5.     return result || (result = func.apply(this,arguments)); 
  6.   } 
  7.  
  8. // 使用Proxy拦截 
  9. const proxy = function(func){ 
  10.   let reuslt; 
  11.   const handler = { 
  12.     construct:function(){ 
  13.       if(!result) result = Reflect.construct(func,arguments); 
  14.       return result; 
  15.     } 
  16.   } 
  17.   return new Proxy(func,hendler); 

5 手写封装一个ajax函数

  1. /*  
  2. 封装自己的ajax函数 
  3. 参数1:{string} method 请求方法 
  4. 参数2:{string} url 请求地址 
  5. 参数2:{Object} params 请求参数 
  6. 参数3:{function} done 请求完成后执行的回调函数 
  7. */ 
  8.  
  9. function ajax(method,url,params,done){ 
  10.   // 1.创建xhr对象,兼容写法 
  11.   let xhr = window.XMLHttpRequest  
  12.   ? new XMLHttpRequest() 
  13.   : new ActiveXObject("Microsoft.XMLHTTP"); 
  14.  
  15.   // 将method转换成大写 
  16.   method = method.toUpperCase(); 
  17.   // 参数拼接 
  18.   let newParams = []; 
  19.   for(let key in params){ 
  20.     newParams.push(`${key}=${params[k]}`); 
  21.   } 
  22.   let str = newParams.join("&"); 
  23.   // 判断请求方法 
  24.   if(method === "GET") url += `?${str}`; 
  25.  
  26.   // 打开请求方式 
  27.   xhr.open(method,url); 
  28.  
  29.   let data = null
  30.   if(method === "POST"){ 
  31.     // 设置请求头 
  32.     xhr.setRequestHeader(("Content-Type","application/x-www-form-urlencoded")); 
  33.     data = str; 
  34.   } 
  35.   xhr.send(data); 
  36.  
  37.   // 指定xhr状态变化事件处理函数 
  38.   // 执行回调函数 
  39.   xhr.onreadystatechange = function(){ 
  40.     if(this.readyState === 4) done(JSON.parse(xhr.responseText)); 
  41.   } 

6 手写“防抖”和“节流”

在Promise的学习中,之前也写过相关的分享文章,敬请参见《一网打尽──他们都在用这些”防抖“和”节流“方法》。

6.1 防抖

  1. /*  
  2. func:要进行防抖处理的函数 
  3. delay:要进行延时的时间 
  4. immediate:是否使用立即执行 true立即执行 false非立即执行 
  5. */ 
  6. function debounce(func,delay,immediate){ 
  7.   let timeout; //定时器 
  8.   return function(arguments){ 
  9.     // 判断定时器是否存在,存在的话进行清除,重新进行定时器计数 
  10.     if(timeout) clearTimeout(timeout); 
  11.     // 判断是立即执行的防抖还是非立即执行的防抖 
  12.     if(immediate){//立即执行 
  13.       const flag = !timeout;//此处是取反操作 
  14.       timeout = setTimeout(()=>{ 
  15.         timeout = null
  16.       },delay); 
  17.       // 触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。 
  18.       if(flag) func.call(this,arguments); 
  19.     }else{//非立即执行 
  20.       timeout = setTimeout(()=>{ 
  21.         func.call(this,arguments); 
  22.       },delay) 
  23.     } 
  24.  
  25.   } 

6.2 节流

  1. // 节流--定时器版 
  2.  function throttle(func,delay){ 
  3.    let timeout;//定义一个定时器标记 
  4.    return function(arguments){ 
  5.      // 判断是否存在定时器 
  6.      if(!timeout){  
  7.        // 创建一个定时器 
  8.        timeout = setTimeout(()=>{ 
  9.          // delay时间间隔清空定时器 
  10.          clearTimeout(timeout); 
  11.          func.call(this,arguments); 
  12.        },delay) 
  13.      } 
  14.    } 
  15.  } 

7 手写apply、bind、call

7.1 apply

传递给函数的参数处理,不太一样,其他部分跟call一样。

apply接受第二个参数为类数组对象, 这里用了《JavaScript权威指南》中判断是否为类数组对象的方法。

  1. Function.prototype._apply = function (context) { 
  2.     if (context === null || context === undefined) { 
  3.         context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window) 
  4.     } else { 
  5.         context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象 
  6.     } 
  7.     // JavaScript权威指南判断是否为类数组对象 
  8.     function isArrayLike(o) { 
  9.         if (o &&                                    // o不是null、undefined等 
  10.             typeof o === 'object' &&                // o是对象 
  11.             isFinite(o.length) &&                   // o.length是有限数值 
  12.             o.length >= 0 &&                        // o.length为非负值 
  13.             o.length === Math.floor(o.length) &&    // o.length是整数 
  14.             o.length < 4294967296)                  // o.length < 2^32 
  15.             return true 
  16.         else 
  17.             return false 
  18.     } 
  19.     const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数 
  20.     context[specialPrototype] = this; // 隐式绑定this指向到context上 
  21.     let args = arguments[1]; // 获取参数数组 
  22.     let result 
  23.     // 处理传进来的第二个参数 
  24.     if (args) { 
  25.         // 是否传递第二个参数 
  26.         if (!Array.isArray(args) && !isArrayLike(args)) { 
  27.             throw new TypeError('myApply 第二个参数不为数组并且不为类数组对象抛出错误'); 
  28.         } else { 
  29.             args = Array.from(args) // 转为数组 
  30.             result = context[specialPrototype](...args); // 执行函数并展开数组,传递函数参数 
  31.         } 
  32.     } else { 
  33.         result = context[specialPrototype](); // 执行函数  
  34.     } 
  35.     delete context[specialPrototype]; // 删除上下文对象的属性 
  36.     return result; // 返回函数执行结果 
  37. }; 

7.2 bind

拷贝源函数:

  • 通过变量储存源函数
  • 使用Object.create复制源函数的prototype给fToBind

返回拷贝的函数

调用拷贝的函数:

  • new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
  • 绑定this+传递参数
  • 返回源函数的执行结果
  1. Function.prototype._bind = function (objThis, ...params) { 
  2.     const thisFn = this; // 存储源函数以及上方的params(函数参数) 
  3.     // 对返回的函数 secondParams 二次传参 
  4.     let fToBind = function (...secondParams) { 
  5.         const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用 
  6.         const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上 
  7.         return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果 
  8.     }; 
  9.     if (thisFn.prototype) { 
  10.         // 复制源函数的prototype给fToBind 一些情况下函数没有prototype,比如箭头函数 
  11.         fToBind.prototype = Object.create(thisFn.prototype); 
  12.     } 
  13.     return fToBind; // 返回拷贝的函数 
  14. }; 

7.3 call

根据call的规则设置上下文对象,也就是this的指向。

通过设置context的属性,将函数的this指向隐式绑定到context上

通过隐式绑定执行函数并传递参数。

删除临时属性,返回函数执行结果

  1. Function.prototype._call = function (context, ...arr) { 
  2.     if (context === null || context === undefined) { 
  3.        // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window) 
  4.         context = window  
  5.     } else { 
  6.         context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象 
  7.     } 
  8.     const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数 
  9.     context[specialPrototype] = this; // 函数的this指向隐式绑定到context上 
  10.     let result = context[specialPrototype](...arr); // 通过隐式绑定执行函数并传递参数 
  11.     delete context[specialPrototype]; // 删除上下文对象的属性 
  12.     return result; // 返回函数执行结果 
  13. }; 

8 手写继承

8.1 构造函数式继承

构造函数式继承并没有继承父类原型上的方法。

  1. function fatherUser(username, password) { 
  2.   let _password = password  
  3.   this.username = username  
  4.   fatherUser.prototype.login = function () { 
  5.       console.log(this.username + '要登录父亲账号,密码是' + _password) 
  6.   } 
  7.  
  8. function sonUser(username, password) { 
  9.   fatherUser.call(this, username, password
  10.   this.articles = 3 // 文章数量 
  11.  
  12. const yichuanUser = new sonUser('yichuan''xxx'
  13. console.log(yichuanUser.username) // yichuan 
  14. console.log(yichuanUser.username) // xxx 
  15. console.log(yichuanUser.login()) // TypeError: yichuanUser.login is not a function 

8.2 组合式继承

  1. function fatherUser(username, password) { 
  2.   let _password = password  
  3.   this.username = username  
  4.   fatherUser.prototype.login = function () { 
  5.       console.log(this.username + '要登录fatherUser,密码是' + _password) 
  6.   } 
  7.  
  8. function sonUser(username, password) { 
  9.   fatherUser.call(this, username, password) // 第二次执行 fatherUser 的构造函数 
  10.   this.articles = 3 // 文章数量 
  11.  
  12. sonUser.prototype = new fatherUser(); // 第二次执行 fatherUser 的构造函数 
  13. const yichuanUser = new sonUser('yichuan''xxx'

8.3 寄生组合继承

上面的继承方式有所缺陷,所以写这种方式即可。

  1. function Parent() { 
  2.   this.name = 'parent'
  3. function Child() { 
  4.   Parent.call(this); 
  5.   this.type = 'children'
  6. Child.prototype = Object.create(Parent.prototype); 
  7. Child.prototype.constructor = Child; 

参考文章

  • 《前端进阶之必会的JavaScript技巧总结》
  • 《js基础-面试官想知道你有多理解call,apply,bind?[不看后悔系列]》

 

责任编辑:姜华 来源: 前端万有引力
相关推荐

2019-12-13 16:00:11

Dubbo面试题Java

2024-02-27 10:11:36

前端CSS@规则

2021-08-05 06:54:05

流程控制default

2021-10-11 07:55:42

浏览器语法Webpack

2023-09-06 18:37:45

CSS选择器符号

2013-08-02 10:52:10

Android UI控件

2024-04-07 08:41:34

2011-12-02 09:22:23

网络管理NetQos

2010-08-25 01:59:00

2015-06-01 10:37:41

数字取证数字取证工具

2020-02-21 08:45:45

PythonWeb开发框架

2019-07-24 15:30:00

SQL注入数据库

2013-10-16 14:18:02

工具图像处理

2023-04-06 09:08:41

BPM流程引擎

2019-12-03 10:45:35

NodeMySQLGit

2021-05-20 11:17:49

加密货币区块链印度

2021-10-29 09:32:33

springboot 静态变量项目

2024-02-27 06:51:53

数据索引数据库

2023-04-03 08:30:54

项目源码操作流程

2020-10-19 06:43:53

Redis脚本原子
点赞
收藏

51CTO技术栈公众号