当前位置: 首页 > news >正文

做了一份前端面试复习计划,保熟~

前言

以前我看到面试贴就直接刷掉的,从不会多看一眼,直到去年 9 月份我开始准备面试时,才发现很多面试经验贴特别有用,看这些帖子(我不敢称之为文章,怕被杠)的过程中对我的复习思维形成影响很大,所以我现在把之前自己好好整理的面试计划分享出来,希望能帮到接下来要找工作的朋友,不喜勿喷哈~

一、简历

简历在找工作过程中是非常非常重要的,无论你是什么途径去面试的,面试你的人一定会看你的简历。

1、重点

简历就像高考作文——阅卷时间非常短。

内容要简洁。

直击重点,表现出自己的优势(只要是符合招人单位要求的都是优势,不是别人不会的你会才叫优势)。

2、简历包含的内容

个人信息。

专业技能。

工作经历。

项目经历。

社区贡献。

2.1 基本信息

必备:姓名 电话 邮箱。

年龄(最好写上,在这个行业年龄还是比较重要的),学历(写好是哪一届)。

头像无所谓(好看就放上呗)。

可以放 github 链接,前提是有内容。

2.2 专业技能

表现出自己的核心竞争力(只要是符合招人单位要求的都是优势)。

内容不要太多,3、5 条即可。

太基础的不要写,例如会用 vscode、lodash。

2.3 工作经历

如实写。

写明公司,职位,入职离职时间即可,多写无益。

如果有空窗期,如实写明即可。

2.4 项目经历

写 2-4 个具有说服力的项目(不要什么项目都写,没用)。

项目名称,项目描述,技术栈,个人角色。

2.5 社区贡献

有博客或者开源作品,会让你更有竞争力。

切记:需要真的有内容,不可临时抱佛脚。

3、注意事项

界面不能太花哨,简洁明了即可。

注意用词,“精通”“熟练”等慎用,可用“熟悉”。

不可造假,会被拉入黑名单。

4、面试前准备

看 JD,是否需要临时准备一下。

打印纸质简历,带着纸和笔(增加好印象)。

最好带着自己电脑,现场可能手写代码(带一个帆布包最适合,又优雅又方便)。

要有时间观念,如果迟到或者推迟,要提前说。

衣着适当,不用正装,也不要太随意。

为何离职?—— 不要吐槽前东家,说自己的原因(想找一个更好的发展平台等)。

能加班吗?—— 能!除非你特别自信,能找到其他机会。

不要挑战面试官,即便他错了(面试一定要保证愉快)。

遇到不会的问题,要表现出自己积极的一面(不好意思哈,确实是我的知识盲区,可以跟我说下 xxx 吗,我回去研究一下)。

二、HTML+CSS 面试题

HTML 和 CSS 面试题答不出来基本可以回去了。

1、HTML 面试题

以下是针对 HTML 相关的面试题,一般来说这地方不会出太多题,面试官也不愿意花太多时间在这上面。

1.1 如何理解 HTML 语义化?

让人更容易读懂(增加代码可读性)。

让搜索引擎更容易读懂,有助于爬虫抓取更多的有效信息,爬虫依赖于标签来确定上下文和各个关键字的权重(SEO)。

在没有 CSS 样式下,页面也能呈现出很好地内容结构、代码结构。

1.2 script 标签中 defer 和 async 的区别?

script :会阻碍 HTML 解析,只有下载好并执行完脚本才会继续解析 HTML。

async script :解析 HTML 过程中进行脚本的异步下载,下载成功立马执行,有可能会阻断 HTML 的解析。

defer script:完全不会阻碍 HTML 的解析,解析完成之后再按照顺序执行脚本。

下图清晰地展示了三种 script 的过程:


1.3 从浏览器地址栏输入 url 到请求返回发生了什么

输入 URL 后解析出协议、主机、端口、路径等信息,并构造一个 HTTP 请求。

强缓存。

协商缓存。

DNS 域名解析。

TCP 连接。

总是要问:为什么需要三次握手,两次不行吗?其实这是由 TCP 的自身特点可靠传输决定的。客户端和服务端要进行可靠传输,那么就需要确认双方的接收和发送能力。第一次握手可以确认客服端的发送能力,第二次握手,确认了服务端的发送能力和接收能力,所以第三次握手才可以确认客户端的接收能力。不然容易出现丢包的现象。

http 请求。

服务器处理请求并返回 HTTP 报文。

浏览器渲染页面。


断开 TCP 连接。

2、CSS 面试题

以下是针对 CSS 相关的面试题,这些题答不出来会给人非常不好的技术印象。

2.1 盒模型介绍

CSS3 中的盒模型有以下两种:标准盒模型、IE(替代)盒模型。

两种盒子模型都是由 content + padding + border + margin 构成,其大小都是由 content + padding + border 决定的,但是盒子内容宽/高度(即 width/height)的计算范围根据盒模型的不同会有所不同:

标准盒模型:只包含 content 。

IE(替代)盒模型:content + padding + border 。

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

box-sizing: content-box :标准盒模型(默认值)。

box-sizing: border-box :IE(替代)盒模型。

2.2 css 选择器和优先级

首先我们要知道有哪些选择器:

常规来说,大家都知道样式的优先级一般为 !important > style > id > class ,但是涉及多类选择器作用于同一个元素时候怎么判断优先级呢?相信我,你在改一些第三方库(比如 antd 😂)样式时,理解这个会帮助很大!

上述文章中核心内容: 优先级是由 A 、B、C、D 的值来决定的,其中它们的值计算规则如下:

如果存在内联样式,那么 A = 1,否则 A = 0 ;

