前端面试题(持续更新~~)

文章目录


一、HTTP

1.GET和POST区别

区别GETPOST
幂等性
应用场景用于对服务器资源不会产生影响的场景(如请求一个网页的资源等)用于对服务器资源会产生影响的场景(如用户注册)
是否缓存
传参方式查询字符串传参请求体传参
安全性将参数放入url中向服务器发送,不安全在请求体中,安全
请求长度浏览器对于url长度有限制,会受到影响在请求体中,不会受到浏览器影响
参数类型ASCLL类型文件、图片等

幂等性:指一个请求方法执行一次或多次的效果完全相同。

2.状态码304

1、为什么会有304?

服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制。当客户端再次请求页面时,服务器会判断请求的页面是否已被缓存,若已经被缓存则返回304,此时客户端将调用缓存内容。

状态码304不应该被认为是一种错误,而是对客户端有缓存情况下服务端的一种响应。

2、产生较多304状态码的原因是什么?

页面更新周期长或者长时间未更新。
纯静态页面或强制生成静态HTM。

3、304状态码过多会造成什么问题?

网站快照停止;
收录减少;
权重下降。

3.http 的请求方式

方法作用
get向服务器获取数据
post向服务器发送数据
put修改数据
patch用于对资源进行部分修改
delete删除指定数据

4.Cookie

(1)定义

Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie的大小只有4kb,它是一种纯文本文件,每次发起HTTP请求都会携带Cookie。

(2)特点

Cookie一旦创建成功,就无法修改;
Cookie是无法跨域的;
每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb;
存在安全问题,一旦被拦截,即可获得session的所有信息;
Cookie在请求一个新的页面的时候都会被发送出去。

(3)如何解决跨域问题?

使用Nginx反向代理;
在一个站点登陆之后,往其他网站写Cookie。服务端的Session存储到一个节点,Cookie存储sessionId。

(4)应用场景

和session结合使用,将sessionId存储到Cookie中,每次发送请求都会携带这个sessionId,以便于服务端识别和响应;
可以用来统计页面的点击次数。

5.cookie 和 session 的区别

1】cookie 数据存放在客户的浏览器上,session 数据放在服务器上。
2】cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗,考虑到安全应当使用 session。
3】session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用 cookie 。
4】单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。
5】所以个人建议:将登陆信息等重要信息存放为 session ;其他信息如果需要保留,可以放在 cookie 中。

6.LocalStorage

(1)定义

LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。
(2)优点
LocalStorage能存储5MB的信息;
LocalStorage能够持久化存储数据,数据不会随着页面的关闭而消失,除非手动清除;
仅存储在本地,发起HTTP请求的时候不会被携带。
(3)缺点
存在兼容性问题,IE8以下版本浏览器不支持;
如果浏览器设置为隐私模式,我们将无法获取到LocalStorage;
受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问。

(4)常用API

API注释
localStorage.setItem(key, value)保存数据到 localStorage
localStorage.getItem(key)从 localStorage 获取数据
localStorage.removeItem(key)从 localStorage 删除key对应的数据
localStorage.clear()从 localStorage 删除所有保存的数据
localStorage.key(index)获取某个索引的Key

(5)应用场景

一些网站配置个人设置的时候,比如肤色、字体等会将数据保存在LocalStorage中;
保存一些不经常变动的个人信息或用户浏览信息。

7.SessionStorage

(1)定义

SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。

(2)SessionStorage与LocalStorage对比

SessionStorage和LocalStorage都在本地进行数据存储;
SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享;
LocalStorage和SessionStorage都不能被爬虫爬取。

(3)常用API

API注释
sessionStorage.setItem(key, value)保存数据到 sessionStorage
sessionStorage.getItem(key)从 sessionStorage获取数据
sessionStorage.removeItem(key)从 sessionStorage删除key对应的数据
sessionStorage.clear()从 sessionStorage删除所有保存的数据
sessionStorage.key(index)获取某个索引的Key

(4)应用场景

由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。

8.cookie 、localstorage 、 sessionstrorage区别

(1)与服务器交互

cookie 是网站为了标示用户身份而储存在用户本地终端上的数据(通常经过加密);
cookie 始终会在同源 http 请求头中携带(即使不需要),在浏览器和服务器间来回传递;
sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。

(2)存储大小

cookie 数据根据不同浏览器限制,大小一般不能超过 4k;
sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大。

(3)有期时间

localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
sessionStorage 数据在当前浏览器窗口关闭后自动删除;
cookie 设置的 cookie 过期时间之前一直有效,与浏览器是否关闭无关。

9.http请求过程

1】浏览器发送请求;
2】浏览器进行DNS域名解析,得到对应的IP地址;
3】根据这个IP,找到对应的服务器建立连接(三次握手);
4】建立TCP连接后发起HTTP请求(一个完整的http请求报文);
5】服务器响应HTTP请求,浏览器得到html代码(服务器如何响应);
6】浏览器解析html代码,并请求html代码中的资源(如js、css、图片等);
7】浏览器对页面进行渲染呈现给用户;
8】服务器关闭TCP连接(四次挥手)。

10.如何优化网站性能

1】压缩图片:图片是网站加载速度的主要因素之一,优化图片大小可以有效提高网站的加载速度。可以使用图片压缩工具,如TinyPNG、Kraken等。
2】使用CDN:CDN(内容分发网络)可以将网站的静态资源分发到全球各地的服务器上,减少资源加载时间,提高网站访问速度。
3】合并和压缩CSS和JavaScript文件:将多个CSS和JavaScript文件合并为一个文件,并将其压缩,可以减少HTTP请求和文件大小,提高网站性能。
4】使用缓存:使用浏览器缓存和服务器缓存可以减少网站请求次数和响应时间,提高网站的性能。
5】减少HTTP请求:减少网站的HTTP请求可以降低服务器的负载,提高网站的性能。可以使用CSS Sprites、Base64编码等技术减少HTTP请求。
6】去除不必要的插件和脚本:不必要的插件和脚本会增加网站的负载和响应时间,应该尽可能去除。
7】优化数据库:优化数据库可以减少数据库查询时间,提高网站的性能。可以使用索引、优化查询语句等技术来优化数据库。
8】使用响应式设计:响应式设计可以自适应不同的设备和屏幕大小,提高用户体验和SEO排名。
9】使用最新的Web技术:使用最新的Web技术可以提高网站的性能和用户体验。如使用HTML5、CSS3、JavaScript ES6等技术来构建网站。
10】定期进行网站性能测试:定期进行网站性能测试可以帮助发现网站性能问题和瓶颈,及时进行优化和调整。可以使用工具如Google PageSpeed Insights、Pingdom等来进行测试。

二、JavaScript

1.js 的执行机制

js 是一个单线程、异步、非阻塞 I/O 模型、 event loop 事件循环的执行机制,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程, 某个异步任务可以执行了,该任务才会进入主线程执行。

2.数据类型

基本数据类型引用数据类型
Undefined、Null、Boolean、String、Number、Symbol(ES6)、BigInt(ES6)Object(包括数组、函数、对象等)

3.堆内存与栈内存

在操作系统中,内存被分为栈区和堆区,栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆区内存一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

在数据结构中,栈中数据的存取方式为先进后出。堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。

数据存储方式

基本数据类型的数据直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

4.数据类型检测的方法

判断数据类型的方法一般可以通过:typeof、instanceof、constructor、toString四种常用方法

类型typeofinstanceofconstructorObject.prototype.toString.call
优点使用简单能检测出引用类型基本能检测所有的类型(除了null 和 undefined)检测出所有的类型
缺点只能检测出基本类 型(除了 null)不能检测出基本类型,且不能跨iframeconstructor 易被修改,也不能跨 iframeIE6 下,undefined 和 null 均为 Object

5.判断数组的方式

1】Object.prototype.toString.call([1, 2, 3]) // [object Array]
2】通过ES6的Array.isArray([1, 2, 3])做判断 // true or false
3】[1, 2, 3] instanceof Array // true or false
4】Array.prototype.isPrototypeOf([1, 2, 3]) // true or false
5】通过原型链去判断:[1, 2, 3].proto === Array.prototype

6.Undefined与Null

1】Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null;
2】undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化;
3】undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0;
4】typeof 类型不同:
typeof null; // ‘object’
typeof undefined; // ‘undefined’
(5)Number() 转数字也不同 
Number(null); // 0
Number(undefined); // NaN

7.this的指向问题

this是一个在运行时才进行绑定的引用,在不同的情况下它可能会被绑定不同的对象。

this 永远指向最后调用它的那个对象

三种调用模式

1】函数调用模式:当一个函数不是一个对象的属性,直接作为函数来调用时,this指向全局对象;
2】方法调用模式:当一个函数作为一个对象的方法来调用时,this指向这个对象;
3】构造器调用模式:如果一个函数使用new调用时,函数在执行前会创建一个新的对象,this就指向这个新的对象。

如何改变this的指向?

1】使用ES6箭头函数,箭头函数不绑定this,箭头函数的this使用指向函数定义时的this;
2】在函数内部定义一个变量_this保存this;
3】使用 apply、call、bind;
4】new 实例化一个对象;

this绑定的优先级:new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级。

8.伪数组(类数组)

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

9.类数组如何转换为数组

(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)通过展开运算符:const array = […arrayLike]

10.for…in与for…of区别

区别for…infor…of
遍历对象对象的键名,会遍历整个原型链,性能差对象的键值,只遍历当前对象
遍历数组返回数组中所有可枚举属性,包括原型链上的属性只返回对应数组的下标对应的属性值

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

11.AJAX

AJAX Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下:

本身是针对MVC编程,不符合前端MVVM的浪潮;
基于原生XHR开发,XHR本身的架构不清晰;
不符合关注分离(Separation of Concerns)的原则;
配置和调用方式非常混乱,而且基于事件的异步模型不友好。

12.Axios

Axios 是一种基于Promise封装的HTTP客户端。

浏览器端发起XMLHttpRequests请求;
node端发起http请求;
支持Promise API;
监听请求和返回;
对请求和返回进行转化;
取消请求;
自动转换json数据;
客户端支持抵御XSRF攻击。

13.数组的遍历方法

方法改变原数组特点
forEach无返回值
map返回新数组,可链式调用
filter过滤数组,返回包含符合条件的元素的数组,可链式调用
for…offor…of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环
every遍历的数组里的元素全部符合条件时,返回true
some遍历的数组里的元素至少有一个符合条件时,返回true
find返回第一个符合条件的值
findIndex返回第一个返回条件的值的索引值
reduce对数组正序操作
reduceRight对数组逆序操作

14.深拷贝

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象(新旧对象不共享同一块内存),且修改新对象不会影响原来的对象(深拷贝采用了在堆内存中申请新的空间来存储数据,这样每个可以避免指针悬挂)。

实现方式如下:JSON.parse(JSON.stringify())

这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。

常见的 “深” 拷贝方法
(1)JSON.parse(JSON.stringify(obj))

JSON.stringify()是目前前端开发过程中最常用的深拷贝方式,原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象。
JSON.stringfy() 将对象序列化成json对象
JSON.parse() 反序列化——将json对象反序列化成js对象

注意:它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object类型,这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。

(2)使用第三方库实现对象的深拷贝,比如:lodash、jQuery

(3)递归

简单封装了一个deepClone的函数,for in遍历传入参数的值,如果值是引用类型则再次调用deepClone函数,并且传入第一次调用deepClone参数的值作为第二次调用deepClone的参数,如果不是引用类型就直接复制。

15.浅拷贝

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

实现方式如下:Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

常见的 “浅” 拷贝方法
(1)Object.assign()

将所有可枚举属性的值从一个或多个源对象分配到目标对象,它将返回目标对象可以实现一个浅拷贝的效果。其方法包括两个参数:
参数一:目标对象
参数二:源对象

注意:可见Object.assign()方法对于一维数据是深拷贝效果,但是对于多维数据是浅拷贝效果。Object.assign是一个浅拷贝,它只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是仍是对象的话依然是浅拷贝。

(2)slice()

数组进行截取,如果不传参数,会使用默认值,得到一个与原数组元素相同的新数组。其方法包括两个参数:
参数一:截取的起始位置
参数二:截取的结束位置

注意:可见slice()方法也只是对一维数据进行深拷贝,但是对于多维的数据还是浅拷贝效果。

(3)concat()

数组的拼接(将多个数组或元素拼接形成一个新的数组),不改变原数组,如果不传参数,会使用默认值,得到一个与原数组元素相同的新数组 (复制数组)。

注意:可见concat()方法也只对一维数据具有深拷贝效果,对于多维的数据任然只是浅拷贝。

(4)ES6拓展运算符

ES6的展开运算符对于一维数据是深拷贝效果,但是对于多维数据任然是浅拷贝效果。

16.赋值与深/浅拷贝的区别

对于引用数据类型

赋值深拷贝浅拷贝
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。

三者区别

和原数据是否指向同一对象第一层数据为基本数据类型且修改基本类型数据时原数据中包含子对象且修改子对象时
赋值改变会使原数据一起改变改变会使原数据一起改变
深拷贝改变不会使原数据一起改变改变不会使原数据一起改变
浅拷贝改变不会使原数据一起改变改变会使原数据一起改变

17.forEach如何跳出循环

forEach是不能通过break或者return来实现跳出循环的,forEach的回调函数形成了一个作用域,在里面使用return并不会跳出,只会被当做continue。

实现方法:try…catch

function getItemById(arr, id) {
    var item = null;
    try {
        arr.forEach(function (curItem, i) {
            if (curItem.id == id) {
                item = curItem;
                throw Error();
            }
        })
    } catch (e) {}
    return item;
}

18.闭包

(1)定义

闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是,一个作用域可以访问另外一个函数内部的局部变量。
创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。

闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。

(2)用途

1】使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
2】使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
简而言之
1】读取内部函数的变量;
2】这些变量的值始终会保持在内存中,不会在外层函数调用后被自动清除。

(3)使用场景

return 回一个函数
函数作为参数
自动执行函数
循环赋值
回调函数
节流防抖
函数柯里化

(4)执行过程

1】形成私有上下文
2】进栈执行
3】开始一系列操作
4】初始化作用域链(两头<当前作用域,上级作用域>)
5】初始化this
6】初始化arguments
7】赋值形参
8】变量提升
9】代码执行
10】正常情况下,代码执行完成之后,私有上下文出栈被回收。但是遇到特殊情况,如果当前私有上下文执行完成之后中的某个东西被执行上下文以外的东西占用,则当前私有上下文就不会出栈释放,也就是形成了不被销毁的上下文。

(5)闭包优点

变量会一直在内存中;
避免全局变量的污染;
私有变量的存在;

(6)闭包缺点

变量长期储存在内存中,会增大内存的使用量,使用不当会造成内存泄露。

(7)判断闭包的3个特点

1】函数嵌套函数;
2】内部函数一定操作了外部函数的局部变量;
3】外部函数一定将内部函数返回到外部并保存在一个全局变量中;

(8)判断闭包的执行结果

1】外部函数被调用了几次就有几个受保护的局部变量的副本;
2】来自一个闭包的函数被调用几次,受保护的局部变量就变化几次;

(9 )闭包特性

1】函数嵌套函数;
2】内部函数可以直接使用外部函数的局部变量;
3】变量或参数不会被垃圾回收机制回收;

注意事项

容易导致内存泄漏。闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。其次的话就是变量执行完以后,可以让它赋值为null,最后利用JS的一个垃圾回收机制进行回收。

19.执行上下文

执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。

执行上下文有三种类型

1】全局执行上下文:任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。
2】函数执行上下文: 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
3】eval函数执行上下文:执行在 eval 函数内部的代码也会有属于它自己的执行上下文。

20.执行上下文的三个阶段

创建阶段 → 执行阶段 → 回收阶段

(1)创建阶段

1】this绑定
2】在全局执行上下文中,this指向全局对象(window对象)
3】在函数执行上下文中,this指向取决于函数如何调用。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined
4】创建词法环境组件
5】词法环境是一种有标识符——变量映射的数据结构,标识符是指变量/函数名,变量是对实际对象或原始数据的引用
6】词法环境的内部有两个组件:环境记录器:用来储存变量个函数声明的实际位置,外部环境的引用:可以访问父级作用域
7】创建变量环境组件
8】变量环境也是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系

(2)执行阶段

在这阶段,执行变量赋值、代码执行。如果 Javascript 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配 undefined 值。

(3)回收阶段

执行上下文出栈等待虚拟机回收执行上下文。

21.作用域Scope

作用域是在运行时代码中的某些特定部分中变量、函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

全局作用域

直接写在script标签的JS代码,都在全局作用域。在全局作用域下声明的变量叫做全局变量(在块级外部定义的变量);
所有末定义直接赋值的变量自动声明为拥有全局作用域;
全局变量在全局的任何位置下都可以使用;全局作用域中无法访问到局部作用域的中的变量;
全局作用域在页面打开的时候创建,在页面关闭时销毁;
所有 window 对象的属性拥有全局作用域。

var和function命令声明的全局变量和函数是window对象的属性和方法
let命令、const命令、class命令声明的全局变量,不属于window对象的属性

函数作用域(局部作用域)

调用函数时会创建函数作用域,函数执行完毕之后,作用域销毁。每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的;
在函数作用域中可以访问全局变量,在全局作用域中一般情况下无法访问函数内的变量(可以通过闭包访问);
在函数作用域中操作一个变量时,它会先在自身作用域内寻找,如果有就直接使用,如果没有就向上一级作用域中寻找,直到找到全局作用域中。如果全局作用域中仍未找到,则报错。

块级作用域

块级作用域可通过新增命令let和const声明,所声明的变量在指定的块级作用域外无法被访问,块级作用域在如下情况被创建:
1】在一个函数内部
2】在一个代码块(由一对花括号包裹)内部

let 声明的语法与 var 的语法一致。基本上可以用 let 来代替 var 进行变量声明,但会将变量的作用域限制在当前代码块中。块级作用域有以下几个特点:
1】声明变量不会提升到代码块顶部
2】禁止重复声明

作用域链

在某个作用域内访问一个变量时,会先在当前作用域内寻找,如果没有找到,则去上一级作用域内寻找,以此类推。这样的变量作用域访问的链式结构,被称为作用域链。
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

22.内存泄漏

内存泄露是指由于疏忽或错误造成程序未能释放已经不再使用的内存。

内存泄露的原因有以下几种:

1】意外的全局变量:由于使用为声明的变量,而意外的创建了一个变量,这个变量将一直留在内存中无法被回收;
2】被遗忘的定时器或回调函数:设置了setInterval定时器而忘记销毁,如果循环函数有对外部的引用的话,这个变量将一直被留在内存中无法被回收;
3】脱离DOM的引用:获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收;
4】闭包:不合理的使用闭包,从而导致某些变量一直被留在内存当中。

23.纯函数

纯函数是对给定的输入返还相同的输出的函数,并且要求所有的数据都是不可变的。

特性

