2020前端面试总结(持续更新)

目录

一:css

1:rem、em 原理区别

2:标准的盒子模型

3:伪类选择器什么时候会用到 ,为什么要用伪类选择器

4:清除浮动的几种方式,及原理?

二:JS

1: 常见的数据类型 以及 基本(原始)数据类型和引用数据类型的区别

2:  闭包的优缺点

3:解释一下原型和原型链

4: 优化前端性能的方法

5: 解释一下axios 和 await

6: promise有几个状态 分别是什么

7: promise 是异步还是同步

8: new 一个构造函数的时候都做了什么

9: display: none 和 class="hidden" 的区别 

10: 如何解决跨域问题 ,为什么会有跨域的问题

11: ajax工作原理

12: sessionId详解

13: 构造函数

14:长连接和短连接

15:Cookie、sessionStorage、localStorage的区别

16:深度拷贝并且不影响原先的object方式

17:get和post 区别和相同点。以及还有哪些别的请求方式?

18:JavaScript中的作用域与变量声明提升

19:面向对象、闭包、原型、原型链

20:事件机制、(冒泡,捕获)

21:异步加载和延迟加载async  defer 

二:React

1. 简述一下react的生命周期

2:说明一下key在react中的作用,为什么要用key

3:介绍一下react的高阶组件

4:setState在react 中你认为是同步还是异步,setState第二个参数

5:export和export default的区别?

6:当你调用 setState 的时候,发生了什么事?

7:React 项目用过什么脚手架

8:什么时候用类组件Class Component,或函数组件Function

9:React 优势

10:react diff 原理(常考,大厂必考)

11.shouldComponentUpdate 是做什么的?

12:React 中 refs 的作用是什么?

13:setState 和 replaceState 的区别

14:React 中有三种构建组件的方式

15:描述事件在 React 中的处理方式

16:应该在 React 组件的何处发起 Ajax 请求

17:调用 super(props) 的目的是什么

18:除了在构造函数中绑定 this,还有其它方式吗

19:为什么setState 的参数是一个 callback 而不是一个值

20:在 React 当中 Element 和 Component 有何区别?

21:状态(state)和属性(props)之间有何区别

22:createElement 和 cloneElement 有什么区别?

23:React的协议?

24:为什么虚拟dom 会提高性能?

25:解释一下mvc和MVP和mvvm


一:css

1:rem、em 原理区别

rem  全部的长度都相对于根元素<html>元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。

em  (1)子元素字体大小的em是相对于父元素字体大小; (2)元素的width/height/padding/margin用em的话是相对于该元素的font-size

2:标准的盒子模型

盒模型的组成,由里向外content,padding,border,margin.

w3c标准合模型 :  width:content

IE盒模型:width: content + padding + border

box-sizing的使用  (box-sizing的默认属性是content-box)

box-sizing: content-box 是W3C盒子模型
box-sizing: border-box 是IE盒子模型

3:伪类选择器什么时候会用到 ,为什么要用伪类选择器

4:清除浮动的几种方式,及原理?

  • ::after / <br> / clear: both
  • 创建父级 BFC(overflow:hidden)
  • 父级设置高度

BFC (块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

触发条件:

  • 根元素
  • position: absolute/fixed
  • display: inline-block / table
  • float 元素
  • ovevflow !== visible

规则:

  • 属于同一个 BFC 的两个相邻 Box 垂直排列
  • 属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
  • BFC 的区域不会与 float 的元素区域重叠
  • 计算 BFC 的高度时,浮动子元素也参与计算
  • 文字层不会被浮动层覆盖,环绕于周围

二:JS

1: 常见的数据类型 以及 基本(原始)数据类型和引用数据类型的区别

首先解释一下栈(stack) 和 堆(heap)

heap:是由malloc之类函数分配的空间所在地。地址是由低向高增长的。 动态分配的内存,大小也不一定会自动释放

stack:是自动分配变量,以及函数调用的时候所使用的一些空间。地址是由高向低减少的。为自动分配的内存空间,它由系统自动释放    

基本(原始)数据类型:String, Null,Undefined,Number,Boole

引用数据类型:Object

区别:基础数据类型是值引用, 地址数据类型是地址引用

原始类型(基本类型)(存放在栈中):按值访问,可以操作保存在变量中实际的值。原始类型汇总中null和undefined比较特殊

  引用类型(存放在堆内存中的对象,每个空间大小不一样,要根据情况进行特定的配置):按地址访问,在操作对象时,实际上是在操作对象的引用而不是实际的对象。

扩展:引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个                               引用地址可以快速查找到保存中堆内存中的对象

总结基本数据类型和引用数据类型区别

1、声明变量时内存分配不同

*原始类型:在栈中,因为占据空间是固定的,可以将他们存在较小的内存中-栈中,这样便于迅速查询变量的值
     *引用类型:存在堆中,栈中存储的变量,只是用来查找堆中的引用地址。
      这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响

2、不同的内存分配带来不同的访问机制

在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。
        而原始类型的值则是可以直接访问到的。

3、复制变量时的不同、

1)原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。
        2)引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)。多了一个指针