B 的值等于 ID选择器(#id) 出现的次数;

C 的值等于 类选择器(.class) 和 属性选择器(a[href="https://example.org"]) 和 伪类(:first-child) 出现的总次数;

D 的值等于 标签选择器(h1,a,div) 和 伪元素(::before,::after) 出现的总次数。

从左至右比较,如果是样式优先级相等,取后面出现的样式。

2.3 重排(reflow)和重绘(repaint)的理解

简单地总结下两者的概念:

重排:无论通过什么方式影响了元素的几何信息(元素在视口内的位置和尺寸大小),浏览器需要重新计算元素在视口内的几何属性,这个过程叫做重排。

重绘:通过构造渲染树和重排(回流)阶段,我们知道了哪些节点是可见的,以及可见节点的样式和具体的几何信息(元素在视口内的位置和尺寸大小),接下来就可以将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘。

如何减少重排和重绘?

最小化重绘和重排,比如样式集中改变,使用添加新样式类名 .class 或 cssText 。

批量操作 DOM,比如读取某元素 offsetWidth 属性存到一个临时变量,再去使用,而不是频繁使用这个计算属性;又比如利用 document.createDocumentFragment() 来添加要被添加的节点,处理完之后再插入到实际 DOM 中。

使用 absolute 或 fixed 使元素脱离文档流,这在制作复杂的动画时对性能的影响比较明显。

开启 GPU 加速,利用 css 属性 transform 、will-change 等,比如改变元素位置,我们使用 translate 会比使用绝对定位改变其 left 、top 等来的高效,因为它不会触发重排或重绘,transform 使浏览器为元素创建⼀个 GPU 图层,这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。

2.4 对 BFC 的理解

BFC 即块级格式上下文,根据盒模型可知,每个元素都被定义为一个矩形盒子,然而盒子的布局会受到尺寸,定位,盒子的子元素或兄弟元素,视口的尺寸等因素决定,所以这里有一个浏览器计算的过程,计算的规则就是由一个叫做视觉格式化模型的东西所定义的,BFC 就是来自这个概念,它是 CSS 视觉渲染的一部分,用于决定块级盒的布局及浮动相互影响范围的一个区域。

BFC 具有一些特性:

块级元素会在垂直方向一个接一个的排列,和文档流的排列方式一致。

在 BFC 中上下相邻的两个容器的 margin 会重叠,创建新的 BFC 可以避免外边距重叠。

计算 BFC 的高度时,需要计算浮动元素的高度。

BFC 区域不会与浮动的容器发生重叠。

BFC 是独立的容器,容器内部元素不会影响外部元素。

每个元素的左 margin 值和容器的左 border 相接触。

利用这些特性,我们可以解决以下问题:

利用 4 和 6 ,我们可以实现三栏(或两栏)自适应布局。

利用 2 ,我们可以避免 margin 重叠问题。

利用 3 ,我们可以避免高度塌陷。

创建 BFC 的方式:

绝对定位元素(position 为 absolute 或 fixed )。

行内块元素,即 display 为 inline-block 。

overflow 的值不为 visible 。

2.5 实现两栏布局(左侧固定 + 右侧自适应布局)

现在有以下 DOM 结构:

<div class="outer">

<div class="left">左侧</div>

<div class="right">右侧</div>

</div>

复制代码

利用浮动,左边元素宽度固定 ,设置向左浮动。将右边元素的 margin-left 设为固定宽度 。注意,因为右边元素的 width 默认为 auto ,所以会自动撑满父元素。

.outer {

height: 100px;

}

.left {

float: left;

width: 200px;

height: 100%;

background: lightcoral;

}

.right {

margin-left: 200px;

height: 100%;

background: lightseagreen;

}

复制代码

同样利用浮动,左边元素宽度固定 ,设置向左浮动。右侧元素设置 overflow: hidden; 这样右边就触发了 BFC ,BFC 的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠。

.outer {

height: 100px;

}

.left {

float: left;

width: 200px;

height: 100%;

background: lightcoral;

}

.right {

overflow: auto;

height: 100%;

background: lightseagreen;

}

复制代码

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

.outer {

display: flex;

height: 100px;

}

.left {

width: 200px;

height: 100%;

background: lightcoral;

}

.right {

flex: 1;

height: 100%;

background: lightseagreen;

}

复制代码

利用绝对定位,父级元素设为相对定位。左边元素 absolute 定位,宽度固定。右边元素的 margin-left 的值设为左边元素的宽度值。

.outer {

position: relative;

height: 100px;

}

.left {

position: absolute;

width: 200px;

height: 100%;

background: lightcoral;

}

.right {

margin-left: 200px;

height: 100%;

background: lightseagreen;

}

复制代码

利用绝对定位,父级元素设为相对定位。左边元素宽度固定,右边元素 absolute 定位, left 为宽度大小,其余方向定位为 0 。

.outer {

position: relative;

height: 100px;

}

.left {

width: 200px;

height: 100%;

background: lightcoral;

}

.right {

position: absolute;

left: 200px;

top: 0;

right: 0;

bottom: 0;

height: 100%;

background: lightseagreen;

}

复制代码

2.6 实现圣杯布局和双飞翼布局(经典三分栏布局)

圣杯布局和双飞翼布局的目的:

三栏布局,中间一栏最先加载和渲染(内容最重要,这就是为什么还需要了解这种布局的原因)。

两侧内容固定,中间内容随着宽度自适应。

一般用于 PC 网页。

圣杯布局和双飞翼布局的技术总结:

使用 float 布局。

两侧使用 margin 负值,以便和中间内容横向重叠。

防止中间内容被两侧覆盖,圣杯布局用 padding ,双飞翼布局用 margin 。

圣杯布局: HTML 结构:

<div id="container" class="clearfix">

<p class="center">我是中间</p>

<p class="left">我是左边</p>

<p class="right">我是右边</p>

</div>

复制代码

CSS 样式:

container {

padding-left: 200px;

padding-right: 150px;

overflow: auto;

}

container p {

float: left;

}

.center {

width: 100%;

background-color: lightcoral;

}

.left {

width: 200px;

position: relative;

left: -200px;

margin-left: -100%;

background-color: lightcyan;

}

.right {

width: 150px;

margin-right: -150px;

background-color: lightgreen;

}

.clearfix:after {

content: "";

display: table;

clear: both;

}

复制代码

双飞翼布局: HTML 结构:

<div id="main" class="float">

<div id="main-wrap">main</div>

</div>

<div id="left" class="float">left</div>

<div id="right" class="float">right</div>

复制代码

CSS 样式:

.float {

float: left;

}

main {

width: 100%;

height: 200px;

background-color: lightpink;

}

main-wrap {

margin: 0 190px 0 190px;

}

left {

width: 190px;

height: 200px;

background-color: lightsalmon;

margin-left: -100%;

}

right {

width: 190px;

height: 200px;

background-color: lightskyblue;

margin-left: -190px;

}

复制代码

tips:上述代码中 margin-left: -100% 相对的是父元素的 content 宽度,即不包含 paddig 、 border 的宽度。

其实以上问题需要掌握 margin 负值问题 即可很好理解。

2.7 水平垂直居中多种实现方式

利用绝对定位,设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 translate 来调整子元素的中心点到父元素的中心。该方法可以不定宽高。

.father {

position: relative;

}

.son {

position: absolute;

left: 50%;

top: 50%;

transform: translate(-50%, -50%);

}

复制代码

利用绝对定位,子元素所有方向都为 0 ,将 margin 设置为 auto ,由于宽高固定,对应方向实现平分,该方法必须盒子有宽高。

.father {

position: relative;

}

.son {

position: absolute;

top: 0;

left: 0;

right: 0;

bottom: 0px;

margin: auto;

height: 100px;

width: 100px;

}

复制代码

利用绝对定位,设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 margin-left 和 margin-top 以子元素自己的一半宽高进行负值赋值。该方法必须定宽高。

.father {

position: relative;

}

.son {

position: absolute;

left: 50%;

top: 50%;

width: 200px;

height: 200px;

margin-left: -100px;

margin-top: -100px;

}

复制代码

利用 flex ,最经典最方便的一种了,不用解释,定不定宽高无所谓的。

.father {

display: flex;

justify-content: center;

align-items: center;

}

复制代码

其实还有很多方法,比如 display: grid 或 display: table-cell 来做。

2.8 flex 布局

这里有个小问题,很多时候我们会用到 flex: 1 ,它具体包含了以下的意思:

flex-grow: 1 :该属性默认为 0 ,如果存在剩余空间,元素也不放大。设置为 1 代表会放大。

flex-shrink: 1 :该属性默认为 1 ,如果空间不足,元素缩小。

flex-basis: 0% :该属性定义在分配多余空间之前,元素占据的主轴空间。浏览器就是根据这个属性来计算是否有多余空间的。默认值为 auto ,即项目本身大小。设置为 0% 之后,因为有 flex-grow 和 flex-shrink 的设置会自动放大或缩小。在做两栏布局时,如果右边的自适应元素 flex-basis 设为 auto 的话,其本身大小将会是 0 。

2.9 line-height 如何继承?

父元素的 line-height 写了具体数值,比如 30px,则子元素 line-height 继承该值。

父元素的 line-height 写了比例,比如 1.5 或 2,则子元素 line-height 也是继承该比例。

父元素的 line-height 写了百分比,比如 200%,则子元素 line-height 继承的是父元素 font-size * 200% 计算出来的值。

三、js 基础

js 的考察其实来回就那些东西,不过就我自己而已学习的时候理解是真的理解了,但是忘也确实会忘(大家都说理解了一定不会忘,但是要答全的话还是需要理解+背)。

1、数据类型

以下是比较重要的几个 js 变量要掌握的点。

1.1 基本的数据类型介绍,及值类型和引用类型的理解

在 JS 中共有 8 种基础的数据类型,分别为: Undefined 、 Null 、 Boolean 、 Number 、 String 、 Object 、 Symbol 、 BigInt 。

其中 Symbol 和 BigInt 是 ES6 新增的数据类型,可能会被单独问:

Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。

BigInt 可以表示任意大小的整数。

值类型的赋值变动过程如下:

let a = 100;

let b = a;

a = 200;

console.log(b); // 200

复制代码


值类型是直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;

引用类型的赋值变动过程如下:

let a = { age: 20 };

let b = a;

b.age = 30;

console.log(a.age); // 30

复制代码


引用类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;

1.2 数据类型的判断

typeof:能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回 object 。

console.log(typeof undefined); // undefined

console.log(typeof 2); // number

console.log(typeof true); // boolean

console.log(typeof "str"); // string

console.log(typeof Symbol("foo")); // symbol

console.log(typeof 2172141653n); // bigint

console.log(typeof function () {}); // function

// 不能判别

console.log(typeof []); // object

console.log(typeof {}); // object

console.log(typeof null); // object

复制代码

instanceof:能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。比如考虑以下代码:

class People {}

class Student extends People {}

const vortesnail = new Student();

console.log(vortesnail instanceof People); // true

console.log(vortesnail instanceof Student); // true

复制代码

其实现就是顺着原型链去找,如果能找到对应的 Xxxxx.prototype 即为 true 。比如这里的 vortesnail 作为实例,顺着原型链能找到 Student.prototype 及 People.prototype ,所以都为 true 。

Object.prototype.toString.call():所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。

Object.prototype.toString.call(2); // "[object Number]"

Object.prototype.toString.call(""); // "[object String]"

Object.prototype.toString.call(true); // "[object Boolean]"

Object.prototype.toString.call(undefined); // "[object Undefined]"

Object.prototype.toString.call(null); // "[object Null]"

Object.prototype.toString.call(Math); // "[object Math]"

Object.prototype.toString.call({}); // "[object Object]"

Object.prototype.toString.call([]); // "[object Array]"

Object.prototype.toString.call(function () {}); // "[object Function]"

复制代码

在面试中有一个经常被问的问题就是:如何判断变量是否为数组?

Array.isArray(arr); // true

arr.__proto__ === Array.prototype; // true

arr instanceof Array; // true

Object.prototype.toString.call(arr); // "[object Array]"

复制代码

1.3 手写深拷贝

这个题一定要会啊!笔者面试过程中疯狂被问到!

/**

深拷贝

@param {Object} obj 要拷贝的对象

@param {Map} map 用于存储循环引用对象的地址

*/

function deepClone(obj = {}, map = new Map()) {

if (typeof obj !== "object") {

returnobj;

}

if (map.get(obj)) {

returnmap.get(obj);

}

let result = {};

// 初始化返回结果

if (

objinstanceofArray||//加 || 的原因是为了防止Array的 prototype 被重写,Array.isArray 也是如此Object.prototype.toString(obj) ==="[object Array]"

) {

result=[];

}

// 防止循环引用

map.set(obj, result);

for (const key in obj) {

// 保证 key 不是原型属性if(obj.hasOwnProperty(key)) {// 递归调用result[key]= deepClone(obj[key],map);}

}

// 返回结果

return result;

}

复制代码

1.4 根据 0.1+0.2 ! == 0.3,讲讲 IEEE 754 ,如何让其相等?

原因总结:

进制转换 :js 在做数字计算的时候,0.1 和 0.2 都会被转成二进制后无限循环 ,但是 js 采用的 IEEE 754 二进制浮点运算,最大可以存储 53 位有效数字,于是大于 53 位后面的会全部截掉,将导致精度丢失。

对阶运算 :由于指数位数不相同,运算时需要对阶运算,阶小的尾数要根据阶差来右移(0舍1入),尾数位移时可能会发生数丢失的情况,影响精度。

解决办法:

转为整数(大数)运算。

function add(a, b) {

const maxLen = Math.max(

a.toString().split(".")[1].length,b.toString().split(".")[1].length

);

const base = 10 ** maxLen;

const bigA = BigInt(base * a);

const bigB = BigInt(base * b);

const bigRes = (bigA + bigB) / BigInt(base); // 如果是 (1n + 2n) / 10n 是等于 0n的。。。

return Number(bigRes);

}

复制代码

这里代码是有问题的,因为最后计算 bigRes 的大数相除(即 /)是会把小数部分截掉的,所以我很疑惑为什么网络上很多文章都说可以通过先转为整数运算再除回去,为了防止转为的整数超出 js 表示范围,还可以运用到 ES6 新增的大数类型,我真的很疑惑,希望有好心人能解答下。

使用 Number.EPSILON 误差范围。

function isEqual(a, b) {

return Math.abs(a - b) < Number.EPSILON;

}

console.log(isEqual(0.1 + 0.2, 0.3)); // true

复制代码

Number.EPSILON 的实质是一个可以接受的最小误差范围,一般来说为 Math.pow(2, -52) 。

转成字符串,对字符串做加法运算。

// 字符串数字相加

var addStrings = function (num1, num2) {

let i = num1.length - 1;

let j = num2.length - 1;

const res = [];

let carry = 0;

while (i >= 0 || j >= 0) {

const n1 = i >=0?Number(num1[i]):0;const n2 = j >=0?Number(num2[j]):0;const sum = n1 + n2 + carry;res.unshift(sum %10);carry =Math.floor(sum / 10);i--;j--;

}

if (carry) {

res.unshift(carry);

}

return res.join("");

};

function isEqual(a, b, sum) {

const [intStr1, deciStr1] = a.toString().split(".");

const [intStr2, deciStr2] = b.toString().split(".");

const inteSum = addStrings(intStr1, intStr2); // 获取整数相加部分

const deciSum = addStrings(deciStr1, deciStr2); // 获取小数相加部分

return inteSum + "." + deciSum === String(sum);

}

console.log(isEqual(0.1, 0.2, 0.3)); // true

复制代码

2、 原型和原型链

可以说这部分每家面试官都会问了。。首先理解的话,其实一张图即可,一段代码即可。

function Foo() {}

let f1 = new Foo();

let f2 = new Foo();

复制代码

千万别畏惧下面这张图,特别有用,一定要搞懂,熟到提笔就能默画出来。


总结:

原型:每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性,其实就是 prototype 对象。

原型链:由相互关联的原型组成的链状结构就是原型链。

先说出总结的话,再举例子说明如何顺着原型链找到某个属性。

3、 作用域与作用域链

作用域:规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。换句话说,作用域决定了代码区块中变量和其他资源的可见性。(全局作用域、函数作用域、块级作用域)

作用域链:从当前作用域开始一层层往上找某个变量,如果找到全局作用域还没找到,就放弃寻找 。这种层级关系就是作用域链。(由多个执行上下文的变量对象构成的链表就叫做作用域链,学习下面的内容之后再考虑这句话)

需要注意的是,js 采用的是静态作用域,所以函数的作用域在函数定义时就确定了。

总结:当 JavaScript 代码执行一段可执行代码时,会创建对应的执行上下文。对于每个执行上下文,都有三个重要属性:

变量对象(Variable object,VO);

作用域链(Scope chain);

this。(关于 this 指向问题,在上面推荐的深入系列也有讲从 ES 规范讲的,但是实在是难懂

4、 闭包

根据 MDN 中文的定义,闭包的定义如下:

在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。可以在一个内层函数中访问到其外层函数的作用域。

也可以这样说:

闭包是指那些能够访问自由变量的函数。 自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。 闭包 = 函数 + 函数能够访问的自由变量。

在回答时,我们这样答:

在某个内部函数的执行上下文创建时,会将父级函数的活动对象加到内部函数的 [[scope]] 中,形成作用域链,所以即使父级函数的执行上下文销毁(即执行上下文栈弹出父级函数的执行上下文),但是因为其活动对象还是实际存储在内存中可被内部函数访问到的,从而实现了闭包。

闭包应用: 函数作为参数被传递:

function print(fn) {

const a = 200;

fn();

}

const a = 100;

function fn() {

console.log(a);

}

print(fn); // 100

复制代码

函数作为返回值被返回:

function create() {

const a = 100;

return function () {

console.log(a);

};

}

const fn = create();

const a = 200;

fn(); // 100

复制代码

闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方。

应用实例:比如缓存工具,隐藏数据,只提供 API 。

function createCache() {

const data = {}; // 闭包中被隐藏的数据,不被外界访问

return {

set: function (key,val) {data[key] =val;},get: function (key) {returndata[key];},

};

}

const c = createCache();

c.set("a", 100);

console.log(c.get("a")); // 100

复制代码

6、 call、apply、bind 实现

这部分实现还是要知道的,就算工作中不会自己手写,但是说不准面试官就是要问,知道点原理也好,可以扩宽我们写代码的思路。

call

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

举个例子:

var obj = {

value: "vortesnail",

};

function fn() {

console.log(this.value);

}

fn.call(obj); // vortesnail

复制代码

通过 call 方法我们做到了以下两点:

call 改变了 this 的指向,指向到 obj 。

fn 函数执行了。

那么如果我们自己写 call 方法的话,可以怎么做呢?我们先考虑改造 obj 。

var obj = {

value: "vortesnail",

fn: function () {

console.log(this.value);

},

};

obj.fn(); // vortesnail

复制代码

这时候 this 就指向了 obj ,但是这样做我们手动给 obj 增加了一个 fn 属性,这显然是不行的,不用担心,我们执行完再使用对象属性的删除方法(delete)不就行了?

obj.fn = fn;

obj.fn();

delete obj.fn;

复制代码

根据这个思路,我们就可以写出来了:

Function.prototype.myCall = function (context) {

// 判断调用对象

if (typeof this !== "function") {

thrownewError("Type error");

}

// 首先获取参数

let args = [...arguments].slice(1);

let result = null;

// 判断 context 是否传入,如果没有传就设置为 window

context = context || window;

// 将被调用的方法设置为 context 的属性

// this 即为我们要调用的方法

context.fn = this;

// 执行要被调用的方法

result = context.fn(...args);

// 删除手动增加的属性方法

delete context.fn;

// 将执行结果返回

return result;

};

复制代码

apply

我们会了 call 的实现之后,apply 就变得很简单了,他们没有任何区别,除了传参方式。

Function.prototype.myApply = function (context) {

if (typeof this !== "function") {

thrownewError("Type error");

}

let result = null;

context = context || window;

// 与上面代码相比,我们使用 Symbol 来保证属性唯一

// 也就是保证不会重写用户自己原来定义在 context 中的同名属性

const fnSymbol = Symbol();

context[fnSymbol] = this;

// 执行要被调用的方法

if (arguments[1]) {

result=context[fnSymbol](...arguments[1]);

} else {

result=context[fnSymbol]();

}

delete context[fnSymbol];

return result;

};

复制代码

bind

bind 返回的是一个函数

Function.prototype.myBind = function (context) {

// 判断调用对象是否为函数

if (typeof this !== "function") {

thrownewError("Type error");

}

// 获取参数

const args = [...arguments].slice(1),

const fn = this;

return function Fn() {

returnfn.apply(thisinstanceof Fn ?this: context,// 当前的这个 arguments 是指 Fn 的参数args.concat(...arguments));

};

};

复制代码

7、 new 实现

首先创一个新的空对象。

根据原型链,设置空对象的 proto 为构造函数的 prototype 。

构造函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)。

判断函数的返回值类型,如果是引用类型,就返回这个引用类型的对象。

function myNew(context) {

const obj = new Object();

obj.__proto__ = context.prototype;

const res = context.apply(obj, [...arguments].slice(1));

return typeof res === "object" ? res : obj;

}

复制代码

8、 异步

这部分着重要理解 Promise、async awiat、event loop 等。

8.1 event loop、宏任务和微任务

简单的例子:

console.log("Hi");

setTimeout(function cb() {

console.log("cb"); // cb 即 callback

}, 5000);

console.log("Bye");

复制代码

它的执行过程是这样的:


Web APIs 会创建对应的线程,比如 setTimeout 会创建定时器线程,ajax 请求会创建 http 线程。。。这是由 js 的运行环境决定的,比如浏览器。

看完上面的视频之后,至少大家画 Event Loop 的图讲解不是啥问题了,但是涉及到宏任务和微任务

注意:1.Call Stack 调用栈空闲 -> 2.尝试 DOM 渲染 -> 触发 Event loop。

每次 Call Stack 清空(即每次轮询结束),即同步任务执行完。

都是 DOM 重新渲染的机会,DOM 结构有改变则重新渲染。

然后再去触发下一次 Event loop。

宏任务:setTimeout,setInterval,Ajax,DOM 事件。 微任务:Promise async/await。

两者区别:

宏任务:DOM 渲染后触发,如 setTimeout 、setInterval 、DOM 事件 、script 。

微任务:DOM 渲染前触发,如 Promise.then 、MutationObserver 、Node 环境下的 process.nextTick 。

从 event loop 解释,为何微任务执行更早?

微任务是 ES6 语法规定的(被压入 micro task queue)。

宏任务是由浏览器规定的(通过 Web APIs 压入 Callback queue)。

宏任务执行时间一般比较长。

每一次宏任务开始之前一定是伴随着一次 event loop 结束的,而微任务是在一次 event loop 结束前执行的。

8.2 Promise

关于这一块儿没什么好说的,最好是实现一遍 Promise A+ 规范,多少有点印象,当然面试官也不会叫你默写一个完整的出来,但是你起码要知道实现原理。

实现一个 Promise.all:

Promise.all = function (promises) {

return new Promise((resolve, reject) => {

// 参数可以不是数组,但必须具有 Iterator 接口

if (typeof promises[Symbol.iterator] !== "function") {

  reject("Type error");

}

if (promises.length === 0) {

  resolve([]);

} else {

  const res = [];

  let count = 0;

  const len = promises.length;

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

    //考虑到 promises[i] 可能是 thenable 对象也可能是普通值

    Promise.resolve(promises[i])

      .then((data) => {

        res[i] = data;

        if (++count === len) {

          resolve(res);

        }

      })

      .catch((err) => {

        reject(err);

      });

  }

}

});

};

复制代码

8.3 async/await 和 Promise 的关系

async/await 是消灭异步回调的终极武器。

但和 Promise 并不互斥,反而,两者相辅相成。

执行 async 函数,返回的一定是 Promise 对象。

await 相当于 Promise 的 then。

tru...catch 可捕获异常,代替了 Promise 的 catch。

9、 浏览器的垃圾回收机制

总结一下:

有两种垃圾回收策略:

标记清除:标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。

引用计数:它把对象是否不再需要简化定义为对象有没有其他对象引用到它。如果没有引用指向该对象(引用计数为 0),对象将被垃圾回收机制回收。

标记清除的缺点:

内存碎片化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块。

分配速度慢,因为即便是使用 First-fit 策略,其操作仍是一个 O(n) 的操作,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢。

解决以上的缺点可以使用 标记整理(Mark-Compact)算法 ,标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存(如下图)

引用计数的缺点:

需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。

解决不了循环引用导致的无法回收问题。

V8 的垃圾回收机制也是基于标记清除算法,不过对其做了一些优化。

针对新生区采用并行回收。

针对老生区采用增量标记与惰性回收。

10、 实现一个 EventMitter 类

EventMitter 就是发布订阅模式的典型应用:

export class EventEmitter {

private _events: Record<string, Array<Function>>;

constructor() {

this._events = Object.create(null);

}

emit(evt: string, ...args: any[]) {

if (!this._events[evt]) return false;

const fns = [...this._events[evt]];

fns.forEach((fn) => {

  fn.apply(this, args);

});

return true;

}

on(evt: string, fn: Function) {

if (typeof fn !== "function") {

  throw new TypeError("The evet-triggered callback must be a function");

}

if (!this._events[evt]) {

  this._events[evt] = [fn];

} else {

  this._events[evt].push(fn);

}

}

once(evt: string, fn: Function) {

const execFn = () => {

  fn.apply(this);

  this.off(evt, execFn);

};

this.on(evt, execFn);

}

off(evt: string, fn?: Function) {

if (!this._events[evt]) return;

if (!fn) {

  this._events[evt] && (this._events[evt].length = 0);

}

let cb;

const cbLen = this._events[evt].length;

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

  cb = this._events[evt][i];

  if (cb === fn) {

    this._events[evt].splice(i, 1);

    break;

  }

}

}

removeAllListeners(evt?: string) {

if (evt) {

  this._events[evt] && (this._events[evt].length = 0);

} else {

  this._events = Object.create(null);

}

}

}

