前端面试题详解整理9|状态码以及浏览器缓存 es6新增 垂直居中weakMap 垃圾回收机制,回流与重绘,具体触发以及优化

51 阅读22分钟

Cider前端一面50min大二菜鸟

1.自我介绍
2.在校情况以及项目情况,如何接触前端以及学习路线
3.性能优化
4.回流与重绘,具体触发以及优化
回流(reflow)和重绘(repaint)是浏览器渲染页面时的两个重要概念:

  1. 回流(Reflow)

    回流是指浏览器重新计算并布局页面中的所有元素的过程。当页面布局发生改变时(如添加、删除、修改元素的位置、大小、内容等),浏览器需要重新计算页面中所有元素的大小和位置,然后重新绘制页面。回流是一种比较消耗性能的操作,特别是在大型复杂页面上,会导致页面的重新渲染,降低页面的性能。

  2. 重绘(Repaint)

    重绘是指当页面中元素的样式(如颜色、背景色、边框等)发生改变,但不影响其布局的情况下,浏览器只需要重新绘制受影响的部分而不需要重新布局页面。重绘的消耗较回流要小,但仍然会影响页面的性能。

触发回流和重绘的情况包括:

  • 修改 DOM 结构:添加、删除、修改元素。
  • 修改元素的布局属性:如修改元素的尺寸(width、height)、位置(top、left)、边距(margin)、边框(border)等。
  • 修改元素的内容:包括修改文本内容、修改图片大小等。
  • 修改元素的样式:如修改颜色、背景色、字体等。

优化回流和重绘的方法包括:

  • 使用 CSS3 动画替代 JavaScript 动画:CSS3 动画使用 GPU 加速,可以减少回流和重绘的次数。
  • 使用 transform 和 opacity:transform 和 opacity 属性只会触发重绘,不会触发回流,可以减少性能开销。
  • 避免频繁操作 DOM:尽量减少对 DOM 结构的修改,可以通过文档片段(DocumentFragment)来批量处理 DOM 操作。
  • 使用事件委托:将事件处理程序绑定在父元素上,通过事件冒泡机制来处理子元素的事件,减少事件处理程序的数量。
  • 对于复杂动画,使用 requestAnimationFrame:requestAnimationFrame 方法可以让浏览器在下一次重绘前执行指定的函数,可以提高动画的性能和流畅度。
  • 优化 CSS 选择器:尽量使用高效的 CSS 选择器,减少匹配的元素数量,以减少样式计算的开销。
  • 使用 CSS will-change 属性:will-change 属性可以提前告诉浏览器某个元素会发生改变,可以让浏览器优化渲染过程。

通过以上方法可以有效地减少页面的回流和重绘,提升页面的性能和用户体验。

5.h5新增api,标签
HTML5 带来了许多新的 API 和标签,以增强 Web 应用的功能和性能。以下是一些 HTML5 新增的 API 和标签:

  1. API

    • Canvas API:提供了在页面上绘制图形、动画和图像的能力。
    • WebSocket API:允许浏览器和服务器进行双向通信,以实现实时更新的数据。
    • Web Worker API:允许在后台线程中运行 JavaScript 代码,以避免阻塞主线程。
    • Web Storage API:提供了一种在客户端存储数据的方式,包括 sessionStorage 和 localStorage。
    • Geolocation API:允许网页获取用户的地理位置信息。
    • File API:允许网页访问用户本地文件系统,以读取和处理文件。
    • IndexedDB API:提供了一种客户端数据库,用于在浏览器中存储大量结构化数据。
    • Service Worker API:允许在浏览器和网络之间建立一个代理层,以便拦截和处理网络请求,实现离线访问和推送通知等功能。
    • Notification API:允许网页向用户发送通知消息。
  2. 标签

    • <video>:用于在页面上嵌入视频内容。
    • <audio>:用于在页面上嵌入音频内容。
    • <canvas>:用于在页面上绘制图形和动画。
    • <section>、<article>、<header>、<footer>、<nav> 等语义化标签:用于更好地描述文档结构和内容。
    • <input type="date">、<input type="time"> 等新的表单输入类型:用于支持日期、时间等新的输入类型。
    • <details><summary>:用于创建可折叠的内容区域。
    • <progress>:用于显示任务的完成进度。
    • <meter>:用于表示数值范围和分级。
    • <figure><figcaption>:用于标记图片和其标题。