4、参数传递的不同(把实参复制给形参的过程)

首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。
     但是为什么涉及到原始类型与引用类型的值时仍然有区别呢?还不就是因为内存分配时的差别。  
     1)原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。
     2)引用值:对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象。

2:  闭包的优缺点

(闭包是将函数内部和函数外部连接起来的桥梁。  说白了就是一个环境,能够读取其他函数内部的变量。)

优点:一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中

缺点:
        1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
        2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

3:解释一下原型和原型链

原型:其实原型就只是个普通对象,里面存放着所有实例对象需要共享的属性和方法!所以,我们把需要共享的放到原型对象里,把那些不需要共享的属性和方法存在在构造函数里! 

function Person(name,age){
     this.name = name;
}
Person.prototype.sex = 'female';

var person1 = new Person("Summer");
var person2 = new Person("Lily");
        
console.log(person1.sex)      // female
console.log(person2.sex)      // female

Person.prototype.sex = 'male';

console.log(person1.sex)      // male
console.log(person2.sex)      // male

修改prototype属性会影响它的所有实例的sex的值!!

实例对象的属性和方法一般分为两种:一种是自身的,一种是引用自prototype的。 

原型链: 是由子对象对父对象进行多次原型继承形成的链式关系。当调用子对象的某个属性或方法时,javascript会向上遍历原型链,直到找到为止,没有返回undefined。但是注意: 属性在查找的时候是先查找自身的属性,如果没有再查找原型,再没有,再往上走,一直插到Object的原型上。

    1: prototype是函数的原型对象,即prototype是一个对象,它会被对应的__proto__引用。
    2: 要知道自己的__proto__引用了哪个prototype,只需要看看是哪个构造函数构造了你,那你的__proto__就是那个构造函数的prototype。
    3: 所有的构造函数的原型链最后都会引用Object构造函数的原型,即可以理解Object构造函数的原型是所有原型链的最底层,即Object.prototype.__proto===null

事实上,js里完全依靠"原型链"(prototype chain)模式来实现继承。

  • proto:事实上就是原型链指针!!

  • prototype:上面说到这个是指向原型对象的

  • constructor:每一个原型对象都包含一个指向构造函数的指针,就是constructor

    下面来详解原型与原型链

对象有__proto__属性,函数有prototype属性;
    对象由函数生成;
    生成对象时,对象的__proto__属性指向函数的prototype属性。
    在没有手动修改__proto__属性的指向时,以上三条便是JavaScript默认原型链指向逻辑

var obj = {};
console.log(obj.prototype); //undefined
console.log(obj.__proto__); //Object {}
var obj2 = function(){}
console.log(obj2.prototype); //obj2 {}
console.log(obj2.__proto__); //function() {}

 

  •  
  • 大致总结一下就是:

    1、Object是作为众多new出来的实例的基类 function Object(){ [ native code ] }

    2、Function是作为众多function出来的函数的基类 function Function(){ [ native code ] }

    3、构造函数的proto(包括Function.prototype和Object.prototype)都指向Function.prototype

    4、原型对象的proto都指向Object.prototype

    5、Object.prototype.proto指向null

4: 优化前端性能的方法

1: 压缩图片

压缩图片的方法:

a、雪碧图
          即css sprites, 就是把很多小图片制作成一个大图,然后作为背景图片使用,定位即可。
          优点: 很明显: 减少了大量的http请求。
          缺点: 背景定位较为麻烦,其实不算缺点。

b、图片压缩
          图片压缩是很简单的,就是无损压缩了。优先使用png而不是GIF,压缩png,去掉jpg的metadata,压缩gif动画,尝试使用  png8,避免使用AlphaImageLoader,压缩动态生成的图像,使 favicon 更小,可缓存、使用css sprites 

c、base64
            base64编码的大小比原图大小更大一些,但是可以减少http请求。
        d、响应式图片
            一般我们对于图片的设定方法都是设置图片为width: 100%, 但是如果加载的图片的实际尺寸很大,而我们所需要的很小,那么不可避免的就会造成浪费, 所以这时就可以使用响应式图片。 主要用到的属性就是 srcset 和 sizes 属性。 

e、延迟加载
            假设我的网页很长很长,图片很多很多(比如多图慎点那种),但是我可能看了一下就没兴趣了,那是不是完全没有必要加载后面的图片了,这个时候就要有一个延迟加载的思想。
            图片延迟加载也称懒加载,通常应用于图片比较多的网页,如果一个页面图片比较多,且页面高度或宽度有好几屏,页面初次加载时,只显示可视区域的图片(非可视区域图片使用1*1的图片占位,图片大小css控制),当页面滚动的时候,图片进入了可视区域再进行加载(修改src),这样可以显著的提高页面的加载速度,更少的图片并发请求数也可以减轻服务器的压力。如果用户仅仅在首屏停留,还可以节省流量。具体的实现方法可以看一个插件,就叫lazeload。

