当前位置:   article > 正文

2024前端面试(都是我一点一滴记录下来的)

2024前端面试(都是我一点一滴记录下来的)

CSS篇

1.清除浮动

为什么:清除浮动主要是为了解决,父元素因为子级元素浮动引起的内部高度为0的问题
方法

  1. clear:both:本质就是闭合浮动, 就是让父盒子闭合出口和入口,不让子盒子出来
    缺点:添加无意义标签,语义化差
  2. 父级添加overflow:hidden属性
    缺点:内容增多的时候容易造成不会自动换行导致内容被隐藏掉,无法显示要溢出的元素
  3. .使用after伪元素清除浮动
    缺点:ie6-7不支持伪元素:after,使用zoom:1触发hasLayout.
  4. .使用before和after双伪元素清除浮动
    缺点:用zoom:1触发hasLayout.
  5. 父级div定义 height
    缺点: 只适合高度固定的布局,要给出精确的高度,如果高度和父级div不一样时,会产生问题

2.display:none和visibility:hidden两者的区别

display: none: 隐藏后不占据额外空间,它会产生回流和重绘
visibility: hidden; 元素虽然隐藏了,但是仍然占据着空间,只会引起页面重绘
opacity: 0; 元素虽然隐藏了,但是仍然占据着空间,只会引起页面重绘

3.repaint(重绘)和 reflow(回流)

repaint(重绘):屏幕的一部分重画,不影响整体布局,比如某个 CSS 的背景色变了,但元素的几何尺寸和位置不变。
reflow(回流或者叫重排): 意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是渲染树的一部分或全部发生了变化。这就是 Reflow,或是 Layout。
如何减少重绘与回流:

  • 避免使用table表格布局方法
  • 使用visibility替换display:none,因为前者只会引起重绘,后者会引发回流(改变了布局)
  • 避免设置多层内联样式
  • 减少触发它们操作的次数
  • 修改样式的时候使用类名的方式一次性进行修改,而不是使用 style 的方式
  • 对一些涉及到复杂动画的元素,在不影响相关布局的情况下,尽量将其 position 属性设置为 absolute 或者 fixed

浏览器的渲染过程

  1. 解析HTML,构建DOM树
  2. 1.解析CSS,生成CSS规则树
  3. 合并DOM树和CSS规则,生成render
  4. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
  5. 绘制render树(paint),绘制页面像素信息
  6. 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上

4.盒子模型

标准模式下,一个块的宽度 = width+padding(内边距)+border(边框)+margin(外边距);
IE模式下,一个块的宽度 = width+margin(外边距) (即怪异模式下,width包含了border以及padding)。
box-sizing:content-box; 将采用标准模式的盒子模型标准
box-sizing:border-box; 将采用怪异模式的盒子模型标准

5.页面布局有几种?

1.瀑布流式布局:
优点:节省空间,外表美观,更有艺术性。
对于触屏设备非常友好,通过向上滑动浏览
用户浏览时的观赏和思维不容易被打断,留存更容易。

缺点:用户无法了解内容总长度,对内容没有宏观掌控。
用户无法了解现在所处的具体位置,不知道离终点还有多远。
回溯时不容易定位到之前看到的内容。
容易造成页面加载的负荷。
容易造成用户浏览的疲劳,没有短暂的休息时间。

2.浮动布局
优点:比较简单,兼容性好;
缺点:浮动会使元素脱离文档流,容易出现高度塌陷等问题,需做好清理浮动。

3.flexbox弹性布局
优点:简便、完整、响应式地实现各种页面布局
缺点:仅支持 IE9 以上浏览器。

4.定位布局
优点:简单直接;
缺点:绝对定位同float布局一样会脱离文档流,高度塌陷问题。

5.table布局
优点:兼容性最佳
缺点:对 seo 不友好,且列高不能自适应,所有列高会保持一致。(且很少有人使用table布局了)

6.flex:0 flex:1 flex:none flex:auto应该在什么场景下使用?

区别:
flex:0: 元素既不会扩展也不会收缩,由于元素不具有弹性也就不会换行,最终表现为最小内容宽度
flex:1 不管内容多少,一般都是平分空间,空间大小都一致
flex:auto 是根据内容的大小来分,不是平的(除非内容都是一样,才平分)
flex:none: 表示项目不会伸缩,保持原始大小。

flex-grow 属性定义项目的放大比例 默认为0 即如果存在剩余空间 也不放大
flex-shrink 属性定义了项目的缩小比例 默认为1 即如果空间不足 该项目将缩小
flex-basis 属性定义了在分配多余空间之前 项目占据的主轴空间(相当于我们设置的width)

flex:1 = flex:1 1 0%; 有剩余空间就放大,空间不够就缩小,项目长度为0
在这里插入图片描述

flex:auto = flex:1 1 auto;表示项目会根据自身大小和剩余空间进行伸缩
在这里插入图片描述
总结:
flex:1 适用等分布局
flex:auto 适用基于内容动态适配的布局

6. 高度塌陷,BFC原理

塌陷原因:子元素浮动之后 脱离文档流 不会把父元素撑开 造成父元素的高度塌陷

BFC就是块级格式上下文,是一个独立渲染区域,他不会影响到外部元素

BFC作用:

  1. 清除浮动
  2. 防止同一BFC容器中的相邻元素间的外边距重叠问题
  3. 阻止正常元素被浮动元素覆盖

BFC特性:

  • 同一个BFC下margin会重叠
  • 计算BFC高度时会算上浮动元素
  • BFC不会影响到外部元素
  • BFC内部元素是垂直排列的
  • BFC区域不会与float元素重叠

JS篇

1.JS中堆和栈的区别

栈(stack): 由操作系统自动分配内存空间,自动释放,存储的是基础变量以及一些对象的引用变量,占据固定大小的空间。
堆(heap): 由操作系统动态分配的内存,大小不定也不会自动释放,一般由程序员分配释放,也可由垃圾回收机制回收。

栈内存的特点:存取速度快,但不灵活,同时由于结构简单,在变量使用完成后就可以将其释放,内存回收容易实现。
堆内存的特点:使用灵活,可以动态增加或删除空间,但是存取比较慢
基本类型是存在栈内存中的,引用类型是存在堆内存中的,但是引用类型的引用还是存在栈内存中的。

2.什么是作用域,什么是作用域链?

1.规定变量和函数的可使用范围称作作用域
2.每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。
作用域链:
当查找变量的时候,会先从当前作用域的变量对象中查找,如果没有找到,就会从父级作用域(上层环境)的变量对象中查找,一直找到全局作用域的变量对象,也就是全局对象。这样由多个作用域的变量对象构成的链表就叫做作用域链。

3.箭头函数跟普通函数的区别

1.call()、apply()、bind()等方法不能改变箭头函数中this的指向
2.箭头函数不能作为构造函数使用
3.箭头函数没有自己的arguments,用rest参数来解决
4.箭头函数没有prototype原型
5.箭头函数的this指向的是父级作用域的this,指定义它的对象,而不是使用时所在的对象

4.JS继承