HTML5 的新增 API 和标签为开发者提供了更多的选择和功能,能够更轻松地实现丰富、交互性强的 Web 应用。

6.css3新增
CSS3 引入了许多新的特性和功能,以增强网页的样式和布局。以下是一些 CSS3 新增的特性:

  1. 选择器

    • 新增通用选择器(Universal Selector):使用 * 符号表示,匹配所有元素。
    • 新增属性选择器(Attribute Selectors):如 [attr][attr="value"][attr^="value"][attr$="value"][attr*="value"],可以根据元素的属性来选择元素。
    • 新增伪类选择器(Pseudo-classes):如 :nth-child():nth-of-type():not():focus-within 等,允许根据元素的状态或位置来选择元素。
    • 新增伪元素选择器(Pseudo-elements):如 ::before::after::first-line::first-letter 等,允许在元素的特定位置插入内容。
  2. 盒子模型

    • 新增盒子阴影(Box Shadow):通过 box-shadow 属性可以为元素添加阴影效果。
    • 新增圆角边框(Border Radius):通过 border-radius 属性可以为元素添加圆角边框。
    • 新增边框图片(Border Image):通过 border-image 属性可以为元素的边框添加图片。
    • 新增多列布局(Multi-column Layout):通过 column-countcolumn-width 等属性可以实现多列文本布局。
  3. 背景和渐变

    • 新增渐变背景(Gradient Background):通过 linear-gradient()radial-gradient() 函数可以为元素添加渐变背景。
    • 新增背景尺寸(Background Size):通过 background-size 属性可以控制背景图片的尺寸和平铺方式。
    • 新增多重背景(Multiple Background):通过 background 属性可以为元素设置多个背景图片。
  4. 动画和过渡

    • 新增过渡效果(Transitions):通过 transition 属性可以实现元素在状态改变时的平滑过渡效果。
    • 新增动画效果(Animations):通过 @keyframes 规则和 animation 属性可以实现自定义的动画效果。
  5. 变换和变形

    • 新增 2D 变换(2D Transforms):通过 transform 属性可以实现元素的位移、旋转、缩放和倾斜等变换效果。
    • 新增 3D 变换(3D Transforms):通过 transform 属性和 perspective 属性可以实现元素的三维变换效果。
  6. Flexbox 布局

    • 新增 Flexbox 布局(Flexible Box Layout):通过 display: flex; 属性可以实现灵活的盒子布局,包括水平居中、垂直居中、等高列布局等。
  7. 网格布局

    • 新增网格布局(Grid Layout):通过 display: grid; 属性可以实现更复杂的网格布局,包括自适应列数、自适应行高、单元格对齐等。
  8. 字体和文本效果

    • 新增 @font-face 规则:允许网页加载自定义字体文件。
    • 新增文本阴影(Text Shadow):通过 text-shadow 属性可以为文本添加阴影效果。

这些 CSS3 新增的特性和功能使得开发者能够更轻松地实现丰富、交互性强的网页样式和布局效果。

7.let,const,var  为啥后者变量提升,前者没
在 JavaScript 中,letconstvar 是声明变量的三种方式,它们在变量提升方面有所不同:

  1. var 声明

    使用 var 声明的变量会被提升到当前作用域的顶部,这意味着在变量声明之前就可以访问到该变量,但变量的赋值操作会留在原来的位置。这就导致了著名的"变量提升"现象。

    示例:

    console.log(x); // undefined
    var x = 10;
    

    上述代码会输出 undefined,而不是抛出 ReferenceError 错误。这是因为变量 x 被提升到作用域的顶部,但赋值操作仍然保留在原来的位置。

  2. letconst 声明

    使用 letconst 声明的变量不会被提升到当前作用域的顶部,而是在代码块中进行声明,只有在声明之后才能访问该变量。这种行为称为"暂时性死区"(Temporal Dead Zone,TDZ),意味着在变量声明之前访问该变量会导致 ReferenceError 错误。

    示例:

    console.log(y); // ReferenceError: y is not defined
    let y = 20;
    

    上述代码会抛出 ReferenceError 错误,因为在变量 y 被声明之前就尝试访问了该变量,处于暂时性死区。