f、 图标字体
            用过bootstrap的同学肯定对方便的fontawesome图标字体印象深刻,可以无损放大缩小,可以修改颜色,只要加个类名就可以使用图标,感觉是不是很爽🌚。优势呢。。。矢量&方便好用,不过图标没有一定的量可能不大需要,而且矢量图可能美工压力也比较大,我们只要知道怎么用就行啦,有的时候用现成的图标减轻一下美工同学的工作量也是极好的。可以看一下一个专门用来做图标字体的网站。

h、 SVG
            svg作为矢量图,和iconfont对比起来优势可能就是字体可能在样式修改方面受限较多,其次字体文件一般都弄的挺大的,然后兼容性差点。svg也可以认为就是图片,使用方法也很简单。

2: 减少http请求
        CSS Sprites:还可通过css中的background-position来定位图片中的某一具体部分,它比图片地图更灵活,建议大家使用。
        内联图片:通过使用data:URL数据形式可以替代http请求,甚至可以用于script和a标签中,其缺陷是Base64编码会增加图片的大小,并且嵌在网页中,会加大网页的数据量,但它可以减少http网络请求耗时。这里提供图片生成dataurl数据的方法。
        

var can = document.createElement("canvas");
    var ctx = can.getContext("2d");
    img.onload = function(){  
         ctx.drawImage(this, 0, 0, width, height);
         can.toDataURL();
 }

3: 合并脚步和样式表

4:配置多个域名和CDN加速
        通常浏览器对于一个域名的并发请求是有限的,比如:有100个文件要加载,但浏览器一次只可能并发请求10个文件,这样并发多次就会耗时。
        因此配置多个域名能够最大限度的增加并发请求量,但这里有个缺点就是会增加浏览器域名解析的次数,这里建议利用CDN来加载不是经常更新和修改的静态资源(图片,css库,js第三方库等等)。一个是CDN域名一般都会缓存到本地中,另一个是CDN网络请求速度是非常快的。
        由于CDN部署在网络运营商的机房,这些运营商又是终端用户的网络服务提供商,因此用户请求路由的第一跳就到达了CDN服务器,当CDN中存在浏览器请求的资源时,从CDN直接返回给浏览器,最短路径返回响应,加快用户访问速度,减少数据中心负载

5: 解释一下axios 和 await

Axios特性
        1、可以在浏览器中发送 XMLHttpRequests
        2、可以在 node.js 发送 http 请求
        3、支持 Promise API
        4、拦截请求和响应
        5、转换请求数据和响应数据
        6、能够取消请求
        7、自动转换 JSON 数据
        8、客户端支持保护安全免受 XSRF 攻击
    Axios用在什么场景?
        在特性里面已经有提到,浏览器发送请求,或者Node.js发送请求都可以用到Axios。像Vue、React、Node等项目就可以使用Axios,如果你的项目里面用了Jquery,此时就不需要多此一举了,jquery里面本身就可以发送请求。

6: promise有几个状态 分别是什么

pending、fulfilled、rejected(未决定,履行,拒绝),同一时间只能存在一种状态,且状态一旦改变就不能再变。promise是一个构造函数,promise对象代表一项有两种可能结果(成功或失败)的任务,它还持有多个回调,出现不同结果时分别发出相应回调。

1.初始化,状态:pending

2.当调用resolve(成功),状态:pengding=>fulfilled

3.当调用reject(失败),状态:pending=>rejected

3.promise的优缺点

​ 优点:

​ 1.Promise 分离了异步数据获取和业务逻辑,有利于代码复用。

​ 2.可以采用链式写法

​ 3.一旦 Promise 的值确定为fulfilled 或者 rejected 后,不可改变。

​ 缺点:代码冗余,语义不清。

详解 Promise几种状态详解

7: promise 是异步还是同步

Promise作用:解决异步回调的问题

promise本身是同步的

let oP = new Promise( (res, rej) => {
     console.log(1);
});
console.log(2);

//  1
//  2

执行的结果先打印出1再打印出2,如果promise是异步的应该先打印出2,所以promise本身是同步

promise的回调then是异步的

let oP = new Promise( (res, rej) => {
     console.log(1);
});
oP.then(res => {
     console.log(3);
});
console.log(2);

// 1
// 2
// 3

执行的结果1,2,3,因为then是异步的,所以先打印了2,最后再执行回调打印出3

ES6 同步和异步、Promise

8: new 一个构造函数的时候都做了什么

    1.新创建一个空对象: var fn = new Object();
    2.构造函数的显示原型等于实例对象的隐式原型,实例对象的constructor属性为构造函数的名称: Fn.prototype = fn.__proto__
    3.通过调用call、apply方法执行构造函数并改变this对象(绑定到实例对象上): Fn.call(f)
    4.如果没有手动返回其他任何对象或返回值是基本类型(Number、String、Boolean)的值,会返回 this 指向的新对象,也就是实例,若返回值是引用类型(Object、Array、Function)的值,则实际返回值为这个引用类型。

