前端JS/TS面试题

JS面试题总结


一、this指向问题

(1)this的理解

JS是一个文本作用域的语言,也就是,变量的作用域在写这个变量的时候确定。this设计的目的就是在函数体内部,指代函数当前的运行环境。This具体来说有四种:
(1)默认的this绑定,非严格模式下,this就是全局变量Node环境中的global,浏览器环境下的window。严格模式下,默认的指向是undefined(es6中的类会自动使用严格模式)
(2)隐式绑定:使用obj.foo()这样的语法调用函数的时候,函数foo中的this绑定到obj对象。
(3)显示绑定:调用call,apply,bind方法
(4)构造绑定:new Foo(),foo中的this引用这个新创建的对象。

this指向最后调用它的对象,如果没有调用,则指向window对象(独自运行的函数也指向window;立即执行函数也指向window);
构造函数中this指向构造函数的实例;
箭头函数始终指向函数定义时的this.
es6中类会默认使用严格模式,禁止this指向全局对象,指向undefined。

(2)怎样改变this的指向问题

通过es6的箭头函数(指向函数定义时的this)
通过call,bind,apply函数改变this指向。

(3)Call,bind,apply三者的区别

Call,bind,apply这三个函数的第一个参数都是this的指向对象,第二个参数不同,call和bind都是接收参数列表apply接收的是一个包含多个参数的数组(也可以是类数组)。而且bind函数还会返回一个新的函数

(4)容易判读错的几种this情况

es6中类会默认使用严格模式,禁止this指向全局对象,所以会指向undefined

	 class C{
   //类
            a(){
   
                console.log(this);
            }
            b=()=>{
   
                console.log(this);
            }
        }
        c = new C();
        c.a();//this指向C
        f = c.a;
        f();//this指向undefined   es6中类会默认使用严格模式,禁止this指向全局对象,所以会指向undefined
        c.b()//this指向C
var name = "windowsName";
        var a = {
   
            fn:function(){
   
                console.log(this.name);
            }
        }
 window.a.fn();//undefined 指向a
var name = "windowsName";
        var a = {
   
            name:'Cherry',
            func1:function(){
   
                console.log(this.name)
            },
            func2:function(){
   
                console.log(this)//this指向a(object)
                setTimeout(function(){
   
                    console.log(this)//this指向window,自己执行的所以指向window
                    this.func1()//报错,window里面没有func1函数
                },100);
            }
        }
        a.func2()
var name = '123';
        var obj = {
   
            name:'456',
            getName:function(){
   
                function printName(){
   
                    console.log(this.name)
                }
                printName()
            }
        }
        obj.getName()//输出123

怎样把结果变为456?

		var name = '123';
        var obj = {
   
            name:'456',
            getName:function(){
   
                function printName(){
   
                    console.log(this.name)
                }
                printName.call(this)//使用call修改this指向
            }
        }
        obj.getName()//456

(5)this指向问题,说输出;这两中a,b写法,在内存消耗上有什么区别

a消耗小,a是直接定义在原型链上的;b相当于在每一个实例上赋值。

	 class C{
   
            a(){
   
                console.log(this);
            }
            b=()=>{
   
                console.log(this);
            }
        }
        c = new C();
        c.a();//c
        f = c.a;
        f();//undefined//es6中类禁止指向全局对象,指向undefined
        c.b();//c

二、手写节流(throttle)和防抖(debounce)

用户点击过快会导致卡顿,可以使用节流/防抖限制函数的执行次数,达到优化的目的。

节流(throttle):

高频事件触发,但在n秒内只执行一次。(购物中的秒杀)

防抖(debounce):

事件被触发n秒后执行回调,如果在n秒内事件又被触发,就重新计时。
在这里插入图片描述

function throttle(fn,delay){
   //手写节流函数
            var preTime = Date.now();//初始时间
            return function(){
   
                var context = this,
                args = arguments,
                nowTime = Date.now();//触发事件的时间
                if(nowTime - preTime >= delay){
   //如果两次时间间隔超过了指定时间,则执行函数
                    preTime = Date.now();//把初始时间更新为最新的时间
                    return fn.apply(context,args);
                }
            }
        }
 function debounce(fn,wait){
   //手写防抖函数
            var timer = null;//创建一个标记来存放定时器的返回值
            return function(){
   
                var context = this;
                args = arguments;
                if(timer){
   
                    clearTimeout(timer);//如果定时器存在,清除定时器重新计时
                    timer = null;
                }
                timer = setTimeout(()=>{
   //设置定时器,使事件间隔指定时间后执行
                    fn.apply(context,args);//确保上下文环境为当前的this,所以不能直接用fn(直接用fn会指向window);因为不确定入参的数量,所以还可以传入扩展后的arguments
                },wait);
            }
        }

