Fork me on GitHub

写给自己的前端面试指南

分类收集整理一些前端面试要点。

熟练掌握JavaScript

this指向 :star:

代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。

  • 全局环境下的this:指向全局对象window

  • 函数内的this:

    • 默认情况:非严格模式下this指向全局对象, 严格模式下this会指向undefined
    • 箭头函数:箭头函数本身没有 this, this的指向由外层作用域决定的
    • 构造函数:构造函数中的this是指向实例,不会被任何方式改变 this
    • 通过call()、apply()或者bind()直接指定this的绑定对象,bind()是创建一个新的函数,需要手动调用才会执行;多次bind,this指向第一次的bind
  • 对象中的this:

    • 函数的定义位置不影响其this指向,this指向只和调用函数的对象有关(this 永远指向最后调用它的那个对象)
    • 多层嵌套的对象,内部方法的this指向离被调用函数最近的对象

this面试题

箭头函数 :star:

箭头函数内部的this是词法作用域(本身没有this),由上下文确定。在以下需要用this的场景尽量不使用:

  • 定义对象的方法
  • 定义原型方法
  • 构造函数使用箭头函数
  • 作为事件的回调函数

原型 :star:

原型:用于实现对象的继承,可以理解为对象的父对象。每个对象都有__proto__属性,指向了创建该对象的构造函数的原型。

原型链是由原型对象组成,__proto__将对象连接起来组成了原型链。是一个用来实现继承和共享属性的对象链。

实例的 constructor 属性指向构造函数,构造函数又通过 prototype 属性指向原型。

__proto__constructor属性是对象所独有的。prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__和constructor属性。

prototype是从函数指向一个对象(以当前函数作为构造函数构造出来的对象的原型对象,包含了constructor),可以让所有实例都共享公用的属性和方法。

constructor是指向该对象的构造函数。可以理解为所有函数(对象)都由Function构造而来,所以constructor的终点就是Function

属性查找机制: __proto__属性(即[[prototype]])是一个对象指向它的原型对象的指针。当访问一个对象的属性,如果找不到对象的该属性,就会沿着__proto__指向的父对象找,如果还没找到就会继续沿着__proto__指向找,直到找到原型链的终点null,这种就是原型链

对象本身 -> 构造函数(constructor) -> 对象的原型(__proto__) -> 构造函数的原型(prototype)

属性修改机制:只会修改实例对象本身的属性,不会直接修改到原型对象。

搞懂JS中的prototype、__proto__与constructor

new操作符

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

操作:

  1. .开辟一个新的内存空间,创建一个空的JS对象;

  2. 为创建的对象添加__proto__属性,该属性指向构造函数的原型对象prototype;

    1
    2
    newobj.__proto__ = father.protetype
    // newobj.__proto__.constuctor = father
  3. 将构造函数的作用域赋给新对象(因此this就指向了这个对象);

    1
    father.call(newobj)

执行构造函数中的代码(为这个新对象添加属性)。
4. 返回的对象赋值给newobj;如果函数没有返回对象,则返回这个this(这也是为什么构造函数通常不写return语句)

继承

  • 原型链继承(原型的共享性会导致实例的引用类型值相同)、
  • 构造函数继承(不能继承原型属性/方法;无法实现函数复用,每个方法都要在实例上重新创建一遍,影响性能)、
  • 原型链+借用构造函数的组合继承
  • class 继承

ES5的继承时通过原型或构造函数机制来实现。ES6通过class定义类实现,里面有构造方法,类之间通过extends关键字实现继承。

ES5和ES6继承机制的区别:
ES5先创建子类,再将父类的方法添加到这个对象上面,即“实例在前,继承在后”;ES6先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,在子类中通过调用super方法访问父级后,通过修改this实现继承,即“继承在前,实例在后”

JavaScript常见的六种继承方式

class继承

修饰符:

  • pubilc
  • peivate(只能在当前定义的类里面进行访问 在其它子类中进行访问会提示错误)
  • protected(子类可以访问它的属性 不属于它的子类 访问会报错)
  • readonly
  • static(可以直接访问的属性 不需要实例化 可以直接在类上进行调用,静态属性通过浅拷贝实现继承)

extends 来实现继承(es6继承 = 寄生组合式继承 + 修改子类__proto__):

1
2
3
4
5
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype); // B.prototype.__proto__ === A.prototype // true

// B 继承 A 的静态属性
Object.setPrototypeOf(B, A); //B.__proto__ === A // true

extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。

为什么子类的构造函数,一定要调用super(),不然就报错?

ES6 要求,子类的构造函数必须执行一次super函数。(新建子类实例时,父类的构造函数必定会先运行一次)

