前端常见面试题

Js基础

原型对象/对象原型/原型链
原型对象(prototype)
原型对象是构造函数的一个属性,它指向另一个对象,原型对象本身就是一个对象,原型对象上的属性和方法都被构造函数所拥有
构造函数通过原型对象定义的属性和方法都被构造函数的实例对象所共享
我们倾向于把不变的,或常用的方法定义在原型对象上,这样都能被实例对象所访问,并且可以避免浪费内存。

对象原型(proto)
对象都会有一个属性__proto__,它指向构造函数的原型对象(prototype),对象之所以可以访问原型对象上的方法,正是因为对象上有原型的存在。
原型与原型对象是等价。
原型存在的意义在于为对象成员的查找机制提供一个方向,它是一个非标准属性,只可读,不可写,日常开发中并不使用它。

原型链
原型链是基于对象原型,为对象成员提供一个查找机制,或者说查找规则

成员查找机制:
当访问一个对象的属性(或方法)时,首先查找该对象自身有没有这个属性(或方法);
如果没有就查找它的原型(原型(proto)指向原型对象(prototype));
如果还没有就查找原型对象的原型(Object的原型对象)
依此类推一直找到Object为止(Object的原型对象的原型指向null)
举例:instanceof实现机制

作用域/作用域链/闭包
作用域
作用域是指对某一变量或方法具有访问权限的代码空间。在js中,作用域是在函数中维护的。表示变量或函数起作用的区域,指代了它们在什么样的上下文中执行。作用域分为两种,全局作用域和本地作用域。本地作用域是按照函数来划分的。
作用域链
作用域都有上下级关系,上下级关系确定函数在那个作用域下创建。变量的取值都会在当前作用域中查找,如果没有查找到,就到上级作用域查找,直到查到全局作用域,这个查找的过程叫做作用域链。
闭包
闭包是指有权访问另一个函数作用域中变量的函数。创建闭包最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包来突破作用域链,将函数内部的变量和方法传递到外部。
优点:
避免全局变量的污染。
业务逻辑连续,当闭包作为另一个函数调用参数时,可以避免脱离当前逻辑而单独编写额外逻辑
缺点:闭包会常驻内存,增大内存使用量,很容易造成内存泄漏。
解决方法:在退出函数前,将不使用的局部变量删除。

闭包的特性:
函数内再嵌套函数
内部函数可以引用外层的参数与变量
参数与变量不会被垃圾回收机制回收
好处:能够实现封装和缓存等
坏处:消耗内存,不正当使用会造成内存溢出问题
闭包的工作原理:
因为闭包只有在被调用时才执行操作,所以它可以被用来定义控制结构。
多个函数可以使用同一环境,这使得他们可以通过改变那个环境来相互交流。

Vue基础

组件中data为什么是函数
组件中的data写成一个函数,数据以函数返回值的形式定义。这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。

v-show与v-if有何区别
v-if是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件会适当的被销毁和重建;也是短路的:如果初始渲染条件为假,则什么也不做。直到条件第一次变为真时才会开始渲染条件块。
v-show就简单得多,它不管初始条件是什么,元素总是会被渲染,并且只是简单地基于css的“display”属性进行切换。
所以,v-if适用于运行时很少改变条件,不需要频繁切换条件的场景;v-show则适用于需要频繁切换条件的场景。

computed和watch的区别和运用场景
computed是计算属性,依赖于其他属性值,并且computed的值是有缓存的,只有它依赖的属性值发生改变,才会重新计算computed的值。需要进行数值计算,且依赖于其他数据时,应该是用computed.
watch更多的【观察】的作用。类似于某些数据的监听回调,每当监听的数据变化时才会执行对调进行后续操作。
需要在数据变化时执行异步或开销较大的操作时,应该是用watch。使用watch选项允许我们执行异步操作,同时会限制我们执行该操作的频率,在我们得到最终结果前,设置中间状态。