函数内部传入指定的值,就会返回唯一确定的值
不会造成超出作用域的变化,例如修改全局变量或引用传递的参数

优势

通过纯函数可以产生可测试的代码
不依赖外部环境计算,不会产生副作用,复用性高
可读性高,不管是不是纯函数,都会有一个语义化的名称,便于阅读
符合模块化概念及单一职责原则

24.高阶函数

高阶函数是指使用其它函数作为参数、或者返回一个函数作为返回值的函数。

常见的高阶函数

Array.prototype.map
Array.prototype.filter
Array.prototype.forEach
Array.prototype.reduce

25.函数柯里化

柯里化(Currying)又叫函数的部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

优点

参数复用:需要输入多个参数,最终只需输入一个,其余通过arguments对象获取
提前确认:避免重复判断某一条件是否符合,不符合则return
延迟运行:避免重复执行程序,等真正需要结果的时候再执行

26.箭头函数

ES6中允许使用“箭头”(=>) 来定义函数。箭头函数相当于匿名函数,并且简化了函数定义。

特点

1】箭头函数不绑定this,箭头函数里的this永远指向定义箭头函数时所处的作用域
2】箭头函数的this永远不会变,call、apply、bind也无法改变
3】箭头函数只能声明成匿名函数,但可以通过表达式的方式让箭头函数具名
4】箭头函数没有原型prototype
5】因为this的指向问题,箭头函数不能作为构造函数使用
6】箭头函数没有 arguments,在箭头函数内部访问这个变量访问的是外部环境的arguments, 可以使用 …代替

27.Promise

ES6新增的一种异步编程的解决方案,比传统的回调函数和事件更加的合理和强大。通过Promise可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。Promise可以解决异步的问题,但不能说Promise是异步的。

特点

1】对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:
pending——进行中
resolved——已成功
rejected——已失败
2】一旦状态改变,就不会再发生变化,任何时候都可以得到这个结果。Promise对象状态的改变只有两种可能:
pending——resolved
pending——rejected
3】Promise内部发生错误,不会影响到外部程序的执行。
4】Promise一旦执行则无法取消。
5】一旦创建就会立即执行,无法中途取消(缺点1)
6】如果不设置回调函数,Promise内部抛出的错误将不会反应到外部(缺点2)
7】当处于pending状态时,无法得知目前进展到哪一阶段,即无法预测是刚刚开始还是即将完成(缺点3)

用法

创建Promise实例时,必须传入一个函数作为参数:
new Promise(() => {})

该函数可以接收另外两个由JavaScript引擎提供的函数,resolve和reject:
1】resolve——将Promise对象的状态从pending变为resolved,将异步操作的结果作为参数传递出去
2】reject——将Promise对象的状态从pending变为rejected,将异步操作报出的错误作为参数传递出去

const promise = new Promise((resolve, reject) => {
    if (true) resolve('value')
    else reject('error')
})

28.防抖

当事件触发时,相应的函数不会立即触发,而是等待一段时间;
当事件连续触发时,函数的触发等待时间会被不断重置(推迟)。

通俗的讲,防抖就是,每次触发事件时,在一段时间后才真正响应这个事件,具体应用如下:

1】输入框中频繁输入内容,如果输入框改变一次就发送一次请求的话,会对服务器造成很大的压力,所以我们希望在连续输入的时候不发送请求,直到用户输入完或者一段时间没有继续输入的话才发送请求;
2】频繁点击按钮触发事件(恶意的行为)
3】用户缩放浏览器时频繁触发resize事件
4】王者荣耀回城

如何实现防抖函数

function debounce(callback, time) {
    let timer
    return function() {
        clearTimeout(timer)
        let args = arguments
        timer = setTimeout(() => {
            callback.apply(this, args)
        }, time)
    }
}

29.节流

如果事件被频繁触发,那么节流函数会按照一定的频率来执行函数;
不管中间触发了多少次,执行函数的频率总是固定的。

节流就是在间隔一段时间执行一次,具体应用如下:

王者荣耀冷却中的技能无法再次释放;
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;
射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)。

如何实现节流函数

function throttle(func, delay) {
    let timer;
    return function() {
        let args = arguments;
        if (!timer) {
            timer = setTimeout(() => {
                timer = null;
                func.apply(this, args)
            }, delay)
        }
    }
}

30.原型链

前提须知:

1】 prototype:所有的函数都有原型prototype属性,这个属性指向函数的原型对象。
2】 proto,这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。
3】 constructor: 每个原型都有一个constructor属性,指向该关联的构造函数。

原型链

获取对象时,如果这个对象上本身没有这个属性时,它就会去它的原型__proto__上去找,如果还找不到,就去原型的原型上去找…一直找直到找到最顶层(Object.prototype)为止,Object.prototype对象也有__proto__属性,值为null。
此外,每一个prototype原型上都会有一个constructor属性,指向它关联的构造函数。

31.JS垃圾回收机制

垃圾回收机制(Garbage Collection)简称GC:是JavaScript中使用的内存管理系统的基本组成部分;
JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时会 “自动” 释放、释放的过程成为垃圾回收;
内存在不使用的时候会被垃圾回收器自动回收。

32.数组常用的方法

1、concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
2、find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined 。
语法: 数组名.find(function (item,index,arr) {})
item : 这个表示的是数组中的每一项
index : 这个表示的是每一项对应的索引
arr : 这个表示的是原数组
3、findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
4、includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true, 否则返回 false。
5、indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。 (通常用它判断数组中有没有这个元素)。lastIndexOf 用法与其一致,区别:前者从左开始检查,后者从右开始检查。
语法一: 数组名.indexOf( 要查询的数据)
语法二: 数组名.indexOf( 要查询的数据, 开始索引)
6、join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。 如果数组只有一个项目,那么将返回该项目而不使用分隔符。
语法: 数组名.join(’ 连接符’)
7、push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
语法: 数组名.push( 数据)
8、pop() 方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
9、shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
10、unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。
11、splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内 容。此方法会改变原数组。 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删 除元素,则返回空数组。
截取数组,返回截取出来的数据,语法一:数组名.splice(开始索引,多少个)
删除并插入数据,返回截取出来的数据,语法二: 数组名.splice(开始索引,多少个,你要插入的数据)
12、slice() ,截取数组的一部分数据
语法: 数组名.slice( 开始索引, 结束索引)
13、reverse() 方法将数组中元素的位置颠倒,并返回该数组。该方法会改变原数组。
14、sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字 符串,然后比较它们的 UTF-16 代码单元值序列时构建的。
语法一: 数组名.sort()会排序 会按照位排序
语法二: 数组名.sort(function (a,b) {return a-b}) 会正序排列
语法三: 数组名.sort(function (a,b) {return b-a}) 会倒序排列
ES6新增的方法
1、forEach() 用来循环遍历的 for
语法: 数组名.forEach(function (item,index,arr) {})
2、map()映射数组的
语法: 数组名.map(function (item,index,arr) {})
3、filter()过滤数组
语法: 数组名.filter(function (item,index,arr) {})
4、every()判断数组是不是满足所有条件
语法: 数组名.every(function (item,index,arr) {})
5、some()数组中有没有满足条件的
语法: 数组名.some(function (item,index,arr) {})
6、reduce()叠加后的效果
语法: 数组名.reduce(function (prev,item,index,arr) {},初始值)
prev :一开始就是初始值 当第一次有了结果以后;这个值就是第一次的结果

33.js中=== ==的区别

1】===:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边 的值,值相同则返回 true,若等号两边的值类型不同时直接返回 false。也就是说三个等号既要判断值也要判断类型是否相等。
2】==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。也就是说两个等号只要值相等就可以。

34.var、let和const的区别

1】块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
内层变量可能覆盖外层变量;
用来计数的循环变量泄露为全局变量;
2】变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
3】给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
4】重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
5】暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
6】初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
7】指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。

区别varletconst
块级作用域✔️✔️
是否存在变量提升✔️
是否添加全局属性✔️
重复声明同名变量✔️
是否存在暂时性死区✔️✔️
是否必须设置初始值✔️
能否改变指针方向✔️✔️

35.call、apply和bind区别

首先,call apply bind三个方法都可以用来改变函数的this指向,具体区别如下:
1】call( ) 是接收一个及其以上的参数,第一个参数表示this要指向的对象,其余参数表示调用函数需要传入的参数,返回调用函数的返回结果,属于立即执行函数;
2】apply( ) 是接收两个参数,第一个参数表示this要指向的对象,第二参数表示调用函数需要传入的参数所组成的数组,返回调用函数的返回结果,属于立即执行函数;
3】bind( ) 是接收一个及其以上的参数,和call()一致,但是其返回是一个函数,而不是调用函数的返回结果。

相同点

都是改变this的指向,传入的第一个参数都是绑定this的指向,在非严格模式中,如果第一个参数是nul或者undefined,会把全局对象(浏览器是window)作为this的值,要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined。

区别

call和apply唯一的区别是:call传入的是参数列表,apply传入的是数组,也可以是类数组;
bind和call、apply的区别: bind返回的是一个改变了this指向的函数,便于稍后调用,不像call和apply会立即调用;bind和call很像,传入的也是参数列表,但是可以多次传入,不需要像call,一次传入。

值得注意:当 bind 返回的函数 使用new作为构造函数时,绑定的 this 值会失效,this指向实例对象,但传入的参数依然生效 (new调用的优先级 > bind调用)。

35.Webpack

在这里插入图片描述

(1)谈谈你对Webpack的理解

