看完这些,前端面试轻松拿捏!

2,959 阅读45分钟

开头

本人大三,双非本科;最近也是赶上了三月份的面试月,于是抱着冲一冲的心态,也是拿了几家公司的offer,当然,也不可避免的挂了几家,有得自有失,放平心态继续面就好啦。

以下是本人在最近在面试里遇到的一些面试题,希望能给大家有所帮助。

面试题

说一说你对HTML语义化的理解

这是我面杭州产链的第一个面试题,总结出来总共有三点:

  1. 利于页面内容结构化,就像标题标签就用 h1~h6 ,在样式文件未加载的时候页面结构清晰。

  2. 有利于 SEO ,即更利于搜索引擎根据标签来上下文内容和权重,利于爬虫爬取(举个例子,就是为什么百度里搜索淘宝,第一个出来的是阿里的淘宝,而不是其它的什么淘啊宝啊之类的,一方面当然是阿里给百度的钱多哈哈,另一方面就是淘宝的页面做的利于搜索引擎搜索展示,页面结构语义化了)

  3. 利于开发人员后期的维护,可读性高,header标签就是头标签,footer就是底部内容,项目开发完后期可维护性高。

样式选择器的优先级排序

这个样式选择器的优先级在面试官问你的时候,你可以不需要说全,毕竟样式选择器确实挺多的(当然,如果你能记下来所有的样式选择器自然更加分)

!important > 行内样式 > 嵌入样式 > 外链样式 > id选择器>(类选择器 | 伪类选择器 | 属性选择器 )> (后代选择器 | 伪元素选择器 )> (子选择器 | 相邻选择器) > 通配符选择器 > 继承样式选择器

什么是标准盒模型?什么是怪异盒模型?

CSS盒子模型是一种用来描述HTML元素在页面上的布局和样式的概念,它把每个元素看作一个盒子,由四个部分组成:内容(content)、内边距(padding)、边框(border)和外边距(margin)。不同的部分可以设置不同的属性,比如宽度、高度、颜色、背景等。

标准盒模型和怪异盒模型的区别主要在于宽度和高度的计算方式不同。标准盒模型的宽度和高度只包括内容(content)的尺寸,而怪异盒模型的宽度和高度还包括内边距(padding)和边框(border)的尺寸。例如,如果一个元素有100px的内容宽度,10px的内边距,5px的边框,那么在标准盒模型中,它的总宽度是100+102+52=130px,在怪异盒模型中,它的总宽度是100px。

CSS怎么做到垂直水平居中

  1. 设置元素相对父级定位 position:absolute;left:50%;right:50% ,让自身平移自身高度50% , transform: translate(-50%,-50%);,这种方式兼容性好,被广泛使用的一种方式。

  2. 设置元素的父级为弹性盒子 display:flex ,设置父级和盒子内部子元素水平垂直都居中 justify-content:center; align-items:center;

  3. 设置元素的父级为网格元素 display: grid,设置父级和盒子内部子元素水平垂直都居中 justify-content:center; align-items:center;

  4. 设置元素的父级为表格元素 display: table-cell,其内部元素水平垂直都居中text-align: center;vertical-align: middle; ,设置子元素为行内块display: inline-block;

  5. (针对文字)父容器设置 line-height 的数值等于 height 的数值做到垂直居中,再用 text-align:center 实现水平居中效果。

JS 的数据类型有哪些?

八种:

  1. String : 字符串类型,属于简单数据类型。
  2. Number :数字类型,属于简单数据类型。
  3. Boolean :布尔值类型,属于简单数据类型。
  4. null :空类型,属于简单数据类型。
  5. undefined :未赋值类型,属于简单数据类型。
  6. bigInt :超数值类型,当 number 类型数值超过最大安全值,可以用 bigInt 表示,属于简单数据类型。
  7. Symbol :定义唯一值类型;常用于定义对象里的 key 值,表示独一无二的 key ;属于简单数据类型。
  8. Object :对象类型,包括 Object 、 Array 、Function,属于引用数据类型

简单数据类型和引用数据类型的区别在于简单数据类型是存放于栈内的,而引用类型是存放在堆内存中的。引用数据类型一般将相应对象的堆的地址和简单数据类型一起存放在栈内存中,每次调用都是先去栈中取到引用地址,再去堆中拿取对应的引用数据。

这里有个小问题嗷,算是之前看文章发现的,字符串虽然属于简单数据类型,但其实它并不是存在栈中的,它存在一个“字符串池”中,因为字符串的长度一般难以定义,所以一旦字符串超长,那么它再存放在栈中就已经不太合适了,所以字符串会被创建在一个字符串池中,无论是简单类型定义还是引用类型定义的字符串类型,都是存在一个字符串池中的。

原型和原型链

什么是原型?

首先,原型是一个函数对象,而原型也分为两种,一种是我们平时所叫的原型,即显式原型,属于构造函数的一个对象属性,另外一种则是隐式原型,是属于构造函数所实例化出来的对象身上的原型,我们通常叫它__proto__,实例对象的隐式原型就是它的构造函数的显式原型。

那么什么是原型链呢?

原型链是一种类似于作用域链的链式结构,它不是实体的,而是我们为了能够解释属性使用时怎么调用的一个称呼而已,一个构造函数所实例化出来的对象里调用一个属性,浏览器引擎会先在这个实例对象里面查找是否存在这个属性,如果实例对象有的这个属性的话就直接使用实例对象里的属性,如果没有的话那么就会顺着实例对象跟它的构造函数之间的一条虚拟的链式结构,去到构造函数里面去找这个属性,还没有的话,就继续顺着这条链式结构去到原型上面去找存不存在属性,一直找到原型链的尽头null为止。

你能聊一聊 this 吗?

this 这个关键字算是常考题了,不论是面试还是笔试题都是经常出了。

这里给你们看个比较简单的有关于 this 的题目:

var a = 10;
var foo = {
    a: 20,
    b: function(){
        var a = 30;
        return this.a;
    },
    c: () => {
        var a = 40;
        return this.a;
    },
}
var d = {
   a: 50,
};
console.log(a);      // 10
console.log(foo.b());     // 20
console.log(foo.c());     // 10
console.log(foo.b.bind(d)());   // 50
console.log(foo.c.bind(d)());   // 10

这个 this 如果想讲清楚一言半语有点难,这里有一篇大佬的文章大家可以看看:

来碗盐焗星球——this关键词

var、let、const 三种声明变量

说到 var,let,const 的区别就要说到声明提升了

声明提升是发生在 js 执行的预编译阶段,此时引擎会优先找到那些声明变量的关键词进行处理,

其中被 var 关键词声明的变量会被提升到当前作用域顶端;

console.log(a);   //undefined
var a = '123';

像这里的打印它就会打印 undefined 而不是报错,因为它被声明了只不过是没有进行赋值操作而已。

这是因为这里的变量 a 经过 var 关键词的声明已经提升到当前作用域顶端(如果存在函数声明,则函数声明会在普通变量的声明提升之前);

而 let 和 const 这么做则会报错,其原因就是它们声明的变量不存在声明提升;