总的来说,var 声明的变量会被提升到作用域的顶部,但赋值操作保留在原来的位置,而 letconst 声明的变量不会被提升,而是在声明的位置开始生效,这样可以更好地避免一些潜在的错误。 var、let、const是JavaScript声明变量的三种方式,其中let和const是ES6为JavaScript新增的两种方式,用法与var类似。曾经问到它们三者之间的区别时,答案之一就有let和const不存在变量提升。但是经过一番调查研究,发现这个答案并不是那么准确。

1. 变量提升

众所周知,var命令会发生变量提升的现象,即变量可以在声明前使用:

javascript
复制代码
console.log(a); // undefined
var a = 1;

这种现象还是很奇怪的,因为按照正常的逻辑,变量应该在声明语句之后才可以使用。其实,JavaScript与其他语言一样,都需要经历编译和执行阶段。但JavaScript编译器在编译阶段会搜集所有的变量声明,并将变量声明提前到变量当前所在作用域的顶部,也就是说,变量声明在编译阶段已经执行,而赋值则在执行阶段执行到对应语句时才会执行。所以才会出现所谓的“变量提升”。上面代码等价于:  

javascript
复制代码
var a;
console.log(a); // undefined
a = 1;

为什么要强调当前呢?因为ES5分为全局作用域和函数作用域,不同作用域中同名变量互不影响。例如:

javascript
复制代码
console.log(b); // ReferenceError: b is not defined
function foo () {
    console.log(b); // undefined
    var b = 1;
}
foo()

当代码执行时,在函数作用域内的变量b会被提升到当前作用域的顶部,也就是foo函数内的顶部,而不是整体代码的顶部,所以函数内输出undefined,而函数外部不存在变量b,所以会报ReferenceError错误。  

隐式全局变量不会被提升

JavaScript
复制代码
function foo () {
    console.log(b) // ReferenceError
    b = 1
    console.log(b) // 1
}
foo()
console.log(b) // 1

使用var声明变量,在函数内部是局部变量,在函数外部是全局变量,没有使用var声明的变量,在函数内部或外部都是全局变量,但如果是在函数内部声明,也叫隐式全局变量,在函数外部使用之前需要先调用方法,告知系统声明了全局变量后方可在函数外部使用。  

另外,函数声明也会提升

javascript
复制代码
foo(); // 1
function foo () {
    console.log(1)
}

函数表达式不会被提升

javascript
复制代码
foo(); // TypeError: foo is not a function
var foo = function () {
    console.log(1)
}

这是因为JavaScript编译器会在编译阶段优先读取函数声明的代码,以确保函数能够被引用到;而对于函数表达式,只有在执行到相应的语句时才进行解析。也可以这么理解,上述代码相当于声明了一个变量foo,然后把函数赋值给变量foo,而变量提升在最开始也说过是将变量的声明提升到顶部,赋值代码留在原地,所以函数表达式不会被提升。等价于:

javascript
复制代码
var foo;
foo();
foo = function () {
    console.log(1)
}

函数提升会优先于变量提升

javascript
复制代码
console.log(foo); // [Function: foo]
var foo = 10;
function foo () {}

上述代码等价于:

javascript
复制代码
function foo () {}
var foo;
console.log(foo);
foo = 10;

同名函数和变量为什么没有被覆盖呢?  

这是因为对于同名的变量声明,Javascript采用的是忽略原则,后声明的会被忽略。对于同名的函数声明,Javascript采用的是覆盖原则,先声明的会被覆盖。对于同名的函数声明和变量声明,采用的是忽略原则,为了确保函数能够被引用到,在提升时函数声明会提升到变量声明之前,变量声明会被忽略,但是变量赋值以后会被覆盖。  

同名变量:

javascript
复制代码
//解析前
var a = 1;
var a =2;
// 解析后
var a;
var a; // 被忽略
a = 1;
a = 2;