1】Webpack是一个模块打包工具,可以使用它管理项目中的模块依赖,并编译输出模块所需的静态文件。
2】它可以很好地管理、打包开发中所用到的HTML,CSS,JavaScript和静态文件(图片,字体)等,让开发更高效。
3】对于不同类型的依赖,Webpack有对应的模块加载器,而且会分析模块间的依赖关系,最后合并生成优化的静态资源。

(2)Webpack的基本功能

1】代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等
2】文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等
3】代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
4】模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
5】自动刷新:监听本地源代码的变化,自动构建,刷新浏览器
6】代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
7】自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统

(3)Webpack构建过程

1】从entry里配置的module开始递归解析entry依赖的所有module
2】每找到一个module,就会根据配置的loader去找对应的转换规则
3】对module进行转换后,再解析出当前module依赖的module
4】这些模块会以entry为单位分组,一个entry和其所有依赖的module被分到一个组Chunk
5】最后Webpack会把所有Chunk转换成文件输出在整个流程中Webpack会在恰当的时机执行plugin里定义的逻辑

(4)如何优化 Webpack 的构建速度

1】使用高版本的 Webpack 和 Node.js
2】压缩代码
a.通过 uglifyjs-webpack-plugin 压缩JS代码
b.通过 mini-css-extract-plugin 提取 chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
3】多线程/多进程构建:thread-loader, HappyPack
4】压缩图片: image-webpack-loader
5】缩小打包作用域
a.exclude/include (确定 loader 规则范围)
b.resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
c.resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)
d.resolve.extensions 尽可能减少后缀尝试的可能性
e.noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
f.ignorePlugin (完全排除模块)

延申
(1)Webpack中loader的含义作用

Loader 是webpack中提供了一种处理多种文件格式的机制,因为webpack只认识JS和JSON,所以Loader相当于翻译官,将其他类型资源进行预处理。
用于对模块的"源代码"进行转换。
loader支持链式调用,原型调用的顺序是从右往左。原型链中的每个loader会处理之前已处理过的资源,最终变为js代码。
可以通过 loader 的预处理函数,为 JavaScript 生态系统提供更多能力。

(2)常见的loader有哪些

less-loader: 将less文件编译成css文件,开发中,我们常常会使用less预处理器编写css样式,使开发效率提高
css-loader: 将css文件变成commonjs模块加载到js中,模块内容是样式字符串
style-loader: 创建style标签,将js中的样式资源插入标签内,并将标签添加到head中生效
ts-loader: 打包编译Typescript文件

(3)Plugin的含义作用

Plugin功能更强大,主要目的就是解决loader 无法实现的事情,比如打包优化和代码压缩等。
Plugin加载后,在webpack构建的某个时间节点就会触发plugin定义的功能,帮助webpack做一些事情。实现对webpack的功能扩展。

(4)常见的Plugin有哪些

html-webpack-plugin 处理html资源,默认会创建一个空的HTML,自动引入打包输出的所有资源(js/css)
mini-css-extract-plugin 打包过后的css在js文件里,该插件可以把css单独抽出来
clean-webpack-plugin 每次打包时候,CleanWebpackPlugin 插件就会自动把上一次打的包删除

(5)Webpack中Loader和Plugin的区别

webpack 就像一条生产线,要经过一系列处理流程(loader)后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。
插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。

35.JS如何实现多线程

(1)什么是JavaScript的多线程

JavaScript本身是单线程的,但是可以通过实现多线程来提高性能和用户体验。多线程允许JavaScript在等待用户交互或网络请求时,执行其他任务,从而提高页面加载速度和响应速度。

(2)JavaScript中实现多线程的方式

JavaScript有多种实现多线程的方式,包括Web Workers、SharedArrayBuffer、WebAssembly等。其中,Web Workers允许在后台线程中运行JavaScript代码,而SharedArrayBuffer和BufferSource API则允许在多个线程之间共享数据。

(3)如何使用Web Workers实现多线程

使用Web Workers实现多线程需要创建一个新的worker线程,并将需要执行的代码作为字符串传递给worker。worker线程可以访问全局对象messageChannel的postMessage方法来发送消息,主线程可以使用onmessage方法来接收消息并执行相应的操作。

(4)如何保证多线程安全

多线程环境下的安全问题主要包括数据竞争和死锁等。为了解决这些问题,需要使用同步机制,如使用Promise、async/await等异步编程方式,或者使用事件循环、共享内存等机制来保证数据的一致性和安全性。

(5)描述一个实际的多线程应用场景

在实际应用中,多线程可以用于提高页面加载速度和响应速度,例如在电商网站中,可以使用Web Workers在后台线程中加载和处理商品图片,从而提高页面加载速度和用户体验。同时,多个并发请求也可以使用Web Workers并行处理,提高系统性能和响应速度。

36.promise和async/await的区别

Promise和async/await是JavaScript中处理异步操作的两种方式。

Promise是一种用于处理异步操作的对象。它可以表示一个异步操作的最终完成或失败,并返回相应的结果或错误信息。Promise有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。通过调用Promise的then()方法可以注册回调函数来处理异步操作的结果。

async/await是ES8引入的一种更加简洁的处理异步操作的方式。async函数是一个返回Promise对象的函数,其中可以使用await关键字来等待一个Promise对象的解决。await关键字可以暂停async函数的执行,直到Promise对象解决为止,并返回解决后的结果。

区别

1】语法上,Promise使用then()和catch()方法来处理异步操作的结果,而async/await使用async函数和await关键字来等待异步操作的结果。
2】可读性上,async/await更加直观和易于理解,代码结构更加清晰,而Promise则需要通过链式调用then()方法来处理多个异步操作。
3】错误处理上,Promise使用catch()方法来捕获错误,而async/await可以使用try-catch语句来捕获错误。

37.常见的继承

1、原型链继承
特点:

1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新 实例不会继承父类实例的属性!

缺点:

1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属 性,另一个实例的原 型属性也会被修改!)

2、借用构造函数继承
重点:

用 .call()和.apply() 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复 制))

特点:

1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点 1、2、3。
3、可以继承多个构造函数属性(call 多个)。
4、在子实例中可向父实例传参。

缺点:

1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。

3、组合继承(组合原型链继承和借用构造函数继承)(常用)
重点:

结合了两种模式的优点,传参和复用

特点:

1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。

缺点:

调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函 数。

4、原型式继承
重点:

用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的 实例或对象。object.create()就是这个原理。

特点:

类似于复制一个对象,用函数来包装。

缺点:

1、所有实例都会继承原型上的属性。
2、无法实现复用。(新实例属性都是后面添加的)

5、class 类实现继承

通过 extends 和 super 实现继承

6、寄生式继承
重点:

就是给原型式继承外面套了个壳子。

优点:

没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成 了创建的新对象。

缺点:

没用到原型,无法复用。

38.数组和字符串的相互转换

使用字符串的 split() 方法可以根据指定的分隔符把字符串切分为数组。
如果使用数组的 join() 方法,可以把数组元素连接为字符串。

三、CSS

1.常见的布局方法

页面布局常用的方法有浮动、定位、flex、grid 网格布局、栅格系统布局
1、浮动: 优点:兼容性好。 缺点:浮动会脱离标准文档流,因此要清除浮动。我们解决好这个问题即可。
2、绝对定位 优点:快捷。 缺点:导致子元素也脱离了标准文档流,可实用性差。
3、flex 布局(CSS3 中出现的) 优点:解决上面两个方法的不足,flex 布局比较完美。移动端基本用 flex 布局。
4、网格布局(grid) CSS3 中引入的布局,很好用。代码量简化了很多。
5、栅格系统布局 优点:可以适用于多端设备。

2.display属性值及其作用

属性值作用
none元素不显示,并且会从文档流中移除
block块元素类型。默认宽度为父元素宽度,可设置宽高,换行显示
inline行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示
inline-block行内块元素类型。默认宽度为内容宽度,可以设置宽高,同行显示
list-item像块类型元素一样显示,并添加样式列表标记
table此元素会作为块级表格来显示
inherit规定应该从父元素继承display属性的值

3.行内元素和块级元素

区别行内元素块级元素
宽高无效有效
padding有效有效
margin水平方向有效有效
自动换行不可以可以
多个元素排列默认从左到右默认从上到下

4.隐藏元素的方法

方法说明
display: none;渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件
visibility: hidden;元素在页面中仍占据空间,但是不会响应绑定的监听事件
opacity: 0;透明度设置为0,来隐藏元素。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件
position: absolute;通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏
z-index: -10;使用其余元素遮盖当前元素实现隐藏
clip/clip-path使用元素裁剪的方法来实现元素的隐藏,这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件
transform: scale(0,0)将元素缩放为 0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件

5.盒模型

盒模型由四个部分组成,分别是margin、border、padding、content.

盒子模型分为两种:

第一种是 W3C 标准的盒子模型(标准盒模型)
第二种 IE 标准的盒子模型(怪异盒模型)

标准盒模型和IE盒模型的区别在于:在设置width和height时,所对应的范围不同

标准盒模型的width和height属性的范围只包含了content
IE盒模型的width和height属性的范围包含了border、padding和content

可以通过修改元素的box-sizing属性来改变元素的盒模型:

box-sizing: content-box 表示标准盒模型(默认值)
box-sizing: border-box 表示IE盒模型(怪异盒模型)

6.实现单行、多行文本溢出隐藏

单行文本溢出

overflow: hidden;           // 溢出隐藏
text-overflow: ellipsis;    // 溢出部分使用省略号显示
white-space: nowrap;        // 规定段落中的文本不可换行

多行文本溢出