new做了下面这些事:

  • 创建一个临时对象
  • 给临时对象绑定原型
  • 给临时对象对应属性赋值
  • 将临时对象return

9: display: none 和 class="hidden" 的区别 

相同点是都能把网页上某个元素隐藏起来 

不同点:

display:none  ---  不为被隐藏的对象保留其物理空间,即该对象在页面上彻底消失,通俗来说就是看不见也摸不到。     

class="hidden"--- 使对象在网页上不可见,但该对象在网页上所占的空间没有改变,通俗来说就是看不见但摸得到

     .hidden {

              display: none!important;

              visibility: hidden!important;

     }

bootstarp

10: 如何解决跨域问题 ,为什么会有跨域的问题

    跨域问题是因为浏览器的同源策略引起的,一种浏览器的安全机制,要求协议,域名,端口,都要一致!

    出于浏览器的同源策略限制,浏览器会拒绝跨域请求。

    什么叫跨域?非同源请求,均为跨域。名词解释:同源 —— 如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(origin)。

 怎么解决跨域?最常用的三种方式:JSONP、CORS、postMessage。
        jsonp,只支持get,不支持post,需要调用前端和被调用后端配合(比较常用)
        后端HttpClient进行转发,两次请求,效率低,安全(类似Nginx反向代理)
        服务端设置响应头,允许跨域,适于小公司快速解决问题
        Nginx搭建API接口网关
        Zuul搭建API接口网关
        后四种都属于服务端设置,对于目前还是一个纯前端的我来说,先把前端的搞懂再说,所以在此只说前端

        jsonp工作原理理解
            jsonp实际上是通过动态插入js的方式实现的跨域,因为通过script标签引入js文件没有跨域一说
            web客户端通过调用脚本的方式去调用跨域服务端动态生成的js文件(一般以json为后缀),同时传递一个callback参数给服务端,服务端以这个参数名为函数名,调用此函数以参数的形式将数据传到web端,这样就实现了前端跨域请求服务端数据。

    【JSONP的优缺点】
   优点:兼容性好(兼容低版本IE)
   缺点:1.JSONP只支持GET请求; 2.XMLHttpRequest相对于JSONP有着更好的错误处理机制

   CORS :是W3C 推荐的一种新的官方方案,能使服务器支持 XMLHttpRequest 的跨域请求。CORS 实现起来非常方便,只需要增加一些 HTTP 头,让服务器能声明允许的访问来源。

    postMessage:  window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持  window.postMessage方法。

11: ajax工作原理

ajax是一种不重载整个页面的情况下局部更新页面数据的一种异步请求方法。核心是XMLHttpRequest对象。

简言之:异步请求数据。原理结合原生ajax 请求步骤 (即下面代码的大致步骤)更加分。

实现流程:

  1. 创建一个 XMLHttpRequest 对象
  2. 创建一个新的 HTTP 请求,并指定该请求的方法、URL 以及是否为异步请求
  3. 设置响应 HTTP 请求状态变化的函数
  4. 发送 HTTP 请求
  5. 对异步返回的数据进行处理
// 创建 XMLHttpRequest 对象
var xhr
if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest()
} else if (window.ActiveXObject) {
    xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
// 创建一个新的请求
xhr.open('GET', 'http://www.example.com', true)
// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// 监听 HTTP 请求状态变化
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        var res = xhr.responseText
    }
}
// 发送 HTTP 请求
xhr.send()

       优点:

    页面无刷新,用户体验好

    使用异步的方式与服务器通信,响应更加迅速

    减轻服务器的压力,节约带宽

    基于标准化并被广泛支持的技术

  缺点:

    破坏了浏览器的回退机制

    暴露了更多的数据和服务器逻辑

    对搜索引擎的支持比较弱

    违背了 URL 与资源定位的初衷

 

12: sessionId详解

sessionid是一个会话的key,浏览器第一次访问服务器会在服务器端生成一个session,有一个sessionid和它对应。服务端在创建了Session的同时,会为该Session生成唯一的sessionId,而sessionId会在随后的请求中会被用来重新获得已经创建的Session;Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发到客户端的只有sessionId;当客户端再次发送请求的时候,会将这个sessionId带上,服务器接受到请求之后就会依据sessionId找到相应的Session,从而再次使用之。当客户端第一次请求session对象时候,服务器会为客户端创建一个session,并将通过特殊算法算出一个session的ID,用来标识该session对象。

13: 构造函数

构造函数是什么?构造函数与其他函数唯一的区别在于调用方式不同。任何函数只要通过new来调用就可以作为构造函数,它是用来创建特定类型的对象。

通过new命令来生成一个person实例:

var person1 = new Female("Summer")

这里,构造函数Female就是实例对象person1的原型!!!Female里的this关键字就指的是person1这个对象! 

14:长连接和短连接

在http1.0中,采用的是短连接,即每一次请求都要建立http连接,随后立刻关掉,比如一个网站要请求100张图片,就要建立100次http连接。

