前端面试总结

755 阅读31分钟

在杭州找了接近两个月的工作,面了10来家公司吧,就收一份offer,薪资1w我主动拒绝了,谁知道再也找不到了,今年被学历卡的死死的,现在想的是有一份工作就行了,工资七八千都行啊,把这两个月的面试题总结一下分享给大家!!!

css 新特性

  • border-radius/ˈreɪdiəs/:圆角边框

  • box-shadow/ˈʃædəʊ/:盒子阴影

  • background-size:背景图片大小

  • transition/trænˈzɪʃ(ə)n/:过渡

  • transform/trænsˈfɔːm/:转换(位移 旋转 缩放)

  • animation/ˌænɪˈmeɪʃ(ə)n/:动画

  • linear/ˈlɪniə(r)/-gradient/ˈɡreɪdiənt/:线性渐变

  • box-sizing:css3 盒子模型

BFC

BFC 的中文意思是块级格式化上下文,是用于布局块级盒子的独立渲染区域,一个创建了新的 BFC 的盒子是独立布局的,盒子内元素的布局 不会影响盒子外面的元素。简单来说就是 BFC 就是 css 的一个布局概念,是一个独立的区域(容器) 满足下列条件之一就可以触发 BFC:

  • HTML 根元素

  • position 为 absolute/ˈæbsəluːt/ 或 fixed/fɪkst/

  • float/fləʊt/ 属性不为 none(常用)

  • overflow 不为 visible/ˈvɪzəb(ə)l/(常用)

  • display 为 inline-block, table-cell, table-caption, flex(常用)

可以解决什么问题:margin 重叠,清除浮动,自适应布局

说说你对盒子模型的理解?

  • 盒子模型组成有 4 部分,分别为:内容 内边距 外边距(一般不计入盒子实际宽度) 边框

  • 盒子模型有 2 种:标准盒子模型与怪异盒子模型

  • 标准盒子模型=content(内容)+border(边框)+padding(内边距)

  • 怪异盒子模型=content(内容)(已经包含了 padding 和 border)

  • css3 种可以通过设置 box-sizing 属性来完成标准或许怪异盒子模型之间的切换,怪异盒子模型:box-sizing: border-box;标准盒子模型:box-sizing:content-box

水平垂直居中

1. 利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过translate来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题

2. 利用绝对定位,设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中

3. 使用flex布局,通过align-items:center和justify-content:center设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。该方法要考虑兼容的问题

CSS 如何画一个三角形?原理是什么?

css 画三角形的原理是利用盒子边框完成的,实现步骤可以分为以下四步: 1.设置一个盒子 2.设置四周不同颜色的边框 3.将盒子宽高设置为 0,仅保留边框 4.得到四个三角形,选择其中一个后,其他三角形(边框)设置颜色为透明

说说 em/px/rem/vh/vw 区别?

em/px/rem/vh/vw 都属于 css 的单位,这些单位可以分为相对单位、绝对单位

  • px:绝对单位,网页按照精确像素来显示.

  • em:相对单位,相对自身定义的 font-size 来计算,自身没有设置字体大小,相对父元素的字体大小

  • rem:相对单位,相对根元素 html 的字体大小来计算

  • vw/vh:相对视口大小布局,把屏幕平均划分为 100 等份.

什么是响应式设计?响应式设计的基本原理是什么?如何做?

响应式布局就是一个网站能够兼容多个设备,可以根据屏幕的大小自动调整页面的的展示方式以及布局,我们不用为每一个设备做一个特定的版本; 应式网站的特点:

  • 同时适配 PC + 平板 + 手机等

  • 网站的布局会根据视口来调整模块的大小和位置

  • 响应式设计的基本原理是通过媒体查询检测不同的设备屏幕尺寸做处理来设

  • 置差异化的 css 样式

实现响应式布局的方式有如下:

  • 媒体查询

  • 百分比

  • vw/vh

  • rem

Flex

flex布局是CSS3新增的一种布局方式。 一个容器默认有两条轴,一个是水平的主轴,一个是与主轴垂直的交叉轴。

  • flex-direction/dəˈrekʃ(ə)n/来指定主轴的方向。

  • flex-wrap/ræp/来规定当一行排列不下时的换行方式。

  • justify-content来指定元素在主轴上的排列方式

  • align-items来指定元素在交叉轴上的排列方式。 

两栏/三栏布局

利用 flex 布局,左边元素固定宽度,右边的元素设置 flex: 1 

利用flex布局,左右两栏设置固定大小,中间一栏设置为flex:1

flex属性是flex-grow,flex-shrink/ʃrɪŋk/和flex-basis/ˈbeɪsɪs/的简写,默认值为0 1 auto

Js

数组的常用方法有哪些?

遍历方法

  • map/mæp/ : 映射数组,得到一个映射之后的新数组

  • filter/ˈfɪltə(r)/:筛选数组

  • forEach:遍历数组

  • some:判断数组是否有元素满足条件(相当于逻辑或:一真则真,全假为假)

  • every/ˈevri/:判断数组是否所有满足都满足条件(相当于逻辑与:一假则假,全真为真)

  • findIndex:查找元素下标,一般用于元素是引用类型

  • reduce/rɪˈdjuːs/:给数组每一个元素执行一次回调,一般用于数组元素求和(也可以求最大值、最小值)

增删改查方法

  • push() : 末尾新增元素,返回值是新数组长度

  • unshift():开头新增元素,返回值是新数组长度

  • pop() :末尾删除元素,返回值是删除的那个末尾元素

  • shift(): 开头删除元素,返回值是开头的那个末尾元素

  • splice() : 删除指定下标元素,第三个参数是一个剩余参数,可以在删除的元素后面插入元素

其他方法

  • reverse/rɪˈvɜːs/:翻转数组,会修改数组自身

  • sort/sɔːt/: 数组排序,会修改数组自身

  • Join: 拼接数组元素,返回值是拼接之后的字符串

  • slice: 根据下标范围查询数组元素,返回值是查询后的新数组

  • indexOf: 查询元素下标,一般用于元素是值类型

reduce

  1. reduce 作用:给每一个元素执行一次回调函数,并且返回最终的结果

  2. reduce 参数有两个:第一个是回调函数,第二个是初始值。 一般初始值都会设置为 0,目的是避免空数组报错

  • 回调函数内部常用的形参有三个,依次是累加值、当前元素、当前下标

  • 回调函数内部的 return 返回值就是下一次回调的累加值

  1. reduce 方法的返回值:就是执行完遍历之后,最终的累加值