复制代码

四、web 存储

要掌握 cookie,localStorage 和 sessionStorage。

1、cookie

本身用于浏览器和 server 通讯。

被“借用”到本地存储来的。

可用 document.cookie = '...' 来修改。

其缺点:

存储大小限制为 4KB。

http 请求时需要发送到服务端,增加请求数量。

只能用 document.cookie = '...' 来修改,太过简陋。

2、localStorage 和 sessionStorage

HTML5 专门为存储来设计的,最大可存 5M。

API 简单易用, setItem getItem。

不会随着 http 请求被发送到服务端。

它们的区别:

localStorage 数据会永久存储,除非代码删除或手动删除。

sessionStorage 数据只存在于当前会话,浏览器关闭则清空。

一般用 localStorage 会多一些。

五、Http

前端工程师做出网页,需要通过网络请求向后端获取数据,因此 http 协议是前端面试的必考内容。

1、http 状态码

1.1 状态码分类

1xx - 服务器收到请求。

2xx - 请求成功,如 200。

3xx - 重定向,如 302。

4xx - 客户端错误,如 404。

5xx - 服务端错误,如 500。

1.2 常见状态码

200 - 成功。

301 - 永久重定向(配合 location,浏览器自动处理)。

302 - 临时重定向(配合 location,浏览器自动处理)。