在http1.1中,添加了keep-alive属性,可以持续一段时间。

长连接虽然提高了连接的复用性,但长时间不关闭也带来了缺点,比如一个网站收到了大量http请求,且都不会在短期释放,那么就会引发性能问题。

15:Cookie、sessionStorage、localStorage的区别

共同点:都是保存在浏览器端,并且是同源的

  • Cookie:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下,存储的大小很小只有4K左右。 (key:可以在浏览器和服务器端来回传递,存储容量小,只有大约4K左右)

  • sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持,localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。(key:本身就是一个回话过程,关闭浏览器后消失,session为一个回话,当页面不同即使是同一页面打开两次,也被视为同一次回话)

  • localStorage:localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。(key:同源窗口都会共享,并且不会失效,不管窗口或者浏览器关闭与否都会始终生效)

补充说明一下cookie的作用:

  • 保存用户登录状态。例如将用户id存储于一个cookie内,这样当用户下次访问该页面时就不需要重新登录了,现在很多论坛和社区都提供这样的功能。 cookie还可以设置过期时间,当超过时间期限后,cookie就会自动消失。因此,系统往往可以提示用户保持登录状态的时间:常见选项有一个月、三个 月、一年等。

  • 跟踪用户行为。例如一个天气预报网站,能够根据用户选择的地区显示当地的天气情况。如果每次都需要选择所在地是烦琐的,当利用了 cookie后就会显得很人性化了,系统能够记住上一次访问的地区,当下次再打开该页面时,它就会自动显示上次用户所在地区的天气情况。因为一切都是在后 台完成,所以这样的页面就像为某个用户所定制的一样,使用起来非常方便

  • 定制页面。如果网站提供了换肤或更换布局的功能,那么可以使用cookie来记录用户的选项,例如:背景色、分辨率等。当用户下次访问时,仍然可以保存上一次访问的界面风格。

16:深度拷贝并且不影响原先的object方式

可以用递归

var deepClone = function fnDeepClone(obj, options = {}) {
        if (['object', 'function'].indexOf(oType(obj)) < 0) {
            return obj;
        }
        let result = typeof obj.splice === 'function' ? [] : {},
            key;
        if (obj && typeof obj === 'object') {
            for (key in obj) {
                if (obj[key] && obj[key] instanceof Date) {
                    if (options.dateHandler && options.dateHandler instanceof Function) {
                        result[key] = options.dateHandler(obj[key])
                    } else {
                        result[key] = new Date(obj[key])
                    }
                    
                } else if (obj[key] && typeof obj[key] === 'object') {
                    result[key] = fnDeepClone(obj[key], options); //如果对象的属性值为object的时候,递归调用deepClone,即再把某个值对象复制一份到新的对象的对应值中
                } else {
                    result[key] = obj[key]; //如果对象的属性值不为object的时候,直接复制参数对象的每一个键/值到新对象对应的键/值中
                }
            }
            return result;
        }
        return obj;
    }
    exports.deepClone = deepClone;

17:get和post 区别和相同点。以及还有哪些别的请求方式?

相同点:

 都是http 请求方式。都属于TCP连接。

区别:

 get 在浏览器是回退时是无害的,而post会再次提交请求。

 get 请求会被浏览器主动cache(缓存),而post不会除非手动设置。

 get请求只能进行url 编码,post支持多种编码方式。

 get请求参数会被完整保留在浏览器历史记录里,post 不会保留

 get 请求在url 传送参数有长度限制 ,post 没有

    get 请求通过url 传参, post 参数放在request body 中

 get 请求因为参数放在url 上的原因 相比较post而言 不安全

 get 请求产生有一个TCP数据包 ,post 会产生两个TCP数据包

18:JavaScript中的作用域与变量声明提升

所有的变量声明都会被提升到作用域的最顶端

  同一个变量声明只进行一次 其他声明会被重置掉

  函数声明的优先级高于变量声明 且函数声明会连带定义一起提升

19:面向对象、闭包、原型、原型链

1.面向对象的基本特征:封装、继承、多态。把客观的事物封装成抽象的类,或者构造函数。

  2.闭包:能够访问另一个函数作用域变量的函数。顾名思义:函数中的函数。缺点:内存泄漏,this指向问题,引用的变量可能会发生变化。

  优点:可以解决递归调用,模仿块级作用域。

  3.原型:每个构造函数都有prototype 属性指向另一个对象。这个对象的所有属性和方法都会被构造函数的实例继承。

  我们可以把不变的属性和方法定义在prototype对象上。prototype默认有两个属性,constructor属性和__proto__属性,

  _proto_是原型链指向实例化的函数原型,总是指向prototype。hasOwnProperty()来查看是否是自身属性 还是继承属性。

20:事件机制、(冒泡,捕获)