改进:实现第一次点击和最后一次点击生效

三、js事件循环机制(宏任务,微任务,同步任务,异步任务)

(1) JS事件循环机制概念

js是一个单线程的脚本语言。当执行栈(执行环境的栈)中所有任务都执行完毕后,去看微任务队列中是否有事件存在,如果存在,则依次执行微任务队列中事件的回调,直到微任务队列为空。然后去宏任务队列中取出一个事件,把对应的回调加入当前执行栈,当执行栈中所有任务都执行完毕后,再去检查微任务队列中是否有事件存在。这个循环的过程就是事件循环。(浏览器的主线程是一个事件循环,)
微任务:promise.then;process.nextTick(Node的) ;promise.catch;promise.finally
宏任务:setTimeout,setInterval,setImmediate,I/O,Ui交互事件(鼠标点击,滚动页面,放大缩小等),script(整体代码)

打印顺序的题:同步任务->微任务->宏任务
New promise中的直接执行,是同步任务
process.nextTick比promise.then(都是微任务),但process.nextTick先执行
Async函数内,await和await之前的都是同步任务;await执行之后放入微任务队列中。

栗子:
setTimeout(()=>{
   
    console.log('quick timer')
},0)
new Promise((resolve,reject)=>{
   
    console.log('init promise')
    process.nextTick(resolve)
}).then(()=>{
   
    console.log('promise.then')
})
process.nextTick(()=>{
   
    console.log('nextTinck')
})
setImmediate(()=>{
   
    console.log('immediate')
})
//init promise-> nextTinck->promise.then->quick timer->immediate

(2)为什么js是单线程

JS是单线程的原因主要和JS的用途有关,JS主要实现浏览器与用户的交互,以及操作DOM。
如果JS被设计为多线程,如果一个线程要修改一个DOM元素,另一个线程要删除这个DOM元素,这时浏览器就不知道该怎么办,为了避免复杂的情况产生,所以JS是单线程的。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

(3)线程和进程是什么?举例说明

进程:cpu分配资源的最小单位(是能拥有资源和独立运行的最小单位)
线程:是cpu最小的调度单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
栗子:比如进程=火车,线程就是车厢

  • 一个进程内有多个线程,执行过程是多条线程共同完成的,线程是进程的部分。
    一个火车可以有多个车厢
  • 每个进程都有独立的代码和数据空间,程序之间切换会产生较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
    【多列火车比多个车厢更耗资源】
    【一辆火车上的乘客很难换到另外一辆火车,比如站点换乘,但是同一辆火车上乘客很容易从A车厢换到B车厢】
  • 同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
    【一辆火车上不同车厢的人可以共用各节车厢的洗手间,但是不是火车上的乘客无法使用别的火车上的洗手间】

(4)js引擎的执行栈