304 - 资源未被修改。

403 - 没权限。

404 - 资源未找到。

500 - 服务器错误。

504 - 网关超时。

1.3 关于协议和规范

状态码都是约定出来的。

要求大家都跟着执行。

不要违反规范,例如 IE 浏览器。

2、http 缓存

关于缓存的介绍。

http 缓存策略(强制缓存 + 协商缓存)。

刷新操作方式,对缓存的影响。

4.1 关于缓存

什么是缓存? 把一些不需要重新获取的内容再重新获取一次

为什么需要缓存? 网络请求相比于 CPU 的计算和页面渲染是非常非常慢的。

哪些资源可以被缓存? 静态资源,比如 js css img。

4.2 强制缓存

Cache-Control:

在 Response Headers 中。

控制强制缓存的逻辑。

例如 Cache-Control: max-age=3153600(单位是秒)

Cache-Control 有哪些值:

max-age:缓存最大过期时间。

no-cache:可以在客户端存储资源,每次都必须去服务端做新鲜度校验,来决定从服务端获取新的资源(200)还是使用客户端缓存(304)。

no-store:永远都不要在客户端存储资源,永远都去原始服务器去获取资源。

4.3 协商缓存(对比缓存)

服务端缓存策略。

服务端判断客户端资源,是否和服务端资源一样。

一致则返回 304,否则返回 200 和最新的资源。

资源标识:

在 Response Headers 中,有两种。

Last-Modified:资源的最后修改时间。

Etag:资源的唯一标识(一个字符串,类似于人类的指纹)。

Last-Modified:

服务端拿到 if-Modified-Since 之后拿这个时间去和服务端资源最后修改时间做比较,如果一致则返回 304 ,不一致(也就是资源已经更新了)就返回 200 和新的资源及新的 Last-Modified。

Etag:

其实 Etag 和 Last-Modified 一样的,只不过 Etag 是服务端对资源按照一定方式(比如 contenthash)计算出来的唯一标识,就像人类指纹一样,传给客户端之后,客户端再传过来时候,服务端会将其与现在的资源计算出来的唯一标识做比较,一致则返回 304,不一致就返回 200 和新的资源及新的 Etag。

两者比较:

优先使用 Etag。

Last-Modified 只能精确到秒级。

如果资源被重复生成,而内容不变,则 Etag 更精确。

4.4 综述

4.4 三种刷新操作对 http 缓存的影响

正常操作:地址栏输入 url,跳转链接,前进后退等。

手动刷新:f5,点击刷新按钮,右键菜单刷新。

强制刷新:ctrl + f5,shift+command+r。

正常操作:强制缓存有效,协商缓存有效。 手动刷新:强制缓存失效,协商缓存有效。 强制刷新:强制缓存失效,协商缓存失效。

面试

比如会被经常问到的: GET 和 POST 的区别。

从缓存的角度,GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会。

从编码的角度,GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。

从参数的角度,GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。

从幂等性的角度,GET 是幂等的,而 POST 不是。(幂等表示执行相同的操作,结果也是相同的)

从 TCP 的角度,GET 请求会把请求报文一次性发出去,而 POST 会分为两个 TCP 数据包,首先发 header 部分,如果服务器响应 100(continue), 然后发 body 部分。(火狐浏览器除外,它的 POST 请求只发一个 TCP 包)

HTTP/2 有哪些改进?(很大可能问原理)

头部压缩。

多路复用。

服务器推送。

六、React

1、 React 事件机制,React 16 和 React 17 事件机制的不同

为什么要自定义事件机制?

抹平浏览器差异,实现更好的跨平台。

避免垃圾回收,React 引入事件池,在事件池中获取或释放事件对象,避免频繁地去创建和销毁。

方便事件统一管理和事务机制。

2、class component

不排除现在还会有面试官问关于 class component 的问题。

2.1 生命周期

初始化阶段。

发生在 constructor 中的内容,在 constructor 中进行 state 、props 的初始化,在这个阶段修改 state,不会执行更新阶段的生命周期,可以直接对 state 赋值。

挂载阶段。

componentWillMount

发生在 render 函数之前,还没有挂载 Dom

render

componentDidMount

发生在 render 函数之后,已经挂载 Dom

复制代码

更新阶段。

更新阶段分为由 state 更新引起和 props 更新引起。

props 更新时:

componentWillReceiveProps(nextProps,nextState)

这个生命周期主要为我们提供对 props 发生改变的监听,如果你需要在 props 发生改变后,相应改变组件的一些 state。在这个方法中改变 state 不会二次渲染,而是直接合并 state。

shouldComponentUpdate(nextProps,nextState)

这个生命周期需要返回一个 Boolean 类型的值,判断是否需要更新渲染组件,优化 react 应用的主要手段之一,当返回 false 就不会再向下执行生命周期了,在这个阶段不可以 setState(),会导致循环调用。

componentWillUpdate(nextProps,nextState)

这个生命周期主要是给我们一个时机能够处理一些在 Dom 发生更新之前的事情,如获得 Dom 更新前某些元素的坐标、大小等,在这个阶段不可以 setState(),会导致循环调用。

一直到这里 this.props 和 this.state 都还未发生更新

render

componentDidUpdate(prevProps, prevState)

在此时已经完成渲染,Dom 已经发生变化,state 已经发生更新,prevProps、prevState 均为上一个状态的值。

state 更新时(具体同上)

shouldComponentUpdate

componentWillUpdate

render

componentDidUpdate

复制代码

卸载阶段。

componentWillUnmount

在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount 中创建的订阅等。componentWillUnmount 中不应调用 setState,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

在 React 16 中官方已经建议删除以下三个方法,非要使用必须加前缀:UNSAVE_ 。

componentWillMount;

componentWillReceiveProps;

componentWillUpdate;

复制代码

取代这两三个生命周期的以下两个新的。

static getDerivedStateFromProps(nextProps,nextState)

在组件实例化、接收到新的 props 、组件状态更新时会被调用

getSnapshotBeforeUpdate(prevProps,prevState)

在这个阶段我们可以拿到上一个状态 Dom 元素的坐标、大小的等相关信息。用于替代旧的生命周期中的 componentWillUpdate。

该函数的返回值将会作为 componentDidUpdate 的第三个参数出现。

复制代码

需要注意的是,一般都会问为什么要废弃三个生命周期,原因是什么。

2.2 setState 同步还是异步

setState 本身代码的执行肯定是同步的,这里的异步是指是多个 state 会合成到一起进行批量更新。 同步还是异步取决于它被调用的环境。

如果 setState 在 React 能够控制的范围被调用,它就是异步的。比如合成事件处理函数,生命周期函数, 此时会进行批量更新,也就是将状态合并后再进行 DOM 更新。

如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。比如原生事件处理函数,定时器回调函数,Ajax 回调函数中,此时 setState 被调用后会立即更新 DOM 。

3、对函数式编程的理解

总结一下: 函数式编程有两个核心概念。

数据不可变(无副作用): 它要求你所有的数据都是不可变的,这意味着如果你想修改一个对象,那你应该创建一个新的对象用来修改,而不是修改已有的对象。

无状态: 主要是强调对于一个函数,不管你何时运行,它都应该像第一次运行一样,给定相同的输入,给出相同的输出,完全不依赖外部状态的变化。

纯函数带来的意义。

便于测试和优化:这个意义在实际项目开发中意义非常大,由于纯函数对于相同的输入永远会返回相同的结果,因此我们可以轻松断言函数的执行结果,同时也可以保证函数的优化不会影响其他代码的执行。

可缓存性:因为相同的输入总是可以返回相同的输出,因此,我们可以提前缓存函数的执行结果。

更少的 Bug:使用纯函数意味着你的函数中不存在指向不明的 this,不存在对全局变量的引用,不存在对参数的修改,这些共享状态往往是绝大多数 bug 的源头。

4、react hooks

现在应该大多数面试官会问 hooks 相关的啦。

4.1 为什么不能在条件语句中写 hook

hook 在每次渲染时的查找是根据一个“全局”的下标对链表进行查找的,如果放在条件语句中使用,有一定几率会造成拿到的状态出现错乱。

4.2 HOC 和 hook 的区别

hoc 能复用逻辑和视图,hook 只能复用逻辑。

4.3 useEffect 和 useLayoutEffect 区别

对于 React 的函数组件来说,其更新过程大致分为以下步骤:

因为某个事件 state 发生变化。

React 内部更新 state 变量。

React 处理更新组件中 return 出来的 DOM 节点(进行一系列 dom diff 、调度等流程)。

将更新过后的 DOM 数据绘制到浏览器中。

用户看到新的页面。

useEffect 在第 4 步之后执行,且是异步的,保证了不会阻塞浏览器进程。 useLayoutEffect 在第 3 步至第 4 步之间执行,且是同步代码,所以会阻塞后面代码的执行。

4.4 useEffect 依赖为空数组与 componentDidMount 区别

在 render 执行之后,componentDidMount 会执行,如果在这个生命周期中再一次 setState ,会导致再次 render ,返回了新的值,浏览器只会渲染第二次 render 返回的值,这样可以避免闪屏。

但是 useEffect 是在真实的 DOM 渲染之后才会去执行,这会造成两次 render ,有可能会闪屏。

实际上 useLayoutEffect 会更接近 componentDidMount 的表现,它们都同步执行且会阻碍真实的 DOM 渲染的。

4.5 React.memo() 和 React.useMemo() 的区别

memo 是一个高阶组件,默认情况下会对 props 进行浅比较,如果相等不会重新渲染。多数情况下我们比较的都是引用类型,浅比较就会失效,所以我们可以传入第二个参数手动控制。

useMemo 返回的是一个缓存值,只有依赖发生变化时才会去重新执行作为第一个参数的函数,需要记住的是,useMemo 是在 render 阶段执行的,所以不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴。

4.6 React.useCallback() 和 React.useMemo() 的区别

useCallback 可缓存函数,其实就是避免每次重新渲染后都去重新执行一个新的函数。

useMemo 可缓存值。

有很多时候,我们在 useEffect 中使用某个定义的外部函数,是要添加到 deps 数组中的,如果不用 useCallback 缓存,这个函数在每次重新渲染时都是一个完全新的函数,也就是引用地址发生了变化,这就会导致 useEffect 总会无意义的执行。

4.7 React.forwardRef 是什么及其作用

一般在父组件要拿到子组件的某个实际的 DOM 元素时会用到。

6、react hooks 与 class 组件对比

react hooks 与 class 组件对比 函数式组件与类组件有何不同

7、介绍 React dom diff 算法

让虚拟 DOM 和 DOM-diff 不再成为你的绊脚石。

8、对 React Fiber 的理解