1.原型链继承
核心:创建父类实例对象作为子类原型
优点:可以访问父类原型上的方法或属性,实现了方法复用
缺点:1.新实例无法向父类构造函数传参,2.继承单一 3.所有的新实例都会共享父类实例的属性(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

2.构造函数继承
核心:在子构造函数中调用父构造函数
优点:1.只继承了父类构造函数的属性,没有继承父类原型的属性
2. 可以继承多个构造函数属性(call多个)
3. 在子实例中可向父实例传参
缺点:1.只能继承父类构造函数的属性
2.无法实现构造函数的复用(每次用都要重新调用)
3.每个新实例都有父类构造函数的脚本,臃肿

3.构造函数+原型链 组合继承
特点:结合了两种模式的有点,传参和复用
优点:1.可以继承父类原型上的属性,可以传参,可复用
2.每个新实例引入的构造函数是私有的
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数

4.原型式继承
特点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象
优点:类似于复制一个对象,用函数来包装
缺点:1.新实例无法向父类构造函数传参,2.继承单一 3.所有的新实例都会共享父类实例的属性(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

5.class类实现继承
通过extends和super实现继承

6.寄生组合式继承
特点:就是给原型式继承外面套了个壳子。
优点:没有创建自定义类型,因为只是套了个壳子返回对象,这个函数就成了新对象
缺点:没用到原型,没法复用

5. 什么是事件循环?

  1. JS是单线程,防止代码阻塞,我们把代码(任务)分为:同步和异步
  2. 同步代码给JS引擎(JS主线程)执行,异步代码交给宿主环境(浏览器/Node)
  3. 同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队
  4. 执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行,这个过程是事件循环

同步代码: JS执行栈/回调栈
异步代码:setTimeout、setInterval 、Ajax/Fetch、事件绑定,promise的then跟catch是异步的,耗时的就是异步代码
JS把异步任务分为宏任务和微任务
在这里插入图片描述
在这里插入图片描述
判断执行顺序大概以下几个重点:
1、promise中的回调函数立刻执行,then中的回调函数会推入微任务队列中,等待调用栈所有任务执行完才执行
2、async函数里遇到await之前的代码是同步里的,遇到await时,会执行await后面的函数,然后返回一个promise,把await下面的代码放入微任务,并且退出这个async函数。
3、调用栈执行完成后,会不断的轮询微任务队列,即使先将宏任务推入队列,也会先执行微任务

6. 防抖跟节流

1.防抖:单位时间内,频繁触发事件,只执行最后一次
典型场景:搜索框搜索输入
代码思路是利用定时器,每次触发先清掉以前的定时器(从新开始)

2.节流:节流不管事件多么的频繁,都会保证在规定时间段内触发事件函数
典型场景:高频时间快速点击、鼠标滑动、scroll事件
代码思路是利用定时器,等定时器执行完毕,才开启定时器(不要打断)

函数防抖(debounce):触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。

函数节流(throttle):高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。

 function debounce(fn,delay) {
       let timeout = null;// 创建一个标记用来存放定时器的返回值
       return function () {
          // 每当用户输入的时候把前一个 setTimeout clear 掉
         clearTimeout(timeout);
         // 然后又创建一个新的 setTimeout, 这样就能保证interval 间隔内如果时间持续触发,就不会执行 fn 函数
         timeout = setTimeout (() => {
           fn.apply(this, arguments);
         },delay)
       }
     }
     // 处理函数
function handle() {
    console.log('防抖:', Math.random());
}
        
//滚动事件
window.addEventListener('scroll', debounce(handle,500));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
//节流throttle代码:
function throttle(fn,delay) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
         // 在函数开头判断标记是否为true,不为true则return
        if (!canRun) return;
         // 立即设置为false
        canRun = false;
        // 将外部传入的函数的执行放在setTimeout中
        setTimeout(() => { 
        // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
        // 当定时器没有执行的时候标记永远是false,在开头被return掉
            fn.apply(this, arguments);
            canRun = true;
        }, delay);
    };
}
 
function sayHi(e) {
    console.log('节流:', e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi,500));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

7.原型跟原型链

原型:每个函数都有prototype属性 称之为原型, 因为这个属性的值是个对象,也称为原型对象
作用:1.存放一些属性和方法(挂载在原型) 2.在JS中实现继承
举例:比如声明了一个数组,就可以使用数组api方法,因为这些方法都是挂载到prototype身上的,这些方法是可以共享给所有数组实例去使用的,

我们的实例为什么能使用这些原型身上的方法呢?
_proto_:每个对象都有_proto_属性
作用:指向它的原型对象,有_proto_属性所以能调用原型方法

原型链:对象都有_proto_属性,该属性指向它的原型对象,原型对象也是对象,也有_proto_属性,指向原型对象的原型对象,这样一层 一层形成的链式结构称为原型链,最顶层找不到则返回Null, 。

8.闭包

作用:1.访问函数内部的变量 2.让变量始终保持在内存中
优点:1.可以减少全局变量的定义,避免全局变量的污染
2.能够读取函数内部的变量
3.在内存中维护一个变量,可以用做缓存
缺点:1)造成内存泄露

闭包会使函数中的变量一直保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
解决方法——使用完变量后,手动将它赋值为null;
2)闭包可能在父函数外部,改变父函数内部变量的值。
3)造成性能损失
由于闭包涉及跨作用域的访问,所以会导致性能损失。
在这里插入图片描述

9.call,apply,bind的区别

三者区别

  • 三者都可以改变函数的this对象指向
  • 三者第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null,则默认指向全局window,三者都可以传参,
  • call是参数列表,apply是数组,callapply是一次性传入参数,
  • bind是参数列表,但可以分为多次传入参数
  • bind是返回绑定this之后的函数,applycall 则是立即执行

应用场景

  • call 经常做继承。
  • apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值。
  • bind 不调用函数,但是还想改变this指向,比如改变定时器内部的this指向。

10.虚拟DOM

虚拟dom是用js模拟一颗dom树,放在浏览器内存中,相当于在js和真实dom中加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
优点:
(1)虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能;
(2)虚拟DOM不会立马进行排版与重绘操作,对虚拟DOM进行频繁修改,最后一次性比较并修改真实DOM中需要改的部分;
(3)虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部;
缺点:
(1)首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢;

11.undefined跟null的区别

null表示"没有对象",即该处不应该有值。典型用法是:
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。

undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2 ) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时或者return后面什么也没有,返回undefined。

12.forEach、for…in 、for…of三者的区别

for-in适合遍历对象属性,for-of适合遍历数组
for-in循环出的是key值,for-of循环出的是value
for-in可以遍历可枚举的属性,for-of遍历的是可迭代的
for-of不能直接遍历普通的对象,需要通过Object.keys()搭配使用
for-of 无法遍历 不可迭代对象
可迭代对象包括: Array,Map,Set,String,TypedArray,arguments等等
for-of 遍历的是值,for-in遍历的是key

迭代器就是一个拥有next()方法的对象,每次调用next()方法都会返回一个结果,该结果有两个属性,一个是value表示当前获取的值,第二个是done表示遍历是否结束
在这里插入图片描述

13.promise

promise的一些问题:
a. 一旦执行,无法中途取消,链式调用多个then中间不能随便跳出来
b. 错误无法在外部被捕捉到,只能在内部进行预判处理,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
c. Promise内部如何执行,监测起来很难,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

14.async/await的优缺点

相比如promise,aysnc/await有什么优势
1.相比于promise,它能更好的处理then链
2.错误处理友好,用try/catch做错误处理
3.Promise传递参数麻烦,很多中间值,aysnc/await没有多余的

缺点:
a. 无法处理promise返回的reject对象,要借助try…catch…
b. 用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性

15.JSBridge(hybrid 即“混合”)

1.H5调用Native:原生将WebViewJavascriptBridge绑定在window上,H5直接调用这个对象中的原生的接收方法
2.Native调用H5:H5将JSBridge锁定在window上,Native通过原生方法调用这个对象上的H5接收方法

16.require与import的区别

1.两者的加载方式不同,require是在运行时加载,而import是在编译时加载
2.规范不同,require是CommonJS/AMD规范,import是ESMAScript6+规范
3.require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;
4.require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变;
5.require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码;
6.require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所所以性能稍高。