谈谈对vue生命周期的理解
Vue实例有一个完整的生命周期,也就是从开始创建,编译模板,挂载dom,渲染更新,卸载等一系列过程,我们称之为Vue的生命周期。
BeforeCreate是new Vue()之后触发的第一个钩子,在当前阶段data,methods,computed,watch上的数据和方法都不能访问。

Created在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。

BeforeMount发生在挂载之前。在这之前template模板已导入渲染函数编译。而当前阶段虚拟dom已经创建完成,即将开始渲染。

Mounted在挂载完成后发生,在当前阶段,真实的dom挂载完成,数据完成双向绑定,可以访问到dom节点,使用$refs属性对dom进行操作。

BeforeUpdate发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重新渲染。

Updated当data数据发生更新完成之后触发。当前阶段组件dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环。

BeforeDestroy发生在实例销毁之前,在当前阶段实例还可以被使用,可以在这里进行一些善后工作,比如清除计时器等。

Destroy发生在实例销毁之后,这个时候只剩下了dom空壳,组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

Keep-alive:
可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性:include/exclude,允许组件you有条件的进行缓存。
两个生命周期:activated/deactivated,用来得知当前组件是否处于活跃状态。

组件间传值
父子相传:父传子:组件属性+props;子传父: e m i t ( 事 件 名 , 参 数 ) 兄 弟 相 传 : l o c a l S t o r a g e , s e s s i o n S t o r a g e , V u e x , p r o v i d e / i n j e c t , emit(事件名,参数) 兄弟相传:localStorage,sessionStorage,Vuex,provide/inject, emit(,)localStoragesessionStorageVuexprovide/injectparent/ c h i l d r e n , e v e n t b u s 举 例 p r o v i d e / i n j e c t : 例 如 在 a 组 件 中 写 一 个 方 法 放 在 p r o v i d e 选 项 中 , b 组 件 通 过 i n j e c t : [ ‘ 方 法 名 ’ ] 选 项 , 使 得 b 组 件 的 t h i s 调 用 该 方 法 : t h i s . 方 法 名 举 例 e v e n t b u s : 新 建 文 件 n e w V u e ( ) 声 明 为 b u s 并 导 出 , 将 其 挂 载 到 V u e 原 型 对 象 上 , 在 v u e 文 件 中 使 用 t h i s . children,event bus 举例provide/inject: 例如在a组件中写一个方法放在provide选项中,b组件通过inject:[‘方法名’]选项,使得b组件的this调用该方法:this.方法名 举例event bus: 新建文件new Vue()声明为bus并导出,将其挂载到Vue原型对象上,在vue文件中使用this. childreneventbusprovide/injectaprovidebinject:[]使bthisthis.eventbus:newVue()busVuevue使this.bus. o n 接 收 数 据 , 在 j s 文 件 中 引 入 b u s , 通 过 b u s . on接收数据,在js文件中引入bus,通过bus. onjsbus,bus.emit发布数据。(跨文件传值)

mvvm框架是什么,哪些场景适合?
Mvvm是一个model-view-viewModel的,是一种前端开发的架构模式。
model表示数据层,view表示视图层,viewModel表示逻辑层,通过viewModel来连接model和view两层。
其核心是提供对view和viewModel的双向数据绑定,这使得viewModel的状态改变可以自动传递给view,即所谓的数据双向绑定,保证了视图和数据的一致性,让前端开发更高效便捷

SPA单页应用的理解
仅在web页面初始化时加载相应的html,css和js。一旦页面加载完,不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现html内容的变换,ui与用户的交互,避免页面的重载。
优点:
用户体验好,避免不必要的跳转和重复渲染,相对服务器压力小些。
前后端职责分离,架构清晰。
缺点:
初次加载耗时多。为实现单页web应用功能和效果展示,需要在加载页面的时候将js,css统一加载,部分页面按需加载。
破坏浏览器前进后退机制,需自己管理路由。
SEO不好。