这么说倒也不完全准确,因为 let 和 const 其实存在创建提升,但是它们会产生暂时性死区,使得其效果和并不存在声明提升一样。

同时,let可以产生除了全局作用域和函数作用域之外的块级作用域;

如:

for (var i = 0; i < 5; i++) {
        setTimeout(function() {
             console.log(i);
        }, i * 1000);
}
// 5 5 5 5 5
for (let i = 0; i < 5; i++) {
        setTimeout(function() {
             console.log(i);
        }, i * 1000);
}
// 0 1 2 3 4

从这里就可以看出来了,let 产生的块级作用域,使得每一个 i 都是不同作用域的 i,而 var 不产生,所以当异步代码 setTimeout 执行时拿到的都是最后一个 i = 5;

const 用来定义常量,即不可修改的值。

你了解的 Event-Loop 吗

Event-Loop 就是前端的事件循环,产生的原因就是,javaScript 是单线程语言,所以在执行异步代码是存在阻塞的,这时就需要处理阻塞问题了,于是事件循环机制就顺应时代的出现了。

第一步:找出同步代码,优先执行,这属于宏任务。

第二步:当执行完所有的同步代码后,执行栈为空,检查是否有异步代码要执行。(注意只是检查,并非此刻检查出来就执行,而是看它是第几层次的异步代码在第几次循环执行,引擎在找到异步代码的时候会将其挂起,即装起来放在一边)

第三步:执行微任务。

第四步:执行完微任务后,有必要的情况下会渲染页面。

第五步:开启下一轮Event-Loop,执行宏任务中的代码。(这就算是下次事件循环的开始第一步,上一次的异步,在此刻也将变为这一次执行的同步)

箭头函数的特点

箭头函数作为ES6新增的语法,有以下特点:

  • 箭头函数有更简洁的语法,可以省略括号和花括号,也可以省略return关键字。
  • 箭头函数不绑定this,它会捕获其所在上下文的this值,作为自己的this值。
  • 箭头函数不绑定arguments对象,如果需要使用参数列表,可以用rest参数代替。
  • 箭头函数不能用作构造函数,因为它没有自己的prototype属性和super关键字。
  • 箭头函数没有原型链,也就是说它没有__proto__属性。

什么是闭包?

闭包 ,一个函数和词法环境的引用捆绑在一起,这样的组合就是闭包(closure)。

一般就是一个函数A,return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的变量背包,A函数执行结束后这个变量背包也不会被销毁,并且这个变量背包在A函数外部只能通过B函数访问。

闭包形成的原理:作用域链,当前作用域可以访问上级作用域中的变量 闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。

闭包带来的问题:由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。

闭包的应用:能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。

什么是柯里化?

柯里化是一种将多参数的函数转化为单参数函数的方法。这样做的好处是可以实现参数复用和提前返回。例如,如果有一个函数 f(x,y,z) ,柯里化后就变成了 f(x)(y)(z) ,这样就可以分别传入 x,y,z 的值,或者只传入 x 的值并返回一个新的函数 g(y,z) = f(x)(y)(z) 。

柯里化的实现原理是利用函数的闭包特性,返回一个新的函数,该函数接受一个参数,并且在内部调用原始函数,将所有的参数合并起来。例如,可以这样实现一个柯里化函数:

function curry(fn) {
  // 获取原始函数的参数个数
  let arity = fn.length;
  // 返回一个新的函数
  return function curried(...args) {
    // 如果传入的参数个数小于原始函数的参数个数,就继续返回新的函数
    if (args.length < arity) {
      return function (...moreArgs) {
        return curried(...args, ...moreArgs);
      };
    }
    // 否则就调用原始函数并返回结果
    else {
      return fn(...args);
    }
  };
}

实现 JS 异步的方式

方式:回调函数、事件监听、setTimeout、Promise、生成器Generators/yield、async/await

所有异步任务都是在同步任务执行结束之后,从任务队列中依次取出执行。

回调函数是异步操作最基本的方法,比如AJAX回调,回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。

此外它不能使用 try catch 捕获错误,不能直接 return Promise包装了一个异步调用并生成一个Promise实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve 和 reject方法,then接收到对应的数据,做出相应的处理。Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,缺点是无法取消 Promise,错误需要通过回调函数捕获。

Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数是一个状态机,封装了多个内部状态,可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。优点是异步语义清晰,缺点是手动迭代Generator 函数很麻烦,实现逻辑有点绕.

async/await是基于Promise实现的,async/await使得异步代码看起来像同步代码,所以优点是,使用方法清晰明了,缺点是await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。

JS 异步编程进化史:callback -> promise -> generator/yield -> async/await。

async/await函数对 Generator 函数的改进,体现在以下三点:

  • 内置执行器。 Generator 函数的执行必须靠执行器,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
  • 更广的适用性。 yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
  • 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。 目前使用很广泛的就是promise和async/await

不过有一说一,我觉得 promise 用起来更舒服。

call、apply、bind的区别

首先,call、apply、bind 三个的区别就是在接收参数上,它们三个所接收的第一个参数都是绑定对象,

而 call 除了第一个参数还可以接收多个参数,apply 则是只接收两个参数,第二个参数是一个数组,bind 除了第一个参数也是可以接收多个参数的,但不同的是前面的 call 和 apply 是可以自己调用的,而 bind 是抛出一个函数,需要人为去调用。

它们三个常用来改变 this 指向问题。

javaScript 判断变量类型的方式有哪些?

javaScript判断数据类型的方式有以下几种:

  • typeof:返回表示数据类型的字符串,可以判断基本数据类型和函数,但不能区分对象、数组、null等(它会把null判定为Object,原因是编码识别,感兴趣的可以去百度搜一下)。
  • instanceof:根据对象的原型链判断是否属于某个构造函数,可以区分对象、数组等,但不能判断基本数据类型和null。
  • Object.prototype.toString.call():是一种利用Object原型上的toString方法来判断数据类型的方式,属于万能的方法;优点: 可以区分所有数据类型,包括null和undefined。可以检测出iframes中的数组和对象。不会受到重写toString方法的影响。缺点:比较繁琐,需要使用call或apply来改变this指向。 不能区分自定义对象,只能返回[object Object]。不能判断对象是否属于某个构造函数或类。
  • constructor:根据对象的构造函数判断数据类型,可以区分对象、数组等,但不能判断null和undefined,而且在类继承时会出错。
  • prototype:根据Object.prototype.toString方法返回的字符串判断数据类型,可以区分所有数据类型,但比较繁琐。
  • jquery.type():返回表示数据类型的字符串,可以区分所有数据类型,但需要引入jquery库。

讲一下cookie、sessionStorage、localStorage 区别

  • 存储位置:cookie是在浏览器和服务器之间来回传递的,而localStorage和sessionStorage只在本地保存。
  • 存储大小:cookie的大小一般限制在4KB以内,而localStorage和sessionStorage可以存储更多的数据,一般为5MB左右。
  • 存储有效期:cookie可以设置过期时间,到期后会自动删除,而localStorage永久保存在本地,除非手动清除。sessionStorage只在当前浏览器窗口或标签页有效,关闭后会自动删除。
  • 存储内容:cookie只能存储字符串类型的数据,而localStorage和sessionStorage可以存储任意类型的数据,但实际上都是以字符串的形式保存。