这和class的继承机制有关,super会生成一个继承父类的this对象,通过这个继承父类。class中的super表示父类的构造函数,用来新建一个父类的实例对象。

super可以作为函数使用,也可以作为对象使用。super内部的this指的是子类的实例。

Promise和 async/await :star:

Promise是ES6中新加的一个函数,用来解决js中进行异步编程的方式。

Promise解决了异步嵌套的怪圈,异步请求方法用链式表达更加清晰,但是如果有大量的异步请求的时候,流程复杂的情况下,会发现充满了屏幕的then,看起来非常吃力,而ES7的Async/Await的出现就是为了解决这种复杂的情况。

错误捕捉方式:promise 使用.catch;async/await 可以.then.catch,也可以 try/catch

调用方式:promise 连续调用,链式调用;async/await 简洁易懂,异步操作同步化

异步Promise及Async/Await可能最完整入门攻略

Promise的特性

  • Promise的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,再对Promise对象添加回调函数,也会立即得到这个结果。

Promise.all

  • all() : 所有都成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
  • race(): 哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
  • allSettled() : 当给定的promise数组中的所有promise被拒绝后会返回一个拒绝的promise数组,一一对应。

Promise中的then第二个参数和catch有什么区别?

如果在then的第一个函数里抛出了异常,后面的catch能捕获到,而then的第二个函数捕获不到。

then的第二个参数和catch捕获错误信息的时候会就近原则,如果是promise内部报错,reject抛出错误后,then的第二个参数和catch方法都存在的情况下,只有then的第二个参数能捕获到,如果then的第二个参数不存在,则catch方法会捕获到。

promise实现并发个数限制

限制并发请求数量

Event loop :star:

事件循环机制指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也是使用异步的原理。

javascript是一门单线程语言,执行事件是先同步再异步,异步中先微任务,再宏任务。event loop它最主要是分三部分:主线程、宏任务(macro-task)、微任务(micro-task)。

  • macro-task(宏任务):setTimeout,setInterval, script (宏任务是由浏览器规定的)
  • micro-task(微任务):Promise.then/catch,process.nextTick (微任务是js语法规定的)

执行流程:主线程从”任务队列”中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查微任务队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有微任务。然后再进入下一个循环去任务队列中取下一个任务执行。

  1. 所有同步任务都在主线程上执行,形成一个执行栈
  2. 主线程之外,还存在一个”任务队列”。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件
  3. 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

js事件循环机制event-loop

为什么区分宏任务和微任务

区分微任务和宏任务的根本原因是为了插队。由于微任务执行快,一次性可以执行很多个,在当前宏任务执行后立刻清空微任务可以达到伪同步的效果,这对视图渲染效果起到至关重要的作用。

  • 微任务是线程之间的切换,速度快。不用进行上下文切换,可以快速的一次性做完所有的微任务。
  • 宏任务是进程之间的切换,速度慢,且每次执行需要切换上下文。因此一个Eventloop中只执行一个宏任务。

node和浏览器循环的区别

区别主要是node的 宏任务 分好几种,而这好几种又有不同的 任务队列,而不同的 任务队列 又有顺序区别,而 微任务是穿插在每一种宏任务之间的。

浏览器:当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。

node中:libuv 引擎中的事件循环分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。

作用域和作用域链

JavaScript采用的是静态作用域(词法作用域),函数的作用域在函数定义的时候就决定了。

作用域就是代码的执行环境,全局执行环境就是全局作用域,函数的执行环境就是私有作用域,它们都是栈内存。(规定变量和函数的可使用范围称为作用域,JS的作用域靠函数形成)

作用域链(作用域形成的链条)是在函数定义的时候创建的(函数有一个内部属性 [[scope]], 创建时保存所有父变量对象到其中)。当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从上级执行上下文的变量对象中查找,一直找到全局上下文的变量对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

JS 作用域和作用域链

js执行环境

js代码的执行分为编译器的编译解析js引擎与作用域执行两个阶段。

执行环境定义了变量或函数是否具有访问其他数据的权限,进而决定各自行为。

JS解释器不会编译代码,而是边解析边执行的,对于语法错误,如果没有被执行到就不会报错。语法检查之后,进入预解析阶段,在预解析阶段会在内存中创建执行上下文,也叫执行环境。

当函数执行/调用时,会进入一个特定的执行环境,分为全局执行环境、函数执行环境、eval()函数的动态执行环境。

每个执行环境由三部分构成:

  • 变量对象(所有的变量(变量 函数 形参 arguments)组成的一个对象)
  • 作用域链([[Scope]]作用域即变量对象,作用域链是一个由变量对象组成的单向链表,作用是用来进行变量查找。)
  • this(指向一个环境对象)

执行环境生命周期:在预解析阶段被创建、代码执行阶段被重新赋值,代码执行完出栈、等待被回收

