前端面试题大全(vue、es6、html、css、js)

文章目录


【Vue】

Vue2.x响应式数据/双向绑定原理

  • 当我们把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
  • 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
  • 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

监听器Observer、解析器Compile、订阅者Watcher、订阅器Dep

1、既然Vue通过数据劫持可以精准探测数据在具体dom上的变化,为什么还需要虚拟DOM diff呢?

vue是通过push来侦测变化的,当vue程序初始化的时候就会对数据data进行依赖收集,一旦数据发生变化,响应式系统就会立刻得知。因此vue是一开始就知道是在哪里发生变化,,然后我们这个vue的响应式系统在绑定一个数据的时候就需要一个watcher(双向数据绑定的原理图)
在这里插入图片描述
一旦我们的绑定细粒度过高就会产生大量的watcher,这会带来内存以及依赖追踪的过度开销。而细粒度过低会无法精确侦测变化,因此vue的设计师选择中等的细粒度方案,在组件级别进行push侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件,然后在组件内部进行VDOM diff获取更加具体的差异,而VDOM diff则是pull操作,vue是push+pull结合的方式进行变化侦测的。

2、vuex和localStorage的区别

1.区别:vuex是一种状态管理模式,它的数据存储在内存,localstorage(本地存储)则以文件的方式存储在本地,永久保存;sessionstorage( 会话存储 ) ,临时保存,浏览器关闭就会清除。localStorage和sessionStorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringifyparse来处理

2.应用场景:vuex用于组件之间的传值,localstorage,sessionstorage则主要用于不同页面之间的传值。

3.永久性:当刷新页面(这里的刷新页面指的是 --> F5刷新,属于清除内存了)时vuex存储的值会丢失,sessionstorage页面关闭后就清除掉了,localstorage不会。

注:很多同学觉得用localstorage可以代替vuex, 对于不变的数据确实可以,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage,sessionstorage无法做到。

3、说一下路由钩子在vue生命周期的体现。

  1. BeforeCreate(使用频率低)
  • 在实例创建以前调用
  • 没有实例化,数据访问不到
  1. Created(使用频率高)
  • 实例被创建完成后调用
  • 能拿到数据、修改数据,且修改数据不会触发updated beforeUpdate钩子函数
  • 可以在这个钩子函数里发送请求,访问后端接口拿数据
  • 判断是否存在el,是否存在template,如果二者都有,以template为主优先,如果没有template,会选择el模板。如果二者都没有,有$mount也可以调用模板
  1. BeforeMount
  • 真实的dom节点挂载到页面之前
  • 编译模板已经结束,虚拟dom已经存在
  • 可以访问数据,也可以更改数据,且修改数据不会触发updated beforeUpdate钩子函数
  • 在beforeMount和mounted之间隐藏了一个render函数,千万不能写,会覆盖系统函数
  1. Mounted(挂载)
  • 真实的dom节点挂载到页面以后
  • this.$refs找到ref表示的节点

<input type="text" ref="txt" />

mounted(){ this.$refs.txt.focus();}

  • 可以访问和更新数据
    且修改数据会触发updated beforeUpdate钩子函数
  1. beforeUpdate
  • 修改之前调用
  1. update
  • 修改之后调用
  • beforeUpdate、update可以监控data里的所有数据变化
  • 不要在update beforeUpdate修改不定数据,否则会引起死循环
    *监听data里的所有的数据,非updated莫属

4、计算属性和普通属性的区别?

1).methods方法和computed计算属性,两种方式的最终结果确实是完全相同

2).不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。

3).methods方法,每当触发重新渲染时,调用方法将总会再次执行函数。不需要缓存的话,就用methods

4).对于任何复杂逻辑,你都应当使用计算属性。

watch和computed各自处理的数据关系场景不同
(1).watch擅长处理的场景:一个数据影响多个数据
(2).computed擅长处理的场景:一个数据受多个数据影响

5、描述下自定义指令(你是怎么用自定义指令的)

自定义指令全解
1、通过 Vue.directive( id, [definition] ) 方式注册全局指令,第一个参数为自定义指令名称(指令名称不需要加 v- 前缀,默认是自动加上前缀的,使用指令的时候一定要加上前缀),第二个参数可以是对象数据,也可以是一个指令函数。

<script>
    Vue.directive("focus", {
        inserted: function(el){
            el.focus();
        }
    })
    new Vue({
        el: "#app"
    })
</script>

2、通过在Vue实例中添加 directives 对象数据注册局部自定义指令

script>
    new Vue({
        el: "#app",
        directives: {
            focus2: {
                inserted: function(el){
                    el.focus();
                }
            }
        }
    })
</script>

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

6、说一下vue中所有带$的方法

vue API

7、vue-router除了router-link,还怎么实现跳转

(1)router-link
不带参数:<router-link :to="{name:'home'}">
带参数:<router-link :to="{name:'home', params: {id:1}}">
(2)this.$router.push() (函数里面调用)
不带参数:this.$router.push('/home')
带参数:this.$router.push({name:'home',params: {id:'1'}})
(3)this.$router.replace() (用法同上,push)
(4)this.$router.go(n)

this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数

ps : 区别

this.$router.push 跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面
this.$router.replace 跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面
(就是直接替换了当前页面)

