写点什么

20 道前端高频面试题(附答案)

作者:Geek_02d948
  • 2023-02-27
    浙江
  • 本文字数:12812 字

    阅读完需:约 42 分钟

ES6 新特性

1.ES6引入来严格模式    变量必须声明后在使用    函数的参数不能有同名属性, 否则报错    不能使用with语句 (说实话我基本没用过)    不能对只读属性赋值, 否则报错    不能使用前缀0表示八进制数,否则报错 (说实话我基本没用过)    不能删除不可删除的数据, 否则报错    不能删除变量delete prop, 会报错, 只能删除属性delete global[prop]    eval不会在它的外层作用域引入变量    eval和arguments不能被重新赋值    arguments不会自动反映函数参数的变化    不能使用arguments.caller (说实话我基本没用过)    不能使用arguments.callee (说实话我基本没用过)    禁止this指向全局对象    不能使用fn.caller和fn.arguments获取函数调用的堆栈 (说实话我基本没用过)    增加了保留字(比如protected、static和interface)
2.关于let和const新增的变量声明
3.变量的解构赋值
4.字符串的扩展 includes():返回布尔值,表示是否找到了参数字符串。 startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。 endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。5.数值的扩展 Number.isFinite()用来检查一个数值是否为有限的(finite)。 Number.isNaN()用来检查一个值是否为NaN。6.函数的扩展 函数参数指定默认值7.数组的扩展 扩展运算符8.对象的扩展 对象的解构9.新增symbol数据类型
10.Set 和 Map 数据结构 ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set 本身是一个构造函数,用来生成 Set 数据结构。
Map它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。11.Proxy Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问 都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。 Vue3.0使用了proxy12.Promise Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。 特点是: 对象的状态不受外界影响。 一旦状态改变,就不会再变,任何时候都可以得到这个结果。13.async 函数 async函数对 Generator 函数的区别: (1)内置执行器。 Generator 函数的执行必须靠执行器,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。 (2)更好的语义。 async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。 (3)正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。 (4)返回值是 Promise。 async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。14.Class class跟let、const一样:不存在变量提升、不能重复声明... ES6 的class可以看作只是一个语法糖,它的绝大部分功能 ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。15.Module ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。 import和export命令以及export和export default的区别
复制代码

常见的 HTTP 请求方法

  • GET: 向服务器获取数据;

  • POST:将实体提交到指定的资源,通常会造成服务器资源的修改;

  • PUT:上传文件,更新数据;

  • DELETE:删除服务器上的对象;

  • HEAD:获取报文首部,与 GET 相比,不返回报文主体部分;

  • OPTIONS:询问支持的请求方法,用来跨域请求;

  • CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行 TCP 通信;

  • TRACE: 回显服务器收到的请求,主要⽤于测试或诊断。

介绍下 promise 的特性、优缺点,内部是如何实现的,动手实现 Promise

1)Promise 基本特性


  • 1、Promise 有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)

  • 2、Promise 对象接受一个回调函数作为参数, 该回调函数接受两个参数,分别是成功时的回调 resolve 和失败时的回调 reject;另外 resolve 的参数除了正常值以外, 还可能是一个 Promise 对象的实例;reject 的参数通常是一个 Error 对象的实例。

  • 3、then 方法返回一个新的 Promise 实例,并接收两个参数 onResolved(fulfilled 状态的回调);onRejected(rejected 状态的回调,该参数可选)

  • 4、catch 方法返回一个新的 Promise 实例

  • 5、finally 方法不管 Promise 状态如何都会执行,该方法的回调函数不接受任何参数

  • 6、Promise.all()方法将多个多个 Promise 实例,包装成一个新的 Promise 实例,该方法接受一个由 Promise 对象组成的数组作为参数(Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例),注意参数中只要有一个实例触发 catch 方法,都会触发 Promise.all()方法返回的新的实例的 catch 方法,如果参数中的某个实例本身调用了 catch 方法,将不会触发 Promise.all()方法返回的新实例的 catch 方法

  • 7、Promise.race()方法的参数与 Promise.all 方法一样,参数中的实例只要有一个率先改变状态就会将该实例的状态传给 Promise.race()方法,并将返回值作为 Promise.race()方法产生的 Promise 实例的返回值

  • 8、Promise.resolve()将现有对象转为 Promise 对象,如果该方法的参数为一个 Promise 对象,Promise.resolve()将不做任何处理;如果参数 thenable 对象(即具有 then 方法),Promise.resolve()将该对象转为 Promise 对象并立即执行 then 方法;如果参数是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 fulfilled,其参数将会作为 then 方法中 onResolved 回调函数的参数,如果 Promise.resolve 方法不带参数,会直接返回一个 fulfilled 状态的 Promise 对象。需要注意的是,立即 resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

  • 9、Promise.reject()同样返回一个新的 Promise 对象,状态为 rejected,无论传入任何参数都将作为 reject()的参数