执行上下文栈

JS实现为单线程,因此同一时刻仅有一个环境处于运行状态。因此,可以把JS的执行实现为栈,每次进入一个新的执行环境,都会把该环境置于栈顶执行,执行完毕后可弹出。因此形成了一个执行上下文栈。而变量的查找正是基于该栈的。

变量提升

函数在运行的时候,会首先创建执行上下文,然后将执行上下文入栈,然后当此执行上下文处于栈顶时,开始运行执行上下文。

在创建执行上下文的过程中:创建变量对象,创建作用域链,确定 this 指向。其中创建变量对象的过程中,首先会为 arguments 创建一个属性,值为 arguments,然后会扫 function 函数声明,创建一个同名属性,值为函数的引用,接着会扫 var 变量声明,创建一个同名属性,值为 undefined,这就是变量提升。

闭包

闭包就是能够读取其他函数内部变量的函数,参数和变量不会被垃圾回收机制收回。

闭包的原理:利用作用域链的特性,所以闭包访问的上级作用域中的变量对象,其值为其运算结束后的最后一个值。(当前环境中存在指向父级作用域的引用)

如何产生闭包:

  • 函数中返回函数
  • 函数当做参数传递

闭包的特点:

  • 里面的变量和参数不会被垃圾回收机制回收(按照作用域链的特点,闭包使用的变量不会销毁,因为函数会一直被调用,所以只能一直存在)
  • 闭包能访问外部变量,闭包的局部变量无法被外部访问(闭包的私有变量在自执行后直接销毁)

闭包的缺点:

  • 可能造成多余的内存消耗
  • 可能造成内存泄漏

闭包的作用:

  • 封装私有变量,防止变量被全局污染
  • 存储变量,实现累加

闭包的作用

判断Object类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1
function isPlainObject() {
return val !== null && !Array.isArray(val) && typeof val === 'object';
}

//2
function isPlainObject(val){
return val.constructor == Object;
}

// 3
function isPlainObject(val){
return Obejct.prototype.toString.call(val) == '[object Object]';
}

instanceof 和 typeof

typeof 对于原始类型来说,除了 null 都可以显示正确的类型。

typeof原理:不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息

instanceof 判断一个对象是否是另一个对象的实例。通过原型链的方式来判断是否为构造函数的实例(用于判断某个实例是否属于某构造函数)。

instanceof 可以准确判断对象(引用)类型,但是不能准确检测原始类型。

PS:可以通过修改原型的指向修改 instanceof。

instanceof原理:查找构造函数的原型对象是否在实例对象的原型链上,如果在返回true,如果不在返回false。a.__proto__ === Fn.prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typeof undefined // 'undefined'
typeof null //object
typeof [] //object

typeof function() {} // 'function'
typeof class a{} // function
typeof /a/ // 'object'
typeof new Map() // 'object'

//instanceof
const Fn = function() {
this.name = '构造函数'
}
Fn.prototype = Object.create(Array.prototype)
let a = new Fn()
console.log(a instanceof Array) // true

console.log instanceof Function //true
console.log instanceof Object //true
1 instanceof Number // false

手写实现:

1
2
3
4
5
6
7
8
9
10
11
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left); // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false; //找到也没有找到 尽头Object.prototype.__proto__ = null
if (proto === prototype) return true; //找到了

proto = Object.getPrototypeOf(proto); // 接着往上找
}
}

垃圾回收机制

在JavaScript中拥有自动的垃圾回收机制,通过一些回收算法,找出不再使用引用的变量或属性,由JS引擎按照固定时间间隔周期性的释放其所占的内存空间。

JavaScript 中主要的内存管理概念是可达性。大概意思是以某种方式可以访问到或者可以使用的值,它们就是需要保存在内存中,无法访问,也无法使用的值,则需要被垃圾回收机制回收。

  • 引用计数(当该引用类型的值的引用次数为0,就说明没有变量被该引用类型的值赋值)
  • 标记清除(销毁那些未带标记的值并回收他们所占用的内存空间)

call apply bind

call的性能优于apply。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Function.prototype.myCall = function(context, ...args){
// 不传第一个参数默认为windows
context = context || window
//普通函数的this指向调用者, 故this就是要改造的那个函数
//将函数挂载到目标对象上
context.func = this
//执行函数,并保存返回值
let res = context.func(...args)
//删除之前挂载在目标对象上的函数,还原目标对象
delete context.func
//return返回值
return res
}

Function.prototype.myApply = function(context, args){
context = context || window
context.func = this
let res = context.func(...args)
delete context.func
return res
}