JavaScript 字符串的常用方法有哪些?

  • indexOf: 查询某个字符下标,一般用于判断字符串中是否包含某些字符

  • split: 切割字符串,返回值是切割后的新数组

  • substring: 截取字符串,返回值是截取后的字符串

  • replace/rɪˈpleɪs/: 替换字符串,返回值是替换后的新字符串

  • toLowerCase /keɪs/: 转小写

  • toUpperCase : 转大写****

数据类型和存储上的差别

在 js 中把我们的数据进行了分类,可以理解为 2 大类

基本数据类型:string,number,null,Boolean,undefined,symbol

引用数据类型: Object,Array,Function

区别:基本数据类型保存在栈里面,可以直接访问他的值,引用数据类型保存在堆里面,栈里面保存的是地址,通过栈里面的地址去访问堆里面的值

typeof 与 instanceof 区别

typeof 一般是用来判断变量是否存在,返回他的类型,其中基本数据类型 null 返回的是一个 object,但 null 不属于引用数据类型,typeof 除了判断 function 函数会识别,其他的引用类型输出为 object

instanceof 一般是用来判断引用数据类型,但不能正确判断基本数据类型,根据在原型链中查找判断当前数据的原型对象是否存在返回布尔类型

深拷贝浅拷贝

浅拷贝:拷贝基本数据类型为他的值,拷贝引用数据类型为地址,生成新的数据,修改新的数据会影响原数据,实际开发常用的方法有:object.assign/əˈsaɪn/,扩展运算符等等

深拷贝:在内存中开辟一个新的栈空间保存新的数据,修改新数据不会影响到原数据,开发中常用的方法有:loadsh 中的_.cloneDeep()方法,JSON.stringify()

原型,原型链 ? 有什么特点?

  1. 原型是我们创建函数的时候,系统帮我们自动生成的一个对象。 主要作用是解决构造函数内部方法内存资源浪费问题。在开发中我们一般把实例对象一些通用的方法放入原型中,在 vue 里面有时候也会给 vue 的原型添加一些公共类方法来实现所有的组件中可以共享成员。像一些常见的routerrouter和store 都是挂载到 vue 的原型上的。

  2. 原型链是 js 对象一种查找机制,遵循就近原则。当我们访问一个对象中的成员的时候,会优先访问自己的,如果自己没有就访问原型的,如果原型也没有就会访问原型的原型,直到原型链的终点 null. 如果还没有,此时属性就会获取 undefined,方法就会报错 xxx is not a function。一般原型链主要是用来实现面向对象继承的。

继承

子类继承父类(子构造函数继承父构造函数)里面的属性和方法,但是子类不能影响父类,开发中用的比较多的就是 组合继承 还有 class 类函数继承

组合继承:借用继承 + 原型继承

  1. 借用继承:利用call或apply将父构建函数中的this指向改成子类的实例对象

  2. 原型继承:将父类的实例对象指向子构造函数的原型(弊端:子类的构造函数会被父类覆盖,需要修改回来)

ES6的class继承

  1. 通过extends关键字实现继承,让子类继承父类的属性和方法

  2. 在子类的构造函数中,只有调用super()之后,才可以使用this关键字,否则会报错

  • 第一种情况,super作为函数调用时,代表父类的构造函数

  • 第二种情况,super作为对象时,在普通方法中,指向父类的原型对象

bind、call、apply 区别?

他们的共同点是都可以修改函数 this 指向

他们两个区别:

  1. 第一个是传参方式不同: call 和 bind 是列表传参,apply 是数组或伪数组传参

  2. 第二个是执行机制不同:call 和 apply 是立即执行,bind 不会立即执行而是生成一个修改 this 之后的新函数

this 对象

this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象

根据不同的使用场合,this 对象有不同的指向,主要分为下面几种情况

1. 全局环境中定义的函数,函数内部的 this 指向 window 对象

2. 函数还可以作为某个对象的方法调用,这时 this 就指这个上级对象

3. 通过构建函数 new 关键字生成一个实例对象,此时 this 指向这个实例对象

4. apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时 this 指的就是这第一个参数

5. 箭头函数没有自己的 this 值,箭头函数中所使用的 this 都是来自函数作用域链,它的取值遵循普通普通变量一样的规则,在函数作用域链中一层一层往上找

箭头函数

箭头函数是定义函数一种新的方式,他比传统函数 function 定义更加方便和简单,他没有绑定自己的 this 指向和伪数组 arguments,无法调用 super 方法生成实例化对象,因为他不是构造函数,一般用来取代匿名函数的写法,最主要的是箭头函数的 this 指向他的上一级作用域中的 this 也可以理解为他的 this 是固定的,而普通函数的 this 是可变的****

new 操作符

(1)声明一个空的实例对象(2)将 this 指向这个对象(3)对象赋值(4)返回实例对象

闭包

闭包是一个可以访问其他函数内部变量的函数,主要作用是解决变量污染问题,也可以用来延长局部变量的生命周期。闭包在 js 中使用比较多,几乎是无处不在的。一般大多数情况下,在回调函数中闭包用的是最多的

内存泄漏

l 内存泄漏一般是指变量的内存没有及时的回收,导致内存资源浪费。一般有三种情况出现内存泄露比较多。(1)常见的声明了一个全局变量,但是又没有用上,那么就有点浪费内存了,(2)定时器没清除 (3)循环引用:A 对象里面有一个属性指向 B 对象,B 对象有一个属性指向 A 对象。互相引用

l 解决内存泄露:我们编译器有一个自动的内存清理。常见的主要是引用记数 和 标记清除。 谷歌浏览器主要是用标记清除,大概流程是给每一个变量添加一个标记,通过内部算法计算引用情况,当不使用的时候就会自动清除。如果遇到定时器的话,我一般会在页面关闭的时候手动清除。如果遇到循环引用,我一般会手动把变量赋值为 null 来清除

防抖和节流

防抖和节流是性能优化手段

防抖:单位时间内,频繁触发事件,只执行最后一次。 防抖的主要应用场景:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求

  • 手机号、邮箱验证输入检测

节流:单位时间内,频繁触发事件,只执行一次。 节流的主要应用场景:

  • 高频事件 例如 resize /ˌriːˈsaɪz/事件、scroll /skrəʊl/事件

  • 手机号、邮箱验证输入检测

Promise

Promise 是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息

promise 的三种状态改变

1. pending 变为 resolved,执行then方法

2. pending 变为 rejected,执行catch方法