17.less与sass的区别

1.变量符不一样,Less是@,而Sass是$。
2.Sass支持条件语句,可以使用if{}else{},for{}循环等等。而Less不支持。
3.Less是基于JS,是在客户端处理的。 Sass是基于Ruby的,是在服务器端处理的。

18.虚拟DOM

虚拟dom是用js模拟一颗dom树,放在浏览器内存中,相当于在js和真实dom中加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
优点:
(1)虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能;
(2)虚拟DOM不会立马进行排版与重绘操作,对虚拟DOM进行频繁修改,最后一次性比较并修改真实DOM中需要改的部分;
(3)虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部;
缺点:
(1)首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢;

19.描述new一个对象的过程?

1.创建一个新对象,新对象的隐式原型__proto__指向new的构造函数的显示原型proptotype
2.改变this指向,将构造函数的作用域赋给新的对象,并且执行构造函数的代码,为新的对象添加属性
3.返回新的对象(return this)

第二种解释:
创建一个空对象,
并且this变量引用该对象同时还继承了该函数的原型prototype
属性和方法被加入到this引用的对象中
最后return返回this

20.深浅拷贝

浅拷贝:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深拷贝:在计算机中开辟一块新的内存地址用于存放复制的对象。
区别:深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

JSON.parse的缺点:

  1. 无法处理某些特殊的 JavaScript 对象,如 RegExp、Error、Date 等,这些对象会被转换成空对象。
  2. 无法处理函数和 undefined 值,这些值会被忽略掉