this.$router.go(n) 向前或者向后跳转n个页面,n可为正整数或负整数

query和params区别
query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传,刷新之后id还在
params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失

8、v-show和v-if指令的共同点和不同点

相同点:v-show和v-if都能控制元素的显示和隐藏。
不同点

  • 实现本质方法不同
    v-show本质就是通过设置css中的display设置为none,控制隐藏
    v-if是动态的向DOM树内添加或者删除DOM元素

9、为什么使用key,为什么key值不能index值?

  • key的作用是辅助判断新旧vdom节点在逻辑上是不是同一个对象。
  • 导致的问题就是以前的数据和重新渲染后的数据随着 key 值的变化从而没法建立关联关系.导致vue会复用错误的旧子节点。

10、Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

vue不能检测以下变动的数组:
1、当你利用索引直接设置一个项时,vm.items[indexOfItem] = newValue
2、当你修改数组的长度时,例如: vm.items.length = newLength

解决办法:
1、使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上
2、强制刷新this.$forceUpdate();
3、vm.item.splice(newlength)

11、对虚拟DOM的理解?虚拟DOM主要做了什么?虚拟DOM本身是什么?

vdom:可以看作是一个使用javascript模拟DOM结构的树形结构。
1、用javascript对象模拟DOM树并且渲染DOM树;
2、通过 diff算法 比较新旧DOM树,得到差异的对象;
3、将差异的对象应用到渲染的DOM树中。

12、vue中v-model是如何实现的,语法糖实际是什么?

在使用 v-model 后既绑定了数据(默认为value) 有添加了一个@input事件监听:

<input v-model='search' />

等价于

<input :bind='search' @input='search = $event.target.value'>

语法糖:一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件。组件的v-model等同于
v-bind:value="…"加上v-on:input=“…”

13、路由的两种模式:

1.hash模式(后面加#号):

  • #后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面
  • 通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。

2.history模式

  • history 模式的实现,主要是 HTML5 标准发布的两个 API,pushStatereplaceState,这两个 API 可以改变 url,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作

区别:

  • url 展示上,hash 模式有“#”,history 模式没有
  • 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
  • 兼容性,hash 可以支持低版本浏览器和 IE。

14、怎么定义 vue-router 的动态路由? 怎么获取传过来的值?

设置:在router目录下的index.js文件中,对path属性加上/:id

获取:使用router对象的params.id

15、单向数据流

父组件中的数据可以通过props流动到子组件中,并且当父组件中的数据发生改变的时候,子组件会自动接收到这个修改后的数据,并且更新页面中的内容。这就是 Vue 中的单项数据流。
所以,数据一般是由父组件提供的,当父组件中的数据发生了改变,子组件就会自动接收到这个数据的变化,从而更新子组件。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

16、computed和watch的区别

computedwatch
支持缓存,只有依赖数据发生改变,才会重新进行计算不支持缓存,数据变,直接会触发相应的操作
不支持异步,当computed内有异步操作时无效,无法监听数据的变化支持异步
computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一的关系,一般用computed当一个属性发生变化时,需要执行对应的操作;一对多
如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,immediate:组件加载立即触发回调函数执行,deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。

总结

  • computed:是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

  • watch:没有缓存性,更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;当我们需要深度监听对象中的属性时,可以打开deep:true选项,这样便会对对象中的每一项进行监听

17、Vue / keep-alive

keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,可以使被包含的组件保留状态,避免重新渲染 ,由于是一个抽象组件,所以在v页面渲染完毕后不会被渲染成一个DOM元素

<keep-alive>
    <loading></loading>
</keep-laive>

当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行
被包裹在keep-alive中的组件的状态将会被保留。

18、Vue中组件生命周期调用顺序是什么样的?

  • 组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
  • 组件的销毁操作是先父后子,销毁完成的顺序是先子后父。

19、接口请求一般放在哪个生命周期中?

  • 可以在钩子函数 createdbeforeMountmounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端返回的数据进行赋值。

  • 但是推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

(1)能更快获取到服务端数据,减少页面loading 时间;
(2)ssr不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

20、说一下diff算法

  • 同级比较,再比较子节点
  • 先判断一方有子节点一方没有子节点的情况(如果新的- children没有子节点,将旧的子节点移除)
  • 比较都有子节点的情况(核心diff)
  • 递归比较子节点

21、Vue的性能优化

(1)编码阶段

  • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
  • v-if和v-for不能连用
  • 如果需要使用v-for给每项元素绑定事件时使用事件代理
  • SPA 页面采用keep-alive缓存组件
  • 在更多的情况下,使用v-if替代v-show
  • key保证唯一
  • 使用路由懒加载、异步组件
  • 防抖、节流
  • 第三方模块按需导入
  • 长列表滚动到可视区域动态加载
  • 图片懒加载
    (2)SEO优化
  • 预渲染
  • 服务端渲染SSR

22、组件之间的传值方式

1、子传父

  • 子组件通过this.$emit(方法名,传递数据)传输数据
  • 父组件通过@方法名接受数据

2、兄弟组件之间传值

  • 创建一个Vue的实例,让各个兄弟共用同一个事件机制。

  • 传递数据方,通过一个事件触发bus.$emit(方法名,传递的数据)

  • 接收数据方,通过mounted(){}触发bus.$on(方法名,function(接收数据的参数){用该组件的数据接收传递过来的数据}),此时函数中的this已经发生了改变,可以使用箭头函数。

23、说一下vue-router的原理

实现原理
vue-router的原理就是更新视图而不重新请求页面
vue-router可以通过mode参数设置为三种模式:hash模式history模式abstract模式

1、hash模式
默认是hash模式,基于浏览器history api,使用window.addEventListener("hashchange",callback,false)对浏览器地址进行监听。当调用push时,把新路由添加到浏览器访问历史中的栈顶。使用replace时,把浏览器访问历史的栈顶路由替换成新路由。
hash值等于url中#及其以后的内容。浏览器是根据hash值的变化,将页面加载到相应的DOM位置。锚点变化只是浏览器的行为,每次锚点变化依然会在浏览器中留下一条历史记录,可以通过浏览器的后退按钮回到上一个位置。

2、history
history模式基于浏览器的history api ,使用window.onpopstate对浏览器地址进行监听。对浏览器history api中pushState()、==replaceState()==进行封装,当方法调用,会对浏览器历史栈进行修改。从而实现URL的跳转而无需加载页面。
但是它的问题在于当刷星页面的时候会走后端路由,所以需要服务端的辅助来兜底,避免URL无法匹配到资源时能返回页面。

3、abstract
不涉及和浏览器地址的相关记录。流程和hash模式一样,通过数组维护模拟浏览器的历史记录栈。
服务端下使用。使用一个不依赖与浏览器的浏览历史虚拟管理后台。

4、总结
hash模式和history模式都是通过window.addEventListener()方法监听hashchangepopState进行相应路由的操作,可以通过back、foward、go等方法访问浏览器的历史记录栈,进行各种跳转。而abstract模式是自己维护一个模拟的浏览器历史记录栈的数组。

24、介绍vue template到render的过程。(渲染)

beforeMount之前执行编译过程,第一步通过html-parser(解析html的一个库)将template解析成ast抽象语法树,第二步通过optimize优化ast并标记静态节点和静态根节点,第三步通过generate(生成器)将ast抽象语法树编译成render字符串并将静态部分放到staticRenderFns中,最后通过new Function(render)生成render函数。在beforeMount和mounted之间执行render函数生成VNode,然后通过patch(VNode)生成dom树并挂载,调用mounted。

25、为什么vue data必须是函数?

这是js的一个特性,js中只有函数能构成作用域,当data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。

26、说一下减少DOM数量的办法?一次性给你大量的DOM怎么优化?

一,减少DOM数量的方法
1、可以使用伪元素,阴影实现的内容尽量不使用DOM实现,如清除浮动、样式实现等
2、按需加载,减少不必要的渲染
3、结构合理,语义化标签
二、大量DOM时的优化
当对Dom元素进行一系列操作时,对Dom进行访问和修改Dom引起的重绘和重排都比较消耗性能,所以关于操作Dom,应该从以下几点出发:
(1)缓存Dom对象
首先不管在什么场景下,操作Dom一般首先会去访问Dom,尤其是像循环遍历这种时间复杂度可能会比较高的操作。那么可以在循环之前就将主节点,不必循环的Dom节点先获取到,那么在循环里就可以直接引用,而不必去重新查询

let rootElem = document.querySelector('#app');
let childList = rootElem.childElementCount; //假设全是Dom节点
for (let i = 0; i < childList.len; i++) {

}

(2)文档片段
利用==document.createDocumentFragment()==方法创建文档碎片节点,创建的是一个虚拟的节点对象。向这个节点添加dom节点,修改dom节点并不会影响到真实的dom结构。
我们可以利用这一点先将我们需要修改的dom一并修改完,保存至文档碎片中,然后用文档碎片一次性的替换真实的Dom节点。与虚拟dom类似,同样达到了不频繁修改Dom

Vue生命周期

生命周期发生了什么
beforecreat在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问
created在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom
beforeMount发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated
mounted在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作
beforeUpdate发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重新渲染
updated发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新
beforeDestroy发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,**比如清除计时器 **
destroyed发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁
activited keep-alive 专属组件被激活时调用
deactivated keep-alive 专属组件被销毁时调用

vue实例

实例详细
vm.$dataVue 实例观察的数据对象。Vue 实例代理了对其 data 对象 property 的访问。
vm.$props当前组件接收到的 props 对象
vm.$elVue 实例使用的根 DOM 元素。
vm.$slots用来访问被插槽分发的内容
vm.$refs一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。
vm.$emit触发当前实例上的事件。附加参数都会传给监听器回调。
vm.$on监听当前实例上的自定义事件

vue内置组件

组件详细
component渲染一个“元组件”为动态组件。依 is 的值,来决定哪个组件被渲染。
slot分发插槽
keep-alive缓存组件

【Vuex】

1、Vuex是什么?

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

2、Vuex的几个模块

  • State:提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。

在组件中访问State的方式:
1).this.$store.state.全局数据名称this.$store.state.count
2).先按需导入mapState函数: import { mapState } from ‘vuex’
然后数据映射为计算属性: computed:{ …mapState([‘全局数据名称’]) }

  • Mutation:在严格模式下是唯一更改 store 中状态的方法,且必须是同步函数,能记录数据每次修改的变化。在mutations中不能编写异步的代码,会导致vue调试器的显示出错。