3. 只有这两种 且一个 promise 对象只能改变一次无论成功还是失败,都会有个结果

  • Promise.resolve返回一个成功/失败的 promise 对象

  • Promise.reject 返回一个失败的 promise 对象

  • Promise.all返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接失败

  • Promise.race返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态****

async/await

async/await其实是Generator/ˈdʒenəreɪtə(r)/ 的语法糖,函数前面加一个async,函数里面异步操作的方法前加一个await关键字,优势:

  • 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担

  • Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅

  • 错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余

  • 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。

类型转换

显示转换:

1. 转为字符串

2. 转为数字

  • undefined -> 'NaN'

  • null -> 0

  • Boolean: true -> 1、false -> 0

  • String: 等于调用 Number(str) , 空字符串 -> 0、 '含非数字' -> 'NaN' 、'数字' -> 数字

  • Symbol 不能转为数字,报错

  • 对象:valueOf()

3. 转为布尔

  • 假值: • undefined • null • false • +0、-0 和 NaN • ""

隐式转换:四则运算转换和判断语句转换

事件循环

  1. 首先执行同步代码,这属于宏任务

  2. 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行

  3. 执行所有微任务

  4. 当执行完所有微任务后,如有必要会渲染页面

  5. 然后开始下一轮 Event Loop,执行宏任务中的异步代码

DOM操作

创建节点:createElement

删除节点:removeChild

更新节点:innerHTML

获取节点:querySelector

BOM对象

window

Location/ləʊˈkeɪʃn/:url地址

Navigator/ˈnævɪɡeɪtə(r)/:获取浏览器的属性,区分浏览器类型

Screen/skriːn/:客户端显示器的信息

History:浏览器URL的历史记录

Ajax

Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面

四部曲:

1. 创建ajax实例对象

2. 利用xhr对象下面的open方法,打开接口

3. 利用xhr对象下面的send方法,发送请求

4. 接受服务器响应onreadystatechange/onload

数字精度丢失

计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和,两个数的二进制都是无限循环的数,0.1和0.2的二进制数相加,再转化为十进制数   

解决方案:

将小数先转换成拆分两个字符串,然后计算小数部分的字符串的长度,然后利用这个长度将小数变成整数!使用第三方库,如Math.js、BigDecimal.js****

本地存储

  • cookie

  • sessionStorage

  • localStorage

  • indexedDB****

关于cookie、sessionStorage、localStorage三者的区别主要如下:

  1. 存储大小:cookie数据大小不能超过4k,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

  2. 有效时间:localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据; sessionStorage数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

  3. 数据与服务器之间的交互方式,cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端; sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存

场景的使用选择:

  • 标记用户与跟踪用户行为的情况,推荐使用cookie

  • 适合长期保存在本地的数据(令牌),推荐使用localStorage

  • 敏感账号一次性登录,推荐使用sessionStorage

  • 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用indexedDB

HTTP 和 HTTPS

http 是无状态的超文本传输协议,连接简单,信息是明文传输,端口为 80。

https 协议是由 http+ss 协议构建的可进行加密传输、身份认证的具有安全性网络协议,端口是 443

优缺点:https 有加密认证相对于 http 安全一些

https 因为需要进行加密解密等过程,因此速度会更慢

HTTP1.0/1.1/2.0 的区别

HTTP1.0:

  • 浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接

HTTP1.1:

  • 引入了持久连接,即 TCP 连接默认不关闭,可以被多个请求复用

  • 在同一个 TCP 连接里面,客户端可以同时发送多个请求

  • 虽然允许复用 TCP 连接,但是同一个 TCP 连接里面,所有的数据通信是按次序进行的,服务器只有处理完一个请求,才会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等着

  • 新增了一些请求方法

  • 新增了一些请求头和响应头

HTTP2.0:

  • 采用二进制格式而非文本格式

  • 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行

  • 使用报头压缩,降低开销

  • 服务器推送

地址栏输入 URL 敲下回车后发生了什么

1. 对www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址

2. 根据这个 IP,找到对应的服务器,发起 TCP 的三次握手

3. 建立 TCP 连接后, 发起 HTTP 请求

4. 服务器响应 HTTP 请求,浏览器得到 html 代码

5. 浏览器解析 html 代码,并请求 html 代码中的资源(如 js、css、图片等)(先得到 html 代码,才能去找这些资源)

6. 服务器响应对应的资源

7. 响应数据完毕, 四次挥手,关闭 TCP 连接

8. 浏览器对页面进行渲染呈现给用户

HTTP 常见的状态码

2XX成功  3XX重定向   4XX 客户端错误  5XX 服务器错误

301:永久重定向会缓存。新域名替换旧域名,旧的域名不再使用时,用户访问旧域名时用 301 就重定向到新的域名

302:临时重定向不会缓存,常用 于未登陆的用户访问用户中心重定向到登录页面

304:协商缓存,告诉客户端有缓存,直接使用缓存中的数据,返回页面的只有头部信息,是没有内容部分

400:参数有误,请求无法被服务器识别

403:告诉客户端进制访问该站点或者资源,如在外网环境下,然后访问只有内网 IP 才能访问的时候则返回

404:服务器找不到资源时,或者服务器拒绝请求又不想说明理由时

503:服务器停机维护时,主动用 503 响应请求或 nginx 设置限速,超过限速,会返回 503

504:网关超时

GET 和 POST 的区别

  1. 应用场景: GET 请求是一个幂等的请求,一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。而 Post 不是一个幂等的请求,一般用于对服务器资源会产生影响的情景,比如注册用户这一类的操作。

  2. 是否缓存: 因为两者应用场景不同,浏览器一般会对 Get 请求缓存,但很少对 Post 请求缓存。

  3. 发送的报文格式: Get 请求的报文中实体部分为空,Post 请求的报文中实体部分一般为向服务器发送的数据。

  4. 安全性: Get 请求可以将请求的参数放入 url 中向服务器发送,这样的做法相对于 Post 请求来说是不太安全的,因为请求的 url 会被保留在历史记录中。

  5. 请求长度: 浏览器由于对 url 长度的限制,所以会影响 get 请求发送数据时的长度。这个限制是浏览器规定的,并不是 RFC 规定的。

  6. 参数类型: post 的参数传递支持更多的数据类型。

TCP三次握手****

1. - 浏览器 请求 服务器建立联系

2. - 服务器同意链接,服务器请求 浏览器 建立连接

3. - 浏览器同意链接****

TCP四次挥手

1. - 浏览器 请求 服务器 断开连接

2. - 服务器 同意断开连接(没有真正断开)

3. - 服务器 需要监听 数据有没有传送完成,如果传送完成,向浏览器发出断开请求

4. - 浏览器同意断开