Function.prototype.myBind = function(context, ...args){
let _this = this
return function Fn(){
//因为返回值是一个函数,需要判断 new Fn() 的情况
//new 会让 this 指向这个新的对象,所以直接判断 Fn 是否在 this 的原型链上
if(this instanceof Fn){
return new _this(...args, ...arguments)
}
//调用 call 改变 this 指向
return _this.call(context, ...args, ...arguments)
}
}

内存泄漏

无用的内存还在占用,得不到释放和归还,比较严重的时候,无用的内存还会增加,从而导致整个系统卡顿,甚至崩溃。

闭包,全局变量的不恰当使用,没清空的定时器,没清理的dom引用等

常用设计模式

设计模式是代码设计经验的总结,为了可重用代码,保证代码的可靠性等。

  • 工厂模式:主要用来创建同一类对象,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
  • 单例模式:只允许实例化一次的对象类,该类负责创建自己的对象,同时确保只有单个对象被创建。
  • 装饰器模式(结合到es7的装饰器):允许向一个现有的对象添加新的功能,同时又不改变其结构,这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
  • 代理模式 (结合es6的proxy):创建具有现有对象的对象,以便向外界提供功能接口。
  • 观察者模式:当对象间存在一对多关系时使用。当一个对象被修改时,则会自动通知依赖它的对象。
  • 原型模式:使用于创建新的对象的类共享原型对象的属性以及方法

JavaScript设计模式

数据结构与算法

数组:

1
2
const arr = [1, 2, 3, 4]   
const arr1 = new Array()

栈:后进先出(LIFO),只用 pop 和 push 完成增删的“数组”

队列:先进先出(FIFO),只用 push 和 shift 完成增删的“数组”

链表:有序的列表、都是线性结构(有且仅有一个前驱、有且仅有一个后继)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 结点格式
{
// 数据域
val: 1,
// 指针域,指向下一个结点
next: {
val:2,
next: ...
}
}
*/
// 创建链表结点
function ListNode(val) {
this.val = val
this.next = null
}
const node = new ListNode(1)
node.next = new ListNode(2)

二叉树:空树或者由根结点、左子树和右子树组成,且左右子树都是二叉树

1
2
3
4
5
6
// 二叉树结点的构造函数
function TreeNode(val) {
this.val = val
this.left = this.right = null
}
const node = new TreeNode(1)

JavaScript 算法与数据结构

进击的CSS

css盒模型

盒模型包括包括margin、border、padding、content

  • 标准盒模型:content 部分不包含其他部分
  • IE盒模型:content 部分包含了 border 和 padding

面试官:谈谈你对 CSS 盒模型的认识?

文档流和BFC :star:

文档流:将窗体自上而下分成一行行,并在每一行中按从左到右的顺序来排放元素,这个我们称之为文档流。

非文档流(脱离文档流):也就是将元素从普通的布局排版中拿走,其他盒子在定位的时候,会当做脱离文档流的元素不存在而进行定位。(float、absolute、fixed)

BFC(Block formatting context): 称为块级格式化上下文,是CSS中的一种渲染机制。BFC是一个独立的渲染区域,它决定了块级元素如何对它的子元素内容进行布局,以及与子元素同级别的兄弟元素的关系和相互作用。

可以理解为:创建了 BFC的元素就是一个独立的盒子,里面的子元素不会在布局上影响外面的元素(里面怎么布局都不会影响外部),BFC仍属于文档中的普通流。

BFC 的原理:

  • 独立的容器,内外元素互不影响
  • 内部的Box会在垂直方向,一个接一个地放置
  • Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
  • BFC 的区域不会与浮动元素的 float 重叠
  • 计算 BFC 高度,浮动元素也参与计算

如何创建BFC:

  • 根元素
  • float属性不为none
  • position为absolute或fixed
  • overflow不为visible
  • display为inline-block, table-cell, table-caption, flex, inline-flex

BFC作用:

  • 防止外边距重叠
  • 清除浮动的影响
  • 防止文字环绕

flex和grid

flex 布局是一维布局,Grid 布局是二维布局。Flex布局是轴线布局,只能指定”项目”针对轴线的位置,可以看作是一维布局,Grid 布局则是将容器划分成“行”和“列”,产生单元格,然后指定”项目所在”的单元格,可以看作是二维布局,Grid布局远比 Flex布局强大。

flex布局详解

grid布局

1
2
flex: 1
// flex: 1; === flex: 1 1 auto;

flex-grow是用来增大盒子的,比如,当父盒子的宽度大于子盒子的宽度,父盒子的剩余空间可以利用flex-grow来设置子盒子增大的占比

flex-shrink用来设置子盒子超过父盒子的宽度后,超出部分进行缩小的取值比例

flex-basis是用来设置盒子的基准宽度,并且basis和width同时存在basis会把width干掉

