写点什么

前端一面必会手写面试题指南

  • 2022-11-09
    浙江
  • 本文字数:10399 字

    阅读完需:约 34 分钟

reduce 用法汇总

语法


array.reduce(function(total, currentValue, currentIndex, arr), initialValue);/*  total: 必需。初始值, 或者计算结束后的返回值。  currentValue: 必需。当前元素。  currentIndex: 可选。当前元素的索引;                       arr: 可选。当前元素所属的数组对象。  initialValue: 可选。传递给函数的初始值,相当于total的初始值。*/
复制代码


reduceRight() 该方法用法与reduce()其实是相同的,只是遍历的顺序相反,它是从数组的最后一项开始,向前遍历到第一项


1. 数组求和


const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num);
// 设定初始值求和const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num, 10); // 以10为初始值求和

// 对象数组求和var result = [ { subject: 'math', score: 88 }, { subject: 'chinese', score: 95 }, { subject: 'english', score: 80 }];const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0); const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10); // 总分扣除10分
复制代码


2. 数组最大值


const a = [23,123,342,12];const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342
复制代码


3. 数组转对象


var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});
复制代码


4. 扁平一个二维数组


var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];var res = arr.reduce((x, y) => x.concat(y), []);
复制代码


5. 数组去重


实现的基本原理如下:
① 初始化一个空数组② 将需要去重处理的数组中的第1项在初始化数组中查找,如果找不到(空数组中肯定找不到),就将该项添加到初始化数组中③ 将需要去重处理的数组中的第2项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中④ ……⑤ 将需要去重处理的数组中的第n项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中⑥ 将这个初始化数组返回
复制代码


var newArr = arr.reduce(function (prev, cur) {    prev.indexOf(cur) === -1 && prev.push(cur);    return prev;},[]);
复制代码


6. 对象数组去重


const dedup = (data, getKey = () => { }) => {    const dateMap = data.reduce((pre, cur) => {        const key = getKey(cur)        if (!pre[key]) {            pre[key] = cur        }        return pre    }, {})    return Object.values(dateMap)}
复制代码


7. 求字符串中字母出现的次数


const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';
const res = str.split('').reduce((pre,next)=>{ pre[next] ? pre[next]++ : pre[next] = 1 return pre },{})
复制代码


// 结果-: 1a: 8c: 1d: 4e: 1f: 4g: 1h: 2i: 2j: 4k: 1l: 3m: 2n: 1q: 5r: 1s: 6u: 1w: 2x: 1z: 1
复制代码


8. compose 函数


redux compose 源码实现


function compose(...funs) {    if (funs.length === 0) {        return arg => arg;    }    if (funs.length === 1) {       return funs[0];    }    return funs.reduce((a, b) => (...arg) => a(b(...arg)))}
复制代码

实现 Promise