跨域

跨域,是指浏览器不能执行其它网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 javascript 实施的安全限制。

同源策略是浏览器最核心也最基本的安全功能,具有以下特点:

  • 协议相同

  • 主机相同

  • 端口相同

只要其中一项不相同,就会产生跨域

常见的解决方案有 3 种:

JSONP: 利用 script 标签,不受跨域限制的特点,缺点是只能支持 get 请求

CORS: 后端设置响应头 Access-Control-Allow-Origin:*

服务器代理:在 DevServer 中配置 proxy

WebSocket

WebSocket 是 HTML5 下一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的

Websocket 和 HTTP 的区别:

  • 都是基于 tcp 的,都是可靠性传输协议

  • 都是应用层协议

不同点:

  • WebSocket 是双向通信协议,模拟 Socket 协议,可以双向发送或接受信息

  • HTTP 是单向的

  • WebSocket 是需要浏览器和服务器握手进行建立连接的

  • 而 http 是浏览器发起向服务器的连接,服务器预先并不知道这个连接

WebSocket 主要应用场景有以下几类:

  • 即时通讯(QQ、微信、客服)

  • 弹幕

  • 协同编辑

  • 体育实况更新

  • 股票基金报价实时更新

  • 可视化大屏数据实时更新

常见的攻击方式:

1.SQL攻击(一种操作数据库的语言):通过对web连接的数据库发送恶意的SQL语句

  • 防御:md5加密(不可逆的加密算法)

2. XSS攻击:跨站脚本攻击,盗取存储cookie

  • 防御:字符转译
  • CSP:本是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行

3. CSRF攻击:跨站请求伪造攻击,攻击者诱导用户进入第三方网站,然后该网站向被攻击网站发送跨站请求。本质是利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充

  • 防御:进行同源检测
  • 使用CSRF Token进行验证
  • 对Cookie进行双重验证

Vue响应式原理

vue2 响应式原理

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

vue3 响应式原理

Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等).相对于 Object.defineProperty() 其有以下特点:Proxy 直接代理整个对象而非对象属性,这样只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。

Proxy 可以监听数组的变化。

虚拟 DOM

Virtual DOM 其实就是一棵以 JavaScript 对象(VNode 节点)作为基础的树,用对象属性来描述节点,相当于在 js 和真实 dom 中间加来一个缓存,利用 dom diff 算法避免没有必要的 dom 操作,从而提高性能。当然算法有时并不是最优解,因为它需要兼容很多实际中可能发生的情况,比如后续会讲到两个节点的 dom 树移动。

在 vue 中一般都是通过修改元素的 state,订阅者根据 state 的变化进行编译渲染,底层的实现可以简单理解为三个步骤:

1、用 JavaScript 对象结构表述 dom 树的结构,然后用这个树构建一个真正的 dom 树,插到浏览器的页面中。

2、当状态改变了,也就是我们的 state 做出修改,vue 便会重新构造一棵树的对象树,然后用这个新构建出来的树和旧树进行对比(只进行同层对比),记录两棵树之间的差异。

3、把记录的差异再重新应用到所构建的真正的 dom 树,视图就更新了。

它的表达方式就是把每一个标签都转为一个对象,这个对象可以有三个属性:tag、props、children

  • tag:必选。就是标签。也可以是组件,或者函数

  • props:非必选。就是这个标签上的属性和方法

  • children/ˈtʃɪldrən/:非必选。就是这个标签的内容或者子节点,如果是文本节点就是字符串,如果有子节点就是数组。换句话说 如果判断 children 是字符串的话,就表示一定是文本节点,这个节点肯定没有子元素

diff 算法

Diff 算法是一种对比算法。对比两者是 旧虚拟 DOM 和新虚拟 DOM,对比出是哪个 虚拟节点更改了,找出这个 虚拟节点并只更新这个虚拟节点所对应的 真实节点而不用更新其他数据没发生改变的节点,实现 精准地更新真实 DOM,进而 提高效率

他的对比过程如下:

在新老虚拟 DOM 对比时:

  1. 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换

  2. 如果为相同节点,进行 patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)

  3. 比较如果都有子节点,则进行 updateChildren,判断如何对这些新老节点的子节点进行操作(diff 核心)。

  4. 匹配时,找到相同的子节点,递归比较子节点

在 diff 中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从 O(n3)降低值 O(n),也就是说,只有当新旧 children 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。

vue 中的 key

概念:页面上的标签都对应具体的虚拟 dom 对象(虚拟 dom 就是 js 对象), 循环中 ,如果没有唯一 key , 页面上删除一条标签, 由于并不知道删除的是那一条! 所以要把全部虚拟 dom 重新渲染, 如果知道 key 为对应标签被删除掉, 只需要把渲染的 dom 为对应标签去掉即可!

作用:主要是为了高效的更新虚拟 DOM

$nextTick

页面的 DOM 还未渲染,这时候也没办法操作 DOM,如果想要操作 DOM,需要使用 nextTick 来解决这个问题

实现原理:nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout 的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。

使用场景:

1、在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的 DOM 结构的时候,这个操作就需要方法在的回调函数中。 2、在 vue 生命周期中,如果在 created()钩子进行 DOM 操作,也一定要放在的回调函数中

$set

数据变化视图不更新问题, 当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为 Object.defineProperty()限制,监听不到变化。

解决方式:this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么 value)

$set 的实现原理

  • 如果目标是数组,直接使用数组的 splice 方法触发相应式;

  • 如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)

补充延伸:vue 源码里缓存了 array 的原型链,然后重写了这几个方法,触发这几个方法的时候会 observer 数据,意思是使用这些方法不用再进行额外的操作,视图自动进行更新。 推荐使用 splice 方法会比较好自定义,因为 splice 可以在数组的任何位置进行删除/添加操作, 总共提供了 7 个方法都可实现响应式: splice()、 push()、pop()、shift()、unshift()、sort()、reverse()

v-show 和 v-if

  • v-if 是通过添加或者移除一个元素来实现显示隐藏(没有频繁切换显示的时候使用)

  • v-show 是通过 display: none; 实现显示隐藏(需要频繁切换显示隐藏的时候使用)

  • 不能在 template标签 上使用 v-show,因为 template 不会被渲染出来

计算属性和 watcher

watch 侦听某一数据的变化从而会触发函数,当数据为对象类型时,对象中的属性值变化时需要使用深度侦听 deep 属性,也可在页面第一次加载时使用立即侦听 immdiate 属性 computed 计算属性是触发函数内部任一依赖项的变化都会重新执行该函数,计算属性有缓存,多次重复使用计算属性时会从缓存中获取返回值,计算属性必须要有 return 关键词

