Javasctipt面试题整理

Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。

let p = new Proxy(target, handler)

target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。

7.对对象与数组的解构的理解?

  • 数组解构是一一对应

  • 对象解构是严格匹配键名

8.如何提取高度嵌套的对象里的指定属性?

  • 对象连续解构

9.对 rest 参数的理解?

  • 它还可以把一个分离的参数序列整合成一个数组

10.ES6中模板语法与字符串处理?

${}//简化以前拼接字符串要写很多引号

三.基础


✅1.说一下new操作符的实现原理?

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

✅2.ES6模块与CommonJS模块有什么异同?

CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能改变其值,也就是指针指向不能变,类似const;

import的接⼝是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。

ES6 Module和CommonJS模块的共同点:

CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。

✅3.for…in和for…of的区别?

for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下

for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;

for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;

对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

**总结:**for…in 循环主要是为了遍历对象而生,不适用于遍历数组;for…of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。

📢✅4.ajax、axios、fetch的区别?

(1)ajax:

本身是针对MVC编程,不符合前端MVVM的浪潮

基于原生XHR开发,XHR本身的架构不清晰

不符合关注分离(Separation of Concerns)的原则

配置和调用方式非常混乱,而且基于事件的异步模型不友好。

(2)Fetch:

语法简洁,更加语义化

基于标准 Promise 实现,支持 async/await

更加底层,提供的API丰富(request, response)

脱离了XHR,是ES规范里新的实现方式

fetch的缺点:

fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。

fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})

fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费

fetch没有办法原生监测请求的进度,而XHR可以

(3)Axios

Axios 是一种基于Promise封装的HTTP客户端,其特点如下:

浏览器端发起XMLHttpRequests请求

node端发起http请求

支持Promise API

监听请求和返回

对请求和返回进行转化

取消请求

自动转换json数据

客户端支持抵御XSRF攻击

✅📢5.什么是尾调用,使用尾调用有什么好处?

尾调用指的是函数的最后一步调用另一个函数。

代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

✅6.说一下map和weakmap的区别?

Map结构原生提供是三个遍历器生成函数和一个遍历方法

  • keys():返回键名的遍历器。

  • values():返回键值的遍历器。

  • entries():返回所有成员的遍历器。

  • forEach():遍历Map的所有成员。

  • WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。

而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用

总结:

  • Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

  • WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。

📢✅7.常用的正则表达式有哪些?

// (1)匹配 16 进制颜色值

var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;

// (2)匹配日期,如 yyyy-mm-dd 格式

var regex = /1{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;

// (3)匹配 qq 号

var regex = /2[0-9]{4,10}$/g;

// (4)手机号码正则

var regex = /^1[34578]\d{9}$/g;

// (5)用户名正则

var regex = /3[a-zA-Z0-9_$]{4,16}$/;

8.说一下Map和Object的区别?

  • Map是集合,键可以是对象且是有序的,Object的键必须是string或者symbol

❌9.JavaScript有哪些内置对象?

❌10.对JSON的理解?

11.说一下JavaScript脚本延迟加载的方式有哪些?

  • defer和async

  • 动态创建DOM

  • setTimeout

12.JavaScript 类数组对象的定义?

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。

常见的类数组转换为数组的方法有这样几种:

(1)通过 call 调用数组的 slice 方法来实现转换

Array.prototype.slice.call(arrayLike);

(2)通过 call 调用数组的 splice 方法来实现转换

Array.prototype.splice.call(arrayLike, 0);

(3)通过 apply 调用数组的 concat 方法来实现转换

Array.prototype.concat.apply([], arrayLike);

(4)通过 Array.from 方法来实现转换

Array.from(arrayLike);

(5)通过扩展运算符

[…arrayLike]

13.数组有哪些原生方法?

  • 记住pop unshift push shift splice sort reverse 可以改变数组 — es6新增3个可以改变数组的

❌14.常见的位运算符有哪些?其计算规则是什么?

15.为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?

arguments是一个对象,它的属性是从 0 开始依次递增的数字,还有calleelength等属性,与数组相似;但是它却没有数组常见的方法属性,如forEach, reduce等,所以叫它们类数组。

要遍历类数组,有三个方法:

(1)将数组的方法应用到类数组上,这时候就可以使用callapply方法,如:

function foo(){

Array.prototype.forEach.call(arguments, a => console.log(a))

}

(2)使用Array.from方法将类数组转化成数组:‌

function foo(){

const arrArgs = Array.from(arguments)

arrArgs.forEach(a => console.log(a))

}

(3)使用展开运算符将类数组转化成数组

function foo(){

const arrArgs = […arguments]

arrArgs.forEach(a => console.log(a))

}

❌16.什么是 DOM 和 BOM?

17.对类数组对象的理解,如何转化为数组?

  • 通过 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);

18.escape、encodeURI、encodeURIComponent 的区别?

  • encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。

  • encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。

  • escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别

19.对AJAX的理解,实现一个AJAX请求?

const SERVER_URL = “/server”;

let xhr = new XMLHttpRequest();//创建xhr实例