overflow: hidden;            // 溢出隐藏
text-overflow: ellipsis;     // 溢出用省略号显示
display:-webkit-box;         // 作为弹性伸缩盒子模型显示。
-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列
-webkit-line-clamp:3;        // 显示的行数

7.实现水平垂直居中

方法1:flex布局

.parent {
    display: flex;
    justify-content: center;
    align-items: center;
}

方法2:使用绝对定位和transform属性

.container {
  position: relative;
}
 
.centered {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

方法3:使用table和table-cell结构

.container {
  display: table;
  width: 100%;
  height: 100%;
}
 
.centered {
  display: table-cell;
  text-align: center;
  vertical-align: middle;
}

8.浮动

CSS 浮动(Float)属性用于创建浮动框,将其放置在包含块的左侧或右侧。浮动框可以向左或向右移动,直到它的外边缘碰到包含块的边界或另一个浮动框。
浮动可以用于创建文本环绕效果,或者用于创建复杂的布局结构。

清除浮动的方式

使用 clear 属性清除浮动。
使用 overflow 属性设置为 auto 或 hidden。
使用 :after 伪元素清除浮动。

9.link 与 @import 的区别和用法

页面导入外部css文件的方法通常有两种,一种在网页中直接link标签加入,另一种在页面中@import引入css文件。两种引入形式如下:

link引入形式:

<link href="styles.css" type="text/css" />

@import引用形式:

<style type="text/css">
    @import url("styles.css");
</style>

区别
(1)适用范围不同

@import可以在网页页面中使用,也可以在css文件中使用,用来将多个css文件引入到一个css文件中;而link只能将css文件引入到网页页面中。

(2)功能范围不同

link属于XHTML标签,而@import是CSS提供的一种方式,link标签除了可以加载CSS外,还可以定义rel连接属性,定义RSS等,@import就只能加载CSS。

(3)加载顺序不同

页面被加载的时候,link引用的CSS会同时被加载,而@import引用的CSS会等到页面全部被下载完再被加载。所以有时候浏览@import加载CSS的页面时开始会没有样式(就是闪烁)。

(4)兼容性

由于@import是css2.1提出的,所以老的浏览器不支持,@import只有在IE5以上的才能识别,而link标签无此问题。

(5)控制样式时的差别

使用link方式可以让用户切换CSS样式.现代浏览器如Firefox,Opera,Safari都支持rel=”alternate stylesheet”属性(即可在浏览器上选择不同的风格),当然你还可以使用Javascript使得IE也支持用户更换样式。

(6)使用DOM控制样式时的差别

当使用JavaScript控制DOM去改变样式的时候,只能使用link标签,因为@import不是DOM可以控制的。

10.display:none与visibility:hidden的区别

这两个属性都是让元素隐藏,不可见。两者区别如下:

(1)在渲染树中
display:none会让元素完全从渲染树中消失,渲染时不会占据任何空间;
visibility:hidden不会让元素从渲染树中消失,渲染的元素还会占据相应的空间,只是内容不可见。

(2)是否是继承属性
display:none是非继承属性,子孙节点会随着父节点从渲染树消失,通过修改子孙节点的属性也无法显示;
visibility:hidden是继承属性,子孙节点消失是由于继承了hidden,通过设置visibility:visible可以让子孙节点显示。

(3)修改常规文档流中元素的 display 通常会造成文档的重排,但是修改visibility属性只会造成本元素的重绘。

(4)如果使用读屏器,设置为display:none的内容不会被读取,设置为visibility:hidden的内容会被读取。

(5)display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。
visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。

11.CSS3新特性

新增各种CSS选择器 (: not(.input):所有 class 不是“input”的节点)
圆角 (border-radius:8px)
多列布局 (multi-column layout)
阴影和反射 (Shadoweflect)
文字特效 (text-shadow)
文字渲染 (Text-decoration)
线性渐变 (gradient)
旋转 (transform)
增加了旋转,缩放,定位,倾斜,动画,多背景

12.img 的 alt 与 title 的异同

alt 是图片加载失败时,显示在网页上的替代文字; title 是鼠标放上面时显示的文字,title是对图片的描述与进一步说明。

13.CSS提高性能的方法

内联首屏关键CSS
异步加载CSS
资源压缩
合理使用选择器
减少使用昂贵的属性
不要使用@import

14.BFC

(1)认识BFC

BFC(Block Formatting Context)格式化上下文,是Web页面中盒模型布局的CSS渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。

BFC 即 Block Formatting Contexts (块级格式化上下文),属于普通流。
可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。

(2)形成BFC条件

条件描述
float属性不为none当元素的float属性设置为左(left)或右(right),该元素会创建一个新的 BFC。
position属性值为absolute或fixed且z-index不是auto绝对定位元素(position: absolute或position: fixed)若其z-index值被显式设置(非auto),也会创建一个 BFC。
display属性值为inline-block通过将元素的display属性设置为inline-block,可以使其创建一个新的 BFC。
display属性值为table-cell表格单元格(display: table-cell)在表格中形成独立的布局环境,即创建了 BFC。
display属性值为table-caption表格标题(display: table-caption)同样在其上下文中创建一个 BFC。
display属性值为flex弹性布局容器(display: flex)会为其子元素创建一个新的 BFC。
display属性值为grid网格布局容器(display: grid)也为其子元素创建一个新的 BFC。
display属性值为flow-rootflow-root是一种 CSS3 新特性,用于直接创建一个新的 BFC,无需其他复杂样式。
overflow属性值不为visible当元素的overflow属性设置为auto、scroll或hidden时,它会创建一个新的 BFC,以便处理溢出内容和清除浮动等布局问题。

以上这些条件满足任意一条时,都会使得元素生成一个新的块级格式化上下文。

(3)BFC作用

1】避免外边距重叠
2】清除浮动
3】防止元素被浮动元素覆盖
4】防止父子元素外边距塌陷

总结
一个BFC区域只包含它的子元素,不包含其子元素的子元素。
成为一个BFC区域要满足一定的条件。
不同的BFC区域相互独立,互不影响。

15.回流和重绘

(1)认识

重绘:当元素的一部分属性发生变化,如外观背景色不会引起布局变化而需要重新渲染的过程叫做重绘(改变样式)。

回流:当render树中的一部分或者全部因为大小边距等问题发生改变而需要重建的过程叫做回流(改变大小)。

注意:回流必将引起重绘,而重绘不一定会引起回流。

(2)回流和重绘发生条件
1】回流发生条件
当页面布局和几何属性改变时就需要回流。下述情况会发生浏览器回流:

添加或者删除可见的 DOM 元素;
元素位置改变;
元素尺寸改变——边距、填充、边框、宽度和高度
内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
页面渲染初始化;
浏览器窗口尺寸改变——resize 事件发生时;

2】重绘发生条件
元素的属性或者样式发生变化。

回流的开销较大,如果每个操作都去回流重绘的话,浏览器可能就会受不了。所以很多浏览器都会优化这些操作,浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会 flush 队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

(3)如何减少回流和重绘

减少回流和重绘其实就是需要减少对 render tree 的操作(合并多次多 DOM 和样式的修改),并减少对一些样式信息的请求,尽量利用好浏览器的优化策略。

1】批量处理DOM操作
①隐藏DOM(display:none)②操作DOM③显示DOM 这种方式只会引发两次回流。
2】避免触发同步布局事件。
因为在读取一写精确数据的时候,比如offsetTop的时候,浏览器会清空DOM操作事件队列,所以需要用到精确数据的时候,可以先保存读取的数据,在操作保存的数值后,再赋值给offsetTop(除老版本浏览器外,现在的浏览器都对DOM处理进行优化,放进队列中,批量处理DOM修改,当到达一定数量或者一定时间点就会清空事件队列,进行重绘回流)。
3】对于复杂的运动效果,使它脱离文档流,避免频繁触发重绘回流
4】使用translate实现动画,不会引发重绘回流。

16.css框架

css框架有bootstrap、foundation、bulma、semantic ui和materialize。

1】Bootstrap:Bootstrap 是目前最受欢迎的CSS框架之一。它由Twitter开发,并提供了一套全面的CSS和JavaScript组件,用于构建响应式、移动优先的网页。Bootstrap具有易于使用的网格系统、按钮、导航栏、表单元素等各种预定义样式和组件。

2】Foundation:Foundation是另一个流行的CSS框架,它提供了一套灵活的工具和组件,用于构建响应式的网页。Foundation具有类似于Bootstrap的网格系统、按钮、导航栏等组件,但它更加注重自定义和可扩展性。

3】Bulma:Bulma是一个轻量级、现代化的CSS框架,它提供了一套简洁、易于使用的样式和组件。Bulma具有响应式网格系统、按钮、表单元素等常见组件,同时还提供了一些有趣的特性,如响应式的导航栏和卡片。

4】Semantic UI:Semantic UI是一个语义化的CSS框架,它强调代码的可读性和可维护性。Semantic UI提供了一套丰富的样式和组件,如网格系统、按钮、表单元素等,同时还提供了一些独特的特性,如语义化的命名和自动化的主题定制。

5】Materialize:Materialize是一个基于Google Material Design的CSS框架,它提供了一套现代化、平面化的样式和组件。Materialize具有响应式网格系统、按钮、卡片等常见组件,同时还提供了一些动画效果和交互特性。

四、Vue

1.Vue的基本原理

当一个Vue实例创建时,Vue会遍历data中的属性,用Object.defineProperty将它们转为getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而使它关联的组件得以更新。

2.vue 响应式原理

vue2.x响应式原理
(1)数据劫持

利用Object.defineProperty劫持对象的访问器,在属性发生变化时我们可以获取变化,从而进行下一步操作。

(2)发布者-订阅者模式

发布者和订阅者都不知道对方的存在,发布者只需发送消息到订阅器里,订阅者只管接收自己订阅的内容。

(3)响应式原理

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤

第一步: 需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
第二步: compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
第三步: Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个 update() 方法
3、待属性变动 dep.notice() 通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。
第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。

在这里插入图片描述

(4)存在问题

新增属性、删除属性, 界面不会更新。
直接通过下标修改数组, 界面不会自动更新。

vue3.x响应式原理
(1)实现原理

通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。

(2)vue3响应式数据的判断

isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

3.MVVM模式

MVVM:MVVM 即 Model-View-ViewModel,(模型-视图-控制器)它是一种双向数据绑定的模式, 用 viewModel 来建立起 model 数据层和 view 视图层的连接,数据改变会影响视图,视图改变会影响数据

优点:

双向绑定。数据层和视图层中任何一层发生变化,都会通过ViewModel使另一层变化,时刻保持数据和视图的一致性;
通过数据驱动视图更新,不再手动操作DOM元素,提高性能。

缺点:

由于双向绑定的技术,产生bug后无法确定问题出现在数据层还是视图层;
数据层过大会占用更多的内存资源,影响性能。

4.vue 的生命周期

定义

每个 Vue 实例在被创建时都要经过一系列的初始化过程。
例如:从开始创建、初始化数据、编译模板、挂载Dom、数据变化时更新DOM、卸载等一系列过程。
我们称这一系列的过程 就是Vue的生命周期。
通俗说就是Vue实例从创建到销毁的过程,就是生命周期。
同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会,利用各个钩子来完成我们的业务代码。

(1)Vue2.x
vue生命周期

vue 实例从创建到销毁的过程就是生命周期。也就是从开始创建、初始化数据、编译模板、挂在 dom -> 渲染、更新 -> 渲染、准备销毁、销毁等一系列过程,vue 的生命周期常见的主要分为4 大阶段 ,8 大钩子函数,另外三个生命周期函数不常用:
keep-alive 主要用于保留组件状态或避免重新渲染。
activated只有在 keep-alive 组件激活时调用。
deactivated只有在 keep-alive 组件停用时调用。
errorCapured 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false, 以阻止该错误继续向上传播。

一、创建前 / 后,在beforeCreate生命周期函数执行的时候,data 和 method 还没有初始化;在created 生命周期函数执行的时候,data 和 method 已经初始化完成。
二、渲染前/后,在beforeMount 生命周期函数执行的时候,已经编译好了模版字符串、但还没有真正渲染到页面中去;在mounted 生命周期函数执行的时候,已经渲染完,可以看到页面。
三、数据更新前/后,在beforeUpdate生命周期函数执行的时候,已经可以拿到最新的数据,但还没渲染到视图中去。在updated生命周期函数执行的时候,已经把更新后的数据渲染到视图中去了。
四、销毁前/后,在beforeDestroy 生命周期函数执行的时候,实例进入准备销毁的阶段、此时 data 、methods 、指令等还是可用状态;在destroyed生命周期函数执行的时候,实例已经完成销毁、此时 data 、methods 、指令等都不可用。

一般在哪个生命周期请求异步数据

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

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

能更快获取到服务端数据,减少页面加载时间,用户体验更好;
SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。

(2)Vue3.x

在这里插入图片描述

(3)区别

1】Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy改名为 beforeUnmount
destroyed改名为 unmounted

2】Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate===>setup()
created=======>setup()
beforeMount =>onBeforeMount
mounted
=====>onMounted
beforeUpdate===>onBeforeUpdate
updated =======>onUpdated
beforeUnmount ==>onBeforeUnmount
unmounted =====>onUnmounted

vue2vue3差异比较
beforeCreatesetupsetup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
createdsetup
beforeMountonBeforeMount组件挂载到节点上之前执行的函数
mountedonMounted组件挂载完成后执行的函数
beforeUpdateonBeforeUpdate组件更新之前执行的函数
updatedonUpdated组件更新完成之后执行的函数
beforeDestroyonBeforeUnmount卸载之前执行的函数,相比改名了
destroyedonUnmounted卸载之后执行的函数
activatedonActivated被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行
deactivatedonDeactivated比如从 A 组件,切换到 B 组件,A 组件消失时执行
errorCapturedonErrorCaptured当捕获一个来自子孙组件的异常时激活钩子函数
onRenderTrackedvue3新增的周期用于开发调试使用的
onRenderTriggeredvue3新增的周期用于开发调试使用的

vue2的beforeCreate和created 变成了setup
除了setup外大部分还是vue2的名字,只是在前面加了个on
vue2的beforDestroy和destroyed变成了beforeUnmount 和unmounted

5.常用指令

指令作用
v-on缩写为@,绑定事件
v-bind简写为:,动态绑定
v-slot简写为#,组件插槽
v-for循环对象或数组元素,同时生成DOM
v-show显示内容与否
v-if显示与隐藏,决定当前DOM元素是否渲染
v-else必须和v-if连用 不能单独使用 否则报错
v-text解析文本
v-html解析html标签

6.动态绑定class与style

classstyle
绑定对象:class=“{ className: isActive }” 或 :class=“classNameObject”:style=“{color: ‘#ffffff’}” 或 :style=“styleObject”
绑定数组:class=“[‘active’, ‘is-success’, { ‘is-disabled’: isDisabled }]”:style=“[styleObject1, styleObject2, styleObject3, …]”

7.常见修饰符

v-on

修饰符作用
.stopevent.stopPropagation(),阻止单击事件继续传播(阻止默认事件)
.preventevent.preventDefault(),提交事件不再重载页面(阻止默认行为)
.native监听组件根元素的原生事件
.once点击事件将只会触发一次

v-model

修饰符作用
.lazy取代 input 监听 change 事件
.number输入值转为数值类型
.trim输入首尾空格过滤

8.v-if 和 v-show区别

(1)原理

v-if会调用addIfCondition方法根据条件渲染,为false时在生成vnode的时候会忽略对应节点,render的时候就不会渲染
添加;
v-show指令的元素一定会渲染,只是通过修改display属性的值来决定是否显示。

(2)切换

v-if切换时,DOM元素会重复生成和销毁,会执行生命周期钩子;
v-show切换时不会执行生命周期钩子,基于 CSS 进行切换, 它操作的是display:none/block属性。

(3)应用场景

需要频繁切换DOM时,使用v-show;反之则使用v-if

9.v-for 循环为什么一定要绑定key

提升vue渲染性能

1】key 的作用主要是为了更高效的更新虚拟 DOM,因为它可以非常精确的找到相同节点,因此 patch 过程会非常高效;
2】Vue 在 patch 过程中会判断两个节点是不是相同节点时,key 是一个必要条件。比如渲染列表时,如果不写 key,Vue 在比较的时候,就可能会导致频繁更新元素,使整个 patch 过程比较低效,影响性能;
3】应该避免使用数组下标作为 key,因为 key 值不是唯一的话可能会导致上面图中表示的 bug,使 Vue 无法区分它他,还有比如在使用相同标签元素过渡切换的时候,就会导致只替换其内部属性而不会触发过渡效果;
4】Vue 判断两个节点是否相同时主要判断两者的元素类型和 key 等,如果不设置 key,就可能永远认为这两个是相同节点,只能去做更新操作,就造成大量不必要的 DOM 更新操作,明显是不可取的。

10.v-model

v-model用于实现视图层与数据层的双向绑定,数据层变化时可以驱动视图层更新,当视图层变化时会改变数据。v-model本质上是一个语法糖,默认情况下相当于:value和@input的结合。

v-model通常使用在表单项上,但也能使用在自定义组件上,表示对某个值的输入和输出控制。使用v-model可以减少大量繁琐的事件处理代码,提高开发效率。

11.computed 和 watch 的区别

computed

1】支持缓存,只有依赖的数据发生了变化,才会重新计算;
2】不支持异步,当computed中有异步操作时,无法监听数据的变化;
3】computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,即data中声明的数据或props传递的数据;
4】如果一个属性是由其他属性计算而来,这个属性依赖其它属性,一般会使用computed;
5】如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。

watch

1】不支持缓存,数据变化会执行相应操作;
2】支持异步监听;
3】监听的函数接收两个参数,第一个参数为更新后的值,第二个参数为更新前的值;
4】当一个属性发生变化时,就需要执行相应的操作;
5】监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数;
6】immediate:默认为false,为true时组件加载会立即触发;
7】deep:默认为false,为true时开启深度监听。需要注意的是,deep无法监听到数组和对象内部的变化。

总结

computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。

12.组件通信

组件通信指的是组件通过某一种方式来传递信息以达到某个目的的过程。每个组件都是独立的,在开发中我们就是通过组件通信使各个组件的功能联动起来。

(1)props/$emit

父组件通过props向子组件传递数据,子组件通过$emit事件和父组件通信。

props只能是父组件向子组件传递数据,使得父子组件直接形成一个向下的单向数据流。子组件不能直接修改props数据,只能通知父组件来修改;
$emit绑定一个自定义事件,可以将参数传递给父组件;父组件中则通过v-on注册监听事件同时接收参数。

(2)provide/inject

这种方式是通过依赖注入的方式实现组件的(可跨级)通信。依赖注入所提供的属性是非响应式的。
provide:用来发送数据或方法
inject:用来接收数据或方法

(3)ref/$refs

在父组件中通过ref可以获取子组件实例,通过实例来访问子组件的属性和方法。

(4)$parent/$children

1】$parent可以获取上一级父组件实例,$root来访问根组件的实例;
2】$children可以让组件访问所有子组件的实例,但是不能保证顺序,访问的数据也不是响应式的;
3】在根组件#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组;
4】$children 的值是数组,而$parent是个对象。

(5)$attrs/$listeners

inheritAttrs:默认值为true,继承所有的父组件属性(除props之外的所有属性),为false表示只继承class属性。

$attrs:继承所有的父组件属性(除了prop传递的属性、class 和 style);
$listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 v-on=“$listeners” 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)。

(6)eventBus事件总线

eventBus事件总线适用于父子组件、非父子组件等之间的通信。这种组件通信方式会造成后期维护困难。vue3中移除了事件总线,取而代之的是插件,使用方式并无变化。

总而言之

props/$emit 适用父子组件通信
ref与parent/children适用父子组件通信
attrs/listeners,provide/inject 适用于隔代组件通信
vuex,EventBus(事件总线) 适用于父子、隔代、兄弟组件通信

总结

子组件不能直接修改父组件传递的数据,这样做是维护父子组件之间形成的单向数据流。如果子组件随意更改父组件传递的数据,会导致数据流混乱,提高开发和维护成本。

13.keep-alive

keep-alive用于缓存组件。在进行动态组件切换的时候对组件内部数据进行缓存,而不是走销毁流程。keep-alive是一个抽象组件,它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。 当组件在<keep-alive>内被切换,它的activated和 deactivated这两个生命周期钩子函数将会被对应执行。

作用

实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染。
即保留组件状态、避免重新渲染。

场景

tabs标签页 后台导航,vue性能优化。

包含的参数:

include - 名称匹配的组件会被缓存 --> include的值为组件的name
exclude - 任何名称匹配的组件都不会被缓存
max - 决定最多可以缓存多少组件

activateddeactivated
在 keep-alive 组件激活时调用在keep-alive 组件停用时调用
该钩子函数在服务器端渲染期间不被调用该钩子在服务器端渲染期间不被调用

设置缓存后的钩子调用情况:

第一次进入:beforeRouterEnter ->created->…->activated->…->deactivated> beforeRouteLeave.
后续进入时:beforeRouterEnter ->activated->deactivated> beforeRouteLeave.

14.slot插槽

slot(插槽)是一种用于分发内容的机制,一种用于在组件中传递内容的机制。
slot 插槽,是子组件提供给父组件使用的一个占位符,父组件可以在这个占位符中填充任何模板代码。主要作用就是更好的拓展和定制化组件,例如弹窗组件、表格组件等

分类

插槽分为默认插槽、具名插槽和作用域插槽。
其中前两个都是渲染在父级,本质上就是替换,父组件渲染完毕之后替换对应的 slot;区别在于具名插槽就是给插槽取了名字;
作用域插槽:把组件内的值取出来自定义显示内容。
而作用域插槽在组件内部渲染。本质上会把父组件的内容渲染成函数,子组件调用函数,并且将数据传递给它。函数的返回值就会替换掉这个占位符。当需要将子组件的数据交给父组件展示,此时就可以使用作用域插槽。给数据一个新的作用域,因此叫做作用域插槽。

总结

v-slot属性只能在template标签上使用,但在只有默认插槽时可以在组件标签上使用;
默认插槽名为default,可以省略default直接写v-slot。缩写为#时不能不写参数,写成#default;
可以通过解构获取v-slot={user},还可以重命名v-slot=“{user: newName}“和定义默认值v-slot=”{user = ‘默认值’}”。

15.为什么data是一个函数而不是一个对象

保证每个组件内数据的独立性,防止出现变量污染。对象为引用类型,当复用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。

在Vue组件中,data选项必须是一个函数,而不能直接是一个对象。这是因为Vue组件可以同时存在多个实例,如果直接使用对象形式的data选项,那么所有的实例将会共享同一个data对象,这样就会造成数据互相干扰的问题。

因此,将data选项设置为函数可以让每个实例都拥有自己独立的data对象。当组件被创建多次时,每个实例都会调用该函数并返回一个新的data对象,从而保证了数据的隔离性。
另外,data选项作为一个函数还具有一个重要的特性,就是它可以接收一个参数,这个参数是组件实例本身。这个特性在一些场景下非常有用,例如在定义组件时需要使用组件实例的一些属性或方法来计算初始数据。
因此,为了避免数据共享和保证数据隔离性,以及方便使用组件实例的属性和方法,Vue组件中的data选项必须是一个函数。

16.Vue-Router

对前端路由的理解

前端路由的核心,就在于改变视图的同时不会向后端发出请求;而是加载路由对应的组件。Vue-Router就是将组件映射到路由, 然后渲染出来的。

17.对vuex的理解

(1)认识

Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。
Vuex 的状态存储是响应式的;当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation, 这样使得我们可以方便地跟踪每一个状态的变化。

Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。它主要用于管理应用中的共享状态,包括全局状态、组件间通信以及状态的持久化等。

(2)Vuex主要包括以下几个核心模块:

1】State:定义了应用的状态数据;
2】Getter:在 store 中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算;
3】Mutation:是唯一更改 store 中状态的方法,且必须是同步函数;
4】 Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作;
5】 Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

(3)Vuex解决了什么问题?

1】解决多个视图依赖同一状态的问题
2】解决多个组件共享状态时,单向数据流的简洁性很容易被破坏的问题
3】解决了数据很多的情况下,想要修改数据时,只需要在vuex中更改处理数据即可,不用去每个页面更改
4】使用路由传递参数时,由于参数过多会出现会导致400 等问题时,可以使用vuex解决

总结:解决了多个组件之间复杂的组件传参问题

(4)应用场景

1】全局状态管理:当多个组件需要共享状态时,可以使用 Vuex 来存储这些状态。这些状态可以包括用户登录状态、购物车中的商品、主题设置等。通过 Vuex,这些状态可以在整个应用程序中被访问和修改。
2】组件通信:Vuex 可以用于解决父子组件之间的状态传递问题。当多个组件需要共享相同的状态时,可以将这些状态放在 Vuex 的 store 中,然后在需要的组件中进行访问和修改,从而实现组件间的通信。
3】异步数据管理:在应用中需要进行异步操作,比如发起网络请求获取数据时,可以使用 Vuex 的 actions 来管理这些异步操作。通过 actions,可以更好地管理异步操作的状态和数据流,以及处理异步操作的结果。
4】持久化存储:Vuex 可以与浏览器的本地存储结合,实现对状态的持久化存储。这样可以在页面刷新或重新加载时,保持状态的持久性,提高用户体验。
5】中大型项目状态管理:对于中大型项目,由于状态管理变得复杂,Vuex 可以提供一种结构化的状态管理解决方案,使得状态变化可追踪、可预测,有利于项目的维护和开发。

18. MVVM和MVC的区别

MVC: MVC 即 model-view-controller(模型-视图-控制器)是项目的一种分层架构思想,它把复杂的业务逻辑,抽离为功能单一的小模块,每个模块看似相互独立,其实又各自有相互依赖关系。它的好处是:保证了模块的智能单一性,方便程序的开发、维护、耦合度低。

MVVM:MVVM 即 Model-View-ViewModel,(模型-视图-控制器)它是一种双向数据绑定的模式, 用 viewModel 来建立起 model 数据层和 view 视图层的连接,数据改变会影响视图,视图改变会影响数据

19.ref的作用

(1)获取DOM元素的引用

ref 加在普通的元素上,用this.ref.name 获取到的是dom元素;
vue给我们提供一个操作dom的属性,ref绑定在dom元素上时,用起来与id差不多,通过this.$refs来调用。

(2)获取子组件的引用

在Vue组件中使用ref可以获取子组件的引用,从而可以在父组件内部调用子组件的方法或访问其数据。
在父组件中将子组件引入,并在子组件标签上添加ref属性,然后就可以通过this.$refs.myChild获取子组件的引用,在父组件内部调用子组件的sayHello方法。

(3)利用 v-for 和 ref 获取一组数组或者dom 节点
注意事项:

ref 需要在dom渲染完成后应用,在使用时确保dom已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。
如果ref 是循环出来的,有多个重名,那么ref值会是一个数组 ,此时要拿到单个ref 只需要循环就可以。

Vue3.0中的ref函数

作用: 定义一个响应式的数据。

20.hash和history的区别

Vue-router的路由分为hash和history模式。

(1)hash模式

1】hash模式是指url中存在 # 的一种方式,是vueRouter的默认模式;
2】当#后面的url地址发生变化时,浏览器不会向服务器发送请求,故不会刷新页面;
3】当#后面的url地址发生变化时,会触发hashChange(hash模式得核心实现原理)事件,从而,我们可以通过监听hashChange事件来知道路由发生变化,从而进一步去更新我们的页面;
4】只可修改hash部分;
5】当浏览器刷新时,浏览器只会向服务器去请求# 前面的域名服务器下根目录下的index.html文件;
6】hash模式会创建hashHistory对象,hashHistory对象有两个方法,push() 和 replace();
7】HashHistory.push()会将新的路由添加到浏览器访问的历史的栈顶,而HasHistory.replace()会替换到当前栈顶的路由。

(2)history模式

1】history模式得路由和域名之间直接通过/连接,无#符分隔,就是普通url形式;
2】history模式当发生路由跳转时,通过HTML5的history.pushState()方法或者history.replaceState()方法改变地址栏地址,并将地址的改变记录到浏览器访问栈中。(这里有一点需要注意,它只改变了浏览器地址栏中的地址,但并不会向服务器去发送请求);
3】当浏览器前进,后退,或者调用back(),forward(), go()等方法时,会触发popstate事件。故,我们可以通过监听popstate事件来获取最新的路由地址,从而更新页面;
4】通过pushstate() 修改的url可以是与当前url同源的任意url;
5】需要和服务器配合使用,否则容易出现页面404的情况。

(3)总结

1】hash模式带#号比较丑,history模式比较优雅;
2】pushState设置的新的URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL;
3】pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中;
4】pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串;
5】pushState可额外设置title属性供后续使用;
6】hash兼容IE8以上,history兼容IE10以上;
7】history模式需要后端配合将所有访问都指向index.html,否则用户刷新页面,会导致404错误。

21.Vue2.0和Vue3.0的区别

1】Vue2和vue3的初始化就存在着⼀定区别,⽐如vue3.0可以在安装脚⼿架同时提前安装好⼀些项⽬开发必备的插件,并且3.0提供了可视化创建脚⼿架,可以更加⽅便的对插件和依赖进⾏管理和配置,同时两个版本的⽬录结构也是有些许差别的。(比如:Vue3相对于Vue2,打包工具Vite替代了webpack;TS替代了JS,pinia替代了vuex;Element-plus替代了Element等等)。
2】在开发过程中两个版本的使⽤⽅法虽然在表⾯上没有太⼤的⼀个区别,但是在他的底层⽅⾯去看的话区别还是很⼤的,其中就包括渲染⽅式,数据监听,双向绑定,⽣命周期,vue3更精准变更通知,这⾥着重说⼀下关于双向绑定的更新:
A] vue2 的双向数据绑定是利⽤ES5的⼀个 API ,Object.definePropert()对数据进⾏劫持 结合发布订阅模式的⽅式来实现的。
B] vue3 中使⽤了 ES6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀个对象都包⼀层 Proxy,通过 Proxy 监听属性的变化,从⽽实现对数据的监控。