生命周期的理解

vue 生命周期分为个四个阶段

1. 初始化阶段: beforeCreate 与 created

2. 挂载阶段: beforeMount 与 mounted

3. 更新阶段: beforeUpdate 与 updated

4. 销毁阶段: beforeDestroy 与 destroyed/dɪˈstrɔɪd/

平时在开发中一般在 created 函数中发送 ajax 请求获取数据,在 mounted 中获取挂载完毕后的真实 DOM,destroy 中销毁定时器,延时器或绑定的一些事件

v-if 和 v-for

v-if 不能和 v-for 一起使用的原因是 v-for 的优先级比 v-if 高,先循环再做分支判断,一起使用会造成性能浪费 解决方案有两种:

1. 把 v-if 放在 v-for 的外层

2. 把需要 v-for 的值先在计算属性中过滤一次

  • v2 中:v-for优先级比v-if高

  • v3 中:v-if优先级比v-for高

组件之间的通信方式

1. 父向子通信: 在子组件的标签上通过自定义属性传递父组件的数据,子组件的内部通过 props 接收父向子传递的数据

2. 子向父通信: 在子组件的标签上自定义事件,自定义事件的值是父组件的方法,在子组件内部通过 this.$emit()方法触发事件,第一个参数为自定义事件,第二个参数可以传递子组件的内部数据,此时父组件中的方法就可以执行了,

3. 兄弟组件通信: 可以采取 eventbus 实现数据传递,但是这种方式我们在开发中基本不用,多组件共享数据都是用的 vuex

4. 后代组件通信: 可以采取依赖注入的方式,在祖先组件中通过 provide 提供数据,在后代组件中通过 inject 接收数据

5. 无关联关系组件通信: 在开发中我们都是使用 vuex

Mixin

混入是用来分发 Vue 组件中的可复用功能,混入对象可以包含任意组件选项。

当组件使用混入对象时,所有混入对象的选项将被混合进入该组件本身的选项。

当组件和混入对象含有同名选项时,以组件数据优先

使用场景:在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立

slot / slɒt /

slot 是插槽,一般在组件内部使用

在封装组件时,在组件内部不确定该位置是以何种形式的元素展示时,,我们可以通过 slot 占据这个位置,该位置的元素需要父组件以内容形式传递过来.

slot 又分为:默认插槽,具名插槽,作用域插槽

使用场景:比如布局组件、表格列、下拉选、弹框显示内容等

SPA 单页面

SPA 称为单页应用程序,也就是整个网站只有一个 html 页面

优点: 相对于传统混合式开发,减少了请求体积,加快页面响应速度,降低了对服务器的压力

缺点: 因为技术使用了 ajax,导致页面不利于 SEO

SEO优化:

1. 利用prerender-spa-plugin插件将单页面应用打包成多页面

2. Vue SSR (服务器端渲染)(放在浏览器执行创建的组件,放到服务端先创建好,然后生成对应的html将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序)

route 和router 的区别

router为VueRouter的实例,是一个全局路由对象,包含了路由跳转的方法、钩子函数等。

route 是路由信息对象||跳转的路由对象,每一个路由都会有一个route对象,是一个局部对象,包含path,params,hash,query,fullPath,matched,name等路由信息参数。

 

Router的传参方式

1.params 2.query 3.hash 4.history

Params只能使用name,不能使用path,参数不会显示在路径上,浏览器强制刷新参数会被清空

Query:参数会显示在路径上,刷新不会被清空,name 可以使用path路径

Hash:原理是onhashchage事件,可以在window对象上监听这个事件

History:利用了HTML5 History Interface 中新增的pushState()和replaceState()方法。需要后台配置支持。如果刷新时,服务器没有响应的资源,会刷出404

vuex

vuex 是为了解决整个网站状态数据共享问题的,虽然有父向子,子向父等数据传递,但在网站开发过程中一些无直接关联关系的组件也需要共享相同的数据时,就需要使用 vuex 了

vuex 中有五个主要的成员:

  • state 是用来存储数据的

  • mutations 是用来修改 state 中的数据的方法

  • actions 是用来处理一些异步操作数据的行为方法

  • getters 有点类似计算属性,是对 state 中的数据做了一些处理

  • modules 是用来对复杂业务分模块的,每个模块也可以有 state,mutaions,actions,getters

数据持久化:vuex 是保存在内存中的,刷新之后就会丢失。可以通过 vuex-presisit 插件来做持久化

vue.use

【是什么】:Vue.use 是用来注册 Vue 插件的一个函数。

【怎么用】:可以传递是一个对象,必须提供 install 方法。也可以传递一个函数,它会被作为 install 方法,install 方法调用时,会将 Vue 作为参数传入。

【注意点】:该方法需要在 new Vue() 之前被调用;当 install 方法被同一个插件多次调用,插件将只会被安装一次。

【场景】:通常用来为 Vue 添加全局功能,例如添加全局方法或 property、添加全局指令、注入组件选项、添加实例方法等

导航守卫

1. 全局前置/钩子:beforeEach、beforeResolve、afterEach

2. 路由独享的守卫:beforeEnter

3. 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

自定义指令

【作用是什么】:可以对普通 DOM 进行底层操作,例如聚焦输入框。

【怎么用】:可以通过 Vue.directive /dəˈrektɪv/全局注册一个指令,或者组件内部通过 directives 进行局部注册,它常用的钩子有 inserted,表示被绑定元素插入父节点时调用,还有 update,它表示指令所在组件的 VNode 更新时调用。

【场景】:我在实际项目中,用自定义指令处理过图片懒加载,原理就是当图片进入可视区的时候把图片的地址给图片的 src 属性就好啦。

【注意】:自定义指令相关的钩子在 Vue3 中发生了变化,主要体现在和组件的生命周期钩子保持了一致,更加容易记忆和理解了

Axios封装

1. 在Utils中的request.js里面,引入axios

2. 创建axios实例,配置请求的baseURL和请求超时的时间

3. 分别配置请求拦截器和响应拦截器,两个拦截器分别接受两个参数,即成功的拦截和失败的拦截

4. 在请求拦截器中,首先要给每个请求的请求头携带token,传给后端让后端进行身份验证;我们要在拦截器里拦截重复请求,一个常用的方法是利用axios的cancelToken

5. 响应拦截器中如果响应成功返回,那就可以拿到响应状态码与返回的数据,根据状态码要做一个判断token是否过期的处理。如果token过期了,就弹窗显示是否重新登录,重新登录则跳转回登陆页面