xhr.open(“GET”, url, true);//设置请求路径xhr.open(“GET”, url, true);

// 设置状态监听函数

xhr.onreadystatechange = function() {

if (this.readyState !== 4) return;

// 当请求成功时

if (this.status === 200) {

handle(this.response);

} else {

console.error(this.statusText);

}

};

// 发送 Http 请求

xhr.send(null);

📢20.JavaScript为什么要进行变量提升,它导致了什么问题?

首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。

  • 在解析阶段,JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。

全局上下文:变量定义,函数声明

函数上下文:变量定义,函数声明,this,arguments

  • 在执行阶段,就是按照代码的顺序依次执行。

那为什么会进行变量提升呢?主要有以下两个原因:

  • 提高性能

  • 容错性更好

  • 总结:

  • 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间

  • 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行

21.常见的DOM操作有哪些?

增:createElement appendChild

删:removeChild

交换:insertBefore

查:querySelectorAll

22.use strict是什么意思 ? 使用它区别是什么?

禁止使用 with 语句。

禁止 this 关键字指向全局对象。

对象不能有重名的属性。

消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;

消除代码运行的不安全之处,保证代码运行的安全;

提高编译器效率,增加运行速度;

为未来新版本的 Javascript 做好铺垫。

📢23.如何判断一个对象是否属于某个类?

第一种方式,使用 instanceof 运算符来判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

第二种方式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,但是这种方式不是很安全,因为 constructor 属性可以被改写。

第三种方式,如果需要判断的是某个内置的引用类型的话,可以使用 Object.prototype.toString() 方法来打印对象的[[Class]] 属性来进行判断。

24.强类型语言和弱类型语言的区别?

两者对比:强类型语言在速度上可能略逊色于弱类型语言,但是强类型语言带来的严谨性可以有效地帮助避免许多错误。

❌25.解释性语言和编译型语言的区别?

**两者主要区别在于:**前者源程序编译后即可在该平台运行,后者是在运行期间才编译。所以前者运行速度快,后者跨平台性好。

📢26.如何使用for…of遍历对象?

如果需要遍历的对象是类数组对象,用Array.from转成数组即可。

var obj = {

0:‘one’,

1:‘two’,

length: 2

};

obj = Array.from(obj);

for(var k of obj){

console.log(k)

}

//如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可。

//方法一:

var obj = {

a:1,

b:2,

c:3

};

obj[Symbol.iterator] = function(){

var keys = Object.keys(this);

var count = 0;

return {

next(){

if(count<keys.length){

return {value: obj[keys[count++]],done:false};

}else{

return {value:undefined,done:true};

}

}

}

};

for(var k of obj){

console.log(k);

}

// 方法二

var obj = {

a:1,

b:2,

c:3

};

obj[Symbol.iterator] = function*(){

var keys = Object.keys(obj);

for(var k of keys){

yield [k,obj[k]]

}

};

for(var [k,v] of obj){

console.log(k,v);

}

27.forEach和map方法有什么区别?

map不会改变原数组,是对数组的每一项进行处理

四、原型与原型链


✅1.对原型、原型链的理解?

在JavaScript中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。一般来说不应该能够获取到这个值的,但是现在浏览器中都实现了 proto 属性来访问这个属性,但是最好不要使用这个属性,因为它不是规范中规定的。ES5 中新增了一个 Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型。

当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype 所以这就是新建的对象为什么能够使用 toString() 等方法的原因。

2.如何获得对象非原型链上的属性?

使用后hasOwnProperty()方法来判断属性是否属于原型链的属性:

function iterate(obj){

var res=[];

for(var key in obj){

if(obj.hasOwnProperty(key))

res.push(key+': '+obj[key]);

}

return res;

}

❌3.原型修改、重写?原型链指向?

4.原型链的终点是什么?如何打印出原型链的终点?

由于Object是构造函数,原型链终点是Object.prototype.proto,而Object.prototype.proto=== null // true,所以,原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype的下一级是Object.prototype.proto

5.如何获得对象非原型链上的属性?

使用后hasOwnProperty()方法来判断属性是否属于原型链的

五.执行上下文/作用域链/闭包


✅对闭包的理解?

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

闭包有两个常用的用途;

闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。

闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。

✅对作用域、作用域链的理解?

(1)全局作用域

最外层函数和最外层函数外面定义的变量拥有全局作用域

所有未定义直接赋值的变量自动声明为全局作用域

所有window对象的属性拥有全局作用域

全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突。

(2)函数作用域

函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到

作用域是分层的,内层作用域可以访问外层作用域,反之不行

2)块级作用域

使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由{ }包裹的代码片段)

let和const声明的变量不会有变量提升,也不可以重复声明

在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部。

在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。

作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。

当查找一个变量时,如果当前执行环境中没有找到,可以沿着作用域链向后查找。

✅对执行上下文的理解?

  1. 执行上下文类型

(1)全局执行上下文

任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。

(2)函数执行上下文

当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个。

(3)eval函数执行上下文