冒泡:事件冒泡是 IE 团队提出的事件流方案,根据名字我们就可以看出,事件冒泡是从最具体的元素开始触发事件,然后向上传播至没有那么具体的元素(文档)。简而言之:由内而外的触发机制。

  捕获:事件捕获是 Netscpe 开发团队提出的事件流解决方案。和事件冒泡相反,事件捕获是从最不具体的节点最先接收事件,向下传播至最具体的节点。事件捕获实际上是为了在事件到达最终目标前拦截事件。简而言之:由外而内的触发机制。

  preventDefault() 方法用于阻止特点事件的默认行为(比如,a 标签有跳转到 href 链接的默认行为,可以阻止这种导航行为)。

  stopPropagation() 方法用于立即阻止事件流在 DOM 结构中的传播,取消后续的事件捕获或冒泡。

21:异步加载和延迟加载async  defer 

异步async:

  async HTML5里为script标签里新增了async属性,用于异步加载脚本: 不保证顺序(独立的个体) 

<script async src="script.js"></script>
/*或*/
<script type="text/javascript" src="alert.js" async="async"></script>

浏览器解析到HTML里的该行script标签,发现指定为async,会异步下载解析执行脚本(即加载后续文档元素的过程将和script.js的加载并行进行)。

  页面的DOM结构里假设<script>在img之前,如果你的浏览器支持async的话,就会异步加载脚本。此时DOM里已经有img了,所以脚本里能顺利取到img的src并弹框。

  延迟defer:

  defer <script>标签里可以设置defer,表示延迟加载脚本:脚本先不执行,延迟到文档解析和显示后执行,有顺序。

<script defer src="script.js"></script>
/*或*/
<script type="text/javascript" src="alert.js" defer="defer"></script>

浏览器解析到HTML里该行<script>标签,发现指定为defer,会暂缓下载解析执行脚本,等到页面文档解析并加载执行完毕后,才会加载该脚本(更精确地说,是在DOM树构建完成后,在DOMContentLoaded事件触发前,加载defer的脚本)。

页面的DOM结构里假设script在img图片之前,如果你的浏览器支持defer的话,就会延迟到页面加载完后才下载脚本。此时DOM里已经有img元素了,所以脚本里能顺利取到img的src并弹框。

详情参考链接:JS脚本异步加载浅析

22:JS作用域以及作用域链

一、作用域 

在 Javascript 中,作用域分为 全局作用域 和 函数作用域

  全局作用域:

    代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。

   函数作用域:

    在固定的代码片段才能被访问

 例子:

作用域

作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。如上,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。

 作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

 变量取值:到创建 这个变量 的函数的作用域中取值

二、作用域链

一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

var x = 10;

function fn(){
    console.log(x);
}

function show(f){
    var x = 20;
    (function(){
       f();    // 10
    })()  
}

show(fn);

作用域链

详情参考 作用域以及作用域链详解

二:React

1. 简述一下react的生命周期

React的生命周期从广义上分为三个阶段:挂载、渲染、卸载

因此可以把React的生命周期分为两类:挂载卸载过程和更新过程。

1. 挂载卸载过程

1.1.constructor()

constructor()中完成了React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。
注意:只要使用了constructor()就必须写super(),否则会导致this指向错误

1.2.componentWillMount()

componentWillMount()一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。

1.3.componentDidMount()

组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染

1.4.componentWillUnmount ()

在此处完成组件的卸载和数据的销毁。

2. 更新过程

2.1. componentWillReceiveProps (nextProps)

  1. 在接受父组件改变后的props需要重新渲染组件时用到的比较多

  2. 接受一个参数nextProps

  3. 通过对比nextProps和this.props,将nextProps的state为当前组件的state,从而重新渲染组件

2.2.shouldComponentUpdate(nextProps,nextState)

render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。

2.3.componentWillUpdate (nextProps,nextState)

shouldComponentUpdate返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。

2.4.componentDidUpdate(prevProps,prevState)

组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state。

2.5.render()

render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。

  1. 主要用于性能优化(部分更新)

  2. 唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新

  3. 因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断

//初始化阶段:
getDefaultProps:获取实例的默认属性

getInitialState:获取每个实例的初始化状态

componentWillMount:组件即将被装载、渲染到页面上

render:组件在这里生成虚拟的 DOM 节点

componentDidMount:组件真正在被装载之后

//运行中状态:
componentWillReceiveProps:组件将要接收到属性的时候调用

shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止 render 调用,后面的函数不会被继续执行了)

componentWillUpdate:组件即将更新不能修改属性和状态

render:组件重新描绘

componentDidUpdate:组件已经更新

//销毁阶段:
componentWillUnmount:组件即将销毁

详情参考React的生命周期

 

2:说明一下key在react中的作用,为什么要用key

key是react用来追踪列表中哪些元素被添加修改移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性

3:介绍一下react的高阶组件

4:setState在react 中你认为是同步还是异步,setState第二个参数

  1. 合成事件中是异步
  2. 钩子函数中的是异步
  3. 原生事件中是同步
  4. setTimeout中是同步

5:export和export default的区别?

使用上的不同