触发mutation的两种方式:

  • this.$store.commit(‘add’,10)
  • …mapMutations([‘add’])
  • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。

触发actions的两种方式:

  • this.$store.dispatch(‘addAsync’,5)
    -…mapActions([‘subAsync’])
  • Getter:用于对Store中的数据进行加工处理形成新的数据它只会包装Store中保存的数据,并不会修改Store中保存的数据,当Store中的数据发生变化时,Getter生成的内容也会随之变化

使用getter的两种方式:
1、this.$store.getters.方法名
2、…mapGetters([‘方法名’])

  • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
  • modules: { moduleA, moduleB }

3、vuex的action有返回值吗?返回的是什么?

  • store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise
  • Action 通常是异步的,要知道 action 什么时候结束或者组合多个 action以处理更加复杂的异步流程,可以通过定义action时返回一个promise对象,就可以在派发action的时候就可以通过处理返回的 Promise处理异步流程

【javascrip】

1、javascrip的值类型和引用类型;分别typeof返回什么类型?

(1)值类型(基本类型):字符串(string)、数值(number)、布尔值(boolean)、undefined、null ;ex6新增了易种基本数据类型symbol(唯一标识)。

  • 基本数据类型的值是不可变的,任何方法都无法改变一个基本数据类型的值,只是指针的指向改变了。
  • 基本数据类型不可以添加属性和方法。
  let user = 'zhangsan'
  user.age = 18
  user.method = function () {
    console.log('12345')
  }
  console.log(user.age) // 输出:undefined
  console.log(user.method) // 输出:undefined

(2)引用类型:对象(Object)、数组(Array)、函数(Function)

// 利用typeof来区分
typeof 'abc' // string
typeof 123 // number
typeof true // boolean
typeof undefined // undefined
typeof null // object
// typeof 区分不出来引用类型(除了函数)
typeof {} // object
typeof [] // object
typeof console.log //function

基本数据类型是存储在栈中,引用类型是存储在堆中

通过Symbol函数生成let s =Symbol()Symbol类型的对象永远不相等,即便创建它们的时候传入了相同的值,因此,可借助此特性解决属性名的冲突问题(比如适用于多人编码的时候),这也是该数据类型存在的主要用途,意为标记

2、IE和DOM事件流的区别?

(1)事件流的区别 :

  • IE采用冒泡型事件
  • DOM使用先捕获后冒泡型事件。

(2)件侦听函数的区bai别:

IE使用: 
[Object].attachEvent("name_of_event_handler", fnHandler); //绑定函数 
[Object].detachEvent("name_of_event_handler", fnHandler); //移除绑定 
DOM使用: 
[Object].addEventListener("name_of_event", fnHandler, bCapture); //绑定函数 
[Object].removeEventListener("name_of_event", fnHandler, bCapture); //移除绑定 

3、什么是事件委托?

就是利用事件冒泡原理,让自己的所触发的事件,让父元素代替执行。

4、js延迟加载的方式有哪些?

  1. defer属性:表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕之后再执行。
  2. async属性:不让页面等待脚本下载和执行,从而异步加载页面其他内容。

async和defer一样,都不会阻塞其他资源下载,所以不会影响页面的加载。
缺点:不能控制加载的顺序

  1. 动态创建DOM方式
  2. 使用jQuery的getScript()方法
$.getScript("outer.js",function(){//回调函数,成功获取文件后执行的函数  
     console.log("脚本加载完成")  
});
  1. 使用setTimeout延迟方法
  2. 让JS最后加载

5、深拷贝和浅拷贝的区别

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
浅拷贝 的实现方式:

  1. Object.assign()

注意:当被复制的对象只有一层的时候,是深拷贝

  1. Array.prototype.concat()
  2. Array.prototype.slice()

Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

深拷贝 的实现方式

  1. JSON.parse(JSON.stringify())序列化

原理: 用JSON.stringify将对象转成JSON字符串,再用==JSON.parse()==把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。但不能处理函数。

  1. 递归方法
  2. 函数库lodash
  3. jQuery的extend方法

$.extend( [deep ], target, object1 [, objectN ] )
deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
target 为Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。

6、页面重绘(repaint)和页面回流(reflow)

css 与 dom 解析完毕后,合并为渲染树(render tree)

  • 重绘(repaint): 当 render tree中的一些元素需要更新属性,但这些属性只会影响元素的外观,风格,而不会影响到元素的布局,此类的页面渲染叫作页面重绘。
  • 回流(reflow) :当 render tree 中的一部分(或全部)因为元素的规模尺寸、布局、隐藏 等改变而引起的页面重新渲染。

7、new操作符具体干了什么?

1,创建一个空的对象 var obj=new Object();

2,设置原型链,让空对象的原型属性指向构造函数的原型链,obj.proto=Func.prototype;

3,让构造函数的this指向obj,并执行函数体 var result=Func.call(obj);

8、函数节流和函数防抖

函数节流:一个函数执行一次后,只有大于设定的执行周期后才会执行第二次。
(应用于手机验证码)

	function throttle(func,delay){
				let timer;
				return function(){
					const context=this;
					const args=arguments;
					//如果定时器存在,就不执行
					if(!timer){
						timer=setTimeout(function(){
							timer=null;
							func.apply(context,args);
						},delay)
					}
				}
			}