最好的深拷贝就是递归函数:
思路: 将每一数据都进行判断,判断的结果分为数组、对象、其他,对数组和对象进行再一次判断。为了方便也就有了递归函数,通过反复回调进行判断,直到既不会数组又不是对象的时候才会拷贝,最后通过return将拷贝成功的对象返回。

        function deepCopy(copyObj){
 
            // 创建一个变量,先不确定变量类型,值为undefined
            var obj 
 
            //判断copyObj是否是一个数组类型
            if(Array.isArray(copyObj)){
 
                //如果符合条件则给 obj一个空数组
                obj = []
 
                //通过数组的遍历进行拷贝
                //由于数组的元素可以是任意数据类型,所以需要对内部在进行判断其数据类型
                //这样也就有了obj[i] = deepCopy(copyObj[i]),通过此语句回调函数,完成判断
                //直到对象遍历完,return obj直接退出deepCopy()函数
                for(var i = 0 ; i < copyObj.length ; i++){
 
                    //通过回调函数完成向下的拷贝,一层一层的判断,一层一层拷贝
                    obj[i] = deepCopy(copyObj[i])
                }
 
                //循环完毕,也就是拷贝完毕,obj就是拷贝完成的数据,返回obj
                return obj
 
            //array和对象类型的typeof()值都是object,由于上面if已经判断了array
            //这里是用来判断copyObj是否是对象
            }else if(typeof copyObj === 'object'){
 
                //给obj一个空对象
                obj = {}
 
                //通过对象的遍历进行拷贝
                //由于对象的属性可以是任意数据类型,所以需要对内部在进行判断其数据类型
                //这样也就有了obj[key] = deepCopy(copyObj[key]),通过此语句回调函数,完成判断
                //直到对象遍历完,return obj直接退出deepCopy()函数
                for(var key in copyObj){
 
                    //通过回调函数完成向下的拷贝,一层一层的判断,一层一层拷贝
                    obj[key] = deepCopy(copyObj[key])
                }
 
                //循环完毕,也就是拷贝完毕,obj就是拷贝完成的数据,返回obj
                return obj
            }else{
 
                //没有上面两种类型的数据,就可以返回到调用它的地方
                //如:obj[i] = deepCopy(copyObj[i])                       
                //obj[key] = deepCopy(copyObj[key])
                //当返回这个数据的时候说明这个属性到底了
                return copyObj
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

21. 数组去重方法

1.has方法可以判断Map对象中是否存在指定元素,有则返回true,否则返回false
set方法可以向Map对象添加新元素 map.set(key, value)
values方法可以返回Map对象值的遍历器对象

let arrObj = [
    { name: "小红", id: 1 },
    { name: "小橙", id: 1 },
    { name: "小黄", id: 4 },
    { name: "小绿", id: 3 },
    { name: "小青", id: 1 },
    { name: "小蓝", id: 4 }
];
let map = new Map();
for (let item of arrObj) {
    if (!map.has(item.id)) {
        map.set(item.id, item);
    };
};
arr = [...map.values()];
console.log(arr);   // 同上

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2.reduce方法,新创建一个空对象,然后利用reduce函数的特性,先看一下这个对象里是否有这个id,如果有则跳过,没有将当前值加入preval,并且赋给这个新创建的对象,最终返回的preval一定是去重后的数组对象。

let arr = [
	{id: 1, name: '周瑜'},
	{id: 3, name: '王昭君'},
	{id: 2, name: '亚瑟'},
	{id: 1, name: '小乔'},
	{id: 2, name: '大桥'},
	{id: 3, name: '韩信'}
];
let obj = {}
arr = arr.reduce((preVal,curVal) => {
	obj[curVal.id] ? "" : obj[curVal.id] = preVal.push(curVal)
		return preVal
	},[])
console.log(arr)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

22. this指向

  1. 在函数体中,非显式或隐式地简单调用函数时,严格模式下函数体内this指向undefined,非严格模式指向全局对象window
  2. 构造函数内this指向新对象
  3. 通过call/apply/bind显式调用函数时,函数体内this指向指定的参数对象
  4. 通过上下文对象调用函数时,函数体内this指向该对象
  5. 箭头函数中的 this

23. 普通函数与构造函数区别

1、构造函数也是普通函数,创建方式和普通函数一样,但构造函数习惯上首字母大写。
2、构造函数和普通函数的区别在于:调用方式不一样。作用也不一样(过偶早函数用来新建实例对象)。
3、调用方式不一样。
​ a. 普通函数的调用方式:直接调用 person();
​ b.构造函数的调用方式:需要使用new关键字来调用 new Person()**;
4、构造函数的名称与类名相同:Person()这个构造函数,Person既是函数名,也是这个对象的类名。
5、构造函数的内部用this来构造属性和方法。

24.ts和js的区别

1、TS中引入了模块的概念,可以把声明、数据、函数和类封装在模块中。
2、js没有重载概念,ts有可以重载
3、ts增加了接口interface、泛型、类、类的多态、继承等
4、ts对比js基础类型上,增加了 void/never/any/元组/枚举/以及一些高级类型

TS优点:
1、类型化思维方式,使开发更严谨,能帮助开发人员检测出错误并修改,提前发现错误,减少改Bug时间
2、类型系统提高了代码可读性,便于开发人员做注释,维护和重构代码更加容易
3、补充了接口、枚举等开发大型应用时JS缺失的功能
【JS的类型系统存在"先天缺陷",绝大部分错误都是类型错误】
4、TypeScript工具使重构更变的容易、快捷。
5、类型安全功能能在编码期间检测错误,这为开发人员创建了一个更高效的编码和调试过程。

在这里插入图片描述

25.const定义的数组跟对象能更改吗

用const声明数组和对象时,const声明的常量保存的仅仅是目标的指针,这就意味着只要保证数组和对象的指针不发生改变,修改其中的值是被允许的。但是不允许直接给对象赋值

26.ES6之Set,Map的区别

Set 和 Map 主要的应用场景在于 数据重组 和 数据储存。
Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。

集合与字典的区别:
共同点:集合、字典 可以储存不重复的值
不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存

Set: ES6 新增的一种新的数据结构,类似于数组,但成员是唯一且无序的,没有重复的值。
Set 本身是一种构造函数,用来生成 Set 数据结构。
Set 对象允许你储存任何类型的唯一值,无论是原始值或者是对象引用

Map: 本质上是键值对的集合,类似集合。
可以遍历,方法很多可以跟各种数据格式转换。

27. key的作用

详细地说:当状态中的数据发生变化时,vue进行新虚拟DOM与旧虚拟DOM的diff比较,比较规则如下:

  • 旧虚拟DOM中找到了与新虚拟DOM相同的key:
  • 若虚拟DOM中内容没变,直接使用之前的真实DOM
  • 若虚拟DOM中内容改变,则生成新的真实DOM,随后替换页面中之前的真实DOM
  • 旧虚拟DOM中未找到与新虚拟DOM相同的key
  • 根据数据创建新的真实DOM,随后进行渲染到页面

用index作为key可能会引发的问题

  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
    会产生没有必要的真实DOM更新==>界面效果没问题,但是效率低。
  2. 如果结构中还包含输入类的DOM:
    会产生错误DOM更新==>界面有问题。

注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表展示,使用index作为key是没有问题的。

28.axios原理

Axios是一个基于PromiseHTTP客户端库,用于浏览器Node.js中发送异步HTTP请求。它提供了一种简洁且易于使用的方式来处理HTTP请求和响应。

  1. 基于XMLHttpRequest或者浏览器的fetch API发送请求:Axios在底层使用XMLHttpRequest对象或者浏览器的fetch API来发送HTTP请求。它通过创建一个XHR对象或者调用fetch方法发送请求,并监听相关事件以获取响应。
  2. 封装请求和响应:Axios封装了请求和响应的细节,使得开发人员可以更简单地处理HTTP请求。它提供了一系列的方法,如axios.get()、axios.post()等,来发送不同类型的请求,并且可以通过配置选项设置请求头、请求参数等。
  3. Promise和异步处理:Axios使用Promise作为异步处理的基础。它返回一个Promise对象,开发人员可以使用.then()和.catch()方法来处理请求的成功和失败。Axios还支持使用async/await来更方便地处理异步操作。
  4. 拦截器:Axios提供了拦截器,可以在发送请求或响应之前拦截和处理数据。它允许开发人员在请求或响应被发送到服务器或浏览器之前,对其进行一些预处理,比如在请求中添加token,在响应中对数据进行处理等。
  5. 错误处理:Axios可以捕获HTTP请求返回的错误状态码,并根据错误类型执行相应的操作。它还提供了一个全局的错误处理方法.catch(),可以用来捕获和处理所有的请求错误。
  6. 取消请求:Axios支持取消请求,可以通过创建一个cancel token来取消正在进行的请求。这在某些场景下非常有用,比如当用户离开当前页面时取消未完成的请求。
    axios原理地址

Vue篇

1.Vue生命周期

Vue 实例从创建到销毁的过程为生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,称之为 Vue 的生命周期。

简述每个周期具体适合哪些场景
答:beforeCreate:在new一个vue实例后,此时的数据观察事件机制都未形成,不能获得DOM节点。data,computed,methods等还没有初始化。可以在这加个loading事件。
create:data computed watch methods都已经被初始化好了,如果要调用 以上的方法和数据,可以在这里结束loading事件

beforeMount:执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
mounted:vue实例挂载完成,可在mounted钩子函数中对挂载的DOM进行操作。可在这发起后端请求,拿回数据

beforeUpdate: 新的虚拟DOM生成,但还没跟旧虚拟DOM对比打补丁
updated:数据更新后,新旧虚拟DOM对比打补丁后,进行真实DOM的更新,组件DOM已完成更新,可执行依赖的DOM操作。注意:不要在此函数中操作数据(修改属性),会陷入死循环。

beforeDestory:Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁
destroyed: 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。

在这里插入图片描述

2.watch的属性用箭头函数定义结果会怎么样?

因为箭头函数默绑定父级作用域的上下文,所以不会绑定vue实例,所以 this 是undefind

3.vue组件通信

1、父子组件通信
props/$emit $refs/$parent/$children provide/inject $attrs/$listeners

// Parent.vue
export default {
    provide() {
        return {
            name: 'Stone'
        }
    }
}
// Child.vue
export default {
   inject: ['name'],
   mounted() {
       console.log(this.name)
   }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

当父组件传递props给子组件时,子组件没有声明对应的prop,则子组件可以通过$attrs/$listeners获取父组件传递过来的特性绑定( class 和 style 除外)。组件也可以通过v-bind, v-on将所有属性和事件传入内部组件。

2、同级组件之间通信
事件总线eventsBus $emit / $on, vuex
3、跨组件通信
vuex$attrs/$listeners

4.sync语法糖
简单解释:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定。
在这里插入图片描述

4.Vue 中 computed 和 watch 有什么区别?

计算属性 computed:
(1)支持缓存,只有依赖数据发生变化时,才会重新进行计算函数;
(2)计算属性内不支持异步操作;
(3)计算属性的函数中都有一个 get(默认具有,获取计算属性)和 set(手动添加,设置计算属性)方法;
(4)计算属性是自动监听依赖值的变化,从而动态返回内容。

侦听属性 watch:
(1)不支持缓存,只要数据发生变化,就会执行侦听函数;
(2)侦听属性内支持异步操作;
(3)侦听属性的值可以是一个对象,接收 handler 回调,deep,immediate 三个属性;

5.vue3中 watch、watchEffect区别

  • watch需要传递监听的对象,watchEffect不需要
  • watch只能监听响应式数据:ref定义的属性和reactive定义的对象,如果直接监听reactive定义对象中的属性是不允许的(会报警告),除非使用函数转换一下。其实就是官网上说的监听一个getter,watchEffect如果监听reactive定义的对象是不起作用的,只能监听对象中的属性
  • watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数
  • watchEffect(effect)是一种特殊watch,传入的函数既是依赖收集的数据源,也是回调函数。如果我们不关心响应式数据变化前后的值,只是想拿这些数据做些事情,那么watchEffect就是我们需要的。watch更底层,可以接收多种数据源,包括用于依赖收集的getter函数,因此它完全可以实现watchEffect的功能,同时由于可以指定getter函数,依赖可以控制的更精确,还能获取数据变化前后的值,因此如果需要这些时我们会使用watch
  • watchEffect只能获取改变后的值,不能获取旧值。

6.reactive、ref、toRef、toRefs区别

  • 从定义数据的角度对比:
    ref 用来定义:基本数据类型
    reactive 用来定义:对象或数组类型
    备注:ref 也可以定义 数组或对象 ,它内部会通过 reactive函数转为代理对象
  • 从原理角度:
    ref 底层通过 Object.defineProperty() 的get 和 set 来实现 响应式(数据劫持)
    reactive 通过使用 Proxy 来实现响应式(数据劫持),并通过 Reflect 来操作源对象内部的数据
  • 从使用角度对比:
    ref 定义的数据:操作数据需要 .value,读取数据时,模板中直接读取,不需要 .value
    reactive 定义的数据:操作数据与读取数据:均不需要 .value

ref、toRef、toRefs 都可以将某个对象中的属性变成响应式数据
ref的本质是拷贝,修改响应式数据,不会影响到原始数据,视图会更新
toRef、toRefs的本质是引用,修改响应式数据,会影响到原始数据,视图会更新
toRef 一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
toRefs接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用toRef执行

7.v-router的两种模式区别

vue是一种单页应用,单页应用就是仅在页面初始化的时候加载相应的html/css/js一单页面加载完成,不会因为用户的操作而进行页面的重新加载或者跳转,用javascript动态的变化html的内容,前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求

vue-router默认的是hash模式—使用URL的hash来模拟一个完整的URL,于是当URL改变的时候,页面不会重新加载,也就是单页应用了,当#后面的hash发生变化,不会导致浏览器向服务器发出请求,浏览器不发出请求就不会刷新页面,并且会触发hasChange这个事件,通过监听hash值的变化来实现更新页面部分内容的操作

对于hash模式会创建hashHistory对象,在访问不同的路由的时候,会发生两件事:
HashHistory.push()将新的路由添加到浏览器访问的历史的栈顶,和HasHistory.replace()替换到当前栈顶的路由
在这里插入图片描述
pushState()可以改变url地址且不会发送请求,replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改

8.Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?

权限管理一般需求是页面权限和按钮权限的管理

具体实现的时候分后端和前端两种方案:

前端方案会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后根据角色过滤出路由表。比如我会配置一个asyncRoutes数组,需要认证的页面在其路由的meta中添加一个roles字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。此过滤过程结束,剩下的路由就是该用户能访问的页面,最后通过router.addRoutes(accessRoutes)方式动态添加路由即可。

后端方案会把所有页面路由信息存在数据库中,用户登录的时候根据其角色查询得到其能访问的所有页面路由信息返回给前端,前端再通过addRoutes动态添加路由信息

按钮权限的控制通常会实现一个指令,例如v-permission,将按钮要求角色通过值传给v-permission指令,在指令的moutned钩子中可以判断当前用户角色和按钮是否存在交集,有则保留按钮,无则移除按钮。

纯前端方案的优点是实现简单,不需要额外权限管理页面,但是维护起来问题比较大,有新的页面和角色需求就要修改前端代码重新打包部署;服务端方案就不存在这个问题,通过专门的角色和权限管理页面,配置页面和按钮权限信息到数据库,应用每次登陆时获取的都是最新的路由信息,可谓一劳永逸!

8.Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题?

实现原理:

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

9.父组件可以监听到子组件的生命周期吗?

// Parent.vue
<Child @mounted="doSomething"/>
    
// Child.vue
mounted() {
  this.$emit("mounted");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},
    
//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},    
    
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...     

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

10. vue3新特性

1.使用了 proxy 替代 Object.defineProperty
•可直接监听数组类型的数据变化
•监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
•可拦截apply、ownKeys、has等13种方法,而Object.defineProperty不行
•直接实现对象属性的新增/删除

Object.defineProperty()存在的问题
1.只能拦截对象属性的 get 和 set 操作,无法拦截 delete、in、方法调用等操作。
2.一次只能对一个属性实现数据挟持,需要递归遍历所有属性进行挟持。
3.无法响应式处理新增属性和删除属性。需使用 this.set()设置新属性,使用this.set()设置新属性,使用 this.set()设置新属性,使用this.delete() 删除属性。
4.无法监听数组下标的变化。
5.对于数组而言,大部分操作都拦截不到,需要对 Array 原型支持的方法进行对应改写。

2.Composition API 合成型api或者叫组合式 API
第一个是代码组织问题: 让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他自己写的代码时,他可以更好的利用代码的组织反推出实际的业务逻辑,或者根据业务逻辑更好的理解代码。
第二个是实现代码的逻辑提取与复用: 当然mixin也可以实现逻辑提取与复用,但是像前面所说的,多个mixin作用在同一个组件时,很难看出property是来源于哪个mixin,来源不清楚,另外,多个mixinproperty存在变量命名冲突的风险。而Composition API刚好解决了这两个问题。

在这里插入图片描述
4.v2为什么只能有一个根节点,而v3可以是多根节点
vue2:
因为vdom是一个单根树形结构描述当前视图结构,patch方法在遍历的时候从根节点开始遍历,它要求只有一个根节点。组件也是会转换成vdom,所以也必须满足单根节点要求

vue3:
因为vue3引入了fragment概念,这是一个抽象的节点,如果发现组件是多根的会自动创建一个fragment节点,把多根节点视为自己的children。在patch时,如果发现这是一个fragment节点,则直接遍历children创建或更新

11. 自定义指令的生命周期

在Vue中,自定义指令的生命周期(也叫钩子函数),有5个事件钩子:1-bind 被绑定, 2-inserted 被插入, 3-update 开始更新, 4-componentUpdated 更新完成,5-unbind 解除绑定。我们可以设置指令在某一个事件发生时的具体行为。

  1. bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行一次的初始化动作。

  2. inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。

  3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。

  4. componentUpdated:被绑定的元素所在模板完成一次更新周期是调用。

  5. unbind:只调用一次,指令与元素解绑时调用

12. $nextTick

回答一: 由于 Vue 的异步更新机制,当我们修改数据时,组件的 DOM 并不会立即更新,此时可以使用 $nextTick 方法在下一次事件循环中获取最新的 DOM 元素状态。

回答二: vue的数据更新是一个异步任务,所以vue修改数据后,页面无法立刻更新,而是放到一个任务队列,并且缓存同一轮事件循环中所有的数据改变。如果同一个watcher被多次触发,只会推入到队列中1次。在缓冲时去除重复的操作,下一轮事件循环时,才开始更新。此刻DOM操作获取的数据还是还是未更新的数据,而$nextTick可以监听DOM更新完成,再执行回调函数,确保拿到更新完成后的数据或者确定更新完成后再进行其他的的操作。

13. Vue MVVM概念

Vue是一个 MVVM框架,其各层的对应关系如下:
View层: 在Vue中是绑定dom对象的HTML(代表UI视图,负责数据的展示;)
ViewModel层:在Vue中是实例的vm对象 (负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;)
Model层: 在Vue中是data、computed、methods等中的数据(代表数据模型,数据和业务逻辑都在Model层中定义;)
在 Model 层的数据变化时,View层会在ViewModel的作用下,实现自动更新

14 Vue 中如何实现监测数组变化

1.使用函数劫持的方式,重写了数组的方法
2.vue 将 data 中的数据,进行了原型链重写,指向了自己定义的数组原型方法,这样当调用数组 api 的时候,可以通知依赖更新,如果数组中包含着引用类型,则会对数组中的引用类型再次进行监控。

15.使用插槽组件之间传递内容

1.默认插槽
父组件调用子组件使用双标签语法,在其中书写需要传递给子组件的内容
在这里插入图片描述

2.具名插槽
在这里插入图片描述

3.作用域插槽
在这里插入图片描述

16.Vue中created和mounted的区别及使用场景

  1. created在组件实例创建后调用,而mounted在组件挂载到DOM后调用。
  2. created阶段无法访问组件的DOM元素,而mounted阶段可以访问组件的DOM元素。
  3. created常用于初始化数据和执行异步操作,mounted常用于执行与DOM相关的操作。
  4. 如果需要在组件实例创建后立即执行一些操作,而不依赖于DOM,则可以使用created。如果需要在组件挂载到DOM后进行DOM操作或访问DOM元素,则可以使用mounted

重点: 讨论这个问题本质就是触发的时机,放在mounted中的请求有可能导致页面闪动(因为此时页面dom结构已经生成),但如果在页面加载前完成请求,则不会出现此情况。建议对页面内容的改动放在created生命周期当中

17.双向绑定的原理是什么

在这里插入图片描述

18.实现双向绑定

在这里插入图片描述

Webpack篇

1.什么是bundle,什么是chunk,什么是module

bundle:是由webpack打包出来的文件

chunk:是指webpack在进行模块依赖分析的时候,代码分割出来的代码块

module:是开发中的单个模块

2.Loader和Plugin的不同?

Loader直译为"加载器"。Loaders是用来告诉webpack如何转化处理某一类型的文件,并且引入到打包出的文件中

Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

3.Webpack构建流程

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数

  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译

  • 确定入口:根据配置中的 entry 找出所有的入口文件

  • 编译模块:从入口文件出发,调用所有配置的 Loader
    对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理

  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系

  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk
    转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会

  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

简单说:
初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler

编译:从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理

输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中

4.webpack打包原理

把一切都视为模块:不管是 css、JS、Image 还是 html 都可以互相引用,通过定义 entry.js,对所有依赖的文件进行跟踪,将各个模块通过 loader 和 plugins 处理,然后打包在一起。

按需加载:打包过程中 Webpack 通过 Code Splitting 功能将文件分为多个 chunks,还可以将重复的部分单独提取出来作为 commonChunk,从而实现按需加载。把所有依赖打包成一个 bundle.js 文件,通过代码分割成单元片段并按需加载

5. webpack如何优化包体积,优化构建时间

webpack优化方案
webpack优化方案2
webpack优化方案3
webpack优化方案4

打包速度优化:
1、使用高版本的webpack和Node.js
2、多进程打包:happyPackthread-loader
3、多进程并行压缩:parallel-uglify-plugin、uglifyjs-webpack-plugin 开启 parallel 参数、terser-webpack-plugin 开启 parallel 参数
4、预编译资源模块:DLLPlugin
5、缓存(babel-loader、terser-webpack-plugin、cache-loader
6、exclude、include缩小构建目标,noParse忽略、IgnorePlugin
7、resolve配置减少文件搜索范围(alias、modules、extensions、mainFields)

打包体积优化:
1、Tree-shaking擦除无用代码
2、Scope Hoisting优化代码
3、图片压缩(image-webpack-loader
4、公共资源提取(CommonsChunkPlugin
5、动态Polyfill
6、分包设置Externals,使用 html-webpack-externals- plugin,将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中
7、删除无用CSS代码(purgecss-webpack-plugin
8、JS、CSS压缩(UglifyJsPlugin(3)、terser-webpack-plugin(4)、optimize-css-assets-webpack-plugin
webpack

6. vite跟webpack

在这里插入图片描述

7. webpack的理解,解决了什么问题?

在这里插入图片描述
在这里插入图片描述

安全篇

1.安全问题

1.XSS:跨站脚本攻击
如何进行:是指恶意攻击者利用网站没有对用户提交数据进行转义处理或者过滤不足的缺点,进而添加一些代码,嵌入到web页面中去。使别的用户访问都会执行相应的嵌入代码。从而盗取用户资料
主要原理:过于信任客户端提交的数据!
防御手段:不信任任何客户端提交的数据,只要是客户端提交的数据就应该先进行相应的过滤处理,然后方可进行下一步的操作。
对输入和 URL 参数进行过滤,过滤掉会导致脚本执行的相关内容。
对动态输出到页面的内容进行 html 编码,使脚本无法在浏览器中执行。

2.CSRF跨站请求伪造
如何进行:当用户在某网页登录之后,在没有关闭网页的情况下,收到别人的链接。点击链接,会利用浏览器的cookie把密码改掉。
主要原理:在没有关闭相关网页的情况下,点击其他人发来的CSRF链接,利用客户端的cookie直接向服务器发送请求。
防御手段:
验证 HTTP 的 Referer 字段。
在请求地址中添加 token 并验证。
在 HTTP 头中自定义属性并验证。
涉及到数据修改操作严格使用 post 请求而不是 get 请求。

3.Sql脚本注入
如何进行:利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
主要原理:通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
防御手段:
限制数据库权限和特权。将数据库用户的功能设置为最低要求,这将限制攻击者可以执行的操作;
加强对用户输入的验证

4.iframe风险
说明:嵌入第三方 iframe 会有很多不可控的问题,同时当第三方 iframe 出现问题或是被劫持之后,也会诱发安全性问题
ii)点击劫持
攻击者将目标网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,诱导用户点击。

预防方案:为 iframe 设置 sandbox(HTML5新特性)属性,通过它可以对iframe的行为进行各种限制,充分实现“最小权限“原则,(举例:不想被该网站操作DOM、不想加载某些js(广告、弹框等)、当前窗口被强行跳转链接等,,我们可以设置 sandbox 属性。如使用多项用空格分隔。)

2.iframe的优缺点

优点:
1.iframe能够原封不动的把嵌入的网页展现出来。
2.如果有多个网页引用iframe,那么只需要修改iframe的内容,就可以实现调用每一个页面的更改,方便快捷。
3.网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe嵌套,可以增加代码的可重用。
4.如果遇到加载缓慢的第三方内容,如图标或广告,这些问题可以由iframe来解决。
5.iframe会堵塞主页面的Onload事件。
6.iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。

缺点:
1.会产生很多的页面,不容易管理
2.iframe框架结构个数多的话,可能会出现上下左右滚动条,会分散访问者的注意,用户体验度差。
3.代码复杂,无法被一些搜索引擎索引到,搜索引擎爬虫还不能很好的处理iframe中的内容,所以不利于搜索引擎优化。
4.很多移动设备无法完全显示框架,设备兼容性差
5.iframe框架页面会增加服务器的http请求,对于大型网站是不可取的

3.跨域

跨域指的是浏览器不能执行其他网站的脚本。原因是浏览器出于安全考虑,有同源策略。
同源策略:协议、域名、端口号都相同,才可以共享资源。
解决:1.JSONP
JSONP利用了script标签没有跨域限制的特性,通过动态创建script标签,将请求返回的数据作为回调函数的参数传入。
缺点:JSONP的优点在于简单适用,老式浏览器全部支持,服务器改造小。不需要XMLHttpRequest或ActiveX的支持;但缺点是只支持 GET 请求。

2.CORS是一种官方的解决跨域问题的方案,支持GET/POST等方法。
开启CORS需要在服务端设置Response Header:

3.nginx反向代理
nginx是一个高性能的HTTP服务器和反向代理服务器,在实现反向代理时可以实现跨域。
在nginx.conf配置文件中,添加以下内容:

复制代码
location /api {
  proxy_pass http://localhost:8080;
  add_header Access-Control-Allow-Origin *;
}
  • 1
  • 2
  • 3
  • 4
  • 5

这样就可以在前端访问localhost:80/api,nginx会将请求转发到localhost:8080,并在Response Header中添加Access-Control-Allow-Origin。

  1. vue设置反向代理
   proxyTable: {
      '/apis': { // 就是接口实际请求的前缀,去代理了我们的实际的接口前缀的公共部分,也就是协议+主机名+端口号
        target: 'http://localhost:8080/',  //要解决跨域的接口的域名
        secure:false,           //如果是https接口,需要配置这个参数
        changeOrigin: true,  // 开启代理:在本地会创建一个虚假服务器,然后发送请求的数据,并同时接收请求的数据,这样服务端和服务端就可以进行数据的交互
        pathRewrite: {
          '^/apis': ''  // 路径重写
        }
      },
    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.怎样保障接口安全

HTTPS:采用HTTPS协议保障接口传输的安全性,防止中间人攻击和数据泄露。
API密钥:为每个客户端分配唯一的API密钥,防止未经授权的访问。
访问频率限制:限制接口访问的频率,防止恶意攻击和DDoS攻击。
日志记录:记录接口的访问日志和异常日志,方便追踪和排查安全问题。
安全审计:定期对接口进行安全审计,发现可能存在的安全漏洞并及时修复。
防火墙:在服务器上配置防火墙,限制对接口的访问,防止未经授权的访问和攻击。

1、Token授权认证,防止未授权用户获取数据;
2、时间戳超时机制;(客户端每次请求接口都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如:1分钟),则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。
例:http://url/getInfo?id=1&timetamp=1559396263)
3、URL签名,防止请求参数被篡改;
4、防重放,防止接口被第二次请求,防采集;
5、采用HTTPS通信协议,防止数据明文传输;
7. 黑名单机制
8.白名单机制

5.get跟post的区别

1.GET请求只能进行url编码,而POST支持多种编码方式。
2.GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
3.GET请求在URL中传送的参数是有长度限制的,而POST没有。
4.数据传输方式不同:GET请求通过URL传输数据,而POST的数据通过请求体传输。
5.安全性不同:POST的数据因为在请求主体内,所以有一定的安全性保证,而GET的数据在URL中,通过历史记录,缓存很容易查到数据信息。
6.数据类型不同:GET只允许 ASCII 字符,而POST无限制
7.GET无害: GET在浏览器回退时是无害的,而POST会再次提交请求。

6.git跟svn的区别

svn是集中式版本控制系统,git是分布式版本控制系统。
svn就是所有人修改的都是服务器上的程序,如果有人修改了同样的部分,那就冲突了。所以呢,一般团队会约定,对于公共部分的程序,尽量标注出开发人员特有标识,又或者A从上添加,B从下添加。
git就是开发人员创建自己的分支,这个分支就相当于将源码copy一份在本机上,之后修改的都是本地的代码,可随时拉取服务器的代码进行同步,git可创建无数分支,开发人员只需将自己修改的代码提交就可以了,这样冲突的几率会小很多。
svn是直接与服务器进行交互,git是将项目缓存在本地再推送到服务器。
svn必须在联网的情况下工作,git可不联网开发。
svn旨在项目管理,git旨在代码管理。
svn适用于多项目并行开发,git适用于单项目开发。
svn适用于企业内部,由项目经理协调多个项目统筹开发,git适用于通过网络多人开发同一项目。
GIT的内容完整性要优于SVN:
GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏

浏览器篇

1.什么是gzip?为什么要用gzip?使用gzip的优势和劣势是什么?

  1. GZIP是网站压缩加速的一种技术,对于开启后可以加快我们网站的打开速度,原理是经过服务器压缩,客户端浏览器快速解压的原理,可以大大减少了网站的流量
    为什么要用gzip?
    在网络传输过程中我们可以使用gzip对数据进行压缩,从而减少数据传输量,减少对网络带宽的消耗,可以加快我们打开网站的速度。同时也可以对静态文件进行压缩,减少我们的存储空间。
    使用gzip的优势是什么?
    可以减少文件的大小,gzip的压缩比率比较高通常在3到10倍左右,可以大幅度的减少服务器的网络带宽。使用在项目中可以减少数据的传输时间,减少网络带宽的消耗,从而加速网站打开的速度。
    使用gzip的劣势是什么?
    使用gzip依赖于服务端和客户端的cpu,会增加服务端和客户端cpu的负担。

2.网页从输入网址到渲染完成经历了哪些过程?

简洁版

  • 浏览器根据请求的URL交给DNS域名解析,找到真实IP,向服务器发起请求;
  • 服务器交给后台处理完成后返回数据,浏览器接收文件(HTML、JS、CSS、图象等);
  • 浏览器对加载到的资源(HTML、JS、CSS等)进行语法解析,建立相应的内部数据结构(如HTML的DOM);
  • 载入解析到的资源文件,渲染页面,完成。

详细版本

  • 浏览器会开启一个线程来处理这个请求,对URL 分析判断如果是http 协议就按照Web 方式来处理;
  • 调用浏览器内核中的对应方法,比如WebView 中的loadUrl 方法;
  • 通过DNS解析获取网址的IP地址,设置UA 等信息发出第二个GET请求;
  • 进行HTTP协议会话,客户端发送报头(请求报头);
  • 进入到web服务器上的Web Server,如Apache、Tomcat、NodeJS 等服务器;
  • 进入部署好的后端应用,如PHP、Java、JavaScript、Python 等,找到对应的请求处理;
  • 处理结束回馈报头,此处如果浏览器访问过,缓存上有对应资源,会与服务器最后修改时间对比,一致则返回304;
    浏览器开始下载html文档(响应报头,状态码200),同时使用缓存;
  • 文档树建立,根据标记请求所需指定MIME类型的文件(比如css、js),同时设置了cookie;
    页面开始渲染DOM,JS根据DOM API操作DOM,执行事件绑定等,页面显示完成。
  1. DNS解析
  2. TCP连接
    HTTP协议是使用TCP协议作为其传输层协议的,在拿到服务器的IP地址后,浏览器客户端会与服务器建立TCP连接。该过程包括三次握手:
  • 第一次握手:客户端向服务器发送一个建立连接的请求(您好,我想认识您);
  • 第二次握手:服务器接到请求后发送同意连接的信号(好的,很高兴认识您)
  • 第三次握手,客户端接到同意连接的信号后,再次向服务器发送了确认信号(我也很高兴认识您)。
    ** 三次握手主要是为了防止已经失效的请求报文字段发送给服务器,浪费资源。**
  1. 发送HTTP请求
    浏览器构建http请求报文,并通过TCP协议传送到服务器的指定端口。http请求报文一共包括三个部分:
  • 请求行:指定http请求的方法、url、http协议版本等
  • 请求头:描述浏览器的相关信息,语言、编码等。
  • 请求正文:当发送POST, PUT等请求时,通常需要向服务器传递数据。这些数据就储存在请求正文中。
  1. 服务器处理请求并返回HTTP报文
    服务器处理http请求,并返回响应报文。响应报文包括三个部分:状态码, 响应报头和响应报文
  2. 浏览器解析渲染页面
    浏览器接受到http服务器发送过来的响应报文,并开始解析html文档,渲染页面。
  3. 连接结束(断开TCP连接)
    客户端与服务器四次挥手,断开tcp连接。
  • 客户端向服务器发送一个断开连接的请求(不早了,我该走了);
  • 服务器接到请求后发送确认收到请求的信号(知道了);
  • 服务器向主机发送断开通知(我也该走了);
  • 客户端接到断开通知后断开连接并反馈一个确认信号(嗯,好的),服务器收到确认信号后断开连接;

3.简述 HTTP1.0/1.1/2.0 的区别

HTTP1.0:
1.仅仅提供了最基本的认证,这时候用户名和密码还未经加密,因此很容易收到窥探。
2.被设计用来使用短链接,即每次发送数据都会经过 TCP 的三次握手和四次挥手,效率比较低。
3.只使用 header 中的 If-Modified-Since 和 Expires 作为缓存失效的标准。
4.不支持断点续传,也就是说,每次都会传送全部的页面和数据。
5.认为每台计算机只能绑定一个 IP,所以请求消息中的 URL 并没有传递主机名(hostname)。

HTTP 1.1
1.使用了摘要算法来进行身份验证
2. 默认使用长连接,长连接就是只需一次建立就可以传输多次数据,传输完成后,只需要一次切断连接即可。长连接的连接时长可以通过请求头中的 keep-alive 来设置
3.中新增加了 E-tag,If-Unmodified-Since, If-Match, If-None-Match 等缓存控制标头来控制缓存失效。
4.支持断点续传,通过使用请求头中的 Range 来实现。
5.使用了虚拟网络,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。

HTTP 2.0
多路复用: 引入了多路复用技术,允许在同一个TCP连接上同时发送多个请求和响应。这样可以避免HTTP 1.x中的队头阻塞问题,提高请求的并发处理能力,加快页面加载速度。
二进制格式: 使用二进制格式传输数据,取代了HTTP 1.x中的文本格式。二进制格式的解析效率更高,减少了数据传输的大小,节省了带宽和时间。
头部压缩: 支持头部压缩,减少了传输的开销。通过使用HPACK算法对请求和响应的头部进行压缩,可以显著减小头部大小,提高性能和效率。
服务器推送: 引入了服务器推送机制,允许服务器主动向客户端推送与当前请求相关的资源。这样可以减少客户端的请求次数,提前将可能需要的资源发送给客户端,加快页面加载速度。
优先级和流控制: 支持优先级和流控制机制,可以对请求和响应设置优先级。通过设置优先级,可以确保重要的资源优先加载,提供更好的用户体验。流控制机制可以控制数据流的速率,防止接收方被过多的数据淹没。
强化安全性: 要求使用TLS加密传输,提供更强的安全性。通过使用TLS,可以保护数据的机密性和完整性,防止中间人攻击和数据篡改

3.1 http和https的区别

1️⃣HTTPS 需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。
2️⃣HTTP 信息是明文传输,HTTPS 则是具有安全性的 ssl 加密传输协议。
3️⃣HTTP 和 HTTPS 连接方式完全不同,端口也不一样,前者是 80,后者是 443。
4️⃣HTTP 的连接很简单,是无状态的。HTTPS 是由 SSL+HTTP 构建的可进行加密传输、身份认证的网络协议,比 HTTP 安全。

4.UDP 和 TCP 的区别

TCP 和 UDP 都位于计算机网络模型中的运输层,它们负责传输应用层产生的数据。
UDP 的全称是 User Datagram Protocol,用户数据报协议。它不需要所谓的握手操作,从而加快了通信速度,允许网络上的其他主机在接收方同意通信之前进行数据传输。
数据报是与分组交换网络关联的传输单元。
UDP 的特点:

  1. 能够支持容忍数据包丢失的带宽密集型应用程序
  2. 具有低延迟的特点
  3. UDP 能够发送大量的数据包
  4. UDP 能够允许 DNS 查找,DNS 是建立在 UDP 之上的应用层协议。

TCP 的全称是Transmission Control Protocol ,传输控制协议。它能够帮助你确定计算机连接到 Internet 以及它们之间的数据传输。通过三次握手来建立 TCP 连接,三次握手就是用来启动和确认 TCP 连接的过程。一旦连接建立后,就可以发送数据了,当数据传输完成后,会通过关闭虚拟电路来断开连接。
TCP 的特点:

  1. TCP 能够确保连接的建立和数据包的发送
  2. TCP 支持错误重传机制
  3. TCP 支持拥塞控制,能够在网络拥堵的情况下延迟发送
  4. TCP 能够提供错误校验和,甄别有害的数据包。

区别:
在这里插入图片描述

5.强制缓存跟协商缓存的区别

强制缓存: 请求的资源本地缓存中有,资源从本地缓存获取,不需要发起请求
Expires:服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求,这个是HTTP 1.0的方式,因为用绝对时间可能存在误差导致缓存失败。

Cache-Control:因为Expires的一些缺点,在 HTTP 1.1 中提出了这个属性,它提供了对资源的缓存的更精确的控制。通过设置 max-age= XXX 来进行强缓存。

协商缓存: 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
设置方式:
如果版本一致,服务器返回304状态码,重定向让浏览器直接在本地缓存里拿到资源。
如果版本不一致,服务器返回200状态码、最新的资源以及新的资源标识,浏览器更新本地缓存。

资源标识

  • Last-Modified/If-Modified-Since:指资源上一次修改的时间
  • ETag/If-None-Match:资源对应的唯一字符串

Etag 的优先级高于 Last-Modified。
在这里插入图片描述

注意 : 刷新浏览器页面,强制缓存会失效,而协商缓存有效。

小程序篇

1.小程序中的路由跳转switchTab navigateTo redirectTo的区别?

  • wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
  • wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
  • wx.switchTab():跳转到 abBar 页面,并关闭其他所有非 tabBar 页面
  • wx.navigateBack()关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages()
  • 获取当前的页面栈,决定需要返回几层 wx.reLaunch():关闭所有页面,打开到应用内的某个页面

2.小程序的生命周期?

onLoad : 页面初始化时触发
onShow : 页面首次出现在前台时触发
onReady : 页面首次渲染完毕时触发
onHide : 页面从前台变为后台时触发
onUnload : 页面销毁时触发
其中 onShow 的触发时间要早于 onReady

  • 小程序注册完成后,加载页面,触发onLoad方法。
  • 页面载入后触发onShow方法,显示页面。
  • 首次显示页面,会触发onReady方法,渲染页面元素和样式,一个页面只会调用一次。
  • 当小程序后台运行或跳转到其他页面时,触发onHide方法。
  • 当小程序有后台进入到前台运行或重新进入页面时,触发onShow方法。
  • 当使用重定向方法wx.redirectTo(OBJECT)或关闭当前页返回上一页wx.navigateBack(),触发onUnload

在这里插入图片描述

3.小程序微信支付流程

  1. 前端调用uni.login/wx.login调用微信接口,获取code,code相当于临时身份证
  2. 前端调公司后台获取openid的接口,获取openid
  3. 前端调公司后台预支付接口,传递openid、商品id、商品单价、商品数量,获取那5个参数。【时间戳timeStamp,随机字符串nonceStr,预支付idpackage,签名算法signType,签名paySign】
  4. 前端调用uni/wx.requestPayment调用微信支付方法,传递5个参数,获取支付结果(成功或失败)

4.小程序登录流程

  1. 通过调用wx.login() API,成功后会拿到用户的code信息
  2. 将code信息通过接口,传给自己的后台(不是微信的后台),在服务端发起对微信后台的请求,成功后拿到用户登录态信息,包括openid、session_key等。也就是通常所说的拿code换openid,这个openid是用户的唯一标识。
  3. 自己的后台,拿到openid、session_key等信息后,通过第三方加密,生成自己的session信息,返回给前端。
  4. 前端拿到第三方加密后的session后,通过wx.setStorage()保存在本地,以后的请求都需要携带这个经过第三方加密的session信息。
  5. 之后如果需要用户重新登录,先去检查本地的session信息,如果存在,再用wx.checkSession()检查是否在微信的服务器端过期。如果本地不存在或者已过期,则重新从步骤1开始走登录流程。

思维题

1.后端一次性传了10w条数据,前端该如何处理?

虚拟列表的实现思路

  1. 首先你得写一个div,这个div得固定高度。通过css的overflow属性使其允许纵向y轴滚动。
  2. 计算可视区域内可以显示的数据条数。可以用可视区域的高度除以一条数据的高度。
  3. 监听滚动条,当滚动条滚动时计算出被卷起的数据的高度。
  4. 计算这个可视区域内数据的起始索引值,也就是第一条数据的下标。通过滚动条卷起的高度除以单条数据的高度,这块可能大家一下反应不过来,起始坐标最开始是0对吧。卷起的高度除以单条数据的高度是不是就是卷起的数据的数量,那么他的索引值是不是就是数据的数量。假如被卷起四条数据,那么第五条数据索引值是不是四。
  5. 计算区域结束数据的索引。通过起始索引加上可视区域显示的数据条数
  6. 截取数据渲染到可视区域。从起始索引截取到结束索引。
  7. 计算起始索引在整个列表中的偏移位置并设置到列表上,因为每次都不可能滚动到整条数据上。
    答案来源链接
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/302352
推荐阅读
相关标签
  

闽ICP备14008679号