讲一下什么是事件循环 Event-Loop?

事件循环Event loop是JavaScript的执行机制,它负责在单线程中处理同步和异步任务。宏任务和微任务都是异步任务,但它们的执行顺序不同:

  • 宏任务(macro task)是指那些由宿主环境(如浏览器或Node.js)发起的任务,如setTimeout、setInterval、setImmediate、requestAnimationFrame、I/O等。宏任务会被放入一个队列中,每次事件循环只会执行一个宏任务。
  • 微任务(micro task)是指那些由JavaScript引擎发起的任务,如Promise.then、MutationObserver、process.nextTick等。微任务会被放入一个队列中,每次事件循环会执行完所有的微任务。

事件循环的过程大致如下:

  • 首先执行主线程上的同步代码,并将其执行。
  • 然后查看在执行上下文中是否存在宏任务异步代码,如果存在,则将其装入一个队列中,挂起在一边,暂时不对其进行操作。
  • 然后检查微任务队列,如果有微任务,则依次执行完所有的微任务。
  • 开启下一次Event-Loop,执行挂起的宏任务队列中的代码。

new关键字的作用

new关键字是JavaScript中用来创建对象的一种方式,它可以实现实例化和继承的功能。具体来说,当我们使用new关键字调用一个构造函数时,它会进行以下操作

  • 创建一个空的简单JavaScript对象(即{});
  • 为新创建的对象添加__proto__属性,将该属性链接至构造函数的原型对象;
  • 将新创建的对象作为this的上下文;
  • 如果该函数没有返回对象,则返回this。

怎么去避免页面渲染里的重排和重绘?

重排和重绘是浏览器渲染页面的两个过程,它们会影响页面的性能和用户体验。重排是指浏览器重新计算元素的位置和大小,重绘是指浏览器重新绘制元素的外观。重排比重绘更耗费资源,因为它会导致整个页面的布局变化

要避免重排和重绘,可以采取以下一些方法:

  • 尽量减少对DOM的操作,比如使用文档碎片或虚拟DOM来批量更新节点;
  • 尽量避免使用会引起布局变化的CSS属性,比如width、height、margin、padding等;
  • 尽量使用transform、opacity等可以通过GPU加速的CSS属性来实现动画效果;
  • 尽量避免在循环中读取或修改元素的样式,可以先缓存计算结果或使用requestAnimationFrame来优化;
  • 尽量避免使用table布局,因为table中任意一个元素的变化都会导致整个table重新布局。

从url输入到页面渲染完成期间发生了什么?

在前端里,从输入url到页面渲染完成期间发生了很多事情,大致可以分为以下几个步骤:

  • 浏览器主进程接收到用户输入的url,判断是否是合法的url格式,如果不是,则将其作为搜索关键字进行搜索;
  • 如果存在强缓存及协商缓存,则执行如下操作:
  1. 当后端做了强缓存策略时,先检查本地缓存中是否有该资源,并且该资源是否在有效期内。如果有,并且没有过期,那么就直接从本地缓存中读取数据,不需要向服务器发送请求。强缓存的有效期由Cache-ControlExpires两个响应头控制)。
  2. 协商缓存是指浏览器在请求资源时,先检查本地缓存中是否有该资源,并且该资源是否已经过期。如果有,并且已经过期,那么就向服务器发送一个带有If-None-MatchIf-Modified-Since两个请求头的请求,询问服务器该资源是否有更新。
  3. 如果服务器返回304状态码,表示该资源没有更新,那么就让浏览器从本地缓存中读取数据。如果服务器返回200状态码和最新的资源数据,表示该资源有更新,那么就让浏览器使用最新的数据,并更新本地缓存。协商缓存的更新标识由EtagLast-Modified两个响应头控制。
  • 浏览器主进程将url交给网络进程,网络进程进行DNS解析,获取目标服务器的IP地址,详细情况如下:
  • 客户端(如浏览器)输入域名后,首先在本地缓存中查找是否有对应的IP地址,如果有,则直接返回;
  • 如果本地缓存中没有找到,客户端会向本地DNS服务器(如网络运营商提供的DNS服务器)发送查询请求;
  • 本地DNS服务器会在自己的缓存中查找是否有对应的IP地址,如果有,则直接返回;
  • 如果本地DNS服务器的缓存中也没有找到,它会向根DNS服务器发送查询请求;
  • 根DNS服务器会根据域名的后缀(如.com、.cn等)返回一个负责该后缀的顶级域名服务器(TLD)的IP地址;
  • 本地DNS服务器再向该顶级域名服务器发送查询请求;
  • 顶级域名服务器会根据域名的主机部分(如zdns.cn中的zdns)返回一个负责该主机部分的权威域名服务器(Authoritative DNS)的IP地址;
  • 本地DNS服务器再向该权威域名服务器发送查询请求;
  • 权威域名服务器会返回该域名对应的IP地址,并将其缓存到本地DNS服务器中;
  • 本地DNS服务器将最终结果返回给客户端,并将其缓存到客户端中。
  • 前后建立请求(即TCP的三次握手和四次挥手):

三次握手:

  1. 第一次握手:客户端向服务器发送一个SYN报文段,表示请求建立连接;
  2. 第二次握手:服务器收到后,回复一个SYN+ACK报文段,表示同意建立连接;
  3. 第三次握手:客户端收到后,回复一个ACK报文段,表示确认建立连接。这样,客户端和服务器之间就建立了一个双向的TCP连接。 四次挥手:
  4. 第一次挥手:客户端向服务器发送一个FIN报文段,表示请求断开连接;
  5. 第二次挥手:服务器收到后,回复一个ACK报文段,表示确认收到请求;
  6. 第三次挥手:服务器向客户端发送一个FIN报文段,表示同意断开连接;
  7. 第四次挥手:客户端收到后,回复一个ACK报文段,表示确认断开连接。这样,客户端和服务器之间就断开了TCP连接。
  • 从后端获取到了相应数据后就会开始进行结构解析和渲染啦:
  1. 将HTML代码解析成DOM树
  2. 将CSS代码解析成CSSOM树
  3. 将DOM树和CSSOM树合并成一棵render树
  4. 执行回流,即重排,将页面布局的集合元素排版渲染上去
  5. 执行重绘,将已经布局好的盒子上的颜色等不改变排版的元素渲染上去

说一说前端三种路由:hash,history,abstract

  • hash 路由是一种把前端路由的路径用井号 # 拼接在真实 URL 后面的模式。当井号 # 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 hashchange 事件,前端可以监听这个事件,根据不同的 hash 值来渲染不同的内容。这种路由不需要服务端的支持,兼容性较好。
  • history 路由是一种利用 HTML5 的 History API 来实现前端路由的模式。History API 可以让前端操作浏览器历史记录,并且在地址栏中显示正常的 URL 路径。当 URL 路径发生变化时,浏览器也不会重新发起请求,而是会触发 popstate 事件,前端可以监听这个事件,根据不同的路径来渲染不同的内容。这种路由需要服务端的支持,在刷新或直接访问某个路径时,要返回对应的页面。
  • abstract 路由是一种没有依赖于浏览器环境的路由模式。它使用一个数组来存储浏览器历史记录,并且在内存中操作这个数组来实现前进、后退等功能。它适用于非浏览器环境或者测试环境。