6. 创建api文件夹,将不同的请求方法按业务进行分模块管理

Proxy 替代 defineProperty

最核心的原因是性能,展开来说如下。

  • 因为 Proxy 代理的直接是整个对象,例如对象中有 100 个属性,用 defineProperty 劫持至少需要循环 100 次,而 proxy 至少一次搞定。

  • defineProperty 对数组中存在大量元素的劫持操作性能不好,所以 Vue2 并没有直接使用 defineProperty 对数组进行劫持,而是提供了额外的方法来处理数组的响应式,例如 set,其实换成 proxy 就不存在这个问题了,当然 $set 方法也就没有必要存在了。

  • Vue2 中,劫持数据的操作在实例创建完成就已经进行完毕了,所以对对象后续新增的属性是劫持不到的,也就意味着后续新增的属性是不具备响应式能力的,所以 Vue 不得不提供了set方法。而换成 proxy 就不存在这个问题了,因为它劫持的整个对象,后续添加的属性也是属于这个对象的,那么 set 也就没有必要了(干掉 $set 方法本身也是性能的一个体现)。

keep-alive

【是什么】:它是 Vue 内置的一个组件,可以用来做组件缓存以提升性能。

【怎么用】:一般用这个内置组件可以包裹住动态组件(component)或路由出口的地方,它提供的也有 include/ɪnˈkluːd/ 属性可以控制缓存哪些组件名,exclude 属性控制不缓存哪些组件,它对应的也有两个钩子,分别是激活时触发的 activated 和失活时触发的 deactivated

常用的修饰符

  • .stop 相当于 event.stopPropagation(),用来阻止冒泡。

  • .prevent/prɪˈvent/ 相当于 envet.preventDefault(),用来阻止默认事件。

  • .native 监听组件根元素的原生事件。

  • .once 只触发一次。

vue3 vue2 的区别

1. 性能更高了,主要得益于响应式的原理换成了 proxy,VNode diff 的算法进行了优化。

2. 体积更小了,删除了一些没必要或不常用到的 API,例如 filter、EventBus 等;按需导入,能配合 Webpack 支持 Tree Shaking。

3. 对 TS 支持更好啦,因为它本身源码就是用 TS 重写的。

4. Composition API(组合 API),相比较 Vue2 的 options api,对于开发大型项目更利于代码的复用和维护。

5. 新特性,例如 Fragment/ˈfræɡmənt/、Teleport、Suspense 等。

组合式api:

ref: 让基本类型数据具备响应式,也可以让引用类型具备响应式

reactive: / riˈæktɪv /  让引用类型的数据具备响应式

小程序 优化

1. 提高页面加载速度

l 控制包的大小。措施:压缩代码,清理无用的代码 使用外部图片

² 代码包的体积压缩可以通过勾选开发者工具中“上传代码时,压缩代码”选项

² 及时清理无用的代码和资源文件

² 减少资源包中的图片等资源的数量和大小(理论上除了小icon,其他图片资源从网络下载),图片资源压缩率有限

l 采用分包策略 。分包预加载 独立分包

² 采取分包加载的操作,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载

² 采取分包加载的操作,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载

2. 提升渲染性能

l 减少使用setData。合并setData的调用,减少通讯的次数,减少每次通讯的数据量

l 上拉加载更多页 考虑使用防抖节流****

小程序的兼容性问题:

小程序ios端的日期为NaN-NaN...

原因:一般日期为"2020-08-08 08:08:08"格式,ios日期只支持"2020/08/08 08:08:08"

解决办法:运用正则转为需要的ios兼容的格式

中文字符传参问题

​ ios端不会自动编码和解码中文字符串,最好自己传参前先用encodeURIComponent转码一下

视频地址中文或特殊字符时ios下闪退问题

​ 当视频的地址中有中文或特殊字符时,ios下播放会闪退

​ 解决办法:更改视频地址使其不包含以上字符,或转换成短链

小程序的生命周期

  • onLoad——页面加载,调一次,发送请求获取数据

  • onShow——页面显示,每次打开页面都调用

  • onReady——初次渲染完成,调一次

  • onHide/haɪd/——页面隐藏,当navigateTo或底部tab切换时调用,终止任务,如定时器或者播放音乐

  • onUnload——页面卸载,当redirectTo或navigateBack时调用****

小程序中路由跳转的方式

  • navigateTo/ˈnævɪɡeɪt/ 保留当前页面,跳转到应用内的某个页面,使用 wx.navigateBack 可以返回到原页

  • redirectTo/ˌriːdəˈrekt/ 关闭当前页面,跳转到应用内的某个页面

  • switchTab 跳转到 tabBar 页面,同时关闭其他非 tabBar 页面

  • navigateBack 返回上一页面

  • reLanch 关闭所有页面,打开到应用内的某个页面

小程序的登录流程

1. 在微信开放平台注册小程序并获取 appid

2. 在 uni-app 项目中使用 uni.login() 方法获取 code

3. 通过 code 换取 openid 和 session_key

4. 使用 uni.request() ,将 openid 和 session_key 作为参数传递给后台服务器,由后台服务器完成登录操作

5. 在 uni-app 项目中使用 uni.getUserInfo() 方法获取用户信息

6. 将用户信息发送给后台服务器

7. 后台服务器进行用户信息的存储和验证

8. 通过 uni.setStorageSync() 或 uni.setStorage() 将用户信息存储到本地,以便下次登录时使用。

9. 在需要验证用户是否已登录时,可以使用 uni.getStorageSync() 或 uni.getStorage() 检查本地存储中是否有用户信息

10. 如果用户没有登录,可以使用 uni.navigateTo() 或 uni.redirectTo() 跳转到登录页面

调用wx.checkSession检查微信登陆态是否过期

微信授权

1. 先获取用户信息——用户授权允许后,通过调用uni.login 可以获取到code。

2. 拿着获取到的code去调用——登录接口,可以获取到token。

3. 把token存入缓存。就可以在页面判断是否登录了。

小程序的发布流程

  1. 上传代码

  2. 提交审核

  3. 发布版本

通过打包后在微信开发者工具上面运行,然后提交代码到微信公众平台,可以设置为体验版测试,测试通过后再提交审核,审核通过后即可直接全局发布

小程序的支付流程

1. 请求创建订单的 API 接口:把(订单金额、收货地址、订单中包含的商品信息)发送到服务器。服务器响应的结果:订单编号。

2. 请求订单预支付的 API 接口:把(订单编号)发送到服务器。服务器响应的结果:订单预支付对象,里面包含了订单支付相关的必要参数。