同名函数:

javascript
复制代码
function foo () {
    console.log(1)
}
function foo () { // 覆盖前一个
    console.log(2)
}
foo(); // 2

同名函数和变量:

javascript
复制代码
// 解析前
console.log(foo); // [Function: foo]
var foo = 10;
function foo () {}
console.log(foo); // 10
// 解析后
function foo () {}
var foo; // 被忽略
console.log(foo);
foo = 10;
console.log(foo); //10

2. let和const

ES6中,为了纠正“变量提升”这一奇怪现象,let和const改变了语法行为,let和const所声明的变量必须要在声明后使用,否则便会报错:

javascript
复制代码
console.log(a); // ReferenceError
let a = 1;

另外,let和const实际上为JavaScript新增了块级作用域的概念。通过let或const声明的变量只能在命令所在的代码块内有效。

javascript
复制代码
let a = 1;
if (true) {
    a = 2; // ReferenceError;
    let a = 3;
}

在上述代码中,虽然if代码块外存在变量a,但是if代码块内let有声明了一个变量a,导致变量a被绑定在这个块级作用域内,不受外部的影响,所以在if代码块内部let声明前使用就会报错。同时,虽然let和const在相同作用域下不允许重复声明,但是由于块级作用域的存在,内层作用域不受外层作用域的影响,所以在不同作用域下可以定义同名变量。

3. let和const存在变量提升么?

弄明白了前两个大概念以后,正式来看看这个问题,通过标题2中的例子可以看出,在let声明变量前使用该变量,会抛出ReferenceError的错误,那么就说明let和const不会被提升么?  

其实,let和const是会被提升的,准确的说是创建被提升了,但是初始化没有被提升。  

根据ECMA-262中13.3.1的NOTE中所述:let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.  

这段话的大概意思就是let和const定义了变量执行的词法环境,变量会在这个环境实例化时被创建,但是在变量的词法绑定之前不允许以任何方式对其进行访问......如果let声明的变量初始化时没有词法绑定,则分配一个未定义的值undefined,同时MDN上关于let介绍中也说到 var 和 let 的不同之处在于后者是在编译时才初始化。(词法环境应该是指上下文环境,而词法绑定应该就是赋值操作。)

也就说可以这么理解,let声明变量分为三部分:1.创建,2.初始化,3.赋值。 创建环节在当前环境实例化时完成。而通过const命令声明的是一个常量,一旦声明,常量的值(内存地址不能改变)就不能改变  ,所以const声明必须立即进行初始化,不能留到以后赋值,所以const声明变量分为两部分:1.创建,2.初始化,没有赋值操作,相当于把初始化赋值整合成了一步,在初始化的时候进行赋值。  

总结:

根据上述ECMA-262中13.3.1和MDN文档还有标题1中所讲,通过var声明的变量,变量提升时相当于把创建和初始化进行了提升,没有提升赋值操作,可以理解为var命令声明变量其实为两部分:第一部分创建的同时进行初始化,第二部分赋值。  所以标题1中的代码会输出undefined:

l
javascript
复制代码
// 通过var声明的变量,创建和初始化都进行提升,不提升赋值操作,所以被提升后初始化为undefined
console.log(a); // undefined
var a = 1;

let和const仅仅提升了变量的创建,初始化及赋值操作都没有进行提升

javascript
复制代码
if (true) {
    a = 2; // ReferenceError;  
    let a = 3; 
}

为了理解方便,可以将上述代码拆分成如下几步:

javascript
复制代码
    if (true) {
       // 此时a的创建已经被提升到了if代码块内的顶部
       a = 2; // ReferenceError;  //此时对a进行赋值,由于a仅仅被创建,还没有初始化,所以会报错,a is not defined
       let a; // 完成a的初始化,根据ECMA-262 此时a为undefined
       a = 3; // 完成对a的赋值操作。
   }

而let从创建被提升到初始化这中间的部分,就是我们平常所说的暂时性死区(TDZ) ,即在使用let命令声明变量初始化之前,该变量都是不可用的。