export default  xxx
import xxx from './'

export xxx
import {xxx} from './'

6:当你调用 setState 的时候,发生了什么事?

将传递给setState的对象与当前组件的state进行合并,并且创新一个新的dom树,与上一个dom树进行比较,从而进行最优化重渲染

将传递给 setState 的对象合并到组件的当前状态,这将启动一个和解的过程,构建一个新的 react 元素树,与上一个元素树进行对比( diff ),从而进行最小化的重渲染。

7:React 项目用过什么脚手架

creat-react-app(目前我只用过这个)  Yeoman等等

8:什么时候用类组件Class Component,或函数组件Function

如果您的组件具有状态( state ) 或 生命周期方法,请使用 Class 组件。否则,使用功能组件

9:React 优势

1、React 速度很快:它并不直接对 DOM 进行操作,引入了一个叫做虚拟 DOM 的概念,安插在 javascript 逻辑和实际的 DOM 之间,性能好。

2、跨浏览器兼容:虚拟 DOM 帮助我们解决了跨浏览器问题,它为我们提供了标准化的 API,甚至在 IE8 中都是没问题的。

3、一切都是 component:代码更加模块化,重用代码更容易,可维护性高。

4、单向数据流:Flux 是一个用于在 JavaScript 应用中创建单向数据层的架构,它随着 React 视图库的开发而被 Facebook 概念化。

5、同构、纯粹的 javascript:因为搜索引擎的爬虫程序依赖的是服务端响应而不是 JavaScript 的执行,预渲染你的应用有助于搜索引擎优化。

6、兼容性好:比如使用 RequireJS 来加载和打包,而 Browserify 和 Webpack 适用于构建大型应用。它们使得那些艰难的任务不再让人望而生畏。

10:react diff 原理(常考,大厂必考)

把树形结构按照层级分解,只比较同级元素。

给列表结构的每个单元添加唯一的 key 属性,方便比较。

React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字) 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.
到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制. 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。

11.shouldComponentUpdate 是做什么的?

shouldComponentUpdate 这个方法用来判断是否需要调用 render 方法重新描绘 dom。因为 dom 的描绘非常消耗性能,如果我们能在 shouldComponentUpdate 方法中能够写出更优化的 dom diff 算法,可以极大的提高性能。

12:React 中 refs 的作用是什么?

Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。

我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回:

class CustomForm extends Component {
  handleSubmit = () =› {
    console.log('Input Value: ', this.input.value);
  };
  render() {
    return (
      ‹form onSubmit={this.handleSubmit}›
        ‹input type='text' ref={input =› (this.input = input)} /›
        ‹button type='submit'›Submit‹/button›
      ‹/form›
    );
  }
}

上述代码中的 input 域包含了一个 ref 属性,该属性声明的回调函数会接收 input 对应的 DOM 元素,我们将其绑定到 this 指针以便在其他的类函数中使用。

另外值得一提的是,refs 并不是类组件的专属,函数式组件同样能够利用闭包暂存其值:

function CustomForm({ handleSubmit }) {
  let inputElement;
  return (
    ‹form onSubmit={() =› handleSubmit(inputElement.value)}›
      ‹input type='text' ref={input =› (inputElement = input)} /›
      ‹button type='submit'›Submit‹/button›
    ‹/form›
  );
}

13:setState 和 replaceState 的区别

setState 是修改其中的部分状态,相当于 Object.assign,只是覆盖,
不会减少原来的状态

replaceState 是完全替换原来的状态,相当于赋值,将原来的 state 替换为另一个对象,如果新状态属性减少,那么 state 中就没有这个状态了

14:React 中有三种构建组件的方式

React.createClass()、ES6 class 和无状态函数。

15:描述事件在 React 中的处理方式

为了解决跨浏览器兼容性问题,您的 React 中的事件处理程序将传递 SyntheticEvent 的实例,它是 React 的浏览器本机事件的跨浏览器包装器。

这些 SyntheticEvent 与您习惯的原生事件具有相同的接口,除了它们在所有浏览器中都兼容。
有趣的是,React 实际上并没有将事件附加到子节点本身。
React 将使用单个事件监听器监听顶层的所有事件。
这对于性能是有好处的,这也意味着在更新 DOM 时,React 不需要担心跟踪事件监听器。

16:应该在 React 组件的何处发起 Ajax 请求

在 React 组件中,应该在 componentDidMount 中发起网络请求。这个方法会在组件第一次“挂载”(被添加到 DOM)时执行,在组件的生命周期中仅会执行一次。

更重要的是,你不能保证在组件挂载之前 Ajax 请求已经完成,如果是这样,也就意味着你将尝试在一个未挂载的组件上调用 setState,这将不起作用。
在 componentDidMount 中发起网络请求将保证这有一个组件可以更新了。

17:调用 super(props) 的目的是什么

在 super() 被调用之前,子类是不能使用 this 的,在 ES2015 中,子类必须在 constructor 中调用 super()。

传递 props 给 super() 的原因则是便于(在子类中)能在 constructor 访问 this.props。