vuex应用场景,什么适合适合用?
不打算开发大型单页应用,使用vuex可能是繁琐的。
涉及到非父子关系,组件,需要数据和组件分离时。

简单介绍一下Vuex
Vuex是一个专为Vue.js应用程序开发的状态管理模式。每一个Vuex应用的核心就是store(仓库)。它是一个容器,包含应用中发部分状态(state)。
Vuex得状态存储是响应式的。当Vuex组件从store中读取状态时,若store中的状态发生变化,那么相应的组件也会得到更新。
改变store中的状态唯一途径就是显示地提交(commit)mutation。这样我们可以方便跟踪每一个状态的变化。
主要包括以下几个模块:
State:定义了应用状态的数据结构,在这里设置默认的初始状态。
Getter:允许组件从store中获取数据,mapGetters辅助函数可以讲store中的getter映射到局部的计算属性。
Mutation:是唯一更改store中状态的方法,且必须是同步函数。
Action:用于提交mutation。而不是直接变更状态,可以包含任意异步操作。
Module:允许将单一的store拆分为多个store且同时保存在单一的状态树中。

vue-router路由模式有几种
vue-router有3种路由模式:hash,history,abstract.
Hash:使用URL hash值来作路由,支持所有浏览器。
History:依赖html5 history API和服务器配置。
Abstract:支持所有js运行环境,如node.js服务器端,如果发现没有浏览器的API,
会自动强制进入这个模式。

vue-router有哪几种导航钩子?
https://www.cnblogs.com/ylyw/p/13307815.html
全局导航钩子
组件内的钩子
单独路由独享组件

全局导航钩子:
前置守卫路由:
router.beforeEach((to,from,next) => {})
这三个参数to,form,next的作用:
to: Route对象,代表要进入的目标,是一个路由对象
from: Route对象,代表正要离开的路由,也是路由对象
next:Function,这是一个必须调用的方法,具体的执行效果依赖next方法调用的参数
后置守卫路由:
Router.afterEach((to,from) => {})

组件内的钩子:
直接在路由组件内部进行定义
BeforeRouteEnter(to,form,next):渲染该组件的对应路由被confirm前调用,没有this,通过next方法中的vm访问组件实例
beforeRouteUpdate(to,form,next):在当前路由改变,但依然渲染该组件时调用
beforeRouteLeave(to,form,next):导航离开该组件的对应路由时被调用

路由独享的钩子:
即单个路由独享的钩子,它是在路由配置上直接定义的:beforeEnter
beforeEnter:(to,form,next) => {}

Vue原理
V-model的原理
本质上是一个语法糖。可以看成是value+@input事件的语法糖。
通过v-bind:绑定响应式数据,触发input事件并传递数据。
可以通过model属性的prop和event属性来自定义。
原生的v-model,会根据标签的不同生成不同的事件和属性。

render函数
vue中我们使用模板html语法组件页面
render函数可以用js语言来构建dom,创建html模板

vue的虚拟dom
虚拟dom(virtual-dom简称vdom),就是通过js来模拟真实的dom,通过这种技术,我们可以精准的改变需要改变的dom结构。
而不是粗暴的直接将整个dom结构改变,从而减少dom开销。
另一方面,dom结构用js来控制的话,就可以实现数据来控制结构的改变。

Vue的响应式原理
Vue.js一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作dom,而是通过修改数据。
1.vue的数据响应式原理是由js标准内置对象方法Object.defineProperty来实现的,当你把一个普通的js对象传入Vue实例作为data选项,Vue将遍历此对象所有的数据(property),并使用Object.defineProperty把这些property全部转为getter/setter
2.vue在初始化数据时,会使用Object.defineProperty重新定义data中的所有属性。当页面使用对应属性时,首先会进行依赖收集(收集当前组件的watcher);如果属性发生变化会通知相关依赖进行更新操作(发布订阅)

vue2.x如何监测数组变化
使用了函数劫持的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。
所以当调用数组api时,就可以通知依赖更新。如果数组中包含引用类型,会对数组中的引用类型再次递归遍历监控。