2)Promise 优点


  • ①统一异步 API

  • Promise 的一个重要优点是它将逐渐被用作浏览器的异步 API ,统一现在各种各样的 API ,以及不兼容的模式和手法。

  • ②Promise 与事件对比

  • 和事件相比较, Promise 更适合处理一次性的结果。在结果计算出来之前或之后注册回调函数都是可以的,都可以拿到正确的值。 Promise 的这个优点很自然。但是,不能使用 Promise 处理多次触发的事件。链式处理是 Promise 的又一优点,但是事件却不能这样链式处理。

  • ③Promise 与回调对比

  • 解决了回调地狱的问题,将异步操作以同步操作的流程表达出来。

  • ④Promise 带来的额外好处是包含了更好的错误处理方式(包含了异常处理),并且写起来很轻松(因为可以重用一些同步的工具,比如 Array.prototype.map() )。


3)Promise 缺点


  • 1、无法取消 Promise,一旦新建它就会立即执行,无法中途取消。

  • 2、如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。

  • 3、当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

  • 4、Promise 真正执行回调的时候,定义 Promise 那部分实际上已经走完了,所以 Promise 的报错堆栈上下文不太友好。


4)简单代码实现 最简单的 Promise 实现有 7 个主要属性, state(状态), value(成功返回值), reason(错误信息), resolve 方法, reject 方法, then 方法