执行栈也叫执行上下文栈,用于存储代码执行期间创建的所有上下文,具有先进后出的特点。 也就是当代码第一次执行的时候,会把浏览器创建的全局执行上下文压入栈中;当以后调用函数时,会把调用函数所创建的函数执行上下文压入栈中。当函数执行完,就会将其从栈中弹出。当所有的代码都执行完毕之后,就把全局执行上下文弹出,执行上下文栈就为空了。(执行上下文分为三种,全局执行上下文,它是由浏览器创建的,也就是常说的window对象;函数执行上下文,它是函数被调用时被创建的,同一个函数被多次调用,会产生多个执行上下文;eval函数执行上下文

(5)setTimeout、Promise、Async/Await 的区别

(1)SetTimeOut的回调函数放到宏任务队列中,等到执行栈清空以后执行。
(2)promise本身是同步的立即执行函数。当执行resolve或reject的时候,此时是异步操作。Promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行。
(3)Async函数返回一个promise对象,当函数执行的时候,一旦遇到await就会先返回。等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了ASYNC函数体。

(6) Web Worker 标准

HTML5则提出了 Web Worker 标准,表示js允许多线程但是子线程完全受主线程控制并且不能操作dom,只有主线程可以操作dom,所以js本质上依然是单线程语言。
JS引擎是单线程单,JS如果执行时间过长,就会阻塞页面。
HTML5中支持了web worker。创建worker时,JS引擎向浏览器申请开一个子线程。子线程是浏览器开的,完全受主线程控制,而且不能操作DOM。JS引擎线程与worker线程之间通过特定单方式通信(postMessage API,需要通过序列化对象与线程交互特定的数据)。
如果有非常耗时的工作,可以单独开一个Worker线程。

(7)浏览器是多进程的

浏览器之所以能够运行,是因为系统给它分配了资源(cpu,内存)
也就是说每打开一个tab页面,就相当于创建了一个独立的浏览器进程(但是不是绝对的,有时候打开多个tab页面,会发现有的进程被合并了)。

浏览器包含的进程

在这里插入图片描述

浏览器多进程的优势

避免某个页面崩溃或者插件崩溃影响整个浏览器
多进程充分利用多核优势
方便使用沙盒模型隔离插件等进程,提高浏览器等稳定性

进程间的通信(进程通信)

管道通信:操作系统在内核中开辟了一段缓存区,进程1可以将需要交互的数据拷贝到这个缓存区里,进程2读取。
消息队列通信:消息队列是用户可以添加和读取消息的列表,消息队列里提供了一种从一个进程向另一个进程发送数据块的方法,不过和管道通信一样每个数据块有最大长度限制。
共享内存通信:映射一段能被其他进程访问的内存,由一个进程创建,但多个进程都可以访问。共享进程最快的是信号量通信:比如信号量初始值是1,进程1来访问一块内存的时候,就把信号量设为0,然后进程2也来访问的时候看到信号量为0,就知道有其他进程在访问了,就不访问了
socket:其他的都是同一台主机之间的进程通信,而在不同主机的进程通信就要用到socket的通信方式了,比如发起http请求,服务器返回数据。

什么是进程的死锁

介绍一下银行家算法

linux中硬链和软链的区别

介绍一下linux文件绝对路径和相对路径

四、跨域

(1)为什么会出现跨域问题?

因为浏览器的同源策略的限制。

(2)同源策略

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器正常的功能都有可能受影响。可以说web是构建在同源策略基础之上的。浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的JS脚本和另一个域的内容进行交互。所谓同源就是指两个页面具有相同的协议,域名,端口号。

(3)为什么需要同源策略(跨域限制)

(1)如果没有XMLHttpRequest同源策略,那么黑客可以进行CSRF攻击(登录正常网站之后,本地产生了cookie,正常网站没有登出的情况下访问了危险网站,危险网站伪装成用户向安全网站发送请求,cookie会自动附加到请求头,这样就会被安全网站认为是用户的操作。也就是说攻击者盗用了你的身份,以你的身份发送恶意请求。----->处理方法:验证码;对来源进行验证;使用token)。
(2)如果没有DOM同源策略,那么不同域的iframe之间可以相互访问。

(4)什么是跨域

当2个URL中的协议,域名,端口号中任意一个不相同时,就算作不同域。不同域之间相互请求资源,就是跨域。
跨域的请求能发出去,服务端能收到请求并返回结果,只是结果被浏览器给拦截了。

(5)怎么允许跨域(跨域解决办法)

A、JSONP

在页面上,js脚本,css样式文件,图片这三种资源是可以与页面本身不同源的。jsonp就利用了script标签进行跨域取得数据。
JSONP允许用户传递一个callback参数给服务器端,然后服务器端返回数据时会将这个callback参数作为函数名来包裹住JSON数据。这样客户端就可以随意定制自己的函数来自动处理返回的数据了。
JSONP只能解决get请求,不能解决post请求。

<script>
        function callback(data){
   
            console.log(data);
        }
    </script>
    <script src="http://localhost:80/?callback=callback"></script>

使用ajax实现跨域:

<script src="http://code.jquery.com/jquery-latest.js"></script> 

  $.ajax({
   
            url:'http://localhost:80/?callback=callback',
            method:'get',
            dataType:'jsonp', //=> 执行jsonp请求
            success:(res) => {
   
                console.log(res);
            }
        })
    
        function callback(data){
   
            console.log(data);
        }

B、 CORS跨域资源共享:

浏览器会自动进行CORS通信,实现CORS通信的关键是后端。服务端设置Access-Control-Allow-Origin就可以开启CORS。该属性表示哪些域名跨域访问资源。
主要设置以下几个属性:
Access-Control-Allow-Origin//允许跨域的域名
Access-Control-Allow-Headers//允许的header类型
Access-Control-Allow-Methods//跨域允许的请求方式

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

简单请求

只要同时满足以下两大条件,就属于简单请求。
在这里插入图片描述
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

非简单请求(预检请求)

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为预检请求(preflight),该请求由option方法得到,通过该请求来指导服务端是否允许跨域请求。
在这里插入图片描述

C、Nginx反向代理

通过nginx配置一个代理服务器将客户机请求转发给内部网络上的目标服务器;并将服务器上返回的结果返回给客户端。

D、webpack (在vue.config.js文件中)中 配置webpack-dev-server

devServer: {
   
        proxy: {
   
          '/api': {
   
            target: "http://39.98.123.211",
            changeOrigin: true,  //是否跨域
          },
        },
      },

五、es6的新特性

增加了块级作用域,关键字let,常量const
解构赋值
模块(将JS代码分割成不同功能的小块进行模块化)
模板字符串(使用反引号创建字符串,字符串里面可以包含用${}包裹的变量)
箭头函数;扩展运算符(…[1,2,3]->1,2,3)
Class类
Promise异步对象
For…of循环
Map,set,weakmap,weakset
新增的数组方法:Array.from()将set类变为数组;Array.find()找出第一个符合条件的数组成员
字符串方法:string.includes(value)->是否包含某个值,是返回true
symbol数据类型,表示独一无二的值。
Proxy代理

common js和es6模块的区别

commonjs模块输出的是一个值的拷贝,es6模块输出的是值的引用。
commonjs模块是运行时加载,es6模块是编译时输出接口。
commonjs是单个值导出,es6模块可以导出多个。
commonjs是动态语法可以写在判断里,es6模块静态语法只能写在顶层。
commonjs的this是当前模块,es6模块的this是undefined

六、Symbol(es6新增)

symbol不能使用new,因为symbol是原始数据类型,不是对象。
每一个symbol的值都是不相等的,是唯一的。主要是为了防止属性名冲突。
symbol不能和其他的数据类型比较,比较的话会报错
如果symbol和symbol比较,值为false。
for…in和object.keys()均不能访问到symbolz作为key的值。可以通过object.getOwnPropertySymbols获取。

Symbol的应用场景

(1)防止属性污染
在某些情况下,我们可能要为对象添加一个属性,此时就有可能造成属性覆盖,用Symbol作为对象属性可以保证永远不会出现同名属性。
(2)借助Symbol类型的不可枚举,我们可以在类中模拟私有属性,控制变量读写
(3)可以防止XSS攻击,JSON中不能存储Symbol类型的变量,这就是防止XSS的一种手段。

七、let var const的区别

(1)var定义的变量,没有块的概念,可以跨块访问(块{}),不能跨函数访问
Let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问
Const用来定义常量,使用时必须初始化(必须赋值),只能在块作用域里面访问,且不能修改。
(2)var可以先使用后声明,因为var有变量提升;let必须先声明后使用
(3)Var允许相同作用域内重复定义,let和const不允许重复定义
(4)Var声明的全局变量会自动成为window属性,但是let和const不会
(5)TDZ(临时性死区):凡是在使用let,const声明的变量之前,这些变量都是不可使用的。CONST

  if (true) {
   
            // TDZ开始
            tmp = 'abc'; // ReferenceError
            console.log(tmp); // ReferenceError
            //在没有声明变量之前tmp都是错误的
            let tmp; // TDZ结束
            console.log(tmp); // undefined
            tmp = 123;
            console.log(tmp); // 123
        }

const是怎么实现的只能赋值一次

八、介绍下set,weakset,map,weakmap的区别

Set:

类似于数组,但是set的值是唯一的;向set加入值的时候不会发生类型转换(set内部判断两个值是否相等,类似于精确相等运算符(===),所以5和’5’是两个不同的值)

Weakset:

和set类似,也是不重复的值的集合。它和set有三个个区别:
Weakset的成员只能是对象;
weakset不能遍历;
weakset中的对象都是弱引用,也就是说如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象占的内存。(没有size属性和foreach)

Map:

类似于对象,是键值对的集合(键不局限于字符串,各种类型都可以当做键)。

Weakmap:

与Map结构类似,是键值对的集合。它和map区别有两点:
weakmap只接受对象作为键名
键名是弱引用,一旦不需要,weakmap里面的键值对会自动消失,不需要手动删除。

九、原型和原型链

(1)构造函数

构造函数是一种特殊的函数,主要用来初始化对象(也就是为对象成员变量赋值),它总与new一起使用。我们可以把对象中一些公共属性和方法抽取出来,然后封装到这个函数里面。

(2)为什么引入原型对象prototype

构造函数方法很好用,但是存在浪费内存的问题。
构造函数中不变的方法在每次new一个实例的时候,都会在堆里面开辟一个空间。我们想让这个不变的方法只占一个空间,所以就有了构造函数原型。
JS规定每一个构造函数都有一个原型对象。我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。

(3) 原型对象

原型是什么:原型是一个对象,我们也称prototype为原型对象。
原型的作用是什么:共享方法

(4)构造函数,实例对象,原型对象三者之间的关系

每个构造函数都有一个原型对象,可以通过构造函数的prototype属性指向这个原型对象。
原型对象里面也有一个constructor属性,这个属性又指回了构造函数本身。
可以通过new和构造函数创建一个实例对象,这个实例对象里面有一个__proto__属性,这个属性指向构造函数的原型对象。
在这里插入图片描述

(5)原型链

当访问一个对象的某个属性时,会先从对象本身的属性去查找,如果没有找到的话,就会通过__proto__属性去构造函数的prototype上去查找;如果还没有找到,就去构造函数的prototype上的__proto__去查找,这样一层一层向上找所形成的链式结构,就是原型链。
(原型链的顶端是object的原型对象,也就是object.prototype.__proto__指向null)

原型链的应用:原型链是实现继承的主要方法

(6)原型链打印题踩坑

	  Function.prototype.a = () => console.log(1);
      Object.prototype.b = () => console.log(2);
      function A(){
   };
      var a = new A();
      a.a();//报错 除非把function改成A,才输出1
      a.b();//2    a的原型链上只有Object
     

原因:new返回了一个新对象{a:4,b:5},这个新的对象原型(proto)不是Foo,所以访问不到Foo原型对象里面的值。(o insatnceof Foo为false)

 	function Foo(){
   
        this.a = 1;
        return{
   
          a:4,
          b:5
        }
      }
      Foo.prototype.a = 6;
      Foo.prototype.b = 7;
      Foo.prototype.c = 8;
      var o = new Foo();
      //console.log(o)//{a:4,b:5}
      //console.log(o instanceof Foo)//false
      console.log(o.a);//4
      console.log(o.b);//5
      console.log(o.c);//undefined

10、了解JS的继承吗?ES5继承与ES6继承的区别

(1)继承:

子类可以继承父类的一些属性和方法

(2)Es5和es6继承的区别:

  • Es5的继承实质是先创建子类的实例对象,然后再将父类的方法添加到this上(Father.call(this))。
    Es6的继承实质是先创建父类的实例对象this,然后再用子类的构造函数修改this。
  • Es5的继承通过构造函数和原型来实现
    Es6通过class关键字定义类,类里面有构造方法,类之间通过关键字extends实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承类父类的this对象,然后对其加工。如果不调用super方法,子类将得不到this对象。

(3)js继承的六种方法:

原型链继承,借用构造函数继承,组合继承(原型加构造函数),原型式继承,寄生式继承,寄生组合式继承

11、js如何定义一个类并实现继承;要求不能使用es6的语法(手写一个寄生组合式继承)

组合继承(构造函数和原型链实现继承)

function Father(name,age){
   
        this.name = name;
        this.age = age;
    }
    Father.prototype.work = function(){
   //父亲的方法
        console.log('父亲工作')
    }
    function Son(name,age){
   
        Father.call(this,name,age)//继承父类的属性(在father中把this指向子构造函数的实例)
    }
    Son.prototype.study = function(){
   
        console.log('孩子上学')
    }
    Son.prototype = new Father();//继承父类的方法(直接赋值,修改了儿子原型对象的constructor)
    Son.prototype.constructor = Son;//把儿子的constructor修改回去
    var son = new Son('小明',18);
    console.log(son);
    console.log(son.__proto__.constructor)
    console.log(son instanceof Father);

在这里插入图片描述

寄生组合式继承

		function Father(name,age){
   
            this.name = name;
            this.age = age
        }
        Father.prototype.work = function(){
   
            console.log('父亲工作')
        }
        function Son(name,age){
   
            Father.call(this,name,age);//继承父类的属性
        }
        Son.prototype.study = function(){
   
            console.log('孩子上学')
        }
        Son.prototype = Object.create(Father.prototype);
        //继承父类的方法:先创建了一个临时的构造函数,然后将父亲的原型对象作为这个构造函数的原型,最后返回临时类型的新实例
        //然后让儿子的原型对象指向临时类型的新实例(这样就可以实现继承了)
        Son.prototype.constructor = Son;
        var son = new Son('aaa',23);
        console.log(Son.prototype);
        console.log(son.__proto__.constructor)
object.create相当于下面的create函数(不用写)
        function create(obj) {
   
            function F() {
   }//先创建了一个临时的构造函数
            F.prototype = obj;//然后将传入的对象作为这个构造函数的原型
            return new F();//返回了临时类型的新实例
        }

在这里插入图片描述

Es6实现继承

class Father{
   
           constructor(x,y){
   
            this.x = x;
            this.y = y;
           }
           sum(){
   
               console.log(this.x + this.y);
               console.log(this.x)
           }
           say(){
   
               return '我是爸爸'
           }
       }
       class Son extends Father{
   
        constructor(x,y){
   
            super(x,y);//调用了父类中的构造函数,必须在子类this之前调用
            this.x = x;
            this.y = y;
        }
        say(){
   
            console.log(super.say() + '的儿子')//super调用父类的普通函数
        }
       }
       var son = new Son(1,2)
       son.sum();

12、讲一讲Promise;语法

promise主要用于异步状态,可以将异步状态操作队列化,按照希望的顺序执行,用来替换es5的异步回调,解决回调地狱。
promise有三种状态,pending进行中,fulfilled成功,rejected失败。异步操作的结果可以决定是哪种状态。对象状态改变只有两种可能:从pending变成fulfilled和从pending变成rejected;状态改变之后不会再变,称为已定型resolved。

Promise的缺点:

无法取消promise,一旦新建它就会立即执行,无法中途取消。
如果不设置回调函数,promise内部抛出的错误,不会反映到外部
当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

Promise用法

Promise的构造函数接收一个函数作为参数,这个函数得传入两个参数:异步操作执行成功后的回调函数resolve和异步操作失败后的回调函数reject。

Promise.then

Promise.then方法也可以接收两个参数,第一个对应resolve的回调,第二个对应reject的回调。第二个参数是可选的,不一定要提供。

Promise.catch

Promise.catch方法和.then方法的第二个参数一样,用来指定reject的回调。不过它还有另外一个作用,在执行resolve回调(也就是.then的第一个参数时),如果代码出错,并不会报错卡死js,而是进入到catch这个方法中。

Promise.all

Promise.all方法接收一个数组参数(可迭代对象,例如数组,map,set),返回一个promise对象。提供了并行执行异步操作的能力,并且在所有异步操作执行完成后才执行回调。也就是将多个promise实例包装成一个新的promise实例。如果有一个promise失败,都失败;如果都成功,结果才返回成功。(项目中写删除选中的全部商品时用到了promise.all。因为之前写好了删除某一个产品的方法,所以把选中的商品遍历,用promise.all封装起来)

Promise.any

Promise.any方法接收一组promise实例作为参数,只要有一个promise成功,就返回已经成功的那个promise,如果没有一个promise成功,就返回一个失败的promise和AggregateError类型(它把单一的错误集合在一起)的实例。

Promise .finally

Promise .finally方法接收一个回调函数作为参数,返回一个promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行这个回调函数。避免了同样的语句.then和.catch中各写一次的情况。

Promise .race

Promise .race方法接收一个可迭代对象作为参数,迭代器中哪个promise获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

Promise.allSettled

Promise.allSettled方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve,reject)=>setTimeout(reject,100,'foo'));
const promise3 = [promise1,promise2];
Promise.allSettled(promise3).then((results)=>results.forEach((result)=>console.log(result)))
结果是:
{
    status: 'fulfilled', value: 3 }
{
    status: 'rejected', reason: 'foo' }