模板编译原理
Vue的编译过程就是将template转化为render函数的过程,会经历一下阶段“
1.生成AST树,优化codegen
首先解析模板,生成AST语法树(用js对象的形式来描述整个模板)。使用大量正则表达式对模本解析。
2.执行钩子
遇到标签,文本的时候都会执行对应的钩子进行相关处理。
模板中不是所有数据都是响应式的,有些数据首次渲染后不会再变化,对应的dom也一样。
3.深度遍历AST树
按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对他们的对比,对运行时的模板起到很大的优化作用。
4.树转化
将优化后的AST keep-alive树转换为可执行的代码

key的作用
key的作用主要是为了高效的更新虚拟dom。其原理是vue在patch过程中通过key可以精准判断两个节点是否为同一个,从而避免频繁更新不同元素。是的整个patch过程更高效,减少了dom操作.

为什么不能用index作为key
使用index,当删除某个index的时候,发现与相应key的绑定关系有变化,所以会被重新渲染,影响性能。
若使用唯一id作为key,删除后,剩下的元素因为与key的关系没有发生变化,所以不会引起渲染。

nextTick的原理
https://blog.csdn.net/qq_42072086/article/details/106987202
当调用nextTixk方法是会传入两个参数,一个是回调函数,一个是执行回调函数的上下文环境。如果没有就提供回调函数,那么将返回一个promise对象。

首先将拿到的对调函数存放在数组中,判断是否正在执行回调函数,如果当前没有pending的时候,就是执行timeFunc这个方法。
多次执行nextTick只会执行一次timeFunc,timeFunc他本质是一个执行异步的方法。

在timeFunc中选择一个异步方法(判断是否支持promise,若支持就将flushCallbacks放在promise中异步执行,并标记使用微任务;不支持则看是否支持MutationObSERVER方法,支持就new一个MutationObserver类,创建一个文本节点进行监听,当数据发生变化就会异步执行flushCallbacks方法,若以上两个都不支持就看是否支持setTmmediate方法,再不支持的话就用setTimeout异步执行)【降级使用异步方法】。
FlushCallballs中就是将传递的函数依次执行。

nextTick多次调用会维持一个数组,之后会异步的把数组中的方法依次执行,这样用户会在视图更新后获取到真实的dom元素.
图示:

nextTick和$nextTick区别:
nextTick(callback):当数据发生变化,更新后执行回调
$nextTick(callback):当dom发生变化,更新后执行回调

浏览器机制相关
内核主要分成两部分:渲染引擎(layout engineer 或者rendering engineer)和js引擎。
最开始两个并没有区分很明确。后来js引擎·越来越独立,内核就倾向于指渲染引擎。
1.渲染引擎
解析dom文档和css规则并将内容排版到浏览器中显示有样式的界面,也称之为排版引

2.js引擎
解析js脚本的模块和执行js来实现网页的动态效果

网络相关

http(超文本传输协议)的特点
1.无状态:协议对客户端没有状态存储,对事物处理没有记忆能力,比如访问一个网站需要反复进行登录操作
2.无连接:http/1.1之前,由于无状态特点。每次请求需要通过tcp三次握手,四次挥手,和服务器重新建立连接
3.基于请求和响应:基本特性
4.通信使用明文,请求和响应不会对同新方进行确认,无法保护数据的完整性。
5.简单快速,灵活

http(超文本传输协议)和https(安全套接字层传输协议)的区别
http是超文本传输协议,https是安全套接字层传输协议。
由于http协议使用明文方式发送方发内容,不提供任何方式的数据加密,所以http不适合传输一些敏感信息,如信用卡号,密码等。
为了解决这一缺陷,就使用了另一种协议,https协议,就是在http基础上加入了ssl/tls协议,ssl依靠证书来验证服务器的身份。并为浏览器之间的通信加密。
所以https的主要作用分为两种,一种是建立一个信息安全通道,保证数据的安全;另一种就是确认网站的真实性。