var PromisePolyfill = (function () {  // 和reject不同的是resolve需要尝试展开thenable对象  function tryToResolve (value) {    if (this === value) {    // 主要是防止下面这种情况    // let y = new Promise(res => setTimeout(res(y)))      throw TypeError('Chaining cycle detected for promise!')    }
// 根据规范2.32以及2.33 对对象或者函数尝试展开 // 保证S6之前的 polyfill 也能和ES6的原生promise混用 if (value !== null && (typeof value === 'object' || typeof value === 'function')) { try { // 这里记录这次then的值同时要被try包裹 // 主要原因是 then 可能是一个getter, 也也就是说 // 1. value.then可能报错 // 2. value.then可能产生副作用(例如多次执行可能结果不同) var then = value.then
// 另一方面, 由于无法保证 then 确实会像预期的那样只调用一个onFullfilled / onRejected // 所以增加了一个flag来防止resolveOrReject被多次调用 var thenAlreadyCalledOrThrow = false if (typeof then === 'function') { // 是thenable 那么尝试展开 // 并且在该thenable状态改变之前this对象的状态不变 then.bind(value)( // onFullfilled function (value2) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true tryToResolve.bind(this, value2)() }.bind(this),
// onRejected function (reason2) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true resolveOrReject.bind(this, 'rejected', reason2)() }.bind(this) ) } else { // 拥有then 但是then不是一个函数 所以也不是thenable resolveOrReject.bind(this, 'resolved', value)() } } catch (e) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true resolveOrReject.bind(this, 'rejected', e)() } } else { // 基本类型 直接返回 resolveOrReject.bind(this, 'resolved', value)() } }
function resolveOrReject (status, data) { if (this.status !== 'pending') return this.status = status this.data = data if (status === 'resolved') { for (var i = 0; i < this.resolveList.length; ++i) { this.resolveList[i]() } } else { for (i = 0; i < this.rejectList.length; ++i) { this.rejectList[i]() } } }
function Promise (executor) { if (!(this instanceof Promise)) { throw Error('Promise can not be called without new !') }
if (typeof executor !== 'function') { // 非标准 但与Chrome谷歌保持一致 throw TypeError('Promise resolver ' + executor + ' is not a function') }
this.status = 'pending' this.resolveList = [] this.rejectList = []
try { executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected')) } catch (e) { resolveOrReject.bind(this, 'rejected', e)() } }
Promise.prototype.then = function (onFullfilled, onRejected) { // 返回值穿透以及错误穿透, 注意错误穿透用的是throw而不是return,否则的话 // 这个then返回的promise状态将变成resolved即接下来的then中的onFullfilled // 会被调用, 然而我们想要调用的是onRejected if (typeof onFullfilled !== 'function') { onFullfilled = function (data) { return data } } if (typeof onRejected !== 'function') { onRejected = function (reason) { throw reason } }
var executor = function (resolve, reject) { setTimeout(function () { try { // 拿到对应的handle函数处理this.data // 并以此为依据解析这个新的Promise var value = this.status === 'resolved' ? onFullfilled(this.data) : onRejected(this.data) resolve(value) } catch (e) { reject(e) } }.bind(this)) }
// then 接受两个函数返回一个新的Promise // then 自身的执行永远异步与onFullfilled/onRejected的执行 if (this.status !== 'pending') { return new Promise(executor.bind(this)) } else { // pending return new Promise(function (resolve, reject) { this.resolveList.push(executor.bind(this, resolve, reject)) this.rejectList.push(executor.bind(this, resolve, reject)) }.bind(this)) } }
// for prmise A+ test Promise.deferred = Promise.defer = function () { var dfd = {} dfd.promise = new Promise(function (resolve, reject) { dfd.resolve = resolve dfd.reject = reject }) return dfd }
// for prmise A+ test if (typeof module !== 'undefined') { module.exports = Promise }
return Promise})()
PromisePolyfill.all = function (promises) { return new Promise((resolve, reject) => { const result = [] let cnt = 0 for (let i = 0; i < promises.length; ++i) { promises[i].then(value => { cnt++ result[i] = value if (cnt === promises.length) resolve(result) }, reject) } })}
PromisePolyfill.race = function (promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; ++i) { promises[i].then(resolve, reject) } })}
复制代码

判断是否是电话号码

function isPhone(tel) {    var regx = /^1[34578]\d{9}$/;    return regx.test(tel);}

复制代码

手写 instanceof 方法

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。


实现步骤:


  1. 首先获取类型的原型

  2. 然后获得对象的原型

  3. 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null


具体实现:


function myInstanceof(left, right) {  let proto = Object.getPrototypeOf(left), // 获取对象的原型      prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上 while (true) { if (!proto) return false; if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto); }}
复制代码


参考:前端手写面试题详细解答

实现 AJAX 请求

AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。


创建 AJAX 请求的步骤:


  • 创建一个 XMLHttpRequest 对象。

  • 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。

  • 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发 onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。

  • 当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。


const SERVER_URL = "/server";let xhr = new XMLHttpRequest();// 创建 Http 请求xhr.open("GET", SERVER_URL, true);// 设置状态监听函数xhr.onreadystatechange = function() {  if (this.readyState !== 4) return;  // 当请求成功时  if (this.status === 200) {    handle(this.response);  } else {    console.error(this.statusText);  }};// 设置请求失败时的监听函数xhr.onerror = function() {  console.error(this.statusText);};// 设置请求头信息xhr.responseType = "json";xhr.setRequestHeader("Accept", "application/json");// 发送 Http 请求xhr.send(null);
复制代码

实现非负大整数相加

JavaScript 对数值有范围的限制,限制如下:


Number.MAX_VALUE // 1.7976931348623157e+308Number.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_VALUE // 5e-324Number.MIN_SAFE_INTEGER // -9007199254740991
复制代码


如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。


实现一个算法进行大数的相加:


function sumBigNumber(a, b) {  let res = '';  let temp = 0;
a = a.split(''); b = b.split('');
while (a.length || b.length || temp) { temp += ~~a.pop() + ~~b.pop(); res = (temp % 10) + res; temp = temp > 9 } return res.replace(/^0+/, '');}
复制代码


其主要的思路如下:


  • 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化

  • 初始化 res,temp 来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算

  • 将两个数组的对应的位进行相加,两个数相加的结果可能大于 10,所以可能要仅为,对 10 进行取余操作,将结果保存在当前位

  • 判断当前位是否大于 9,也就是是否会进位,若是则将 temp 赋值为 true,因为在加法运算中,true 会自动隐式转化为 1,以便于下一次相加

  • 重复上述操作,直至计算结束

实现防抖函数(debounce)

防抖函数原理:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。


那么与节流函数的区别直接看这个动画实现即可。


手写简化版:


// 防抖函数const debounce = (fn, delay) => {  let timer = null;  return (...args) => {    clearTimeout(timer);    timer = setTimeout(() => {      fn.apply(this, args);    }, delay);  };};
复制代码


适用场景:


  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次

  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似


生存环境请用 lodash.debounce

手写 call 函数

call 函数的实现步骤:


  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。

  3. 处理传入的参数,截取第一个参数后的所有参数。

  4. 将函数作为上下文对象的一个属性。

  5. 使用上下文对象来调用这个方法,并保存返回结果。

  6. 删除刚才新增的属性。

  7. 返回结果。


// call函数实现Function.prototype.myCall = function(context) {  // 判断调用对象  if (typeof this !== "function") {    console.error("type error");  }  // 获取参数  let args = [...arguments].slice(1),      result = null;  // 判断 context 是否传入,如果未传入则设置为 window  context = context || window;  // 将调用函数设为对象的方法  context.fn = this;  // 调用函数  result = context.fn(...args);  // 将属性删除  delete context.fn;  return result;};
复制代码

实现节流函数(throttle)

防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。


// 手写简化版


// 节流函数const throttle = (fn, delay = 500) => {  let flag = true;  return (...args) => {    if (!flag) return;    flag = false;    setTimeout(() => {      fn.apply(this, args);      flag = true;    }, delay);  };};
复制代码


适用场景:


  • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动

  • 缩放场景:监控浏览器 resize

  • 动画场景:避免短时间内多次触发动画引起性能问题

实现数组的 map 方法

Array.prototype._map = function(fn) {   if (typeof fn !== "function") {        throw Error('参数必须是一个函数');    }    const res = [];    for (let i = 0, len = this.length; i < len; i++) {        res.push(fn(this[i]));    }    return res;}
复制代码

手写类型判断函数

function getType(value) {  // 判断数据是 null 的情况  if (value === null) {    return value + "";  }  // 判断数据是引用类型的情况  if (typeof value === "object") {    let valueClass = Object.prototype.toString.call(value),      type = valueClass.split(" ")[1].split("");    type.pop();    return type.join("").toLowerCase();  } else {    // 判断数据是基本数据类型的情况和函数的情况    return typeof value;  }}
复制代码

字符串查找

请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1)。


a='34';b='1234567'; // 返回 2a='35';b='1234567'; // 返回 -1a='355';b='12354355'; // 返回 5isContain(a,b);

复制代码


function isContain(a, b) {  for (let i in b) {    if (a[0] === b[i]) {      let tmp = true;      for (let j in a) {        if (a[j] !== b[~~i + ~~j]) {          tmp = false;        }      }      if (tmp) {        return i;      }    }  }  return -1;}

复制代码

模拟 new 操作

3 个步骤:


  1. ctor.prototype为原型创建一个对象。

  2. 执行构造函数并将 this 绑定到新创建的对象上。

  3. 判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。


function newOperator(ctor, ...args) {  if (typeof ctor !== 'function') {    throw new TypeError('Type Error');  }  const obj = Object.create(ctor.prototype);  const res = ctor.apply(obj, args);
const isObject = typeof res === 'object' && res !== null; const isFunction = typeof res === 'function'; return isObject || isFunction ? res : obj;}
复制代码

用 Promise 实现图片的异步加载

let imageAsync=(url)=>{            return new Promise((resolve,reject)=>{                let img = new Image();                img.src = url;                img.οnlοad=()=>{                    console.log(`图片请求成功,此处进行通用操作`);                    resolve(image);                }                img.οnerrοr=(err)=>{                    console.log(`失败,此处进行失败的通用操作`);                    reject(err);                }            })        }
imageAsync("url").then(()=>{ console.log("加载成功");}).catch((error)=>{ console.log("加载失败");})
复制代码

将数字每千分位用逗号隔开

数字有小数版本:


let format = n => {    let num = n.toString() // 转成字符串    let decimals = ''        // 判断是否有小数    num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals    let len = num.length    if (len <= 3) {        return num    } else {        let temp = ''        let remainder = len % 3        decimals ? temp = '.' + decimals : temp        if (remainder > 0) { // 不是3的整数倍            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp        } else { // 是3的整数倍            return num.slice(0, len).match(/\d{3}/g).join(',') + temp         }    }}format(12323.33)  // '12,323.33'
复制代码


数字无小数版本:


let format = n => {    let num = n.toString()     let len = num.length    if (len <= 3) {        return num    } else {        let remainder = len % 3        if (remainder > 0) { // 不是3的整数倍            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')         } else { // 是3的整数倍            return num.slice(0, len).match(/\d{3}/g).join(',')         }    }}format(1232323)  // '1,232,323'
复制代码

手写 Promise.all

1) 核心思路


  1. 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数

  2. 这个方法返回一个新的 promise 对象,

  3. 遍历传入的参数,用 Promise.resolve()将参数"包一层",使其变成一个 promise 对象

  4. 参数所有回调成功才是成功,返回值数组与参数顺序一致

  5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。


2)实现代码


一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了


function promiseAll(promises) {  return new Promise(function(resolve, reject) {    if(!Array.isArray(promises)){        throw new TypeError(`argument must be a array`)    }    var resolvedCounter = 0;    var promiseNum = promises.length;    var resolvedResult = [];    for (let i = 0; i < promiseNum; i++) {      Promise.resolve(promises[i]).then(value=>{        resolvedCounter++;        resolvedResult[i] = value;        if (resolvedCounter == promiseNum) {            return resolve(resolvedResult)          }      },error=>{        return reject(error)      })    }  })}// testlet p1 = new Promise(function (resolve, reject) {    setTimeout(function () {        resolve(1)    }, 1000)})let p2 = new Promise(function (resolve, reject) {    setTimeout(function () {        resolve(2)    }, 2000)})let p3 = new Promise(function (resolve, reject) {    setTimeout(function () {        resolve(3)    }, 3000)})promiseAll([p3, p1, p2]).then(res => {    console.log(res) // [3, 1, 2]})
复制代码

Array.prototype.forEach()

Array.prototype.forEach = function(callback, thisArg) {  if (this == null) {    throw new TypeError('this is null or not defined');  }  if (typeof callback !== "function") {    throw new TypeError(callback + ' is not a function');  }  const O = Object(this);  const len = O.length >>> 0;  let k = 0;  while (k < len) {    if (k in O) {      callback.call(thisArg, O[k], k, O);    }    k++;  }}
复制代码

Function.prototype.call

call唯一不同的是,call()方法接受的是一个参数列表


Function.prototype.call = function(context = window, ...args) {  if (typeof this !== 'function') {    throw new TypeError('Type Error');  }  const fn = Symbol('fn');  context[fn] = this;
const res = context[fn](...args); delete context[fn]; return res;}
复制代码

实现 bind

实现 bind 要做什么


  • 返回一个函数,绑定 this,传递预置参数

  • bind 返回的函数可以作为构造函数使用。故作为构造函数时应使得 this 失效,但是传入的参数依然有效


// mdn的实现if (!Function.prototype.bind) {  Function.prototype.bind = function(oThis) {    if (typeof this !== 'function') {      // closest thing possible to the ECMAScript 5      // internal IsCallable function      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');    }
var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用 return fToBind.apply(this instanceof fBound ? this : oThis, // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的 aArgs.concat(Array.prototype.slice.call(arguments))); };
// 维护原型关系 if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype; } // 下行的代码使fBound.prototype是fNOP的实例,因此 // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例 fBound.prototype = new fNOP();
return fBound; };}
复制代码

查找字符串中出现最多的字符和个数

例: abbcccddddd -> 字符最多的是 d,出现了 5 次


let str = "abcabcabcbbccccc";let num = 0;let char = '';
// 使其按照一定的次序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"
// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; }});console.log(`字符最多的是${char},出现了${num}次`);

复制代码


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
前端一面必会手写面试题指南_JavaScript_helloworld1024fd_InfoQ写作社区