然后我们再阅读下其它作者对于 React Fiber 的理解,再转化为我们自己的思考总结

9、React 性能优化手段

使用 React.memo 来缓存组件。

使用 React.useMemo 缓存大量的计算。

避免使用匿名函数。

利用 React.lazy 和 React.Suspense 延迟加载不是立即需要的组件。

尽量使用 CSS 而不是强制加载和卸载组件。

使用 React.Fragment 避免添加额外的 DOM。

九、性能优化

代码层面:

防抖和节流(resize,scroll,input)。

减少回流(重排)和重绘。

事件委托。

css 放 ,js 脚本放 最底部。

减少 DOM 操作。

按需加载,比如 React 中使用 React.lazy 和 React.Suspense ,通常需要与 webpack 中的 splitChunks 配合。

构建方面:

压缩代码文件,在 webpack 中使用 terser-webpack-plugin 压缩 Javascript 代码;使用 css-minimizer-webpack-plugin 压缩 CSS 代码;使用 html-webpack-plugin 压缩 html 代码。

开启 gzip 压缩,webpack 中使用 compression-webpack-plugin ,node 作为服务器也要开启,使用 compression。

常用的第三方库使用 CDN 服务,在 webpack 中我们要配置 externals,将比如 React, Vue 这种包不打倒最终生成的文件中。而是采用 CDN 服务。

其它:

使用 http2。因为解析速度快,头部压缩,多路复用,服务器推送静态资源。

使用服务端渲染。

图片压缩。

使用 http 缓存,比如服务端的响应中添加 Cache-Control / Expires 。

十、常见手写

以下的内容是上面没有提到的手写,比如 new 、Promise.all 这种上面内容中已经提到了如何写。

1、防抖

function debounce(func, wait, immediate) {

let timeout;

return function () {

let context = this;

let args = arguments;

if (timeout) clearTimeout(timeout);

if (immediate) {

  let callNow = !timeout;

  timeout = setTimeout(function () {

    timeout = null;

  }, wait);

  if (callNow) func.apply(context, args);

} else {

  timeout = setTimeout(function () {

    func.apply(context, args);

  }, wait);

}

};

}

复制代码

2、节流

// 使用时间戳

function throttle(func, wait) {

let preTime = 0;

return function () {

let nowTime = +new Date();

let context = this;

let args = arguments;

if (nowTime - preTime > wait) {

  func.apply(context, args);

  preTime = nowTime;

}

};

}

// 定时器实现

function throttle(func, wait) {

let timeout;

return function () {

let context = this;

let args = arguments;

if (!timeout) {

  timeout = setTimeout(function () {

    timeout = null;

    func.apply(context, args);

  }, wait);

}

};

}

复制代码

3、快速排序

function sortArray(nums) {

quickSort(0, nums.length - 1, nums);

return nums;

}

function quickSort(start, end, arr) {

if (start < end) {

const mid = sort(start, end, arr);

quickSort(start, mid - 1, arr);

quickSort(mid + 1, end, arr);

}

}

function sort(start, end, arr) {

const base = arr[start];

let left = start;

let right = end;

while (left !== right) {

while (arr[right] >= base && right > left) {

  right--;

}

arr[left] = arr[right];

while (arr[left] <= base && right > left) {

  left++;

}

arr[right] = arr[left];

}

arr[left] = base;

return left;

}

复制代码


4、instanceof

这个手写一定要懂原型及原型链。

function myInstanceof(target, origin) {

if (typeof target !== "object" || target === null) return false;

if (typeof origin !== "function")

thrownewTypeError("origin must be function");

let proto = Object.getPrototypeOf(target); // 相当于 proto = target.__proto__;

while (proto) {

if (proto===origin.prototype) return true;proto=Object.getPrototypeOf(proto);

}

return false;

}

复制代码

5、数组扁平化

重点,不要觉得用不到就不管,这道题就是考察你对 js 语法的熟练程度以及手写代码的基本能力。

function flat(arr, depth = 1) {

if (depth > 0) {

// 以下代码还可以简化,不过为了可读性,还是....

return arr.reduce((pre, cur) => {

  return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur);

}, []);

}

return arr.slice();

}

复制代码

6、手写 reduce

先不考虑第二个参数初始值:

Array.prototype.reduce = function (cb) {

const arr = this; //this就是调用reduce方法的数组

let total = arr[0]; // 默认为数组的第一项

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

total= cb(total, arr[i], i, arr);

}

return total;

};

复制代码

考虑上初始值:

Array.prototype.reduce = function (cb, initialValue) {

const arr = this;

let total = initialValue || arr[0];

// 有初始值的话从0遍历,否则从1遍历

for (let i = initialValue ? 0 : 1; i < arr.length; i++) {

total= cb(total, arr[i], i, arr);

}

return total;

};

复制代码

7、带并发的异步调度器 Scheduler

JS 实现一个带并发限制的异度调度器 Scheduler,保证同时运行的任务最多有两个。完善下面代码中的 Scheduler 类,使得以下程序能正确输出。

class Scheduler {

add(promiseMaker) {}

}

const timeout = (time) =>

new Promise((resolve) => {

setTimeout(resolve,time);

});

const scheduler = new Scheduler();

const addTask = (time, order) => {

scheduler.add(() => timeout(time).then(() => console.log(order)));

};

addTask(1000, "1");

addTask(500, "2");

addTask(300, "3");

addTask(400, "4");

// output:2 3 1 4

// 一开始,1,2两个任务进入队列。

// 500ms 时,2完成,输出2,任务3入队。

// 800ms 时,3完成,输出3,任务4入队。

// 1000ms 时,1完成,输出1。

复制代码

根据题目,我们只需要操作 Scheduler 类就行:

class Scheduler {

constructor() {

this.waitTasks = []; // 待执行的任务队列

this.excutingTasks = []; // 正在执行的任务队列

this.maxExcutingNum = 2; // 允许同时运行的任务数量

}

add(promiseMaker) {

if (this.excutingTasks.length < this.maxExcutingNum) {

  this.run(promiseMaker);

} else {

  this.waitTasks.push(promiseMaker);

}

}

run(promiseMaker) {

const len = this.excutingTasks.push(promiseMaker);

const index = len - 1;

promiseMaker().then(() => {

  this.excutingTasks.splice(index, 1);

  if (this.waitTasks.length > 0) {

    this.run(this.waitTasks.shift());

  }

});

}

}

复制代码

8、去重

利用 ES6 set 关键字:

function unique(arr) {

return [...new Set(arr)];

}

复制代码

利用 ES5 filter 方法:

function unique(arr) {

return arr.filter((item, index, array) => {

return array.indexOf(item)===index;

});

}

复制代码

十一、其它

requestAnimationFrame

如何排查内存泄漏问题,面试官可能会问为什么页面越来越卡顿,直至卡死,怎么定位到产生这种现象的源代码(开发环境)?

vite 大火,我复习的时候是去年 9 月份,还没那么火,可能现在的你需要学一学了~

vue3 也一样,如果你是 React 技术栈(就像我之前一样)当我没说。



喜欢的朋友记得点赞、收藏、关注哦!!!

相关文章:

做了一份前端面试复习计划,保熟~

前言 以前我看到面试贴就直接刷掉的&#xff0c;从不会多看一眼&#xff0c;直到去年 9 月份我开始准备面试时&#xff0c;才发现很多面试经验贴特别有用&#xff0c;看这些帖子&#xff08;我不敢称之为文章&#xff0c;怕被杠&#xff09;的过程中对我的复习思维形成影响很大…...

虚幻引擎开发命名规则

UE的命名规则如下&#xff1a; 模版类以T作为前缀&#xff0c;例如TArray, TMap, TSet。UObject派生类都以U前缀。AActor派生类都以A前缀。SWidget派生类都以S前缀。全局对象使用G开头&#xff0c;如GEngine。抽象接口以I前缀。枚举以E开头。bool变量以b前缀&#xff0c;如bPe…...

【蓝桥杯每日一题】砍竹子

砍竹子 2024-12-7 蓝桥杯每日一题 砍竹子 STL 贪心 题目大意 这天, 小明在砍竹子, 他面前有 nn 棵竹子排成一排, 一开始第 ii 棵竹子的 高度为 h i h_i hi​. 他觉得一棵一棵砍太慢了, 决定使用魔法来砍竹子。魔法可以对连续的一 段相同高度的竹子使用, 假设这一段竹子的高度为…...

Lambda表达式随记

学习链接 目录 作用定义[capture list] 捕获列表(paramter) 参数列表mutable 可变规格throw() 异常说明-> return-type 返回类型{function statement} lambda函数体 Lambda表达式的优缺点Lambda表达式工作原理适用场景STL算法库短小不需要复用函数场景 作用 Lambda表达式&…...

Vulhub:Log4j[漏洞复现]