22.Vue3带来的改变

(1)性能的提升

打包大小减少41%
初次渲染快55%, 更新渲染快133%
内存减少54%

(2)源码的升级

使用Proxy代替defineProperty实现响应式
重写虚拟DOM的实现和Tree-Shaking

(3)拥抱TypeScript

Vue3可以更好的支持TypeScript

(4)新的特性

1】Composition API(组合API)
A]setup配置
B]ref与reactive
C]watch与watchEffect
D]provide与inject
2】新的内置组件
A]Fragment
B]Teleport
C]Suspense
3】其他改变
A]新的生命周期钩子
B]data 选项应始终被声明为一个函数
C]移除keyCode支持作为 v-on 的修饰符

24.vue3的常用 Composition API

(1)拉开序幕的setup

理解:1】Vue3.0中一个新的配置项,值为一个函数。
2】setup是所有Composition API(组合API)“ 表演的舞台 ”。
3】组件中所用到的:数据、方法等等,均要配置在setup中。
4】setup函数的两种返回值:
若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重关注!)
若返回一个渲染函数:则可以自定义渲染内容。(了解)

5】setup的几个注意点:
setup执行的时机
在beforeCreate之前执行一次,this是undefined。
setup的参数
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
context:上下文对象
attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
slots: 收到的插槽内容, 相当于 this.$slots
emit: 分发自定义事件的函数, 相当于 this.$emit。
尽量不要与Vue2.x配置混用

Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
如果有重名, setup优先。
setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