如果想要知道hash路由和history路由的实现原理可以看一看这一篇文章:

前端路由概念及实现

ES6新增的属性方法

  • let 和 const 关键字,用来声明块级作用域的变量和常量,避免了使用 var 带来的一些问题。
  • 箭头函数 (=>),用来简化函数的定义和绑定 this 值。
  • 模板字符串 (``),用来创建多行字符串或者插入变量。
  • 解构赋值 ({ } 和 [ ]),用来从对象或数组中提取值并赋给变量。
  • 默认参数值 (=),用来给函数参数设置默认值。
  • 展开运算符 (…),用来将数组或对象展开为逗号分隔的序列。
  • 剩余参数 (…),用来将函数的多余参数收集为一个数组。
  • 类 (class),用来定义类和继承关系,实现面向对象编程。
  • 模块 (import 和 export),用来将代码分割为不同的文件,并且可以导入和导出变量、函数、类等。
  • Promise 对象,用来表示异步操作的结果,并提供了统一的接口处理成功或失败的回调。
  • 生成器函数 (function*),用来返回一个可遍历的对象,可以通过 yield 关键字暂停和恢复执行。
  • 迭代器 (iterator) 和 for…of 循环,用来遍历可迭代的对象,如数组、字符串、Map、Set 等。
  • Map 和 Set 数据结构,分别用来存储键值对和不重复的值,并提供了相关的操作方法。
  • Symbol 类型,用来创建独一无二的标识符,并可以作为对象属性名使用。
  • Proxy 对象,用来创建一个对象的代理,可以拦截和修改对象的一些操作,如读取、赋值、枚举、调用等。
  • Reflect 对象,用来提供一些与 Proxy 对应的静态方法,可以对对象进行反射操作,如获取、设置、删除、构造等。
  • Set 和 WeakSet 数据结构,分别用来存储不重复的值和弱引用的值,并提供了相关的操作方法。
  • Map 和 WeakMap 数据结构,分别用来存储键值对和弱引用的键值对,并提供了相关的操作方法。
  • Iterator 和 Generator 接口,分别用来定义遍历器和生成器对象,可以实现自定义的迭代逻辑和惰性求值。
  • Promise 和 async/await 语法,分别用来表示异步操作的结果和简化异步编程的方式,并提供了统一的接口处理成功或失败的回调。
  • Class 和 extends 语法糖,分别用来定义类和继承关系,实现面向对象编程。
  • Module 和 import/export 语法,分别用来将代码分割为不同的文件,并且可以导入和导出变量、函数、类等。
  • Array 的一些新增方法,如 find(), findIndex(), includes(), flat(), flatMap() 等。
  • Object 的一些新增方法,如 Object.keys(),Object.values()等。

隐式转换

隐式转换规则是指在JavaScript中,当运算符在运算时,如果两边数据类型不一致,编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算

根据不同的运算符和场景,隐式转换规则有以下几种:

  • 转为Number类型:+ - * / ++ – (算术运算符) > < >= <= == != === !== (比较运算符)。
  • 转为String类型:+ (字符串连接符)。
  • 转为Boolean类型:! (逻辑非运算符)。
  • 对象到字符串的转换:如果对象有toString()方法,调用该方法并返回原始值;否则调用valueOf()方法并返回原始值;如果都没有原始值,抛出TypeError异常。
  • 对象到数字的转换:如果对象有valueOf()方法,调用该方法并返回原始值;否则调用toString()方法并返回原始值;如果都没有原始值,抛出TypeError异常。

怎么让对象上的属性成为不可更改的属性

  • Object.defineProperty 上有一个 writable 属性设置为 false 可以使其对象上的属性不可修改。

  • 对象冻结:Object.freeze(obj),使被冻结的对象不可被修改,不可添加新属性,不可修改值,不可删除

  • 对象密封:Object.seal(obj),使被密封的对象不可添加,不可以改动键值,但能改value

  • 阻止扩展:Object.preventExtensions(obj),阻止对象增加属性,但不会阻止其它操作

浏览器的垃圾回收机制

浏览器垃圾回收机制是指浏览器在运行JavaScript代码时,会自动管理内存的分配和释放,从而避免内存泄漏或内存不足的问题

浏览器垃圾回收机制主要有两种方法:

  • 标记清除算法:这种算法会对所有的对象进行标记,然后清除那些没有被引用的对象。这样可以有效地回收那些不再使用的对象,但是也有一个缺点,就是会产生内存碎片
  • 引用计数算法:这种算法会记录每个对象被引用的次数,当一个对象的引用次数为零时,就将其回收。这样可以及时地释放无用的对象,但是也有一个缺点,就是无法处理循环引用的情况

V8垃圾回收机制:

V8垃圾回收机制是指V8引擎在运行JavaScript代码时,会自动管理内存的分配和释放,从而提高性能和效率1234

V8垃圾回收机制主要有以下特点:

  • 分代式垃圾回收。V8将内存分为新生代和老生代两部分,新生代存放短期存在的对象,老生代存放长期存在的对象。
  • Scavenge算法。V8对新生代内存采用Scavenge算法,即将新生代内存分为两个相等的空间,一个为From空间,一个为To空间。活动对象存储于From空间,当From空间占用到一定程度时,就会触发垃圾回收,将仍然存活的对象复制到To空间,并清除From空间。
  • 标记-清除算法。V8对老生代内存采用标记-清除算法,即先遍历所有的对象并标记活动对象,然后清除没有被标记的对象。
  • 标记-整理算法。由于标记-清除算法会产生内存碎片,所以V8在执行标记-清除算法之前或之后,会进行一次标记-整理操作,即将活动对象向一端移动,并清理掉端边界外的内存。
  • 增量标记。由于全停顿(stop-the-world)会影响用户体验和性能,所以V8在执行老生代垃圾回收时,并不是一次性完成所有的标记工作,而是将标记过程分为若干个小步骤,在每个步骤完成后暂停标记并让应用逻辑执行一会儿再继续。

在javaScript中你知道哪些继承方法,它们的优缺点呢?