http的url开头是http,https则是https
https不安全,https反之,
http端口为80,https为443
在OSI网络模型汇总,http工作在应用层,https的安全传输机制工作在传输层。
http明文传输,https加密。https需要ssl证书

页面形成过程
浏览器输入网址到渲染完成大致会经历两个过程:
加载过程
渲染过程

加载过程
1.DNS(domain name serve 域名服务器)解析:域名->IP地址 (将域名解析为ip地址)
2.浏览器根据ip地址向服务器发起http请求 (涉及三次握手四次挥手,等会说)
3.服务器接收并处理请求,并返回给浏览器

渲染过程
1.根据html代码生成DOM(文档对象模型) Tree
2.根据css代码生成CSSOM (Object Model)
3.浏览器将DOM Tree和CSSOM整合形成Render Tree
4.根据Render Tree 渲染页面
5.遇到

说明浏览器根据ip地址向服务器发起http请求:
中间经历了tcp连接的三次握手

三次握手:
三次握手其实不准确,官网文档使用“handshake”一词描述tcp建立整个连接的过程,所以说,建立整个tcp连接只握了一次手,所以日常中说的三次握手建立连接是不准确的。它说的三次是发送三个报文。所以可以说他是三报文握手。
建立tcp连接之前,客户机处于closed(关闭)的状态,服务器也是closed(关闭)的状态。客户机主动打开,向服务器发送一次报文到达服务器。服务器被动打开了后,就处于监听状态。等待客户机发送第一次报文。(第一次握手)
收到之后,立刻向客户机发送一个确认的报文(第二次握手),客户端这边收到确认报文了。客户端再发送一次握手的报文给服务器端(第三次握手),这一次是来确认客户端是否收到了刚刚服务器发送的确认报文。

问题:为什么客户端已经收到了服务器端已经发送的确认报文确认已经接收到了需要连接的请求,还是需要客户端再发一次确认刚刚收到服务端确认报文的报文?
答:因为第一次发送报文可能是失效的,不是说这个报文没有作用了,而是发送的过程中由于网络中的某种原因而导致这条报文发生滞留或阻塞,就停留在了网络中。但是tcp连接整个过程是有超时,重传这样的机制;所以客户机因为这些原因又去发送一遍,但是这次整个握手过程成功了,建立连接了。这个时候,刚刚滞留的那条连接突然又生效了,它到达服务器了,服务器收到了,给客户机发送确认报文,如果没有第三次确认报文的发送,那么tcp连接岂不是建立了。但是这条旧的连接我们已经不需要建立了,因为刚刚已经重传过了,所以会有第三次确认报文的发出。
https://gitee.com/tanh-coding/work_area/blob/master/14%E9%9D%A2%E8%AF%95/03-%E5%8A%A0%E8%BD%BD%E4%B8%8E%E6%B8%B2%E6%9F%93/02-%E9%A1%B5%E9%9D%A2%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B%E5%90%84%E7%A7%8D%E6%83%85%E5%86%B5.html

四次挥手:
首先客户机主动关闭,关闭之前还是建立状态,客户机需要关闭tcp连接的时候,就发送一个报文给服务器端,服务器接收后知道客户机需要断开连接了。然后服务器端就发送一个确认报文给到客户机,客户机收到确认报文后,这个时候,客户端到服务器端方向的tcp连接已经断开了。此时客户端已无法向服务器端发送其他任何的信息。但是服务器到客户端的方向的tcp还是可以连接的。
之后呢,服务器端又再次发送一个报文给到客户端,客户端接收后发送确认刚刚收到了服务器要关闭的报文的报文。
问题:最后一次报文都要关闭连接到了,为什么客户端要等待一定时间?
因为最后一次报文服务器端可能没有接收到,服务器就可能认为是不是我发送的通知关闭的报文是没发出去的,所以客户端才没有发送确认过来。那么服务端又发送一个关闭报文过来,如果不加这个等待时长,这条连接就关闭了。