执行在eval函数中的代码会有属于他自己的执行上下文,不过eval函数不常使用,不做介绍。

  1. 执行上下文栈

JavaScript引擎使用执行上下文栈来管理执行上下文

当JavaScript执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文。

let a = ‘Hello World!’;

function first() {

console.log(‘Inside first function’);

second();

console.log(‘Again inside first function’);

}

function second() {

console.log(‘Inside second function’);

}

first();

//执行顺序

//先执行second(),在执行first()

  1. 创建执行上下文

创建执行上下文有两个阶段:创建阶段和执行阶段

1)创建阶段

(1)this绑定

在全局执行上下文中,this指向全局对象(window对象)

在函数执行上下文中,this指向取决于函数如何调用。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined

(2)创建词法环境组件

词法环境是一种有标识符——变量映射的数据结构,标识符是指变量/函数名,变量是对实际对象或原始数据的引用。

词法环境的内部有两个组件:加粗样式:环境记录器:用来储存变量个函数声明的实际位置外部环境的引用:可以访问父级作用域

(3)创建变量环境组件

变量环境也是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。

2)执行阶段

此阶段会完成对变量的分配,最后执行完代码。

简单来说执行上下文就是指:

在执行一点JS代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。这一步执行完了,才开始正式的执行程序。

在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。

全局上下文:变量定义,函数声明

函数上下文:变量定义,函数声明,this,arguments

六.this/call/apply/bind?

✅1.对this对象的理解

this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。

第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。

第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。

第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。

第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。

✅2.call() 和 apply() 的区别?

它们的作用一模一样,区别仅在于传入参数的形式的不同。

✅3.实现call、apply 及 bind 函数(建议看一下鲨鱼哥的掘金手写)?

手写

七.异步编程

✅1.说一下对Promise的理解?

Pending(进行中)

Resolved(已完成)

Rejected(已拒绝)

✅2.Promise的基本用法?

all方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected。

javascript

let promise1 = new Promise((resolve,reject)=>{

setTimeout(()=>{

resolve(1);

},2000)

});

let promise2 = new Promise((resolve,reject)=>{

setTimeout(()=>{

resolve(2);

},1000)

});

let promise3 = new Promise((resolve,reject)=>{

setTimeout(()=>{

resolve(3);

},3000)

});

Promise.all([promise1,promise2,promise3]).then(res=>{

console.log(res);

//结果为:[1,2,3]

})调用all方法时的结果成功的时候是回调函数的参数也是一个数组,这个数组按顺序保存着每一个promise对象resolve执行时的值。

(4)race()

race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected。

let promise1 = new Promise((resolve,reject)=>{

setTimeout(()=>{

reject(1);

},2000)

});

let promise2 = new Promise((resolve,reject)=>{

setTimeout(()=>{

resolve(2);

},1000)

});

let promise3 = new Promise((resolve,reject)=>{

setTimeout(()=>{

resolve(3);

},3000)

});

Promise.race([promise1,promise2,promise3]).then(res=>{

console.log(res);

//结果:2

},rej=>{

console.log(rej)};

)那么race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

5. finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

promise

.then(result => {···})

.catch(error => {···})

.finally(() => {···});上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。

server.listen(port)

.then(function () {

// …

})

.finally(server.stop);

finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。finally本质上是then方法的特例:

promise

.finally(() => {

// 语句

});

// 等同于

promise

.then(

result => {

// 语句

return result;

},

error => {

// 语句

throw error;

}

);

上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

✅3.Promise.all和Promise.race的区别的使用场景?

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

Promise.all中传入的是数组,返回的也是是数组,并且会将进行映射,传入的promise对象返回的值是按照顺序在数组中排列的,但是注意的是他们执行的顺序并不是按照顺序的,除非可迭代对象为空。

需要注意,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景,就可以使用Promise.all来解决。

(2)Promise.race

顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。当要做一件事,超过多长时间就不做了,可以用这个方法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

✅4.对async/await 的理解?

async/await其实是Generator 的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await则为等待,所以很好理解async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。当然语法上强制规定await只能出现在asnyc函数中,先来看看async函数返回了什么:

async function testAsy(){

return ‘hello world’;

}

let result = testAsy();

console.log(result)

所以,async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,当然应该用原来的方式:then() 链来处理这个 Promise 对象,就像这样:

async function testAsy(){

return ‘hello world’

}

let result = testAsy()

console.log(result)

result.then(v=>{

console.log(v) // hello world

})那如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。

联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。

注意:Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。

5.异步编程的实现方式?

6.setTimeout、Promise、Async/Await 的区别?

7.Promise解决了什么问题?

  • 回调地狱,异步编程

await 到底在等啥?

**await 在等待什么呢?**一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。

await 表达式的运算结果取决于它等的是什么。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

8.async/await的优势?

Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它

9.async/await对比Promise的优势?

代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担

Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅

错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余

调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。

10.async/await 如何捕获异常?

try catch

11.什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?


  1. 0-9 ↩︎

  2. 1-9 ↩︎

  3. a-zA-Z$ ↩︎

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值