css动画

animation: name duration timing-function delay iteration-count direction;

@keyframes(@keyframes动画是循环的,而transform 只执行一遍)

深入浅出 CSS 动画

必备的HTML

history api

window 对象通过 history 对象提供了对浏览器的会话历史的访问,提供了对history栈中内容的操作。可以在不刷新页面的前提下动态改变浏览器地址栏中的URL地址,动态修改页面上所显示资源。

1
2
3
4
5
6
7
8
9
10
//在history中向后跳转:
window.history.back()
window.history.go(-1)

//向前跳转:
window.history.forward()
window.history.go(1)

//历史记录中页面总数
history.length

HTML5 引入了history.pushState()history.replaceState()方法,它们分别可以添加和修改历史记录条目。

  • history.pushState(state, title, url): 添加一条历史记录,不刷新页面
  • history.replaceState(state, title, url): 替换当前的历史记录,不刷新页面
  • popstate 事件:历史记录发生改变时触发,调用history.pushState()或者history.replaceState()不会触发popstate事件
  • hashchange 事件:当页面的hash值改变的时候触发,常用于构建单页面应用

主流的前端框架

几大框架知识点梳理

模块化和工程化

前端工程化是一种工程化的思想,而并不是一种技术手段。前端工程化的目标就是让前端项目,更利于团队的协作,解耦团队的分工,提高团队的开发效率。对于后期,更利于前端项目的维护。

工程化可以理解为,系统的、规范的进行开发和部署的流程。

结合代码实践,全面学习前端工程化

模块化

在浏览器中使用esm,在Node中使用CommonJS的模块化支持。

  • CommonJs (典型代表:node.js 早期,require / module.exports):用于服务端

    • 都运行在模块作用域,不会污染全局作用域
    • 同步加载的,即只有加载完成,才能执行后面的操作
    • 首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存
    • require 返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值
    • 缺点:在浏览器环境同步加载大量模块会导致性能问题
  • AMD (典型代表:require.js,require / defined):异步模块定义。
    所有依赖模块的语句,都定义在一个回调函数中,等到模块加载完成之后,这个回调函数才会运行
    require。

  • ES6 的 Module:import/export(或者在script中指定type=”module”)

    • CommonJS 和 AMD 模块,都只能在运行时确定这些东西
    • 编译时确定依赖:ES6 设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量
    • 按需加载:import()允许您仅在需要时动态加载模块,而不必预先加载所有模块

脚手架

脚手架的出现就是为减少重复性工作而引入的命令行工具,实现脚手架的核心思想就是自动化思维。

webpack