性能与优化

Vue项目
1.代码层面
v-if和v-show区分使用场景
computed和watch区分使用场景
v-for遍历必须为item添加key,且避免同时使用v-if
长列表性能优化
事件的销毁,图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表
服务器端渲染ssr或者预渲染
webpack层面
减少es6转es5的冗余代码
提取公共代码
模板预编译
提取组件的css
优化sourceMap
构建结果输出分析
基础的web技术的优化
开启gzip压缩
浏览器缓存
CDN的使用
使用Chrome Performance查找性能瓶颈

2.详述cdn加速
1.先找一些免费的cdn的网站,获取想要移除的包的cdn链接
2.在vue.config.js文件中设置一个cdn的对象,用来管理js和css链接,设置一个移除包的对象
3.获取环境变量,只在测试/生产环境使用cdn
4.在webpack中的externals选项中配置移除对象;在html的插件中传入cdn对象
5.在index.html中获取htmlWebpackPlugin对象的options对象,去除cdn遍历

3.解决首屏加载速度过慢
https://blog.csdn.net/weixin_44475093/article/details/110675962
用DomContentLoaded事件或者performance计算首屏时间
几种优化首屏的方式
减小入口文件体积
静态资源本地缓存
UI框架按需加载
图片资源压缩
组件重复打包
开启gzip压缩
使用ssr

减小入口文件体积
路由懒加载:
把不同路由对应的组件分割成不同的代码块,等路由被请求是单独打包路由,在配置router时,采用动态加载路由的形式(使用箭头函数包裹,import引入组件路径,函数形式加载)

ui框架按需加载,将需要的ui组件使用Vue.use()来单独包裹
解决组件重复打包
假设a.js文件是一个常用的库,现在有多个路由使用a.js文件,就造成了重复下载,在vue.config.js中将webpack的CommonsChunkPlugin配置中的minChunks设置3.,表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免重复加载组件

Webpack相关
https://blog.csdn.net/sinat_17775997/article/details/84314006
什么是webpack?
Webpack是一个打包模块化js的工具,在webpack中一切文件皆模块。
通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件。
Webpack专注于构建模块化项目。
优点:
专注处理模块化的项目,能开箱即用,一步到位。
可以通过plugin扩展,好用灵活
使用场景不限于web开发
社区庞大活跃,经常引入新特性

常见的loader
File-loader:把文件输出到一个文件夹中,在代码中通过相对URL去引用输出的文件
url-loader: 和file-loader类似,但能在文件很小的情况下以base64的方式把文件内容注入到代码中去
source-map-loader:加载额外的sourceMap文件,以方便断点调试
image-loader:加载并压缩图片
babel-loader:把es6转换为es5
css-loader:加载css,支持模块化,压缩,文件导入等特性
style-loader:把css代码注入到js中,通过dom操作去加载css
eslint-loader:通过eslint检查js代码

常见的plugin
Define-plugin:定义环境变量
Terser-webpack-plugin:通过terserPlugin压缩es6代码
Html-webpack-plugin:为html文件中引入外部资源,可以生成创建html入口文件
Mini-css-extract-plugin:分离css文件
Clean-webpack-plugin:删除打包文件
Happypack:实现多线程加速编译
Loader和plugin分别是什么
Loader:模块转换器,用于将模块的原内容转换成你想要的
Plugin:在webpack构建流程中的特定时机注入扩展逻辑,来改变构建结果

介绍bundle,chunk,module是什么
boundle是由webpack打包出来的文件
chunk:代码块,一个chunk由多个模块组合
module:是开发中的单个模块。
模块热更新
是webpack的一个功能,使得代码修改后不用刷新浏览器就可以更新

什么是tree-shaking
用来剔除js中不用的死代码,它依赖静态的es6模块化语法,最先在rollup中出现,webpack2.0中引入。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个不想秃头的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值