CVE-2017-5645(Log4j反序列化) 启动靶场环境 docker-compose up -d 靶机IPV4地址 ifconfig | grep eth0 -A 5 ┌──(root㉿kali)-[/home/kali/Desktop/temp] └─# ifconfig | grep eth0 -A 5 eth0: flags4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 in…...

ubuntu系统生成SSL证书配置https

自签名【Lets Encrypt】的测试证书&#xff0c;有效期三个月。 第一步&#xff1a;安装acme&#xff0c;如果没有安装git&#xff0c;需要提前安装 下载came资源 git clone https://github.com/Neilpang/acme.sh.git 无法访问&#xff0c;可以试用gitee的资源&#xff0c;安…...

记录 idea 启动 tomcat 控制台输出乱码问题解决

文章目录 问题现象解决排查过程1. **检查 idea 编码设置**2. **检查 tomcat 配置**3.检查 idea 配置文件4.在 Help 菜单栏中&#xff0c;修改Custom VM Options完成后保存&#xff0c;并重启 idea 问题现象 运行 tomcat 后&#xff0c;控制台输出乱码 解决排查过程 1. 检查 id…...

C++ unordered_map和unordered_set的使用

1.unordered_set系列的使用 1.1unordered_set和unordered_multiset参考文档 unordered_set和unordered_multiset参考文档 1.2unordered_set类的介绍 • unordered_set的声明如下&#xff0c;Key就是unordered_set底层关键字的类型 • unordered_set默认要求Key⽀持转换为整…...

【探商宝】OpenAI 发布 Sora:视频生成领域的重大突破

2024 年 12 月 10 日&#xff0c;OpenAI 正式推出了备受瞩目的人工智能视频生成模型 Sora&#xff0c;这一举措在科技界引起了轩然大波&#xff0c;为视频创作领域带来了全新的可能性和变革. 一、Sora 的功能与特性 1. 强大的视频生成能力 Sora 能够根据用户输入的文本描述生…...

[代码随想录Day32打卡] 理论基础 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯

理论基础 题型 动归基础&#xff08;这一节就是基础题&#xff09;背包问题打家劫舍股票问题子序列问题 动态规划五部曲 确定dp数组及其下标的含义确定递推公式dp数组如何初始化遍历顺序打印dp数组 509. 斐波那契数 简单~ dp数组及下标含义&#xff1a; dp[i]表示第i各斐…...

【实操GPT-SoVits】声音克隆模型图文版教程

项目github地址&#xff1a;https://github.com/RVC-Boss/GPT-SoVITS.git官方教程&#xff1a;https://www.yuque.com/baicaigongchang1145haoyuangong/ib3g1e/tkemqe8vzhadfpeu本文旨在迅速实操GPT-SoVits项目&#xff0c;不阐述技术原理&#xff08;后期如果有时间研究&#…...

开发一套SDK 第一弹

自动安装依赖包 添加条件使能 #ex: filetypesh bash_ls 识别 达到预期,多个硬件环境 等待文件文件系统挂在完成 或者创建 /sys/class/ 属性文件灌入配置操作 AI 提供的 netlink 调试方法,也是目前主流调用方法,socket yyds #include <linux/module.h> #include <linux…...

2024149读书笔记|Hans的阿狸五部曲——成长的路上分离在所难免