webpack 是一个模块打包工具(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

深入探究Webpack原理

基础配置和常用插件 :star:

webpack安装使用

打包优化 :star:

玩转 webpack,使你的打包速度提升 90%

Webpack构建速度优化

摇树的原理

采用删除不需要的额外代码的方式优化代码体积的技术。ES6的模块方案:import()引入模块的方式采用静态导入,可以采用一次导入所以的依赖包再根据条件判断的方式,获取不需要的包,然后执行删除操作。

webpack4和3区别

webpack3和webpack4区别

rollup

对于组件库项目,支持按需加载需要满足:组件库以ES模块化方式导出。 而rollup本来就支持ES模块的导出。

单元测试

JavaScript 缺少类型检查,编译期间无法定位到错误,单元测试可以帮助你测试多种异常情况。通过编写测试用例,可以做到一次编写,多次运行。

使用框架:Jest(内置了集成度比较高的断言库expect.js,自带断言、测试覆盖率工具,实现了开箱即用)。
它会自动识别一些常用的测试文件,比如*.spec.js和 *.test.js后缀的测试脚本,所有的测试脚本都放在tests或__tests__目录下。

浏览器

浏览器存储

Cookie、localStorage、sessionStorage、IndexedDB

常见的浏览器本地存储

浏览器原理 :star:

前端都该懂的浏览器工作原理

输入url到浏览器渲染 :star:

URL 解析 -> DNS 查询 -> TCP 连接 -> HTTP 请求 -> 服务器响应请求 -> 页面渲染

从输入URL到页面渲染的整个过程

浏览器的本地缓存

优先级:MemoryCache > ServiceWorker> DiskCache。

  • Memory Cache:内存中的缓存,主要包含的是当前中页面中已经抓取到的资源。内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。(关闭Tab页面就释放)。浏览器使用的内存是有限的,一般优先存小的文件或请求,优先存文件。
  • Disk Cache: 硬盘中的缓存,读取速度慢,胜在容量大和存储时效性上(根据 HTTP Herder 中的字段判断哪些资源需要缓存)绝大多数缓存都是disk cache。
  • Service Worker:必须HTTPS中使用(Service Worker 中涉及到请求拦截),运行在浏览器背后的独立线程。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
  • Push Cache:HTTP/2中的,缓存时间短暂,只在会话(Session)中存在,一旦会话结束就被释放。(当以上三种缓存都没有命中时,它才会被使用。)

浏览器的缓存机制

强缓存:浏览器直接从本地缓存中获取数据,不与服务器进行交互。强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间。(请求返回Code 200)

协商缓存:浏览器发送请求到服务器,服务器判断是否可使用本地缓存(主要通过请求头中的Etag和If-None-Match、Last-Modified和If-Modified-Since标识通信)更改了就重新请求,没有改变就直接用缓存。

ETag:协商缓存中判断是否变更的标志(优先级更高)。ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。

ETag一般不以明文形式相应给客户端。在资源的各个生命周期中,它都具有不同的值,用于标识出资源的状态。当资源发生变更时,那么ETag也随之发生变化。

强缓存和协商缓存

Etag详解

网络

http协议 :star:

http:是一个客户端和服务器端请求和应答的标准(TCP),是一个超文本传输协议。

https:是以安全为目标的 HTTP 通道,即 HTTP 下 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性。

HTTP 1.1 和 HTTP 2.0 的区别:

  • HTTP/2采用二进制格式而非文本格式
  • HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行
  • 使用报头压缩,HTTP/2降低了开销
  • HTTP/2让服务器可以将响应主动“推送”到客户端缓存中

HTTPS如何防止中间人攻击:
服务器是通过 SSL 证书来传递公钥,客户端会对 SSL 证书进行验证,其中证书认证体系就是确保SSL安全的关键。

HTTP和HTTPS协议

详解HTTP/1.0、HTTP/1.1、HTTP/2、HTTPS

HTTPS怎么避免中间人攻击

CSRF和XSS、SQL 注入攻击 :star:

CSRF:跨站请求伪造,攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

防御办法:

  • 在服务器端验证请求来源的站点,禁止第三方站点的请求(验证 HTTP Referer 字段, Referer记录了该 HTTP 请求的来源地址)
  • 随机token(在请求地址中添加 token 并验证)
  • 为Set-Cookie响应头新增Samesite属性(在 HTTP 头中自定义属性并验证)

XSS:跨站脚本攻击,攻击者通过“注入”,在网页中插入恶意代码,从而被浏览器执行所造成的一种攻击方式。

  • 反射型:url请求中加上script代码
  • 存储型:输入框中输入脚本提交到服务器,服务器返回信息执行
  • DOM型:通过脚本修改页面的DOM节点

防御办法:

  • 对输入进行检查和转码;
  • CSP(内容安全策略): 设置 HTTP Header 的 Content-Security-Policy; 或者设置 meta 标签的方式

SQL 注入: 主要是利用前端表单提交,比如输入框、富文本框,输入sql语句提交到后端,当后端服务通过提交的字段拼接成sql语句进行数据库查询的时候,恶意sql被执行,从而达到攻击的目的。

中间人攻击: 攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的。攻击者不仅能获得双方的通信信息,还能修改通信信息。防御办法:https

防止token泄露:HTTPS认证、对称加密、将请求 URL、时间戳、token 三者进行合并加盐签名,服务端校验有效性

xss和csrf

websocket

Websocket是 HTML5 新增的基于TCP的网络通信协议(全双工通讯的协议)。websocket不属于http无状态协议,协议名为”ws”。(没有同源限制)

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的。

优点:

  • 较少的控制开销
  • 更强的实时性
  • 保持连接状态
  • 性能更好

过程:

  1. 客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息;
  2. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据;
  3. 客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信(可以通过 send() 方法来向服务器发送数据,通过 onmessage 事件来接收服务器返回的数据)

最初的握手阶段是http协议,握手完成后就切换到websocket协议,借助于TCP传输信道进行全双工通信。建立通讯时,是由客户端主动发起连接请求,服务端被动监听,通信的数据是基于“帧(frame)”的,可以传输文本数据,也可以直接传输二进制数据,效率高。

websocket深入浅出

WebSocket

可视化

canvas常用API

Canvas API 提供了一个通过JavaScript和HTML的<canvas>元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

rect、clearRect、beginPath、moveTo、lineTo、closePath、stroke、arc、rotate、translate、transform

Canvas常用API

html5中canvas的绘图API

webgl 和 threejs

js动画库

Anime.js(可以处理CSS属性,单个CSS转换,SVG 或任 何 DOM 属性以及 JS 对象)、Mo.js(用于 Web 的动态图形工具带)

其他

TS

TypeScript 是基于 JavaScript 创造的强类型编程语言,可以进行任意程度的扩展。

优点:

  • 类型定义:良好的类型定义和对 ES6 的支持
  • 可维护性:增长了代码的可读性和可维护性
  • 提早报错:类型错误会在编译过程中被编译器发现,便于发现BUG
  • 代码可预测性:声明的变量一旦指定类型,它的类型就再也不能修改。这样变量就具有可预测性。
  • 便于重构

缺点:

  • 因为需要兼容 JavaScript 的缘故,TypeScript 的类型是可选的。所以还是会有些类型问题(如any)
  • 需要编译:浏览器和 Nodejs 并不支持 TypeScript,所以多了一步编译操作。
  • 增加开发成本:如类型定义和接口声明

「面试题」TypeScript

与js的区别

  • ts是静态语言,js是动态类语言
  • ts对比js基础类型上,增加了 void/never/any/元组/枚举/以及一些高级类型
  • js没有重载概念,ts可以重载

js项目如何升级为ts

安装typescript -> 安装eslint相关依赖 -> 新建tsconfig.json -> 修改文件后缀

配置项

  • target: 控制编译后输出的是什么js版本
  • lib: 指定要引入的库文件,是内置对象所应用的声明(如配置:[‘ES2015’, ‘DOM’])
  • module: 指定要使用的模块标准

Nodejs

一个基于Chrome V8引擎的JavaScript运行环境。它是一个轻量级框架,用于创建服务器端 Web 应用程序并扩展 JavaScript API 以提供常用的服务器端功能,创建前端工具库等。

NodeJS面试题

NodeJS面试题

应用场景

  • 基于 Express 框架 (opens new window),可以快速构建 Web 应用
  • 基于 Electron 框架 (opens new window),可以构建跨平台的桌面应用
  • 基于 restify 框架 (opens new window),可以快速构建 API 接口项目
  • 读写和操作数据库、创建实用的命令行工具辅助前端开发

Buffer

Buffer是nodejs全局上的一个內置模块,作用就是让JavaScript可以直接操作二进制数据。

全局对象

global、 process, console、 module和 exports

事件循环

事件循环其实就是一个事件队列,先加入先执行,执行完一次队列,再次循环遍历看有没有新事件加入队列。

目的:处理非阻塞 I/O 操作的机制 (指以异步来执行函数,先执行同步任务,耗时任务放在事件队列中,以此轮询执行)

IO事件→ setImmediate→ setTimeout/setInterval→ process. nextTick

libuv 引擎中的事件循环分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。
事件循环中细分为这六个阶段:

  • Timers: 执行定时器回调事件(依次执行完所有定时器回调才到下个阶段,微任务队列是在每个阶段完成后立即执行)
  • Pending: 一些系统级回调将会在此阶段执行
  • Idle,prepare: 此阶段”仅供内部使用”
  • Poll: IO回调函数,这个阶段较为重要也复杂些,
  • Check: 执行 setImmediate() 的回调
  • Close: 执行 socket 的 close 事件回调

如何支撑高并发

虽然js是单线程(缺点:不能充分利用多核,容易阻塞),node支持高并发主要基于事件循环机制。
nodejs 是异步非阻塞的,所以能扛住高并发。

stream

Node.js 中有四种基本的流类型:

  • Writable: 可写入数据的流(例如 fs.createWriteStream())。
  • Readable: 可读取数据的流(例如 fs.createReadStream())。
  • Duplex: 可读又可写的流(例如 net.Socket)。
  • Transform: 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate())。