class Promise{  constructor(executor) {    this.state = 'pending';    this.value = undefined;    this.reason = undefined;    let resolve = value => {      if (this.state === 'pending') {        this.state = 'fulfilled';        this.value = value;      }    };    let reject = reason => {      if (this.state === 'pending') {        this.state = 'rejected';        this.reason = reason;      }    };    try {      // 立即执行函数      executor(resolve, reject);    } catch (err) {      reject(err);    }  }  then(onFulfilled, onRejected) {    if (this.state === 'fulfilled') {      let x = onFulfilled(this.value);    };    if (this.state === 'rejected') {      let x = onRejected(this.reason);    };  }}
复制代码


5)面试够用版


function myPromise(constructor){ let self=this;  self.status="pending" //定义状态改变前的初始状态   self.value=undefined;//定义状态为resolved的时候的状态   self.reason=undefined;//定义状态为rejected的时候的状态   function resolve(value){    //两个==="pending",保证了了状态的改变是不不可逆的     if(self.status==="pending"){      self.value=value;      self.status="resolved";     }  }  function reject(reason){     //两个==="pending",保证了了状态的改变是不不可逆的     if(self.status==="pending"){        self.reason=reason;        self.status="rejected";       }  }  //捕获构造异常   try{      constructor(resolve,reject);  }catch(e){    reject(e);    } }myPromise.prototype.then=function(onFullfilled,onRejected){   let self=this;  switch(self.status){    case "resolved": onFullfilled(self.value); break;    case "rejected": onRejected(self.reason); break;    default:   }}
// 测试var p=new myPromise(function(resolve,reject){resolve(1)}); p.then(function(x){console.log(x)})//输出1
复制代码


6)大厂专供版


const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";const resolvePromise = (promise, x, resolve, reject) => {  if (x === promise) {    // If promise and x refer to the same object, reject promise with a TypeError as the reason.    reject(new TypeError('循环引用'))  }  // if x is an object or function,  if (x !== null && typeof x === 'object' || typeof x === 'function') {    // If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.    let called    try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.      let then = x.then // Let then be x.then      // If then is a function, call it with x as this      if (typeof then === 'function') {        // If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)        // If/when rejectPromise is called with a reason r, reject promise with r.        then.call(x, y => {          if (called) return          called = true          resolvePromise(promise, y, resolve, reject)        }, r => {          if (called) return          called = true          reject(r)        })      } else {        // If then is not a function, fulfill promise with x.        resolve(x)      }    } catch (e) {      if (called) return      called = true      reject(e)    }  } else {    // If x is not an object or function, fulfill promise with x    resolve(x)  }}function Promise(excutor) {  let that = this; // 缓存当前promise实例例对象  that.status = PENDING; // 初始状态  that.value = undefined; // fulfilled状态时 返回的信息  that.reason = undefined; // rejected状态时 拒绝的原因   that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数  that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数  function resolve(value) { // value成功态时接收的终值    if(value instanceof Promise) {      return value.then(resolve, reject);    }    // 实践中要确保 onFulfilled 和 onRejected ⽅方法异步执⾏行行,且应该在 then ⽅方法被调⽤用的那⼀一轮事件循环之后的新执⾏行行栈中执⾏行行。    setTimeout(() => {      // 调⽤用resolve 回调对应onFulfilled函数      if (that.status === PENDING) {        // 只能由pending状态 => fulfilled状态 (避免调⽤用多次resolve reject)        that.status = FULFILLED;        that.value = value;        that.onFulfilledCallbacks.forEach(cb => cb(that.value));      }    });  }  function reject(reason) { // reason失败态时接收的拒因    setTimeout(() => {      // 调⽤用reject 回调对应onRejected函数      if (that.status === PENDING) {        // 只能由pending状态 => rejected状态 (避免调⽤用多次resolve reject)        that.status = REJECTED;        that.reason = reason;        that.onRejectedCallbacks.forEach(cb => cb(that.reason));      }    });  }
// 捕获在excutor执⾏行行器器中抛出的异常 // new Promise((resolve, reject) => { // throw new Error('error in excutor') // }) try { excutor(resolve, reject); } catch (e) { reject(e); }}Promise.prototype.then = function(onFulfilled, onRejected) { const that = this; let newPromise; // 处理理参数默认值 保证参数后续能够继续执⾏行行 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason; }; if (that.status === FULFILLED) { // 成功态 return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try{ let x = onFulfilled(that.value); resolvePromise(newPromise, x, resolve, reject); //新的promise resolve 上⼀一个onFulfilled的返回值 } catch(e) { reject(e); // 捕获前⾯面onFulfilled中抛出的异常then(onFulfilled, onRejected); } }); }) } if (that.status === REJECTED) { // 失败态 return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try { let x = onRejected(that.reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); } if (that.status === PENDING) { // 等待态// 当异步调⽤用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中 return newPromise = new Promise((resolve, reject) => { that.onFulfilledCallbacks.push((value) => { try { let x = onFulfilled(value); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); that.onRejectedCallbacks.push((reason) => { try { let x = onRejected(reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); }};
复制代码

对事件委托的理解

(1)事件委托的概念

事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件委托(事件代理)。


使用事件委托可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理还可以实现事件的动态绑定,比如说新增了一个子节点,并不需要单独地为它添加一个监听事件,它绑定的事件会交给父元素中的监听函数来处理。

(2)事件委托的特点

  • 减少内存消耗


如果有一个列表,列表之中有大量的列表项,需要在点击列表项的时候响应一个事件:


<ul id="list">  <li>item 1</li>  <li>item 2</li>  <li>item 3</li>  ......  <li>item n</li></ul>
复制代码


如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件时再去匹配判断目标元素,所以事件委托可以减少大量的内存消耗,节约效率。