2024149读书笔记|Hans的阿狸五部曲——成长的路上分离在所难免 1. 《阿狸和小小云》2. 《阿狸和小玉》3. 《阿狸呓语》4. [202480读书笔记|《阿狸和弯月亮》——生的再普通&#xff0c;也是限量版](https://blog.csdn.net/qq_40985985/article/details/139731131)5. 《阿狸永远…...

外包干了5天,技术明显退步。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入南京某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了2年的功能测试&…...

Ubuntu22.04 安装Isaac Lab

目录 1.1 安装IsaacLab 1.1.1 下载文件 1.1.2 创建Isaac Sim符号链接 1.1.3 创建并激活conda环境 1.1.4 安装依赖 1.1.5 安装IsaacLab扩展 1.1.6 安装完成&#xff0c;要source一下 1.2 验证IsaacLab安装 1.1 安装IsaacLab 1.1.1 下载文件 将 Isaac Lab 仓库克隆到您的…...

unity 2D像素种田游戏学习记录(自用)

一、透明度排序轴 改变sprite的排序方式&#xff0c;默认按照z轴进行排序&#xff08;离摄像机的远近&#xff09;。可以将其改变成y轴的排序方式&#xff0c;这样可以使2D人物走在草丛的下方就不被遮挡&#xff0c;走在草丛上方就被遮挡&#xff0c;如下图。 在项目设置-图形…...

NIFI使用

1 从Kafka接收消息&#xff0c;存储到数据库中。 &#xff08;1&#xff09; ConsumerKafka processor &#xff08;2&#xff09;Execute Scripts Processor 我这里是使用JS脚本进行处理。 还有很多其他语言的脚本。 var flowFile session.get(); if (flowFile ! null) {v…...

vue3+vite+ts 使用webrtc-streamer播放海康rtsp监控视频

了解webrtc-streamer webrtc-streamer 是一个使用简单机制通过 WebRTC 流式传输视频捕获设备和 RTSP 源的项目&#xff0c;它内置了一个小型的 HTTP server 来对 WebRTC需要的相关接口提供支持。相对于ffmpegflv.js的方案&#xff0c;延迟降低到了0.4秒左右&#xff0c;画面的…...

Nanolog起步笔记-9-log解压过程(3)寻找meta续

Nanolog起步笔记-9-log解压过程-3-寻找meta续 当前的目标新的改变decompressNextLogStatementmetadata查看业务面的log语句注释掉 runBenchmark();改过之后&#xff0c;2条记录之后&#xff0c;这里就直接返回了 小结 当前的目标 没有办法&#xff0c;还要继续。 当前的目标&a…...

未来网络技术的新征程:5G、物联网与边缘计算(10/10)

一、5G 网络&#xff1a;引领未来通信新潮流 &#xff08;一&#xff09;5G 网络的特点 高速率&#xff1a;5G 依托良好技术架构&#xff0c;提供更高的网络速度&#xff0c;峰值要求不低于 20Gb/s&#xff0c;下载速度最高达 10Gbps。相比 4G 网络&#xff0c;5G 的基站速度…...

【北京迅为】iTOP-4412全能版使用手册-第六十八章 U-boot基础知识

iTOP-4412全能版采用四核Cortex-A9&#xff0c;主频为1.4GHz-1.6GHz&#xff0c;配备S5M8767 电源管理&#xff0c;集成USB HUB,选用高品质板对板连接器稳定可靠&#xff0c;大厂生产&#xff0c;做工精良。接口一应俱全&#xff0c;开发更简单,搭载全网通4G、支持WIFI、蓝牙、…...

go 中线程安全map

在 Go 语言中&#xff0c;官方包 sync.Map 确实提供了线程安全的映射数据结构。然而&#xff0c;正如你所提到的&#xff0c;使用 sync.Map 时&#xff0c;有时需要进行类型断言&#xff0c;这可能会让代码显得冗长或不直观。 如果你希望使用一个更加易用的线程安全映射&#…...

封装类与普通类的区别

1 封装类的好处 数据隐藏&#xff1a;通过封装&#xff0c;我们可以将类的内部实现细节隐藏起来&#xff0c;只暴露有限的接口。这样&#xff0c;外部代码就不能直接访问或修改类的内部状态&#xff0c;从而保证了数据的安全性和完整性。 数据保护&#xff1a;封装可以…...

StarRocks-hive数据类型导致的分区问题

背景&#xff1a; 有个hive的表&#xff0c;是月分区的&#xff08;month_id&#xff09;&#xff0c;分区字段用的是string类型。数据量比较大&#xff0c;为了保证计算性能&#xff0c;所以把数据导入到SR里&#xff0c;构建一个内部表。但是在建表的时候想使用月分区使用pa…...

Java面试题精选:设计模式(二)

1、装饰器模式与代理模式的区别 1&#xff09;代理模式(Proxy Design Pattern ) 原始定义是&#xff1a;让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问&#xff0c;并允许将请求提交给对象前后进行一些处理。 代理模式的适用场景 功能增强 当需要对一个对…...

【JavaEE】多线程(7)

一、JUC的常见类 JUC→java.util.concurrent&#xff0c;放了和多线程相关的组件 1.1 Callable 接口 看以下从计算从1加到1000的代码&#xff1a; public class Demo {public static int sum;public static void main(String[] args) throws InterruptedException {Thread …...

技术型企业如何高效搭建企业博客以增强品牌影响力和市场竞争力

在数字化时代&#xff0c;技术型企业面临着激烈的市场竞争和快速变化的行业环境。为了在这场竞争中脱颖而出&#xff0c;企业需要寻找新的营销渠道和品牌建设工具。企业博客作为一种低成本、高效率的在线内容平台&#xff0c;已经成为技术型企业增强品牌影响力和市场竞争力的重…...

【qt环境配置】windows下的qt与vs工具集安装\版本对应关系

vs工具集安装通过vs的在线安装器勾选工具集即可 工具包下载路径&#xff1a;https://www.microsoft.com/zh-cn/download/details.aspx?id40784 配置工具集在qt中可以自动扫描到 《正确在 Windows 上配置 MSVC(2019) 作为 Qt 编译器》https://b3logfile.com/pdf/article/15922…...

XTuner 微调实践微调

步骤 0. 使用 conda 先构建一个 Python-3.10 的虚拟环境 cd ~ #git clone 本repo git clone https://github.com/InternLM/Tutorial.git -b camp4 mkdir -p /root/finetune && cd /root/finetune conda create -n xtuner-env python3.10 -y conda activate xtuner-env…...

docker compose

Docker的理念是一个容器只运行一个服务。而 Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具 1 2。通过 docker-compose.yml 文件&#xff0c;用户可以配置应用程序服务&#xff0c;并使用简单的一条命令便可以创建和启动所有服务。这是关于 Docker Compose …...

Java 中的方法重写

在 Java 中&#xff0c;方法重写&#xff08;Method Overriding&#xff09;是面向对象编程的一个重要概念&#xff0c;它指的是子类中存在一个与父类中相同名称、相同参数列表和相同返回类型的方法。方法重写使得子类可以提供特定的实现&#xff0c;从而覆盖&#xff08;或改变…...

阿里云ECS服务器域名解析

阿里云ECS服务器域名解析&#xff0c;以前添加两条A记录类型&#xff0c;主机记录分别为www和&#xff0c;这2条记录都解析到服务器IP地址。 1.进入阿里云域名控制台&#xff0c;找到域名 ->“解析设置”->“添加记录” 2.添加一条记录类型为A,主机记录为www&#xff0c…...

非父子通信(扩展)-- event bus 事件总线

创建一个空实例Bus&#xff0c; export default 导出Bus 过程:由A组件对Bus组件进行监听&#xff0c;B组件触发Bus对应的事件&#xff0c;由于A组件进行监听&#xff0c;触发事件之后就会进行A组件的回调&#xff0c;那么就可以将消息发送给A了 在src文件夹下新建utils文件夹&a…...

【Linux系列】获取当前目录

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

大模型:把GPT搬回家 - chatGPT的本地化API -Node.js调用

chatGPT拒绝了中国大陆和中国香港的访问&#xff0c;包括api的调用。这使得我们无法使用目前来讲确实YYLX的生产工具&#xff0c;仔细想一下其实还是可以曲线解决的&#xff0c;本文的介绍仅供学习参考。 用Node.jschatGPT提供的API&#xff0c;就可以在自己本地或者自己的服务…...

【Qt项目实战】使用脚本拓展CPP应用程序(2)——Lua脚本及编辑器

考古 《【Qt项目实战 】&#xff1a;使用脚本拓展CPP应用程序&#xff08;1&#xff09;——Lua脚本及编辑器》 一、LuaBridge 最近在项目的某个模块再次使用Lua作为程序拓展语言&#xff0c;开发了一些新的功能。 这里借助 LuaBridge 轻松实现Qt调用C类成员函数及成员对象等…...

【RBF SBN READ】hadoop社区基于RBF的SBN READ请求流转

读写分离功能的背景及架构 当前联邦生产集群的各个子集群只有Active NameNode在工作,当读写任务变得繁忙的时候,只有一个Active负责处理的话,此时集群的响应和处理能力业务侧感知会明显下降,为此,我们将引入Observer架构,实现读写功能的分离,使得Active只负责写请求,而…...

【教学类-36-08】20241210对称蝴蝶——去白边(图案最大化)一大和一大二小

背景需求 前期制作了对称蝴蝶&#xff0c;用来涂色&#xff0c;幼儿很喜欢 【教学类-36-07】20230707三只对称蝴蝶&#xff08;midjounery-niji&#xff09;&#xff08;涂色、裁剪、游戏&#xff08;飞舞的蝴蝶&#xff09;&#xff09;_对称图案涂色-CSDN博客文章浏览阅读49…...

Flume——sink连接hdfs的参数配置(属性参数+时间参数)

这可不是目录 配置文件官网说明属性参数时间参数 配置文件官网说明 可以参考官网的说明 属性参数 属性名称默认值说明channel-type-组件类型名称&#xff0c;必须是hdfshdfs.path-HDFS路径&#xff0c;例如&#xff1a;hdfs://mycluster/flume/mydatahdfs.filePrefixFlumeDa…...

lc字符串相加——模拟

415. 字符串相加 - 力扣&#xff08;LeetCode&#xff09; 不准调用封装好的那些库。手动模拟两数相加&#xff0c;记录进位。主要当其中短的数字计算完了怎么办&#xff0c;技巧为下标为负数时就当做0&#xff0c;相当于补0一样。 class Solution {public String addStrings…...

框架建设实战6——缓存组件

说起分布式缓存,如今redis大行其道。不过,我们在创建缓存组件时,需要着重考虑如下几点: 1.客户端选型 本组件基于springboot2的默认实现,即lettuce客户端。不同客户端区别如下: 名称描述 优缺点分析 jedis1.springboot1.5.*默认 2.老牌客户端,使用稳定…...

基于springboot使用Caffeine

Caffeine 是一个基于 Java 的高性能、现代化的缓存库。它由 Ben Manes 开发&#xff0c;受 Google Guava 缓存库的启发&#xff0c;但具有更好的性能和更多的功能。 Caffeine 的核心特点 高性能 基于 Java 8 的现代化设计&#xff0c;利用高级并发算法&#xff0c;提供极低的延…...

深入解析 JavaScript 中的 Blob 对象:二进制数据处理的核心

文章目录 1.Blob是什么2.Blob用法实例属性Blob方法slice方法text方法 示例1&#xff1a;字符串 Blob示例2&#xff1a;数组和字符串 Blob示例3&#xff1a;从文件输入创建 3.使用场景1.创建 Blob 并生成 URL&#xff0c;下载文件2.文件上传3.切片上传3.Blob用于URL在线预览PDF文…...

fastboot

Fastboot 是 Android 和嵌入式开发中常见的一个低级工具&#xff0c;它允许通过 USB 与设备直接交互&#xff0c;用于分区的管理和系统的刷写等任务。以下是其工作原理和核心机制的解析&#xff1a; 1. Fastboot 的基本概念 Fastboot 是一种通信协议&#xff0c;通常在设备的引…...

yosys内部数据结构

一、参考链接 1.Yosys内部结构doxygen文件 yosys-master: RTLIL Namespace Reference 2.yosys内部结构介绍 https://yosyshq.readthedocs.io/projects/yosys/en/docs-preview-cellhelp/yosys_internals/formats/rtlil_rep.html 二、概览 图 1 网表核心数据结构 如图 1所示…...

前端导出excel实战(xlsx库和exceljs库)

一. 概览 前端导出excel是比较常见的需求&#xff0c;比如下载excel模板和批量导出excel。目前比较常用的库有xlsx和excel&#xff0c;接下来就着两种方式进行梳理。 二. 下载模板 xlsx库实现&#xff1a; 示例核心代码如下&#xff1a; const excelColumn {details: {ma…...

TCP/IP杂记

TCP三次握手、四次挥手 从应用角度&#xff0c;不用多考虑为什么有三次&#xff0c;遵循标准即可。 ubuntu 下 wireshark安装&#xff1a; sudo add-apt-repository universe sudo apt install wireshark 三次握手实证&#xff1a; 第一次握手的情况如下&#xff1a;&#…...

深入解析 Spring Security —— 打造高效安全的权限管理体系

目录 前言1. 初识 Spring Security1.1 Spring Security 的两大核心功能1.2 Spring Security 的主要特点 2. 配置 Spring Security2.1 配置类概述2.2 基础配置示例2.3 示例解析 3. Spring Security 的进阶功能3.1 自定义用户服务3.2 注解式权限控制3.3 动态权限控制 4. 实战应用…...

PostGIS分区表学习相关

在Postgresql中对空间数据进行表分区的实践_postgresql空间数据-CSDN博客文章浏览阅读1.4k次&#xff0c;点赞26次&#xff0c;收藏21次。Postgresql的分区功能允许将一个大表按照特定的规则拆分成多个小的分区表。这样做的好处在于&#xff0c;在查询数据时&#xff0c;可以只…...

避大坑!Vue3中reactive丢失响应式的问题

在vue3中,我们定义响应式数据无非是ref和reactive。 但是有的小伙伴会踩雷&#xff01;导致定义的响应式丢失的问题。 reactive丢失响应式的情况1&#xff08;直接赋值&#xff09; 场景: 1.你定义了一个数据:let datareactive({name:"",age:"" }) 2.然后你…...