五、ES

1、async await含义及作用

async await 是es7里面的新语法、它的作用就是 async 用于申明一个 function 是异步的,而 await 用 于等待一个异步方法执行完成。它可以很好的替代promise 中的 then。
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇 到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

2、es6新特性

1、新增了let、 const,let 和 const具有块级作用域,不存在变量提升的问题。
2、新增了箭头函数,简化了定义函数的写法,同时 可以巧用箭头函数的 this、(注意箭头函数本身没有 this,它的 this 取决于外部的环境)。
3、新增了promise 解决了回调地域的问题。
4、新增了模块化、利用 import 、export 来实现导入、导出。
5、新增了结构赋值, ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构 (Destructuring)。
6、新增了class 类的概念,它类似于对象。

2、数组有哪几种循环方式?分别有什么作用?

1、every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔 值。
2、filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
3、forEach() 方法对数组的每个元素执行一次提供的函数。
4、some() 方法测试是否至少有一个元素可以通过被提供的函数方法。该方法返回一个 Boolean 类型 的值。

3、字符串常用的方法

1、获取字符串长度
2、获取字符串指定位置的值
1】charAt() 方法从一个字符串中返回指定位置(索引)的字符。
2】charCodeAt()方法获取的是指定位置字符的Unicode值。
3、检索字符串是否包含特定序列
3】indexOf(),查找某个字符,有则返回第一次匹配到的位置,否则返回-1。
语法:string.indexOf(searchvalue,fromindex)
4】lastIndexOf(),查找某个字符,有则返回最后一次匹配到的位置,否则返回-1。
5】includes() 方法用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false。
6】startsWith():该方法用于检测字符串是否以指定的子字符串开始。如果是以指定的子字符串开头返回 true,否则 false。
7】endsWith():该方法用来判断当前字符串是否是以指定的子字符串结尾。如果传入的子字符串在搜索字符串的末尾则返回 true,否则将返回 false。
4、连接多个字符串
8】concat() 方法将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回。
5、字符串分割成数组
9】split() 方法用于把一个字符串分割成字符串数组。该方法不会改变原始字符串。
语法:string.split(separator,limit)
separator:必需。字符串或正则表达式,从该参数指定的地方分割 string。
limit:可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。
如果把空字符串用作 separator,那么字符串中的每个字符之间都会被分割。
其实在将字符串分割成数组时,可以同时拆分多个分割符,使用正则表达式即可实现。
6、 截取字符串
substr()、substring()和 slice() 方法都可以用来截取字符串。
10】slice() 方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串。
语法:string.slice(start,end)
start:必须。 要截取的片断的起始下标,第一个字符位置为 0。如果为负数,则从尾部开始截取。
end:可选。 要截取的片段结尾的下标。若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置。
11】 substr() 方法返回一个字符串中从指定位置开始到指定字符数的字符。
语法:string.substr(start,length)
start 必需。要抽取的子串的起始下标。必须是数值。如果是负数,那么该参数声明从字符串的尾部开始算起的位置。也就是说,-1 指字符串中最后一个字符,-2 指倒数第二个字符,以此类推。
length:可选。子串中的字符数。必须是数值。如果省略了该参数,那么返回从 stringObject 的开始位置到结尾的字串。
12】substring() 方法用于提取字符串中介于两个指定下标之间的字符。
语法:string.substring(from, to)
7】字符串大小写转换
13】toLowerCase():该方法用于把字符串转换为小写。
14】toUpperCase():该方法用于把字符串转换为大写。
8、字符串模式匹配
15】replace()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
语法:string.replace(searchvalue, newvalue)
16】match():该方法用于在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
17】search()方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
9、移除字符串收尾空白符
18】trim() 方法会从一个字符串的两端删除空白字符。
trimStart()、trimEnd()与trim()一致
10、重复一个字符串
19】repeat(n) 方法返回一个新字符串,表示将原字符串重复n次。
11、补齐字符串长度
20】padStart()和padEnd()方法用于补齐字符串的长度。如果某个字符串不够指定长度,会在头部或尾部补全。
(1)padStart()
padStart()用于头部补全。该方法有两个参数,其中第一个参数是一个数字,表示字符串补齐之后的长度;第二个参数是用来补全的字符串。​
如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
如果省略第二个参数,默认使用空格补全长度。
padStart()的常见用途是为数值补全指定位数,如将一位数字补齐为三位数字,1——>001
(2)padEnd()
padEnd()用于尾部补全。该方法也是接收两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。

