共计 27490 个字符,预计需要花费 69 分钟才能阅读完成。
前言
以前我看到面试贴就间接刷掉的,从不会多看一眼,直到去年 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”) {
return obj;
}
if (map.get(obj)) {
return map.get(obj);
}
let result = {};
// 初始化返回后果
if (
obj instanceof Array ||
// 加 || 的起因是为了避免 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) {return data[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”) {
throw new Error("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”) {
throw new Error("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”) {
throw new Error("Type error");
}
// 获取参数
const args = […arguments].slice(1),
const fn = this;
return function Fn() {
return fn.apply(
this instanceof 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”)
throw new TypeError("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 技术栈(就像我之前一样)当我没说。
最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163 互相学习,咱们会有业余的技术答疑解惑
如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点 star:http://github.crmeb.net/u/defu 不胜感激!
PHP 学习手册:https://doc.crmeb.com
技术交换论坛:https://q.crmeb.com