JavaScript里面继承的方法有以下几种:

  • 原型链继承:即子类的原型对象指向父类的实例,从而继承父类的属性和方法。优点是简单易实现,缺点是无法传递参数给父类构造函数,且所有子类实例共享同一个原型对象,修改一个会影响其他。

  • 构造函数继承:即在子类构造函数中调用父类构造函数,并使用call或apply改变this指向,从而继承父类的属性。优点是可以传递参数给父类构造函数,且每个子类实例都有自己的属性副本,互不影响。缺点是无法继承父类原型上的属性和方法。

  • 组合继承:即结合原型链继承和构造函数继承,既能继承父类的属性和方法,又能传递参数给父类构造函数,并保持每个子类实例的独立性。优点是较完善地实现了JavaScript中的继承功能,缺点是会调用两次父类构造函数,导致一些冗余。

  • 原型式继承:即使用Object.create()方法创建一个新对象,并将其原型指向一个已有对象,从而实现对已有对象的复用和拓展。优点是不需要创建自定义类型就能实现简单的对象复制和拓展,缺点是无法传递参数给已有对象,并且所有新创建的对象共享同一个原型。

  • 寄生式继承:即在原型式继承的基础上,在新创建的对象上添加一些属性或方法,从而增强这个对象。优点是可以对已有对象进行进一步封装和拓展,缺点是同样无法传递参数给已有对象,并且所有新创建的对象共享同一个原型。

  • 寄生组合式继承:即在组合继承的基础上,在子类原型上添加一个空对象作为中介,并将其原型指向父类原型,从而避免了调用两次父类构造函数并重复创建属性。优点是完美地解决了组合继承中存在的问题,并被认为是最理想最有效率地实现JavaScript中的继承功能。

map 和 forEach 迭代方法的区别?

map 和 forEach 方法的主要区别是:

  • map方法会对数组中的每个元素应用回调函数,并返回一个新的数组,而 forEach 方法不会返回任何东西。
  • forEach 方法可以用来改变原始数组,但这不是它的本意。map 方法则不会改变原始数组。
  • map 方法通常用于将一个集合的元素通过一个函数转换为另一个集合,而 forEach 方法只是对每个元素执行一个动作。
  • forEach 方法不可以被打断,break 和 return 也不行。

数组去重的方式有哪些?

JavaScript中数组去重的方式有很多,下面是一些常见的方法:

  • 使用 Set 去重:Set 数据结构中不能有重复元素,可以将数组转成 Set 类型,再转回数组。
  • 使用 indexOf() 去重:遍历数组,利用 indexOf() 判断元素是否在新数组中存在,不存在则添加到新数组中。
  • 使用对象属性的唯一性去重:遍历数组,利用对象的属性不能相同的特点,如果没有该属性则添加该属性并放入新数组。
  • 使用 Map 方法去重:Map 数据结构类似于对象,也能存储键值对,并且键可以是任意类型。遍历数组,如果 Map 中没有该键,则设置该键并放入新数组。
  • 使用 reduce() 方法去重:reduce() 方法对数组进行累计操作,传入一个回调函数和一个初始值。回调函数接收一个累积值和当前值作为参数,返回一个新的累积值。利用这个特性,可以定义一个空对象或者空 Map 作为初始值,然后判断当前值是否在对象或者 Map 中存在,不存在则添加并放入新数组。

什么是跨域?解决跨域拦截的方法有哪些?

跨域拦截是浏览器为了阻止被脚本攻击而衍生一种防御手段,通常被我们叫做同源策略;通常不做人任何设置的情况下只有协议号,域名,端口号全部相等的情况下才能避开同源策略的拦截。

解决跨域的方法有以下几种:

  • JSONP:利用<script>等标签可以加载跨域的脚本,通过回调函数获取数据。
  • CORS:服务器设置响应头Access-Control-Allow-Origin来允许浏览器加载跨域的资源
  • node代理:利用后端服务器或第三方服务来转发请求和响应,避免浏览器的同源策略限制。
  • postMessage:利用window.postMessage()方法在不同窗口之间传递数据,实现跨文档通信。
  • websocket:
  • nginx反向代理

vue 中的生命周期有哪些,作用是什么?

生命周期钩子是指在vue组件实例创建、挂载、更新或销毁的过程中运行的函数,它们可以让用户在不同的阶段执行自定义的逻辑。

vue中有以下几种生命周期钩子:

  • beforeCreate:在组件实例创建之前调用,此时还没有观察数据或编译模板。

  • created:在组件实例创建之后调用,此时已经观察数据和初始化事件,但还没有挂载到DOM。

  • beforeMount:在组件实例挂载到DOM之前调用,此时已经编译模板,但还没有渲染。

  • mounted:在组件实例挂载到DOM之后调用,此时已经渲染出真实DOM。

  • beforeUpdate:在组件实例更新之前调用,此时可以访问旧的虚拟DOM,并且可以修改数据。

  • updated:在组件实例更新之后调用,此时已经渲染出新的虚拟DOM,并且可以操作真实DOM。

  • beforeUnmount:在组件实例卸载之前调用,此时还没有移除事件监听器或子组件。

  • unmounted:在组件实例卸载之后调用,此时已经移除事件监听器和子组件。

  • activated:在keep-alive组件激活时调用,此时可以访问组件实例并执行相关逻辑。

  • deactivated:在keep-alive组件停用时调用,此时可以清理组件实例或保存状态。

Vue2 和 Vue3 中响应式的区别?

Vue2和Vue3中响应式的区别主要在于使用的数据劫持的方式不同。

Vue2使用的是Object.defineProperty()进行数据劫持,结合发布订阅的方式实现;

  • Vue2 的对象数据是通过 Object.defineProperty 对每个属性进行监听,当对属性进行读取的时候,就会触发 getter,对属性进行设置的时候,就会触发 setter。

  • 总的来说就是通过 Object.defineProperty 监听对象的每一个属性,当读取数据时会触发 getter,修改数据时会触发 setter。

  • 然后我们在 getter 中进行依赖收集,当 setter 被触发的时候,就去把在 getter 中收集到的依赖拿出来进行相关操作,通常是执行一个回调函数。

  • 我们收集依赖需要进行存储,对此 Vue2 中设置了一个 Dep 类,相当于一个管家,负责添加或删除相关的依赖和通知相关的依赖进行相关操作。

  • 在 Vue2 中所谓的依赖就是 Watcher。值得注意的是,只有 Watcher 触发的 getter 才会进行依赖收集,哪个 Watcher 触发了 getter,就把哪个 Watcher 收集到 Dep 中。当响应式数据发生改变的时候,就会把收集到的 Watcher 都进行通知。

  • 由于 Object.defineProperty 无法监听对象的变化,所以 Vue2 中设置了一个 Observer 类来管理对象的响应式依赖,同时也会递归侦测对象中子数据的变化。

Vue3使用的是Proxy代理,使用ref或者reactive将数据转化为响应式数据。

经过上面的解释,我们知道 vue2 的 Object.defineProperty 那么强大,那 Vue3 为何会抛弃它呢?那肯定是因为它存在某些局限性。

主要原因:无法监听对象或数组新增、删除的元素。

Vue2 相应解决方案:针对常用数组原型方法push、pop、shift、unshift、splice、sort、reverse进行了hack处理;提供Vue.set监听对象/数组新增属性。对象的新增/删除响应,还可以new个新对象,新增则合并新属性和旧对象;删除则将删除属性后的对象深拷贝给新对象。

  • Proxy Proxy 是 ES6 新特性,通过第2个参数 handler 拦截目标对象的行为。相较于 Object.defineProperty 提供语言全范围的响应能力,消除了局限性。