13、如何中止promise链式调用

(1)抛出一个异常,然后用catch接收
(2)返回一个pending状态的promise对象也可以结束回调,但是这样finally和catch都不会执行。
(3)返回一个rejected状态的promise

14、promise发生错误,怎么捕捉

  • 在then中指定异常处理函数,在then中添加两个函数,第二个函数用来处理失败的情况。
  • 用.catch实现全部捕获。
  • 在浏览器端,用window对象监听异常,监听方式是onunhandledrejection和onrejectionhandled,然后在回调函数中都会创建一个参数,该参数是个对象,包含了type,reason(错误原因),promise。

15、手写promise;手写promise.all;手写promise.any;手写promise.finally;手写Promise.race;手写Promise.allSettled

手写Promise

class WPromise {
   
            static pending = 'pending';//static静态方法/静态属性;表示该方法不会被实例继承;而是通过类来直接调用
            static fulfilled = 'fulfilled';
            static rejected = 'rejected';
            constructor(executor) {
   
                this.status = WPromise.pending;//初始化状态为pending
                this.value = undefined;//存储操作成功(this._resolve)返回的值
                this.reason = undefined;//存储操作失败(this._reject)返回的值
                this.callbacks = [];//存储then中传入的参数,同一个promise的then方法可以调用多次,所以用数组存
                executor(this._resolve.bind(this), this._reject.bind(this));//_方法名:表示这是只限于内部使用的私有方法
            }
            //onFulfilled是成功时执行的函数;onRejected是失败时执行的函数
            then(onFulfilled, onRejected) {
   
                //返回一个新的promise
                return new WPromise((nextResolve, nextReject) => {
   //把下一个promsie的resolve函数和reject函数也存在callback中,是为了将
                    //onFulfilled的执行结果通过nextResolve传入到下一个promise作为它的value值。
                    this._handler({
   
                        nextResolve,
                        nextReject,
                        onFulfilled,
                        onRejected
                    })
                })
            }
            _resolve(value) {
   //处理onFulfilled执行结果是一个promise时的情况
                if (value instanceof WPromise) {
   //当为true,说明promise肯定不是第一个promise,而是后续then方法返回的promise
                    value.then(//获取value;当value是个promsie时,内部会存有value变量
                        this._resolve.bind(this),
                        this._reject.bind(this)
                    );
                    return;
                }
                this.value = value;
                this.status = WPromise.fulfilled;//将状态设置为成功
                this.callbacks.forEach(cb => this._handler(cb));//通知事件执行
            }
            _reject(reason) {
   
                if (reason instanceof WPromise) {
   
                    reason.then(
                        this._resolve.bind(this),
                        this._reject.bind(this)
                    );
                    return;
                }
                this.reason = reason;
                this.status = WPromise.rejected;//将状态设置为失败
                this.callbacks.forEach(cb => this._handler(cb));
            }
            _handler(callback) {
   
                const {
   
                    onFulfilled,
                    onRejected,
                    nextResolve,
                    nextReject
                } = callback
                if (this.status === WPromise.pending) {
   
                    this.callbacks.push(callback);
                    return;
                }
                if (this.status === WPromise.fulfilled) {
   
                    const nextValue = onFulfilled ? onFulfilled(this.value) : this.value;//传入存储的值,未传入onFulfilled时,value传入
                    nextResolve(nextValue);
                    return;
                }
                if (this.status === WPromise.rejected) {
   
                    const nextReason = onRejected ? onRejected(this.reason) : this.reason;//传入存储的错误信息,未传入onRejected时,reason传入
                    nextReject(nextReason);

                }
            }
        }

测试:

  let p = new WPromise((resolve,reject)=>{
   
            //resolve('ok')
            reject('error')
        })
        p.then((data)=>{
   
            console.log('成功' + data)
        },(err)=>{
   
            console.log('失败' + err)
        })

手写Promise.all:

思路:接收一个可迭代对象作为参数,方法返回一个promise实例,参数中的所有promise都完成时回调完成(resolve);如果参数中有一个promise失败,这个实例回调失败(reject)。

Promise.myAll = function(promises){
   
    if(!Array.isArray(promises)) return//判断参数是不是数组,是返回
    return new Promise((resolve,reject)=>{
   //返回一个promise实例
        const result = [];
        let count = 0;
        for(let i = 0;i < promises.length;i++){
   
            Promise.resolve(promises[i])
            .then(value=>{
   
                result.push(value);
                count++;
                if(count===promises.length){
   //相等也意味着promise都完成
                    resolve(result)
                }
            })
            .catch((reason)=>{
   
                reject(reason)
            })
        }
    })
}

测试:

Const promise1 = Promise.resolve(3)
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值