4、数组去重方法

第一种,利用filter()

    const array = ['a1','b1','c1',9,'e1','a1','b1',true,false,undefined,12,9,undefined,null,null,NaN,NaN,0,{},{},0];
    function unique(arr){
        return arr.filter(function(item,index,arr){
            return arr.indexOf(item,0) === index
        })
    }

    console.log("+++++++",unique(array))
    结果:['a1', 'b1', 'c1', 9, 'e1', true, false, undefined, 12, null, 0, {}, {}]

第二种,利用ES6 Set去重

    function unique(arr){
        return Array.from(new Set(arr))
    }

    console.log("+++++++",unique(array))
    结果: ['a1', 'b1', 'c1', 9, 'e1', true, false, undefined, 12, null, NaN, 0, {}, {}]

第三种,利用for嵌套for,然后splice去重(ES5常用)

    function unique(arr){
        for(var i = 0; i < arr.length; i++){
            for(var j = i + 1; j < arr.length; j++){
                if(arr[i] == arr[j]) {
                    arr.splice(j,1);
                    j--;
                }
            }
        }
        return arr
    }

    console.log("+++++++",unique(array))
    结果:['a1', 'b1', 'c1', 9, 'e1', true, false, undefined, 12, NaN, NaN, {}, {}]

5.常用的git命令

git config
用法:git config –global user.name “[name]”
用法:git config –global user.email “[email address]”
git init
用法:git init [repository name]
该命令可用于创建一个新的代码库
git clone
用法:git clone [url]
该命令可用于通过指定的URL获取一个代码库。
git add 添加文件到仓库
git status 查看仓库当前的状态,显示有变更的文件。
git diff 比较文件的不同,即暂存区和工作区的差异。
git commit 提交暂存区到本地仓库。
git reset 回退版本。
git push
用法:git push [variable name] master
该命令可以将主分支上提交的变更发送到远程代码库。

参考资料:资料1,资料2

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值