3. 调用 uni.requestPayment()/ˈpeɪmənt/ 这个 API,并传递订单预支付对象,发起微信支付。监听 uni.requestPayment() 这个 API 的 success,fail,complete 回调函数

Git 常用的命令

  • git add 提交文件到缓存区

  • git commit 提交代码到本地仓库

  • git pull 拉取远程仓库的分支与本地当前分支合并

  • git fetch 拉取远程仓库的分支

  • git push 上传本地指定分支到远程仓库

  • git status /ˈsteɪtəs/查看当前分支状态

  • git diff 显示工作区中未添加至暂存区的内容与上一次 commit 的差异

  • git checkout 新建一个分支或切换分支或撤销工作区文件更改

  • git merge /mɜːdʒ/合并指定分支到当前分支

fork, clone,branch 区别

  • fork /fɔːk/不是一个 git 操作,而是一个类似 github 等在线代码托管平台的提出来的操作,fork 操作会复制一份目标仓库到你自己的 GitHub 帐号下。fork 后这个代码库是完全独立的,自己可以在库中做任何修改。如果想自己的修改合并到原项目中,可通过 pull request 提交。

  • clone 译为克隆,它的作用是将文件从远程代码仓下载到本地,从而形成一个本地代码仓

  • branch /brɑːntʃ/译为分支,其作用简单而言就是开启另一个分支, 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线

pull 和 ****fetch 的区别

相同点:

  • 在作用上他们的功能是大致相同的,都是起到了更新代码的作用

不同点:

  • git pull 是相当于从远程仓库获取最新版本,然后再与本地分支 merge,即 git pull = git fetch + git merge

  • 相比起来,git fetch 更安全也更符合实际要求,在 merge 前,我们可以查看更新情况,根据实际情况再决定是否合并

git reset 和 git revert 的 区别

  • git revert 是用一次新的 commit 来回滚之前的 commit,git reset 是直接删除指定的 commit

  • git reset 是把 HEAD 向后移动了一下,而 git revert 是 HEAD 继续前进,只是新的 commit 的内容和要 revert 的内容正好相反,能够抵消要被 revert 的内容

  • 在回滚这一操作上看,效果差不多。但是在日后继续 merge 以前的老版本时有区别

  • 如果回退分支的代码以后还需要的情况则使用git revert, 如果分支是提错了没用的并且不想让别人发现这些错误代码,则使用git reset

git rebase 和 git merge 的 区别

  • git rebase 和 git merge 都是将一个分支的提交合并到另一分支上

  • 通过 merge 合并分支会新增一个 merge commit 然后将两个分支的历史联系起来

  • rebase 会将整个分支移动到另一个分支上,主要的好处是历史记录更加清晰,不好的是会丢失一些分支从何时创建及合并进来的一些信息

Stash

当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态, 而这时你想要切换到另一个分支或者拉下远端的代码去做一点别的事情,这时你可以用 git stash

typescript 的数据类型

  • 因为 ts 最大特点就是类型检查,所以会有很多种数据类型供我们使用

  • ts 和 js 共有的类型有: boolean number string array object (symbol,undefined,null 不常用)

  • TS 独有的数据类型 any(任意类型), unknown, void/vɔɪd/,never, tuple/ˈtʌpəl/(元组类型), enum /ˈenəm/(枚举类型)

Void类型用于标识方法返回值的类型,表示该方法没有返回值

never类型一般用来指定那些总是会抛出异常、无限循环

元祖类型,允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

函数

  • 函数是 JavaScript 应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块, TypeScript 为 JavaScript 函数添加了额外的功能,让我们可以更容易地使用

  • TS 中的函数大部分和 JS 相同,不同之处在于 ts 会在函数的(参数)这中间和后面加上类型声明

ts中函数声明和实现分离的写法

a. 利用 type 声明一个函数 type AddFun = (a:number, b:number)=>number;

b. 利用 interface 声明函数, interface AddFun { (a:number, b:number):number }

接口

  • TypeScript 的核心原则之一是对值所具有的结构进行类型检查,并且只要两个对象的结构一致,属性和方法的类型一致,则它们的类型就是一致的。 在 TypeScript 里,接口的作用就是为这些类型命名和为代码或第三方代码定义契约。

  • 在定义接口时,不要把它理解为是在定义一个对象,{}括号包裹的是一个代码块,里面是声明语句,只不过声明的不是变量的值而是类型。声明也不用等号赋值,而是冒号指定类型。每条声明之前用换行分隔即可,也可以使用分号或者逗号。

  • 接口属性,分为可选属性 ?修饰,只读属性 readonly 修饰,多余属性(索引签名)

  • 接口继承, 一个接口可以继承自另一个或者多个接口,关键字 extends

webpack

WebPack 是一个模块打包工具,可以使用 WebPack 管理模块。在 webpack 看来,项目里所有资源皆模块,分析模块间的依赖关系,最终编绎输出模块为 HTML、JavaScript、CSS 以及各种静态文件(图片、字体等),让开发过程更加高效。

对于不同类型的资源,webpack 有对应的模块加载器 loader,比如说,

CSS

  • 解析 CSS 的 css-loader、style-loader,

  • 解析 less 的 less-loader,sass 的 sass-loader

JS

  • 解析将 TypeScript 转换成 JavaScript 的 ts-loader,

  • 解析 ES6 为 ES5 的 babel-loader,

  • 解析 JavaScript 代码规范的 eslint-loader

Vue

  • 解析.vue 文件的 vue-loader、

  • 静态资源:音视频、文件、json

  • 解析常用图片以及音视频资源的 url-loader、

  • 解析文件的 file-loader,

  • 解析 JSON 文件的 json-loader

Loader 和 Plugin 的区别

1. loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中

2. plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事

3. loader 运行在打包文件之前

4. plugins 在整个编译周期都起作用****

借助 webpack 来优化前端性能

  • JS 代码压缩

  • CSS 代码压缩

  • Html 文件代码压缩

  • 文件大小压缩

  • 图片压缩

  • Tree Shaking

  • 代码分离

  • 内联 chunk

数组去重

  1. 方式一:利用 Set() + Array.from()
const arr = [1, 2, 3, 5, 3, 2, 5];

const res = Array.from(new Set(arr));

console.log(res);

注意:以上去方式对NaN和undefined类型去重也是有效的,是因为NaN和undefined都可以被存储在Set中, NaN之间被视为相同的值

  1. 方式二:利用两层循环 + 数组的splice方法,此方法对NaN是无法进行去重的,因为 进行比较时NaN !== NaN