局限性:对象/数组的新增、删除、监测 .length 修改、Map、Set、WeakMap、WeakSet 的支持

基本用法:创建对象的代理,从而实现基本操作的拦截和自定义操作。

为什么 vue2 中的 data 数据源得是一个函数?而不能是一个对象?

在 Vue2 中,data 属性是用来存储组件的状态数据的。

在 Vue2 实例中,data 属性可以是一个对象,也可以是一个函数。

但是在组件中,data 属性必须是一个函数,而不是一个对象。

这是因为组件可能会被多次复用,如果 data 属性是一个对象,那么所有复用的组件实例会共享同一个 data 对象,导致数据相互影响和污染。

如果 data 属性是一个函数,那么每个组件实例都会返回一个独立的 data 对象,保证了数据的隔离和安全a

Vue 中 v-if 和 v-show 的区别

v-if和v-show都是Vue中用来控制元素显示和隐藏的指令,但是它们有以下区别:

  • v-if是根据条件来动态添加或删除元素,当条件为false时,元素会被完全移除。
  • v-show只是改变元素的display样式属性,当条件为false时,元素仍然存在于DOM树中。
  • v-if的切换开销比较高,因为涉及到DOM操作。
  • v-show的首次渲染开销比较高,因为需要渲染所有元素。

一般来说,如果元素不需要频繁切换显示和隐藏,可以使用v-if;如果元素需要频繁切换显示和隐藏,可以使用v-show。

vue 中的 computed 、 watch 、 WatchEffect 的区别

computed:

  • 支持缓存,只有依赖数据发生改变,才会重新进行计算

  • 不支持异步,当computed内有异步操作时无效,无法监听数据的变化

  • computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值

  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed

  • 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。

watch:

  • 不支持缓存,数据变,直接会触发相应的操作;

  • watch支持异步;

  • 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;

  • 当一个属性发生变化时,需要执行对应的操作;一对多;

  • 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数:

watchEffect:

  • 跟 computed 一样 watchEffect 也是对出现在里面的数据进行依赖收集进行监听。

  • 在生命周期执行的同时也会执行一次,相当于它会默认执行一次

Vue 中的 $nextTick

$nextTick是Vue中的一个方法,用来在下次DOM更新循环结束之后执行一个回调函数。

由于Vue的DOM更新是异步的,也就是说当你修改了响应式数据时,视图不会立即更新,而是会等待同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

这样做的好处是可以提高性能,避免重复渲染。但是有时候你可能需要在数据变化后立即获取更新后的DOM,比如执行一些动画或者滚动等操作。这时候你就可以使用$nextTick来确保在DOM更新完成后执行你的回调函数。 例如:

// 修改数据
this.message = 'Hello'
// DOM 还没有更新
this.$nextTick(function () {
  // DOM 更新了
  // 在这里可以获取到更新后的DOM
})

vue3 相对于 Vue2 中 diff 算法的优化

diff算法是Vue中用来比较新旧虚拟DOM树的差异,并更新视图的算法。

Vue3相对于Vue2在diff算法上做了以下优化:

  • Vue2中的diff算法是全量对比,也就是说每个节点不论是静态的还是动态的都会一层一层比较,这就浪费了大部分时间在对比静态节点上。

  • Vue3中的diff算法引入了静态提升和PatchFlag的概念,可以跳过静态节点和动态节点中不需要更新的部分,只对比和更新有变化的部分,从而提高性能。

  • Vue2中的diff算法使用了双端比较,也就是说在同一层级中,会同时从头和尾两端开始比较,找到相同或不同的节点,并进行相应的操作。

  • Vue3中的diff算法使用了快速比较,也就是说在同一层级中,只从头开始比较,找到第一个不同的节点后,就停止比较,并将后面所有节点都替换掉。这样做可以避免多余的移动操作,并且可以支持乱序更新。

vue 中组件传值的方法有哪些?

Vue2和Vue3中组件传值的方法有以下几种:

  • 父传子:父组件通过绑定属性的方式将数据传递给子组件,子组件通过props接收数据。
  • 子传父:子组件通过$emit触发自定义事件,父组件通过v-on监听事件并获取数据。
  • 兄弟传值:兄弟组件之间可以通过共同的祖先组件或者Vuex来实现数据的共享和同步。
  • provide/inject:祖先组件可以通过provide提供数据,后代组件可以通过inject注入数据,实现跨层级的通信。
  • EventBus:可以创建一个空的Vue实例作为事件总线,用来在任意两个组件之间发送和接收事件和数据。
  • Vuex 或者 Pinia
  • $children 和 $parents

Vue 中的指令有哪些?作用都是什么?

Vue内置了以下指令:

  • v-if:根据条件触发过渡效果。
  • v-else:表示v-if或v-if/v-else-if链的“else块”。
  • v-else-if:表示v-if的“else if块”。
  • v-show:根据条件切换元素的显示/隐藏状态。
  • v-for:基于源数据多次渲染元素或模板块。
  • v-on:绑定事件监听器。
  • v-bind:动态地绑定一个或多个特性,或一个组件 prop 到表达式。
  • v-model:在表单控件或者组件上创建双向绑定。
  • v-slot:提供具名插槽或需要接收 prop 的插槽。
  • v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
  • v-cloak:这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。

Vue 在进行 v-for 时为什么要加 :key?

key是一个特殊的属性,用于给每个v-for中的元素绑定一个唯一的标识符。这样可以让Vue跟踪每个节点的身份,从而重用和重新排序现有元素。

当Vue更新一个列表时,默认使用“就地更新”的策略。如果数据项的顺序发生了变化,Vue不会移动DOM元素来匹配数据项的顺序,而只是简单地复用此处的每个元素,并确保它在特定索引下显示已被渲染过的每个元素。

这种默认模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。

理想的 key 值是每项都有唯一 id 。 这个特殊的属性相当于 Vue 1.x 的 track-by ,但它的工作方式类似于一个属性,所以你需要用 v-bind 来绑定动态值(在这里使用简写)

<li v-for="item in items" :key="item.id">
  {{ item.message }}
</li>

在 vue 中怎么自定义指令?

Vue 自定义指令是一种将数据的变化映射为 DOM 行为的机制。你可以用 Vue.directive (id, definition) 方法注册一个全局自定义指令,也可以在组件中使用 directives 选项注册一个局部自定义指令。

自定义指令有以下几个钩子函数:

  • bind: 只调用一次,当指令第一次绑定到元素时调用。
  • inserted: 被绑定元素插入父节点时调用。
  • update: 所在组件的 VNode 更新时调用。
  • componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind: 只调用一次,当指令与元素解绑时调用。

vue 双向数据绑定原理,双向绑定怎么实现?