18:除了在构造函数中绑定 this,还有其它方式吗

你可以使用属性初始值设定项(property initializers)来正确绑定回调,create-react-app 也是默认支持的。

在回调中你可以使用箭头函数,但问题是每次组件渲染时都会创建一个新的回调。

19:为什么setState 的参数是一个 callback 而不是一个值

因为 this.props 和 this.state 的更新可能是异步的,不能依赖它们的值去计算下一个 state。

20:在 React 当中 Element 和 Component 有何区别?

React Element 是描述屏幕上所见内容的数据结构,是对于 UI 的对象表述。
典型的 React Element 就是利用 JSX 构建的声明式代码片然后被转化为 createElement 的调用组合。

React Component 是一个函数或一个类,可以接收参数输入,并且返回某个 React Element

21:状态(state)和属性(props)之间有何区别

State 是一种数据结构,用于组件挂载时所需数据的默认值。

State 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。

Props(properties 的简写)则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的(immutable)。

组件不能改变自身的 props,但是可以把其子组件的 props 放在一起(统一管理)。Props 也不仅仅是数据--回调函数也可以通过 props 传递。

22:createElement 和 cloneElement 有什么区别?

<p>
传入的第一个参数不同</p>
<p>
<strong>React.createElement()</strong>:JSX 语法就是用 React.createElement()来构建 React 元素的。</p>
它接受三个参数,第一个参数可以是一个标签名。如 div、span,或者 React 组件。第二个参数为传入的属性。第三个以及之后的参数,皆作为组件的子组件。

 

<pre>
React.createElement(type, [props], [...children]);
</pre>
<p>

React.cloneElement()与 React.createElement()相似,不同的是它传入的第一个参数是一个 <strong>React</strong> 元素,而不是标签名或组件。新添加的属性会并入原有的属性,传入到返回的新元素中,而旧的子元素将被替换。将保留原始元素的键和引用。

</p>
<pre>
React.cloneElement(element, [props], [...children]);
</pre>

23:React的协议?

答:React遵循的协议是“BSD许可证 + 专利开源协议”,这个协议比较奇葩,如果你的产品跟facebook没有竞争关系,你可以自由的使用react,但是如果有竞争关系,你的react的使用许可将会被取消

24:为什么虚拟dom 会提高性能?

虚拟dom 相当于在 JS 和真实 dom 中间加了一个缓存,利用 diff 算法避免了没有必要的 dom 操作,从而提高性能。

25:解释一下mvc和MVP和mvvm

1、MVC:

MVC是应用最广泛的软件架构之一

一般MVC分为:Model( 模型 )、Controller( 控制器 )、View( 视图 )。

MVC的思想:一句话描述就是Controller负责将Model的数据用View显示出来,换句话说就是在Controller里面把Model的数据赋值给View,比如在controller中写document.getElementById("box").innerHTML = data[”title”],只是还没有刻意建一个Model类出来而已。

Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。

View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。

Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

这主要是基于分层的目的,让彼此的职责分开。View 一般通过 Controller 来和 Model 进行联系。Controller是 Model和 View 的协调者,View和Model不直接联系。基本联系都是单向的。

1、View 传送指令到 Controller

2、Controller 完成业务逻辑后,要求 Model 改变状态

3、Model 将新的数据发送到 View,用户得到反馈

8dc60f84d90663862bb9a7e9bfe67944.png

2、MVP:

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

1、各部分之间的通信,都是双向的。

2、View 与 Model 不发生联系,都通过 Presenter 传递。

3、View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。

d35452a43b671dbc661a6b4031405eae.png

3、MVVM

MVVM 是把 MVC 的 Controller 和 MVP 的 Presenter 改成了 ViewModel。

View 的变化会自动更新到 ViewModel,ViewModel 的变化也会自动同步到 View上显示。这种自动同步是因为 ViewModel中的属性实现了 Observer,当属性变更时都能触发对应的操作。

5549cf7ca8b4a4ce223c78197f6178dc.png

  • 150
    点赞
  • 1324
    收藏
    觉得还不错? 一键收藏
  • 22
    评论
JAVA相关基础知识 1、面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。 2.继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。 3.封装: 封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。 4. 多态性: 多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。 2、String是最基本的数据类型吗? 基本数据类型包括byte、int、char、long、float、double、boolean和short。 java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类 3、int 和 Integer 有什么区别 Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。 原始类型封装类 booleanBoolean charCharacter byteByte shortShort intInteger longLong floatFloat doubleDouble 引用类型和原始类型的行为完全不同,并且它们具有不同的语义。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为 null,而原始类型实例变量的缺省值与它们的类型有关。 4、String 和StringBuffer的区别 JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。 5、运行时异常与一般异常有何异同? 异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 6、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法,请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其destroy方法。 与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。 7、说出ArrayList,Vector, LinkedList的存储性能和特性 ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。 8、EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别。 EJB包括Session Bean、Entity Bean、Message Driven Bea

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值