  • 动态绑定事件


给上述的例子中每个列表项都绑定事件,在很多时候,需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的,所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。


// 来实现把 #list 下的 li 元素的事件代理委托到它的父层元素也就是 #list 上:// 给父层元素绑定事件document.getElementById('list').addEventListener('click', function (e) {  // 兼容性处理  var event = e || window.event;  var target = event.target || event.srcElement;  // 判断是否匹配目标元素  if (target.nodeName.toLocaleLowerCase === 'li') {    console.log('the content is: ', target.innerHTML);  }});
复制代码


在上述代码中, target 元素则是在 #list 元素之下具体被点击的元素,然后通过判断 target 的一些属性(比如:nodeName,id 等等)可以更精确地匹配到某一类 #list li 元素之上;

(3)局限性

当然,事件委托也是有局限的。比如 focus、blur 之类的事件没有事件冒泡机制,所以无法实现事件委托;mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的。


当然事件委托不是只有优点,它也是有缺点的,事件委托会影响页面性能,主要影响因素有:


  • 元素中,绑定事件委托的次数;

  • 点击的最底层元素,到绑定事件元素之间的DOM层数;


在必须使用事件委托的地方,可以进行如下的处理:


  • 只在必须的地方,使用事件委托,比如:ajax的局部刷新区域

  • 尽量的减少绑定的层级,不在body元素上,进行绑定

  • 减少绑定的次数,如果可以,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行分发。

代码输出问题

window.number = 2;var obj = { number: 3, db1: (function(){   console.log(this);   this.number *= 4;   return function(){     console.log(this);     this.number *= 5;   } })()}var db1 = obj.db1;db1();obj.db1();console.log(obj.number);     // 15console.log(window.number);  // 40
复制代码


这道题目看清起来有点乱,但是实际上是考察 this 指向的:


  1. 执行 db1()时,this 指向全局作用域,所以 window.number * 4 = 8,然后执行匿名函数, 所以 window.number * 5 = 40;

  2. 执行 obj.db1();时,this 指向 obj 对象,执行匿名函数,所以 obj.numer * 5 = 15。

说一下怎么把类数组转换为数组?

//通过call调用数组的slice方法来实现转换Array.prototype.slice.call(arrayLike)
//通过call调用数组的splice方法来实现转换Array.prototype.splice.call(arrayLike,0)
//通过apply调用数组的concat方法来实现转换Array.prototype.concat.apply([],arrayLike)
//通过Array.from方法来实现转换Array.from(arrayLike)
复制代码


参考 前端进阶面试题详细解答

代码输出结果

Promise.resolve('1')  .then(res => {    console.log(res)  })  .finally(() => {    console.log('finally')  })Promise.resolve('2')  .finally(() => {    console.log('finally2')      return '我是finally2返回的值'  })  .then(res => {    console.log('finally2后面的then函数', res)  })
复制代码


输出结果如下:


1finally2finallyfinally2后面的then函数 2
复制代码


.finally()一般用的很少,只要记住以下几点就可以了:


  • .finally()方法不管 Promise 对象最后的状态如何都会执行

  • .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是无法知道 Promise 最终的状态是resolved还是rejected

  • 它最终返回的默认会是一个上一次的 Promise 对象值,不过如果抛出的是一个异常则返回异常的 Promise 对象。