javascript
复制代码
   ...
    if (true) {
       // a的创建被提升,TDZ的开始
       a = 2; // ReferenceError;  
       let a// 完成a的初始化,TDZ的结束
       a = 3; // 完成对a的赋值操作。
   }

作者:真相只有一个
链接:juejin.cn/post/699367…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

8.说一下箭头函数
9.weakMap 垃圾回收机制
WeakMap 是 JavaScript 中的一种数据结构,它类似于 Map,但有一些区别,其中最显著的区别是其键(key)只能是对象,并且键所引用的对象在 WeakMap 中是弱引用的。这意味着如果一个对象是 WeakMap 中的键,并且没有其他地方引用它,那么该对象将被垃圾回收,从而使 WeakMap 中的键值对自动被清除。

垃圾回收(Garbage Collection)是 JavaScript 运行时的一项重要任务,它负责回收不再使用的内存空间,以便提高内存利用率和性能。在使用 WeakMap 时,垃圾回收机制的工作原理如下:

  1. 当一个对象作为 WeakMap 的键时,该对象的引用计数不会增加,即使它被作为键存储在 WeakMap 中。
  2. 如果一个对象作为 WeakMap 的键,并且没有其他地方引用它,那么该对象会被视为不再需要,进而被垃圾回收器回收。
  3. 当垃圾回收器回收了一个对象时,与该对象相关联的 WeakMap 中的键值对也会被自动清除,因为这些键对应的对象已经不存在了。

使用 WeakMap 的主要场景是在需要与对象关联的额外数据时,但又不希望这些数据阻止对象被垃圾回收时。典型的应用场景包括缓存、存储私有数据等。

需要注意的是,WeakMap 中的键必须是对象,不能是原始值(如字符串、数字等),而且 WeakMap 本身是无法迭代的,因此无法获取其所有键或值。WeakMap 中的键值对也不会被枚举,因此不能通过迭代器或 forEach 方法进行遍历。

10.垂直居中
在 CSS 中实现垂直居中有多种方法,以下是其中的几种常见方法:

  1. 使用 Flexbox 布局

    使用 Flexbox 布局是一种简单且强大的方法来实现垂直居中。可以将父容器设置为 display: flex;,然后使用 align-items: center; 属性将子元素垂直居中。

    .container {
        display: flex;
        align-items: center; /* 垂直居中 */
    }
    
  2. 使用表格布局

    将父容器设置为 display: table;,然后将子元素设置为 display: table-cell; vertical-align: middle;,即可实现垂直居中。

    .container {
        display: table;
    }
    
    .item {
        display: table-cell;
        vertical-align: middle; /* 垂直居中 */
    }
    
  3. 使用绝对定位和 transform 属性

    将子元素设置为绝对定位,并使用 top: 50%;transform: translateY(-50%); 属性来实现垂直居中。

    .item {
        position: absolute;
        top: 50%; /* 相对于父元素的上边距为50% */
        transform: translateY(-50%); /* 将元素向上平移自身高度的一半 */
    }
    
  4. 使用 Grid 布局

    如果你使用了 CSS Grid 布局,你可以将子元素的对齐方式设置为 align-self: center; 来实现垂直居中。

    .container {
        display: grid;
    }
    
    .item {
        align-self: center; /* 垂直居中 */
    }
    

以上是几种常见的实现垂直居中的方法,选择适合你需求的方法即可。