说到 Vue 的双向数据绑定原理,大多数人估计会想到响应式原理;

  • 可是双向数据绑定跟响应式是有一定区别的,响应式是响应式,双向绑定是双向绑定;

  • Vue 的响应式数据是单向数据流哦,是当 js 中数据源的数据进行更新后,进行依赖收集带动视图容器中的数据进行更新,只是逻辑层到视图层的单向数据流;

  • 而双向数据绑定则是逻辑层的改变可以带动视图层的数据改动,而视图层的数据也可以带动逻辑层的数据改动;这才是双向绑定,双向数据流;

  • 双向绑定是由两方面构成,一方面就是响应式原理中的数据劫持,收集逻辑层的依赖监听,来更新视图;另一方面就是解析者,解析视图层标签内的数据流,对其进行监听,当视图层的 value 进行改变时,反馈到逻辑层,set 逻辑层中的数据;

那么,双向绑定怎么实现?

  • 其实实现双向绑定数据的方法还是挺多的,最简单的无非是 v-model ,它是 vue 提供出来的一个语法糖指令;可以说是 v-bind 和 v-on 的结合体,双向监听;

vuex 和 pinia 的区别

vuex和pinia都是Vue.js的状态管理库,但它们有一些区别:

  • vuex是Vue核心团队推荐的状态管理库,它高度关注应用程序的可扩展性、开发人员的工效和信心。pinia是一个轻量级的状态管理库,它使用Vue 3中的新反应系统来构建一个直观且完全类型化的状态管理库。
  • vuex有mutation、action、getter等概念,需要遵循一定的规范和流程来修改状态。pinia没有mutation,只有state、getter、action(同步或异步),可以更灵活地修改状态。
  • vuex需要在每个组件中通过mapState、mapGetters等辅助函数来访问状态和方法。pinia可以直接通过模块导入导出的方式来访问状态和方法,使状态的来源更加清晰可见。

vue.use() 做了什么?

vue.use()的作用是通过全局方法 Vue.use() 使用插件。插件通常用来为 Vue 添加全局功能,可以在你调用 new Vue() 启动应用之前完成。vue.use()接收函数或者一个包含install属性的对象为参数,如果参数带有install就执行install, 如果没有就直接将参数当install执行, 第一个参数始终为vue对象。

为什么 vue3 抛弃了 vue2 的选项式 API,而选择了 Vue3 中的组合式 API

Vue3 没有抛弃选项式 API,而是提供了一种新的组织代码逻辑的写法,叫做组合式 API。组合式 API 的特点是将特定功能相关的所有东西都放到一起维护,比如响应式数据、操作方法、watch 监听、computed 计算属性等。这样可以提高代码的可复用性、可读性和维护性。

Vue3 采用组合式 API 的主要原因是为了在大型组件中提高代码逻辑的可复用性。组合式 API 使我们可以使用函数而不是声明选项的方式书写 Vue 组件,这样可以更好地逻辑复用和代码组织。组合式 API 不是取代选项式 API,而是增强它,Vue3 对两种 API 都支持。

怎么解决 Vue 和 React 这种单页应用框架首屏加载时间过长的问题?

Vue 和 React 单页应用(SPA)的首屏加载时间过长的问题,是由于 SPA 需要加载大量的资源文件,导致页面渲染被阻塞。为了解决这个问题,可以采用以下一些优化方法:

  • 使用 CDN 加速资源文件的加载
  • 使用路由懒加载,按需加载组件和路由
  • 使用 gzip 压缩代码,减少文件体积
  • 使用 SSR 技术,服务端渲染首屏内容
  • 在首屏显示一个 loading 动画,提高用户体验

前端性能优化手段有哪些?

前端性能优化有很多手段,根据不同的场景和需求,可以选择合适的方法来提升网站的加载速度和用户体验。一些常见的前端性能优化手段有:

  • 减少 HTTP 请求,合并资源文件,使用雪碧图,节流防抖,强缓存,协商缓存等
  • 使用 HTTP2 协议,利用多路复用、头部压缩、服务器推送等特性
  • 使用服务端渲染(SSR),减少客户端渲染的时间和资源消耗
  • 使用 CDN 加速静态资源的加载,减少网络延迟
  • 将 CSS 放在文件头部,JavaScript 文件放在底部,避免阻塞渲染
  • 使用字体图标代替图片图标,减少图片的大小和请求数量
  • 善用缓存,利用浏览器缓存、Service Worker 缓存等技术
  • 压缩文件,使用 gzip、webpack 等工具压缩代码和图片

什么是 HTTP ?HTTP 和 HTTPS 的区别?

HTTP是一种基于TCP/IP协议的应用层协议,用于浏览器和服务器之间的通信。它的发展历史可以分为以下几个阶段:

  • HTTP/0.9:1991年发布,只有GET命令,不支持数据包传输,只能传输纯文本。
  • HTTP/1.0:1996年发布,增加了POST、HEAD等命令,支持多种数据格式,引入了状态码、缓存等概念。
  • HTTP/1.1:1997年发布,成为最广泛使用的版本,优化了性能和安全性,增加了持久连接、分块传输、管道化等特性。
  • HTTP/2.0:2015年发布,基于Google的SPDY协议,改进了传输效率和并发能力,采用了二进制格式、多路复用、头部压缩等技术。
  • HTTP/3.0:2018年提出草案,基于Google的QUIC协议,使用UDP代替TCP作为传输层协议,解决了TCP的队头阻塞问题。

HTTPS是HTTP的安全版本,它使用了SSL/TLS协议对数据进行加密处理,保护了通信的隐私和完整性。HTTPS和HTTP的主要区别有以下几点:

  • HTTPS需要申请证书,一般需要花费一定的费用,而HTTP不需要。
  • HTTPS使用443端口,而HTTP使用80端口。
  • HTTPS可以有效防止中间人攻击,而HTTP容易被窃听、篡改或伪造。

什么是对称加密?什么是非对称加密?

对称加密和非对称加密是两种常见的加密方式,它们的主要区别如下:

  • 对称加密使用同一个密钥来进行加密和解密,非对称加密使用一对密钥,公钥用来加密,私钥用来解密。
  • 对称加密的速度比非对称加密快得多,非对称加密的计算量较大,效率较低。
  • 对称加密需要双方事先共享同一个秘钥,存在秘钥传输和管理的问题,非对称加密不需要共享秘钥,只需公开公钥即可。

比较常见的状态码有哪些?

你好,这是Bing。根据我找到的信息,请求里返回的状态码有以下几种:

  • 1xx:表示服务器端已经接受了请求,但还未处理完成。

  • 2xx:表示请求已经被服务器端成功接收和处理,最常见的有200(请求成功)、201(已创建)和206(部分内容)。

  • 200 (OK) 请求成功,返回想要的数据(正常状态);

    • 201 : 已创建,请求成功且服务器创建了新资源;
    • 202 : 已接受,但尚未处理;
    • 203 : 非授权信息,服务器处理了请求,只是返回的信息来自于第三方;
    • 204 : 无内容,服务器成功处理请求,但没有返回任何内容;
    • 205 : 重置内容,同204,但要求请求者重置文档视图
    • 206 : 服务器成功处理部分GET请求
  • 3xx:表示路径被服务器端重定向到了一个新的URL,最常见的有301(永久重定向)、302(临时重定向)和304(未修改)。

    • 301:表示永久重定向,请求的资源已经被移动到了新的地址。
    • 302:表示临时重定向,请求的资源暂时被移动到了新的地址。
    • 304:表示资源未修改,客户端可以使用缓存中的资源。
  • 4xx:表示客户端的错误,如请求错误、禁止访问、未经授权等,最常见的有400(错误请求)、401(未经授权)、403(禁止访问)和404(请求错误)。

    • 400: 错误请求,语义错误,请求无法被服务器理解,或者请求参数有误;
    • 401: 未授权,请求需要请求者验证;
    • 403: 禁止,服务器拒绝该请求;
    • 404: 未找到,找不到请求网页;
  • 5xx:表示服务器端的错误,如内部服务器错误、服务不可用等,最常见的有500(内部服务器错误)、502(网关错误)和503(服务不可用)。

    • 500: 服务器内部错误,服务器代码报错,无法完成请求;
    • 502: 错误网关,服务器作为网关或代理,从上游服务器收到无效响应;
    • 503: 服务器不可用,服务器目前无法使用(由于超载或停机维护),通常,这只是暂时状态;