函数防抖:一个需要频繁触发的函数,在规定的时间内,只让最后一次执行,前面的不生效。

function debounce(func,delay){
				let timer;
				return function(){
					const context = this;
					const args=arguments;
					clearTimeout(timer);
					timer=setTimeout(function(){
						func.apply(context,args);
					},delay);
				}
			}

9、从一个url地址到网页渲染完成,发生了什么?

1、DNS解析:将域名地址解析成ip地址
2、TCP连接:TCP三次握手
3、发送请求报文:HTTP协议的通信内容
4、接受响应:响应报文
5、渲染页面
6、断开连接:TCP四次挥手

10、有关字符串的方法。

方法解析
charAt()返回给定位置的那个字符
charCodeAt()返回给定位置的字符编码
concat()将一个或多个字符串拼接起来,返回一个新的字符串(浅拷贝)
slice()返回被操作字符串的一个子字符串
substring()返回被操作字符串的一个子字符串,不接受负数,负数转为0
substr()返回被操作字符串的一个子字符串,两个参数分别是起始位置和长度
indexOf()从一个字符串搜索指定的子字符串,返回子字符串的第一个位置(没有找到返回-1)
lastindexOf()从一个字符串搜索指定的子字符串,返回子字符串的最后位置(没有找到返回-1)
trim()会创建一个字符串副本,删除前置以及后缀的所有空格
search()返回字符串中第一个匹配项的索引
replace()替换

11、有关数组的方法

改变原数组

方法解析
reverse()数组翻转
sort()排序(a-b升序)(b-a降序)
splice()返回剪接的元素数组,原数组变化 ,索引从0开始(第一参数是索引(从0开始),第二是长度,第三是添加新的数据)
push()可以在数组的末属添加一个或多个元素
pop()把数组中的最后一个元素删除
shift()把数组中的第一个元素删除
unshift()可以在数组的前端添加一个或多个元素

不改变原数组

方法解析
join()数组转成字符串(原数组不变)默认分隔符为逗号
split()字符串变数组
slice()返回从原数组中指定开始索引(包含)到结束索引(不包含)之间的项组成的新数组,原数组木变 ,索引从0开始
foreach()用于调用数组的每个元素,并将元素传递给回调函数。
concat()返回合并后的新数组,原数组没变
filter()创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。(筛选)
map()返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值
reduce()数组的归并方法,但是reduce() 可同时将前面数组项遍历产生的结果与当前遍历项进行运算
reduceRight()遍历的顺序相反,它是从数组的最后一项开始,向前遍历到第一项。

12、判断数据类型

方法解析
typeof一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型
instanceof用来判断 A 是否为 B 的实例
Array.isArray()用以确认某个对象本身是否为 Array 类型
constructorprototype上添加一个 constructor 属性,并让其指向 F 的引用
toString.call()toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]]

13、有关函数的方法

方法解析
caller函数的一个属性,引用当前函数的函数
callee不是函数对象的属性,是函数上下文中arguments对象的属性

14、DOM操作方法

方法解析
obj.appendChild()添加
obj.removeChild()删除
obj.replaceChild()替换
obj.insetBefore()插入

15、阻止冒泡事件和阻止默认事件

阻止冒泡事件:e.stopPropagation()
阻止默认事件:e.preventDefault()

16、原型和原型链

  • 在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。
  • 每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。

17、闭包

在一个函数里声明的函数叫闭包函数,闭包函数能够访问到所在的外部函数的变量和参数,形成闭包。闭包函数return出去之后才能使用。

【Ajax】

1、fetch、axios、jQuery的Ajax用法

  • jQuery(默认表单数据)
$.ajax({
			method:'POST',//设置请求方式
			headers:{'Content-type':'application/json'},//json格式提交数据
			data:JSON.stringify({a:10})
			url:'...',
			success:function(data){console.log(data)},
			
		})
  • fetch(IE浏览器不带fetch)
fetch("url",{
			method:"POST",
			body:"a=2&b=3",//数据,不能用data这个参数
		}).then(res=>res.json()).then(data=>console.log(data))
  • axios(默认json数据)
	
		axios({
			url:'',
			method:'',
			data:{a:12}
		}).then(res=>{console.log(res.data)})

用法详解

2、创建原生的ajax请求

<script type="text/javascript">
window.onload = function(){
		
			var xhr=null;
			if(window.XMLHttpRequest){
				xhr=new XMLHttpRequest();
			}
			xhr.open('get','01.php',true);
			xhr.send(null);
			xhr.onreadystatechange=function(){
				if(xhr.readyState==4){
					if(xhr.status==200){
						var data=xhr.responseText;
					}
				}
			}
		}
		</script>

promise版本:

function queryData(url) {
            var p = new Promise(function (resolve, reject) {
                var xhr = new XMLHttpRequest();
                xhr.open('get', url);
                xhr.send(null);
                xhr.onreadystatechange = function () {
                    if (xhr.readyState != 4) return;
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        //处理正常请况
                        resolve(xhr.responseText);
                    } else {
                        //处理异常情况
                        reject('服务器错误');
                    }
                };
                
            });
        }

【es6】

1、箭头函数与普通函数的区别是什么?构造函数可以使用new生成实例,那么箭头函数可以吗?