/ 通过两层循环对数组元素进行逐一比较,然后通过splice方法来删除重复的元素。

function removeDuplicate(arr) {

let len = arr.length;

for (let i = 0; i < len; i + ) {

for (let j = i + 1; j < len; j + ) {

if (arr[i] = arr[j]) {

arr.splice(j, 1);

len - ; / 减少循环次数提高性能

j - ; / 保证j的值自加后不变

}

}

}

return arr;

}
  1. 方式三:利用数组的 indexOf 方法,此方法也无法对NaN去重

  2. 方式四:利用数组的 includes 方法,可以对 NaN 去重

  3. 方式五:利用数组的 filter() + indexOf() ,此方法也无法对NaN去重

  4. 方式六:利用 Map(),可以对 NaN 去重

注意:使用Map()也可对NaN去重,原因是Map进行判断时认为NaN是与NaN相等的,剩下所有其它的值是根据 === 运算符的结果判断是否相等。

  1. 方式七:利用对象,实现方式和 Map() 差不多

如何判断后台返回的数据是一个空对象

方式一:将对象转换成字符串,再判断是否等于“{}”

方式二:for in循环

方式三:Object.keys()方法,返回对象的属性名组成的一个数组,若长度为0,

排序方法

1. sort()排序****

arr.sort(function (a, b) {

  return a - b//正序}) //[1, 2, 3, 5, 7, 8, 9, 11, 32, 44, 66, 78, 100]

arr.sort(function (a, b) {

  return b - a//倒序})

2. 冒泡排序

1、依次比较相邻的两个元素,如果前一个比后一个大,则交换位置;

2、第一轮的时候最后一个元素是最大的一个(以此类推,第二轮的时候倒数第二个元素是第二大的);

3、按照步骤一的方法进行相邻两个元素的比较,交换位置之后,由于最后一个元素已经是最大的了,所以最后一个元素不用参与下一轮比较(以此类推,第二轮结束后,后面的两个元素就不用参与下一轮的比较了)。

function bubbleSort(arr) {

  for (let i = 0; i < arr.length1; i++) {//代表第几轮比较

    for (let j = 0; j < arr.length1 - i; j++) {//每一轮的两两相邻元素比较

      if (arr[j] > arr[j + 1]) {//相邻元素比较

        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]//满足条件,交换位置

      }

    }

  }

  return arr}

3. 快速排序

function quickSort(arr) {

  if (arr.length <= 1) {

    return arr

  }

  let pivotIndex = Math.floor(arr.length2)

  let pivot = arr.splice(pivotIndex, 1)[0]

  let left = []

  let right = []

  for (let i = 0; i < arr.length; i++) {

    if (arr[i] < pivot) {

      left.push(arr[i])

    } else {

      right.push(arr[i])

    }

  }

  return quickSort(left).concat([pivot], quickSort(right))

  //return [...quickSort(left), pivot, ...quickSort(right)]//或者这么写,更直观一点}

4. 选择排序

1、在数组中找到最小元素,放在起始位置;
2、从剩下的元素中,再找到第二小的元素,放在第二位;
3、以此类推,可以得到排好序的数组。

function selectSort(arr) {

  for(let i = 0; i < arr.length1; i++) {

    let index = i

    for(let j = i + 1; j < arr.length; j++) {

      if(arr[j] < arr[index]) {

        index = j

      }

    }

    [arr[i], arr[index]] = [arr[index], arr[i]]

  }

  return arr}

将数字每千分位用逗号隔开

1、对数字进行取模

2、将数字转换成字符串,进行切片

3、使用正则表达式

4、使用Number自带的toLocaleString()方法

方法一:

function func1 (num) {

  if (num >= 1000) {

        let num1 = parseInt(num / 1000);

        let num2 = parseInt(num % 1000);

        num2 += '';

        num2.length3 && (num2 = '0'.repeat(3 - num2.length) + num2)

        const floatPart = !Number.isInteger(num) && '.' + num.toString().split('.')[1] || ''

        return func1(num1) + ',' + num2 + floatPart;

    } else {

        return num;

    }}

方法二:

function func2(num) {

  if (num > 1000) {

    [num, floatNum] = (num || 0).toString().split('.');

    let result = '';

    let str = ',';

    while (num.length3) {

        result = str + num.slice(-3) + result;

        num = num.slice(0, num.length3);

    }

    if (num) { result = num + result; }

    return result + (floatNum && `.${floatNum}` || '');

  }

  return num}

这个方法将数字转成字符串,然后把字符串当成数组来处理,每次从字符串末尾取三个字符,加逗号,直到拿不出三个字符来,输出结果。

方法三:

function func3 (num) {

  // return num.toString().replace(/(?=(\B)(\d{3})+$)/g, ",");

  if (num < 1000return num;

  if (Number.isInteger(num)) {

    return String(num).replace(/(?!^)(?=(\d{3})+$)/g',')

  } else {

    return String(num).replace(/(?!^)(?=(\d{3})+\.)/g',')

  }}

方法四

function func4(num) {

  return num.toLocaleString();}

toLocaleString在将数字转换为字符串的同时,会使用三位分节法进行显示。如果是浮点数,只保留小数点后三位数,并进行了四舍五入

深拷贝

function deepClone(obj) { 
// 首先判断 obj 是否为对象或数组
 if (obj === null || typeof obj !== 'object') { 
  return obj; 
 } 
 // 根据 obj 的类型创建一个新的对象或数组
 let copy = Array.isArray(obj) ? [] : {}; 
 for (let key in obj) { 
 // 判断 obj 的某个属性是否为自身属性,而不是原型链上的属性
  if (Object.prototype.hasOwnProperty.call(obj, key)) { 
  // 如果 obj 的某个属性是对象或数组,递归调用 deepClone 函数进行深拷贝
   copy[key] = deepClone(obj[key]); 
  } 
 } 
 // 返回新的对象或数组
 return copy; 
}

防抖

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

节流

function throttle(fn,delay){ // 记录上一次函数出发的时间 var lastTime = 0 return function(){ // 记录当前函数触发的时间 var nowTime = new Date().getTime() // 当当前时间减去上一次执行时间大于这个指定间隔时间才让他触发这个函数 if(nowTime - lastTime > delay){ // 绑定this指向 fn.call(this) //同步时间 lastTime = nowTime } } }

继承

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
  }
  
  sayName() {
    console.log(this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
  
  sayAge() {
    console.log(this.age);
  }
}

闭包

function createCounter() {
  let count = 0;
  return function() {
    count++;
    console.log(count);
  };
}