npm安装机制

  • 查询 node_modules 目录之中是否已经存在指定模块
  • 若存在,不再重新安装
  • 若不存在,npm 向 registry 查询模块压缩包的网址
  • 下载压缩包,存放在根目录下的.npm目录里
  • 解压压缩包到当前项目的 node_modules 目录

移动端

uniapp

Flutter

Flutter 和 React Native 不同主要在于 Flutter UI是直接通过 skia 渲染的 ,而 React Native 是将 js 中的控件转化为原生控件,通过原生去渲染的。

Flutter面试指南

小程序

小程序的逻辑层和渲染层是在两个不同的线程的。

直接把JavaScript执行的逻辑层环境放到沙盒,一个纯JavaScript的执行环境,没有浏览器的概念,这样就没有DOM相关的API了,那小程序也得有页面,所以渲染层就单独开一个线程了。

  • 不能直接操作DOM, 保护页面上用户隐私
  • 限制一些API的调用, 规避了XSS攻击
安装axios
1
npm i axios axios-miniprogram-adapter

安装 -> 工具 -> 构建npm -> 引入

性能优化

  • 资源打包分析:webpack-bundle-analyzer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    npm install --save-dev webpack-bundle-analyzer
    // webpack.config.js 文件
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
    module.exports={
    plugins: [
    new BundleAnalyzerPlugin({
    analyzerMode: 'server',
    analyzerHost: '127.0.0.1',
    analyzerPort: 8889,
    reportFilename: 'report.html',
    defaultSizes: 'parsed',
    openAnalyzer: true,
    generateStatsFile: false,
    statsFilename: 'stats.json',
    statsOptions: null,
    logLevel: 'info'
    }),
    ]
    }
    // package.json
    "analyz": "NODE_ENV=production npm_config_report=true npm run build"
  • Gzip压缩: Compression-webpack-plugin

  • JavaScript、Css、Html压缩:UglifyJS、webpack-parallel-uglify-plugin、terser-webpack-plugin、mini-css-extract-plugin