区别
1、箭头函数语法上比普通函数更加简洁(ES6中每一种函数都可以使用形参默认值和剩余预算符)
2、箭头函数中的this始终指向箭头函数定义时的离this最近的一个函数,如果没有最近的函数就指向window。使用call或apply等任何方式都无法改变this指向。
3、箭头函数中没有arguments(类数组),只能给予…ARG(剩余参数)获取传递的参数集合(数组)。
4.箭头函数不能new执行(因为:箭头函数没有prototype)

2、异步操作:promise和async await

  • new promise的时候,里面的内容会立即执行,因而为了能实现调用时执行,promise一般都是作为函数的返回值
  • resolve接受正确的结果,导入then中。reject接收错误,导入catch中
  • await会拿到resolve结果,是then函数的语法糖
  • async+await是promise+then的语法糖
  • 避免回调地狱
  • await拿的是resolve的数据或者说传给then的数据,reject的数据需要try…catch
			async function(){
				try{
					var data1=await query(sql1);
					var data2=await query(sql2);
					var data3=await query(sql3);
				}catch(e){
					console.log(e);
				}
				
			}
  • 利用同步代码的形式实现异步效果

3、模板字符串

用一对反引号(`)标识,它可以当作普通字符串使用,也可以用来定义多行字符串,也可以在字符串中嵌入变量,js表达式或函数,变量、js表达式或函数需要写在${ }中。

4、解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
数组中的值会自动被解析到对应接收该值的变量中,数组的解构赋值要一一对应 如果有对应不上的就是undefined

5、 let、const、var 的区别

在这里插入图片描述

6、Promise.allSettled了解吗?动手实现一下

Promise.allSettled(iterable)概念

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved,rejected]);
allSettledPromise.then(function (results){
	console.log(results);
})
// 输出:
// [
//   { status: 'fulfilled', value: 42 },
//   { status: 'rejected', reason: -1 }
// ]
  1. Promise.allSettled()方法接受一组Promise实例作为参数,返回一个新的Promise实例。
  2. 只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
  3. 返回新的Promise实例,一旦结束,状态总是fulfilled,不会变成rejected。
  4. 新的Promise实例给监听函数床底一个数组results。该数组的每一个成员都是一个对象,对应传入Promise.allSettled的Promise实例。每个对象都有status属性,对应着fulfilled和rejected。fulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。
    5.有时候我们不关心异步操作的结果,只关心这些操作有没有结束时,这个方法会比较有用。

【HTML】

1、HTML5中的新特性

  • 用于绘画的canvas
  • 用于媒介回放的 video 和 audio 元素。
  • 语义化更好的标签:article、footer、header、nav等
  • 表单控件:calendar、date、time、email、url、search
  • 本地离线存储:localStorage、sessionStorage
  • 媒体查询:@media only screen and (min-width:500px) and (max-width:700px){......}
  • 地理定位:Geolocation

2、如何获取html元素实际的样式值?

实际的样式值可以理解为浏览器的计算样式

style对象中包含支持style属性的元素为这个属性设置的样式信息,但不包含从其他样式表层叠继承的同样影响该元素的样式信息。

DOM2 style 在document.defaultView上增加了getComputedStyle()方法。这个方法接收两个参数:要取得计算样式的元素和伪元素字符串(如":after")。如果不需要查询伪元素,则第二个参数可以传null。getComputedStyle()方法返回一个CSSStyleDeclaration对象(与Style属性的类型一样),包含元素的计算样式。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			#myDiv {
				background-color: blue;
				width: 100px;
				height: 200px;
			}
		</style>
	</head>
	<body>
		<div id="myDiv" style="background-color: red; border: 1px solid black;">

		</div>
	</body>
	<script>
		let myDiv = document.getElementById("myDiv");
		let computedStyle = document.defaultView.getComputedStyle(myDiv,null);

		console.log(computedStyle.backgroundColor);
	</script>
</html>

【CSS】

1、CSS3新特性

  • 过渡:transition
  • 动画:animation
  • 2D、3D旋转:transform
  • 伪类和伪元素
  • 阴影:box-shadow
  • 边框图片:boder-image
  • 文字阴影:text-shadow
  • 渐变:Gradient

2、优先级

  1. !important,style属性
  2. 描述精确的
  3. id选择器多的
  4. class选择器多的
  5. tag(标签)选择器多的
  6. 写在后面的

3、css居中方式

(1)绝对定位:

  • top:50%;left:50%;margin-top:1/2height; margin-left:1/2width;
  • margin:auto; top:0;bottom:0;left:0;right:0;

(3)flex布局:display:flex;justify-content:center;align-item:center;
(4)对父容器使用display: table-cell+vertical-align: middle;使其内的子元素实现垂直居中;

4、简述src和href的区别

src和href都是属于外部资源的引用,像一些图片,css文件,js文件,或者其他的web页面。
src通常用作“拿取”(引入),href 用作 “连结前往”(引用)
可以用这样的一句话来概括:src用于替换这个元素,而href用于建立这个标签与外部资源的关系。

【前端性能优化】

1、跨域问题(浏览器为什么要阻止跨域请求?如何解决跨域?每次跨域请求都需要到达服务端吗?)

一、什么是跨域
跨域是针对浏览器的"同源策略"提出的说法。之所以有"同源策略"这种模式是基于网络安全方面电视考虑。所谓的同源策略关注三点:1、协议 2、域名 3、端口

二、哪些网络资源涉及到跨域
"同源策略"对于跨域网络资源的设定非常清晰。
这些场景涉及到跨域禁止操作:
1、无法获取非同源网页的cookie、localstorage和indexedDB
2、无法访问非同源网页的DOM(iframe)。
3、无法非同源地址发送AJAX请求或fetch请求(可以发送,但浏览器拒绝接受响应)
为什么要阻止跨域呢?上边我们说过是基于安全策略:比如一个恶意网站的页面通过iframe嵌入了银行的登录页面(二者不同源),如果没有同源限制,恶意网页上的javaScript
cors跨域资源共享

通过href或者src去请求的js脚本、css、图片、视频不存在跨域问题,只有通过ajax请求才会存在跨域问题

  1. 修改响应头
  2. jsonp
#app.js
 app.get("/",function(req,res){
				var funcname=res.query.callback;//回调函数f()
				res.send(funcname+"('你好')";//f('你好')
			})
#index.html
<script>
	function f(data){
		alert(data);
	}
	
	
</script>
<script src="http://localhost:91?callback=f">	</script>

2、HTTP协议

(1) 在 HTTP 中,数据称为资源,可以是 html 文档、图片、也可以是普通文本。资源是通过 URL 进行定位的。
(2)URL 的组成部分有:
http://jsonplaceholder.typicode.com/comments?postId=1

  • http:// - 协议 Protocal
  • jsonplaceholder.typicode.com - 主机 Host
  • /comments - 路径 path
  • ?postId=1 - 查询参数

服务端收到 url 会解析它们并返回相应的数据。

(3)HTTP 请求包括下边几个部分
在这里插入图片描述

  • 请求方式, 告知服务器要进行的操作,
    区别是 GET 和 DELETE 一般没有请求体。而POST 和 PUT 通常带有请求体,用于向服务端发送资源信息。
  • 使用accept设置接受响应资源的类型

在这里插入图片描述
在这里插入图片描述
(4)HTTP 协议是无状态的,每次客户端发出的请求都被认为是从全新的客户端发出来的,如果需要记录状态则需要使用 cookie 和 session 来保持会话,实现登录和购物车之类的功能。
(5)现在 HTTP/2已经可以正式开始使用了,它跟 HTTP 1.1不同的是:

  • 数据通过二进制形式传输,不再是文本形式
  • 多路复用 - 建立连接后一次可以发送多个 HTTP 请求
  • 压缩 http headers,减少负载
  • 支持 server push

3、DNS Prefetching

DNS是什么 — 域名系统,作为域名和IP地址相互映射的一个分布式数据库

浏览器根据自定义的规则,提前去解析后面可能用到的域名,来加速网站的访问速度。简单来讲就是提前解析域名,以免延迟。

使用方式

<link rel="dns-prefetch" href="//wq.test.com"/>

这个功能有个默认加载条件,所有的a标签的href都会自动去启用DNS Prefetching,也就是说,你网页的a标签href带的域名,是不需要在head里面加上link手动设置的。但a标签的默认启动在HTTPS不起作用。

这个时候要使用meta里面http-equiv来强制启动功能。

<meta http-equiv="x-dns-prefetch-control" content="on"/>

总结

  1. DNS Prefetching是提前加载域名解析的,省去了解析时间。a标签的href是可以在Chrome。firfox包括高版本的IE,但是在HTTPS下面不起作用,需要meta来强制开启功能。
  2. 这是DNS的提前解析,并不是css,js之类的文件缓存,大家不要混淆了两个不同的概念。
  3. 如果直接做了js的重定向,或者在服务端做了重定向,没有在link里面手动设置,是不起作用的。
  4. 这个对于什么样的网站更有作用呢,类似taobao这种网站,你的网页引用了大量很多其他域名的资源,如果你的网站,基本所有的资源都在你本域名下,那么这个基本没有什么作用。因为DNS Chrome在访问你的网站就帮你缓存了。

可以用在web下的性能优化1(网络方向)

4、GET/POST请求传参长度有什么特点

我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。这是一个错误的说法,实际上HTTP协议从未规定get/post的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:

  1. HTTP协议未规定GET和POST的长度限制。
  2. GET的最大长度显示是因为浏览器和web服务器限制了URL的长度。
  3. 不同否浏览器和web服务器,限制的最大长度不一样。
  4. 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度8182byte。

5、从输入URL到页面加载全过程

1、 首先回答加载的流程,回答要点

  • 浏览器根据DNS服务器得到域名的IP地址
  • 向这个IP的机器发送HTTP请求
  • 服务器收到、处理并返回HTTP请求
  • 浏览器得到返回内容

2、 稍加分析

  • 例如输入https://www.baidu.com的时候,首先经过DNS解析,baidu.com对应的IP是101.211.145.250,然后浏览器向该IP发送HTTP请求。
  • sever端接收到HTTP请求,然后经过计算(向不同的用户推送不同的内容),返回HTTP请求,返回的内容其实就是一堆HTML格式的字符串,因为只有HTML格式浏览器才能正确解析,这是W3C标准的要求。

3、接下来会是渲染过程,回答要点

  • 根据HTML结构生成DOM树
  • 根据CSS生产CSSOM
  • 将DOM和CSSOM整合形成RenderTree
  • 根据RenderTree开始渲染和展示
  • 遇到<script>时,会执行并阻塞渲染

4、加以分析

  • 浏览器已经拿到了server端返回的HTML内容,开始解析并渲染。最初拿到的内容就是一堆字符串,必须先结构化成计算机擅长处理的基本数据结构,因此要把HTML字符串转化成DOM树–树是最基本的数据结构之一。
  • 解析过程中,如果遇到<link href="..."><script src="...">这种外链加载CSS和JS的标签,浏览器会异步下载,下载过程和上文中下载HTML的流程一样。只不过,这里下载下来的字符串是CSS或者JS格式的。
  • 浏览器将CSS生成CSSOM,再将DOM和CSSOM整合成RenderTree,然后针对RenderTree即可进行渲染了。大家可以想一下,有DOM结构、有样式,此时就能满足渲染的条件了。另外,这里也可以解释一个问题–为何要将CSS放在HTML头部?–这样会让浏览器尽早拿到CSS尽早生成CSSOM,然后在解析HTML之后可一次性生成最终的RenderTree,渲染一次即可。如果CSS放在HTML底部,会出现渲染卡顿的情况,影响性能和体验。
  • 最后,渲染过程中,如果遇到<script>就停止渲染,执行JS代码。因为浏览器渲染和JS执行共用一个线程,而且这里必须是单线程操作,多线程会产生渲染DOM冲突。待<script>内容执行完之后,浏览器继续渲染。最后再思考一个问题–为何要将JS放在HTML底部?–JS放在底部可以保证让浏览器优先渲染完现有的HTML内容,让用户先看到内容,体验好。另外,JS执行如果涉及DOM操作,得等待DOM解析完成才行,JS放在底部执行时,HTML肯定都解析成了DOM结构。JS如果放在HTML顶部,JS执行的时候HTML还没来的及转换为DOM结构,可能会报错。

6、说一下平时项目是怎么优化的?优化之后是怎么度量的?首屏时间是怎么计算的?

针对每个过程进行优化
网页从加载到呈现会经历一系列过程,针对每个过程进行优化

  • 网络连接
  • 请求优化
  • 响应优化
  • 浏览器渲染
    在这里插入图片描述
    通过performance.timingAPI。可以获取各个阶段的执行时间:

1.1网络连接方面优化
主要是针对重点向、DNS、TCP连接进行优化

  • 避免重定向
  • DNS查找优化:页面采用预解析dns-prefetch,同时将同类型的资源放到,减少domain数量也是可以减少DNS查找
  • 使用CDN(内容分发网络)
  • HTTP/1.1版本,客户端可以通过Keep-Alive选项和服务器建立长连接,让多个资源通过一个TCP连接传输。

1.2请求方面优化
减少浏览器向浏览器发送的请求数目以及请求资源的大小是请求优化的核心思想

  • 合理使用文件的压缩和合并

1、合理运用浏览器对于资源并行加载的特性,在资源的加载的数量和资源的大小之间做一个合理的平衡
2、在移动端页面中,将首屏的请求资源控制在5个以内,每个资源在Gzip之后的大小控制在28.5KB之内,可以显著的提升首屏时间。

  • 压缩图片,使用雪碧图,小图片使用Base64内联
  • 组件延迟加载
  • 给Cookie瘦身

静态资源使用CDN等方式放在和当前域不同的域上,以避免请求静态资源时携带Cookie

  • 善用CDN提升浏览器资源加载能力

资源分散到多个不同的CDN中,最大化的利用浏览器的并行加载能力

  • 合理运用缓存策略缓存静态资源,Ajax响应等

1、利用Manifest + 本地存储做持久化缓存
2、将对访问实时性要求不高的其他资源,如图片、广告脚本等内容存放在IndexDB或WebSQL中,IndexDB后WebSQL的存储容量比LocalStorage大的多,可以用来存放这些资源。
3、使用localForage操作持久化缓存
4、库文件放入CDN或者开启强缓

1.3、响应优化

  • 优化服务端处理流程,如使用缓存、优化数据库查询,减少查询次数等
  • 优化响应资源的大小,如对响应的资源开启Gzip压缩等

1.3.1 页面加载的核心指标

  • TTFB(首个字节)
  • FP(首次绘制,只有div跟节点,对应vue生命周期的created)
  • FCP(首次有内容的绘制,页面的基本框架,但是没有数据内容,对应vue生命周期的mounted)
  • FMP(首次有意义的绘制,包含所有元素和数据内容,对应vue生命周期的updated)
  • TTI(首次能交互时间)
  • Long Task(>=50ms的任务)
  • SSR&CSR(服务端渲染和客户端渲染)
  • lsomorphic javascript(同构化)

1.4 浏览器首屏渲染优化
1.4.1 首屏时间

指用户打开网站开始,到浏览器首屏内容渲染完成的时间。对于用户体验来说,首屏时间是用户对一个网站的重要体验因素。通常一个网站,如果首屏时间在5秒以内是比较优秀的,10秒以内是可以接受的,10秒以上就不可容忍了。超过10秒的首屏时间用户会选择刷新页面或立刻离开。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值