TCP 请求和 UDP 请求的区别

TCP请求和UDP请求的区别有以下几点:

  • TCP是面向连接的协议,即发送数据之前必须和对方建立可靠的连接;UDP是无连接的协议,即发送数据之前不需要建立连接。
  • TCP提供可靠的服务,可以进行丢包时的重发控制、次序控制和拥塞控制;UDP尽最大努力交付,即不保证可靠交付,也没有这些控制机制。
  • TCP是面向字节流的协议,只支持点对点通信;UDP是面向数据报文的协议,支持一对一、一对多、多对多通信。
  • TCP报文首部20个字节,UDP首部8个字节。
  • TCP是全双工通讯。

TCP为什么要进行四次挥手?三次行不行?

TCP为什么要四次挥手,是因为TCP是全双工的,通信是双向的,A到B是一个通道,B到A又是另一个通道。当一方发送FIN请求断开连接时,只是表示它不再发送数据了,但还可以接收数据,这就是半关闭。

三次挥手行不行呢?其实理论上也可以的。如果服务器端收到客户端的FIN后,同时也要关闭连接,那么就可以把ACK和FIN合并到一起发送,节省了一个包。但这样做的前提是服务器端没有数据要发送给客户端了。如果服务器端还有数据要发送给客户端,那么就需要先回复ACK表示收到了客户端的FIN请求,并继续发送数据直到完成后再发送FIN请求断开连接。

计算机网络的七层模型分别是哪些?

计算机网络的七层模型,也称为OSI(Open System Interconnection)参考模型,是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系。它自上而下分别是:

  • 应用层:提供用户界面和网络服务,如HTTP、FTP、SMTP等.
  • 表示层:负责数据格式转换、加密解密、压缩解压等.
  • 会话层:负责建立、管理和终止会话,如RPC、SSL等.
  • 传输层:负责端到端的可靠传输,如TCP、UDP等.
  • 网络层:负责路由选择和数据报文转发,如IP、ICMP等.
  • 数据链路层:负责物理地址寻址和数据帧的封装与解封装,如MAC、PPP等.
  • 物理层:负责物理媒介的接口标准和电气特性,如RJ45、光纤等.

怎么处理大文件上传?

大文件上传是指上传超过几十兆或者几百兆的文件,它可能会遇到以下问题:

  • 文件上传超时:原因是前端请求框架或者代理/网关限制了最大请求时长。
  • 文件大小超限:原因是后端对单个请求大小做了限制。
  • 上传耗时久:原因是网络带宽有限,大文件需要更多的时间传输。
  • 上传失败后需要重传:原因是没有断点续传的机制,导致之前的进度丢失。

为了解决这些问题,可以采用以下方案:

  • 分片上传:将大文件分割成小块,每次只上传一小块,这样可以避免超时和超限的问题,也可以实现断点续传。
  • 压缩上传:将大文件进行压缩处理,减少文件大小,这样可以加快上传速度和节省带宽。
  • 使用专业的大文件传输工具:如奶牛快传等,它们提供了便捷的方式来分享和下载大文件。

分片上传实现:

  • 将文件按一定的分割规则(静态或动态设定,如手动设置20M为一个分片),用slice分割成多个数据块。
  • 为每个文件生成一个唯一标识Key,用于多数据块上传时区分所属文件。
  • 使用FormData对象将每个数据块和Key封装成一个表单项,通过XMLHttpRequest对象发送给服务端。
  • 服务端接收到每个数据块后,根据Key和Part编号存储在临时目录中。
  • 所有分片上传完成后,客户端通知服务端校验合并标识为Key的所有分片为一个最终文件。

webpack 和 vite 的区别?

webpack和vite都是前端构建工具,但它们有一些区别:

  • webpack是先打包再启动开发服务器,vite是直接启动开发服务器,然后按需编译依赖文件。这样可以提高启动速度和节省内存。
  • webpack使用node.js来编译代码,vite使用esbuild(用Go语言编写)来预构建依赖。
  • webpack在热更新方面,当某个模块内容改变时,会重新将该模块的所有依赖重新编译。vite在热更新方面,当某个模块内容改变时,只让浏览器去重新请求该模块即可。这样可以提高更新速度和减少资源消耗。

webpack 中的 plugin 和 loader 分别是什么作用?

webpack是一个模块打包工具,它可以将多个模块和资源打包成一个或多个文件。webpack中的plugin和loader是两种不同的扩展机制,它们有以下区别:

  • loader是用来处理模块的,它可以对模块进行转换、编译、压缩等操作。loader可以通过在require()语句中使用loadername!前缀来激活,或者通过webpack配置中的rules属性来自动应用。
  • plugin是用来扩展webpack功能的,它可以在整个构建过程中监听各种事件,并执行相应的操作。plugin可以通过在webpack配置中的plugins属性中添加插件实例来使用。

如何用 webpack 做到性能优化?

webpack性能优化可以从两个方面进行:提升webpack编译速度和提升输出代码的质量。下面是一些常用的优化技巧:

  • 提升webpack编译速度:

    • 升级webpack版本,使用最新的特性和优化。
    • 使用多进程/多实例构建,利用 happypack 或者 thread-loader 来并行执行任务。
    • 使用缓存,利用 cache-loader 或者 hard-source-webpack-plugin 来缓存模块和依赖,避免重复构建。
    • 使用DllPlugin,将一些不经常变动的库提前打包成动态链接库,减少打包时间。
    • 减少文件搜索范围,配置 resolve.modules、resolve.mainFields 和 resolve.extensions 来缩小模块查找的范围。
  • 提升输出代码的质量:

    • 使用Tree Shaking,移除未引用的代码,减少代码体积。
    • 使用Code Splitting,按需加载或者并行加载资源,避免加载过多无用代码。
    • 使用Preloading/Prefetching,预先加载或者预取一些将来可能需要的资源,提高用户体验。
    • 使用Lazy Loading,懒加载一些非关键资源,降低首屏渲染时间。
    • 使用CompressionPlugin,对输出的文件进行压缩处理。

结尾

如果面试过程中碰到什么新的问题,后续还会加上来,暂时大概就这么多啦,希望对你们有所帮助!