11.es6新增
ES6(ECMAScript 2015)是 JavaScript 的一个重要版本,引入了许多新的语言特性和功能,以提高开发效率和代码可读性。以下是一些 ES6 中新增的特性:

  1. let 和 const 声明

    • letconst 是新的变量声明方式,let 声明的变量具有块级作用域,而 const 声明的变量是常量,不可被重新赋值。
  2. 箭头函数

    • 箭头函数是一种更简洁的函数声明方式,可以用来替代传统的函数表达式,并且具有词法作用域。
  3. 模板字面量

    • 模板字面量允许在字符串中插入变量和表达式,使得字符串拼接更加简洁和直观。
  4. 解构赋值

    • 解构赋值允许从数组或对象中提取值,并将其赋值给变量,使得代码更加简洁和可读。
  5. 默认参数

    • 默认参数允许为函数的参数指定默认值,在调用函数时如果没有提供参数,则会使用默认值。
  6. 展开运算符

    • 展开运算符 ... 可以将数组或对象展开为一组参数或属性,使得在函数调用和对象合并时更加方便。
  7. 剩余参数

    • 剩余参数允许在函数定义中捕获剩余的参数为一个数组,使得函数可以接受任意数量的参数。
  8. 类和继承

    • ES6 引入了类的概念,可以通过 class 关键字定义类,并使用 extends 关键字实现类的继承。
  9. 模块化

    • ES6 引入了模块化的概念,可以使用 importexport 关键字来导入和导出模块。
  10. Promise 对象

    • Promise 是一种用来处理异步操作的对象,可以更加优雅地处理异步代码,并且支持链式调用和错误处理。
  11. Symbol 类型

    • Symbol 是一种新的原始数据类型,表示唯一的标识符,可以用来创建对象的私有属性名。
  12. 迭代器和生成器

    • ES6 引入了迭代器(Iterator)和生成器(Generator)的概念,可以更方便地遍历数据结构和生成迭代器对象。
  13. Map 和 Set 数据结构

    • ES6 引入了 Map 和 Set 数据结构,分别用于存储键值对和不重复的值,提供了更方便的数据存储和操作方式。

以上是 ES6 中的一些主要新增特性,它们使得 JavaScript 更加强大和灵活,提高了开发效率和代码质量。

12.状态码以及浏览器缓存
HTTP 状态码是用于表示 HTTP 请求结果的数字代码。常见的状态码有以下几种:

  1. 1xx(信息性状态码):表示请求已被接收,继续处理。

    • 100 Continue:服务器已经收到了请求的部分,并且需要客户端继续发送剩余的请求。
    • 101 Switching Protocols:服务器正在根据客户端的请求切换协议。
  2. 2xx(成功状态码):表示请求已成功处理。

    • 200 OK:请求成功,一般用于 GET 和 POST 请求。
    • 201 Created:请求已被成功处理,并且创建了新的资源。
    • 204 No Content:请求成功处理,但没有返回任何内容。
  3. 3xx(重定向状态码):表示需要客户端采取进一步的操作才能完成请求。

    • 301 Moved Permanently:永久重定向,请求的资源已被永久移动到新的 URL。
    • 302 Found:临时重定向,请求的资源暂时移动到新的 URL。
    • 304 Not Modified:客户端缓存资源有效,未修改。
  4. 4xx(客户端错误状态码):表示客户端提交的请求有错误。

    • 400 Bad Request:请求参数有误,服务器无法理解。
    • 401 Unauthorized:未经授权,需要提供身份验证信息。
    • 404 Not Found:请求的资源不存在。
  5. 5xx(服务器错误状态码):表示服务器在处理请求时发生了错误。

    • 500 Internal Server Error:服务器内部错误。
    • 503 Service Unavailable:服务器暂时无法处理请求,通常是因为服务器过载或正在维护。

浏览器缓存是浏览器存储数据的一种方式,可以减少服务器的负载,提高网页加载速度。常见的浏览器缓存策略包括:

  • 强缓存:通过设置 HTTP 响应头中的 Cache-ControlExpires 字段来控制客户端的缓存行为。如果资源在缓存有效期内没有过期,则直接从缓存中读取,不发送请求到服务器。

  • 协商缓存:通过设置 HTTP 响应头中的 Last-ModifiedETag 字段来标识资源的版本信息,客户端在发送请求时会携带 If-Modified-SinceIf-None-Match 字段,服务器根据这些字段来判断资源是否已经被修改。如果资源没有修改,则返回 304 Not Modified 状态码,客户端直接从缓存中读取,否则返回新的资源。

浏览器缓存可以减少网络传输时间和服务器负载,提高网页加载速度,但需要注意在更新资源时要正确设置缓存策略,以确保新的资源能够被及时地获取到。

13. 手写题 防抖与回流
14.反问

第一次面试,整个人紧张到不行😭

作者:LeeWahJoel
链接:www.nowcoder.com/feed/main/d…
来源:牛客网