  • finally 本质上是 then 方法的特例


.finally()的错误捕获:


Promise.resolve('1')  .finally(() => {    console.log('finally1')    throw new Error('我是finally中抛出的异常')  })  .then(res => {    console.log('finally后面的then函数', res)  })  .catch(err => {    console.log('捕获错误', err)  })
复制代码


输出结果为:


'finally1''捕获错误' Error: 我是finally中抛出的异常
复制代码

代码输出结果

function Person(name) {    this.name = name}var p2 = new Person('king');console.log(p2.__proto__) //Person.prototypeconsole.log(p2.__proto__.__proto__) //Object.prototypeconsole.log(p2.__proto__.__proto__.__proto__) // nullconsole.log(p2.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错console.log(p2.constructor)//Personconsole.log(p2.prototype)//undefined p2是实例,没有prototype属性console.log(Person.constructor)//Function 一个空函数console.log(Person.prototype)//打印出Person.prototype这个对象里所有的方法和属性console.log(Person.prototype.constructor)//Personconsole.log(Person.prototype.__proto__)// Object.prototypeconsole.log(Person.__proto__) //Function.prototypeconsole.log(Function.prototype.__proto__)//Object.prototypeconsole.log(Function.__proto__)//Function.prototypeconsole.log(Object.__proto__)//Function.prototypeconsole.log(Object.prototype.__proto__)//null
复制代码


这道义题目考察原型、原型链的基础,记住就可以了。

代码输出结果

async function async1 () {  console.log('async1 start');  await new Promise(resolve => {    console.log('promise1')  })  console.log('async1 success');  return 'async1 end'}console.log('srcipt start')async1().then(res => console.log(res))console.log('srcipt end')
复制代码


输出结果如下:


script startasync1 startpromise1script end
复制代码


这里需要注意的是在async1await后面的 Promise 是没有返回值的,也就是它的状态始终是pending状态,所以在await之后的内容是不会执行的,包括async1后面的 .then

代码输出结果

function foo() {  console.log( this.a );}
function doFoo() { foo();}
var obj = { a: 1, doFoo: doFoo};
var a = 2; obj.doFoo()
复制代码


输出结果:2


在 Javascript 中,this 指向函数执行时的当前对象。在执行 foo 的时候,执行环境就是 doFoo 函数,执行环境为全局。所以,foo 中的 this 是指向 window 的,所以会打印出 2。

setInterval 模拟 setTimeout

描述:使用setInterval模拟实现setTimeout的功能。


思路setTimeout的特性是在指定的时间内只执行一次,我们只要在setInterval内部执行 callback 之后,把定时器关掉即可。


实现


const mySetTimeout = (fn, time) => {    let timer = null;    timer = setInterval(() => {        // 关闭定时器,保证只执行一次fn,也就达到了setTimeout的效果了        clearInterval(timer);        fn();    }, time);    // 返回用于关闭定时器的方法    return () => clearInterval(timer);}
// 测试const cancel = mySetTimeout(() => { console.log(1);}, 1000); // 一秒后打印 1
复制代码

说一下 vue3.0 你了解多少?

 <!-- 响应式原理的改变 Vue3.x 使用Proxy取代 Vue2.x 版本的Object.defineProperty --> <!-- 组件选项声明方式Vue3.x 使用Composition API setup 是Vue3.x新增的一个选项,他    是组件内使用Composition API 的入口 --> <!-- 模板语法变化slot具名插槽语法 自定义指令 v-model 升级 --> <!-- 其它方面的更改Suspense支持Fragment(多个根节点) 和Protal (在dom其他部分渲染组建内容)组件     针对一些特殊的场景做了处理。基于treeshaking优化,提供了更多的内置功能。 -->
复制代码

代码输出结果

function runAsync (x) {    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))    return p}
Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
复制代码


输出结果如下:


123[1, 2, 3]
复制代码


首先,定义了一个 Promise,来异步执行函数 runAsync,该函数传入一个值 x,然后间隔一秒后打印出这个 x。


之后再使用Promise.all来执行这个函数,执行的时候,看到一秒之后输出了 1,2,3,同时输出了数组[1, 2, 3],三个函数是同步执行的,并且在一个回调函数中返回了所有的结果。并且结果和函数的执行顺序是一致的。

Promise.race

描述:只要promises中有一个率先改变状态,就返回这个率先改变的Promise实例的返回值。


实现


Promise.race = function(promises){    return new Promise((resolve, reject) => {        if(Array.isArray(promises)) {            if(promises.length === 0) return resolve(promises);            promises.forEach((item) => {                Promise.resolve(item).then(                    value => resolve(value),                     reason => reject(reason)                );            })        }        else return reject(new TypeError("Argument is not iterable"));    });}
复制代码

异步任务调度器

描述:实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有 limit 个。


实现


class Scheduler {    queue = [];  // 用队列保存正在执行的任务    runCount = 0;  // 计数正在执行的任务个数    constructor(limit) {        this.maxCount = limit;  // 允许并发的最大个数    }    add(time, data){        const promiseCreator = () => {            return new Promise((resolve, reject) => {                setTimeout(() => {                    console.log(data);                    resolve();                }, time);            });        }        this.queue.push(promiseCreator);        // 每次添加的时候都会尝试去执行任务        this.request();    }    request() {        // 队列中还有任务才会被执行        if(this.queue.length && this.runCount < this.maxCount) {            this.runCount++;            // 执行先加入队列的函数            this.queue.shift()().then(() => {                this.runCount--;                // 尝试进行下一次任务                this.request();            });        }    }}
// 测试const scheduler = new Scheduler(2);const addTask = (time, data) => { scheduler.add(time, data);}
addTask(1000, '1');addTask(500, '2');addTask(300, '3');addTask(400, '4');// 输出结果 2 3 1 4
复制代码

说一下常见的检测数据类型的几种方式?

typeof  其中数组、对象、null都会被判断为Object,其他判断都正确
instanceof 只能判断引用数据类型,不能判断基本数据类型
constructor 它有2个作用 一是判断数据的类型,二是对象实例通过constructor对象访问它的构造函数。需要注意的事情是如果创建一个对象来改变它的原型,constructor就不能来判断数据类型了
Object.prototype.toString.call()
复制代码

Number() 的存储空间是多大?如果后台发送了一个超过最大自己的数字怎么办

Math.pow(2, 53) ,53 为有效数字,会发生截断,等于 JS 能支持的最大数字。

Promise.allSettled

描述:等到所有promise都返回结果,就返回一个promise实例。


实现


Promise.allSettled = function(promises) {    return new Promise((resolve, reject) => {        if(Array.isArray(promises)) {            if(promises.length === 0) return resolve(promises);            let result = [];            let count = 0;            promises.forEach((item, index) => {                Promise.resolve(item).then(                    value => {                        count++;                        result[index] = {                            status: 'fulfilled',                            value: value                        };                        if(count === promises.length) resolve(result);                    },                     reason => {                        count++;                        result[index] = {                            status: 'rejected'.                            reason: reason                        };                        if(count === promises.length) resolve(result);                    }                );            });        }        else return reject(new TypeError("Argument is not iterable"));    });}
复制代码

Object.is 实现

题目描述:


Object.is不会转换被比较的两个值的类型,这点和===更为相似,他们之间也存在一些区别。    1. NaN在===中是不相等的,而在Object.is中是相等的    2. +0和-0在===中是相等的,而在Object.is中是不相等的
复制代码


实现代码如下:


Object.is = function (x, y) {  if (x === y) {    // 当前情况下,只有一种情况是特殊的,即 +0 -0    // 如果 x !== 0,则返回true    // 如果 x === 0,则需要判断+0和-0,则可以直接使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断    return x !== 0 || 1 / x === 1 / y;  }
// x !== y 的情况下,只需要判断是否为NaN,如果x!==x,则说明x是NaN,同理y也一样 // x和y同时为NaN时,返回true return x !== x && y !== y;};
复制代码

大数相加

题目描述:实现一个 add 方法完成两个大数相加


let a = "9007199254740991";let b = "1234567899999999999";
function add(a ,b){ //...}

复制代码


实现代码如下:


function add(a ,b){   //取两个数字的最大长度   let maxLength = Math.max(a.length, b.length);   //用0去补齐长度   a = a.padStart(maxLength , 0);//"0009007199254740991"   b = b.padStart(maxLength , 0);//"1234567899999999999"   //定义加法过程中需要用到的变量   let t = 0;   let f = 0;   //"进位"   let sum = "";   for(let i=maxLength-1 ; i>=0 ; i--){      t = parseInt(a[i]) + parseInt(b[i]) + f;      f = Math.floor(t/10);      sum = t%10 + sum;   }   if(f!==0){      sum = '' + f + sum;   }   return sum;}
复制代码


用户头像

Geek_02d948

关注

还未添加个人签名 2022-09-08 加入

还未添加个人简介

评论

发布
暂无评论
20道前端高频面试题(附答案)_JavaScript_Geek_02d948_InfoQ写作社区