1. 首页

前端面试-手撕代码篇

前言

在前端面试有一个非常重要的环节,也是面试者最担心的一个环节。对“手撕代码”的考察需要面试者平时总结和积累(临时抱佛脚是不好使的),在这里笔者就自己如何攻破“手撕代码”环节总结了一些经验,希望能帮助你挑战高薪,迎娶白富美😄😄😄。

  1. 使用IDE时尽量避免直接使用提示API,亲自输入(孰能生巧,当然感觉没问题的API就不用浪费时间了)
  2. 遇到不熟悉的API,一定要查文档研究清楚(参数个数和具体意义以及返回值)
  3. 如在模拟某个原生API时,先写出原生API并分析出形参和返回值
  4. 感觉功能完成时,需要在考虑一下边界条件(参数非比填情况、undefined、null)
  5. 平常有空时多刷刷一二线大厂的面试题(扩充自己的知识广度)
  6. 多关照一些前端动态(比如说curry、compose你没听过,这就有点尴尬)

常见的“手撕代码”,都是高频题哦

curry(柯里化)


function curry(fn: any) { return function judgeCurry(...args: any) { return fn.length > args.length ? (...args1: any) => judgeCurry(...args,...args1): fn(...args); } }

compose(函数组合)


function compose(...args: any[]) { return (subArgs: any) => { // for(let i = args.length - 1; i >= 0; i--) { // res = args[i](res); // } return args.reverse().reduce((acc, func,index) => { return func(acc); }, subArgs); } }

pipe(函数管道)


export function pipe(...args: any[]) { return (subArgs: any) => { // for(let i = args.length - 1; i >= 0; i--) { // res = args[i](res); // } return args.reduce((acc, func,index) => { return func(acc); }, subArgs); } }

throttle(函数节流)


function throttle(fn: any, wait: number){ let last: any; return function() { let now: any = Date.now(); // 初次执行 if (!last) { fn.apply(this, arguments); last = now; return; } // 以后触发,需要判断是否到延迟 if(now - last >= wait) { fn.apply(this, arguments); last = now; } } }

debounce(函数防抖)


function debounce(func: any, delay: number) { // 初次触发定时器为null,后面产生一份定时器并记下定时器id let timer: any = null; // 闭包使定时器id逃逸 return function() { let args = arguments; // 如果已有定时器id,则需要清除,重新开始延迟执行 if (timer) { clearTimeout(timer); timer = null; } timer = setTimeout( () => { func.apply(this, args); // 销毁定时器id,以便下次节流函数触发 timer = null; }, delay); } }

formatMoney(千分位)

function fmoney(num: number){
    /* 正则实现 */
    // 参考:https://www.cnblogs.com/lvmylife/p/8287247.html
    let [integer, decimal] = String(num).split('.');
    let regExp = /\d{1,3}(?=(\d{3})+$)/g;
    integer = integer.replace(regExp, '$&,');
    return `${integer}${decimal === undefined ? '': '.'+decimal}`;
    // 正则解释
    // 正则表达式 \d{1,3}(?=(\d{3})+$)  表示前面有1~3个数字,后面的至少由一组3个数字结尾
    // 先行肯定断言(?=)会作为匹配校验,但不会出现在匹配结果字符串里面
    // ?=表示正向引用,可以作为匹配的条件,但匹配到的内容不获取,并且作为下一次查询的开始
    // $& 表示与正则表达式相匹配的内容,具体的可查看 w3school的replace()方法

    /* Number.prototype.toLocaleString()实现 */
    // Number.prototype.toLocaleString()
    // return num.toLocaleString('en');

    /* Intl.NumberFormat().format(number)实现 */
    // Intl.NumberFormat().format(number)
    // return Intl.NumberFormat('en').format(num);

    // reduce 方案
    // let arr = String(num).split('.');
    // let char = arr[0].split('').reverse();
    // let IntStr = char.reduce((acc, value, index) => {
    //     return `${index % 3 === 0 ? String(value)+',' : String(value)}${acc}`;
    // }, '').slice(0, -1);
    // return `${IntStr}${arr[1]? '.'+arr[1] : '' }`;
}

deepClone(深拷贝)

说明:通过new WeakMap()来避免循环引用(拷贝引用类型时并保存其地址,后面遇到引用类型先检查是否已经保存了)
通过Reflect.ownKeys(obj)遍历出obj自身的所有可枚举和不可枚举的属性以及symbol属性
拷贝对应属性的属性描述符


function checkType(obj: any): string { const type = Object.prototype.toString.call(obj); return type.slice(8, -1); } // 深拷贝(hash = new WeakMap()考虑循环引用的问题) export function deepClone(obj: any, hash = new WeakMap()) : any{ if(checkType(obj) === 'RegExp') { // regExp.source 正则对象的源模式文本; // regExp.flags 正则表达式对象的标志字符串; // regExp.lastIndex 下次匹配开始的字符串索引位置 let temp = new RegExp(obj.source, obj.flags); temp.lastIndex = obj.lastIndex; return temp; } if(checkType(obj) === 'Date') { return new Date(obj); } // 非复杂类型(null、undefined、string、number、symbol、boolean、function) if(obj === null || typeof obj !== 'object') { return obj; } // 还可以扩展其他类型。。。 // 与后面hash.set()防止循环引用 if(hash.has(obj)) { return hash.get(obj); } let newObj = new obj.constructor(); hash.set(obj, newObj); // Object.keys(obj)类型于 for in 和 obj.hasOwnProperty // 是否应该拷贝自身属性(可枚举的和不可枚举的以及symbol) Reflect.ownKeys(obj).forEach(function(key) { if(typeof obj[key] === 'object' && obj[key] !== null) { newObj[key] = deepClone(obj[key], hash); }else{ // 直接赋值 // newObj[key] = obj[key]; // 是否应该保留属性描述符 Object.defineProperty(newObj, key, Object.getOwnPropertyDescriptor(obj, key)); } }); return newObj; }

模拟instanceof


function instance_of(L: Object, R: any){ let protoChain = Object.getPrototypeOf(L); const Lprototype = R.prototype; // 最坏情况递归查到Object.prototype === null while(protoChain) { // 两个对象指向同一个内存地址,则为同一个对象 if(protoChain === Lprototype) { return true; } protoChain = Object.getPrototypeOf(protoChain); } // 找到终点还没找到,那就没有了呗 return false; }

实现call方法


Function.prototype.myCall = function myCall() { let [thisArg, ...args] = Array.from(arguments); if (!thisArg) { //context 为 null 或者是 undefined thisArg = typeof window === 'undefined' ? global : window; } // this 的指向的是当前函数 func (func.call) // 为thisArg对象添加func方法,func方法又指向myCall,所以在func中this指向thisArg thisArg.func = this; // 执行函数 let result = thisArg.func(...args); // thisArg 上并没有 func 属性,因此需要移除 delete thisArg.func; return result; }

实现apply方法


Function.prototype.myApply = function myApply() { // 第一个参数为this对象,第二个参数为数组(与myCall唯一的区别就在第二个参数是数组) let [thisArg, args] = Array.from(arguments); if (!thisArg) { //context 为 null 或者是 undefined thisArg = typeof window === 'undefined' ? global : window; } // this 的指向的是当前函数 func (func.call) thisArg.func = this; // 执行函数 let result = thisArg.func(...args); // thisArg 上并没有 func 属性,因此需要移除 delete thisArg.func; return result; }

实现bind方法


Function.prototype.myBind = function myBind() { let [thisArg, ...args] = [...arguments]; if (!thisArg) { //context 为 null 或者是 undefined thisArg = typeof window === 'undefined' ? global : window; } let that = this; return function() { // 防止第二次调用 func 是,该func已经被delete了,需要重新赋值 if(!thisArg.func) { thisArg.func = that; } let result = thisArg.func(...args); // thisArg原本没有func方法 delete thisArg.func; return result; } }

模拟Promise.all(多个Promise并行执行)

目前还存在参数适配的问题


var p1 = function(){ return new Promise((resolve, reject) => {setTimeout(function(){resolve('12')}, 1000)}) }; var p2 = function(){ return new Promise((resolve, reject) => {setTimeout(function(){resolve(2)}, 2000)}) }; var p3 = function(){ return new Promise((resolve, reject) => {setTimeout(function(){resolve(3)}, 1000)}) }; function promiseAll(tasks) { let ary = new Array(tasks.length).fill(1).map(item => {return {val: undefined, success: false}}); return new Promise((resolve, reject) => { for(let i = 0; i < tasks.length; i++) { tasks[i]().then(res => { ary[i].val = res; ary[i].success = true; if(ary.every(item => item.success === true)){ resolve(ary.map(item => item.val)) } }).catch(err => { reject(err); }); } }); } // test promiseAll([p1, p2, p3]).then(res => console.log(res)).catch(err => { console.log(err); });

多个Promise串行执行(两种方式)


function parallelPromises1(tasks){ var result = []; return tasks.reduce((accumulator,item,index)=>{ return accumulator.then(res=>{ item = typeof item === 'function' ? item() : item; return item.then(res=>{ // debugger result[index] = res return index == tasks.length - 1 ? result : item }) }) },Promise.resolve()) } async function parallelPromises2(tasks) { let ary = []; for (let task of tasks) { let temp = await task(); ary.push(temp); } return ary; }

写在后面

上面代码完全是笔者手敲,难免有错误,还望斧正。这种题目还有很多(实现简易版的EventEmitter、简易版模版引擎等),笔者会持续更新。如果对你有帮助,俺希望送上你的github小星星,在此感谢。
本文同步发布于个人博客掘金知乎专栏

作者:fanerge
链接:https://juejin.im/post/5de7286e6fb9a016107952f7

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:前端面试-手撕代码篇

链接:https://www.javascriptc.com/3079.html

« Webpack 模块机制
TS in JS 实践指北»
Flutter 中文教程资源

相关推荐

QR code