前端性能优化 24 条建议
聊一聊前端性能优化

electron

介绍

Electron 是使用 JavaScript等前端技术构建跨平台的桌面应用程序的框架,可构建出兼容 Mac、Windows 和 Linux 三个平台的应用程序。允许使用Node.js(作为后端)和Chromium(作为前端)完成桌面GUI应用程序的开发。

Electron = Chromium + Node.js + Native API

原理

它通过集成浏览器内核,使用前端的技术来实现不同平台下的渲染,并结合了 Chromium 、Node.js 和用于调用系统本地功能的 API 三大板块。

  • Chromium 为 Electron 提供强大的 UI 渲染能力,由于 Chromium 本身跨平台,因此无需考虑代码的兼容性。
  • Chromium 并不具备原生 GUI 的操作能力,因此 Electron 内部集成 Node.js,编写 UI 的同时也能够调用操作系统的底层 API,例如 path、fs、crypto 等模块。
  • Native API 为 Electron 提供原生系统的 GUI 支持,借此 Electron 可以调用原生应用程序接口。

架构

基于Chromium 架构(Chromium 是 Chrome 的开源版,也是一个浏览器)。

当用Electron启动一个应用,会创建一个主进程。这个主进程负责与你系统原生的GUI进行交互并为你的应用创建GUI。

electron由主进程main process启动,通过BrowserWindow实例创建渲染进程renderer process(渲染Web页面),每个渲染进程都是相互独立渲染。考虑到安全问题,在渲染进程里面不允许直接调用GUI API,如果想要调用,必须通过和主进程通讯,请求主进程完成相应的调用。

每个 Electron 应用有且只要一个主进程(Main Process)、以及一个或多个渲染进程(Renderer Process), 对应多个 Web 页面。除此之外还有 GPU 进程、扩展进程等等。主进程负责创建页面窗口、协调进程间通信、事件分发。为了安全考虑,原生 GUI 相关的 API 是无法在渲染进程直接访问的,它们必须通过 IPC 调用主进程。这种主从进程模型缺点也非常明显,即主进程单点故障。主进程崩溃或者阻塞,会影响整个应用的响应。

Node.js 事件循环基于 libuv,但 Chromium 基于 message_pump。
将 Node.js 集成到 Chromium 中的原理:
Electron 起了一个新的安全线程去轮询 backend_fd,当 Node.js 有一个新的事件后,通过 PostTask 转发到 Chromium 的事件循环中,这样就实现了 Electron 的事件融合。

生命周期

  • ready:应用程序初始化完成
  • dom-ready:一个窗口中的文本加载完成
  • did-finish-load: 导航完成时触发
  • window-all-closed:所有窗口都被关闭时触发;监听了此事件,需要主动执行app.quit() 事件,before-quit、will-quit、quit 这三个生命周期才会生效
  • before-quit:窗口关闭之前
  • will-quit:所有窗口都已经关闭,应用程序将退出触发
  • quit:应用程序关闭
  • closed:当窗口关闭时触发,此时应删除窗口引用

修改自定义图标

窗口图标:

1
2
3
4
5
new BrowserWindow({ 
width: 900,
height: 600,
icon: path.join(__dirname,'./public/icons/login.png')
}),

桌面图标:在package.json中配置图标路径,或者在vue.config.js配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pluginOptions: {
electronBuilder: {
builderOptions: {
nsis: {
allowToChangeInstallationDirectory: true,
oneClick: false,
installerIcon: "./public/icon.ico", //安装logo
installerHeaderIcon: "./public/icon.ico" //安装logo
},
electronDownload: {
mirror: "npm.taobao.org/mirrors/" //镜像设置
},
win: {
icon: './public/icon.ico' //打包windows版本的logo
},
productName: "vfirstss", //应用的名称
}
}
}
-------------完结撒花 -------------

本文标题:写给自己的前端面试指南

文章作者:咕噜咕噜

发布时间:2022年04月19日

最后更新:2022年10月17日

原始链接:https://aartemida.github.io/2022/04/19/写给自己的前端面试指南/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。