2022高频前端面试题总结

1. HTML+CSS篇

1.1 css盒模型

盒模型的组成,由里向外content,padding,border,margin.
在IE盒子模型中,width表示content+padding+border这三个部分的宽度
在标准的盒子模型中,width指content部分的宽度
box-sizing的使用

 
12
box-sizing: content-box 是W3C盒子模型 box-sizing: border-box 是IE盒子模型

box-sizing的默认属性是content-box

1.2 html5、css3新特性

html5新特性

html5总的来说比html4多了十个新特性,但其不支持ie8及ie8以下版本的浏览器

  1. 语义标签
  2. 增强型表单
  3. 视频和音频
  4. Canvas绘图
  5. SVG绘图
  6. 地理定位
  7. 拖放API
  8. WebWorker
  9. WebStorage
  10. WebSocket
    详细介绍

CSS3新特性

新增各种CSS选择器 (: not(.input):所有 class 不是“input”的节点)
圆角 (border-radius:8px)
多列布局 (multi-column layout)
阴影和反射 (Shadow\Reflect)
文字特效 (text-shadow、)
文字渲染 (Text-decoration)
线性渐变 (gradient)
旋转 (transform)
缩放,定位,倾斜,动画,多背景
例如:transform:\scale(0.85,0.90)\ translate(0px,-30px)\ skew(-9deg,0deg)\Animation:

1.3 父容器高度塌陷

什么是父元素高度塌陷

在文档流中,父元素的高度默认是被子元素撑开的,也就是子元素多高,父元素就多高。但是当子元素设置浮动之后,子元素会完全脱离文档流,此时将会导致子元素无法撑起父元素的高度,导致父元素的高度塌陷。

如何解决父与元素高度塌陷

  1. 给父元素添加样式 overflow: hidden | auto | scroll

给父元素新建一个BFC(块格式化上下文)
该方法的原理是:父元素在新建一个BFC时,其高度计算时会把浮动子元素的包进来。

BFC块级格式上下文,简单来说就是只要样式或方法触发了BFC就可以防止高度塌陷.

以下就是可以触发BFC的部分样式
flaot: left | right;
overflow: auto | hidden | scorll;
dispaly: table-cell | table-caption | inline-block;
position: fixed | absolute;

  1. 利用伪元素
 
12345
div::after { display: block; content: ""; clear: both; }
  1. 在浮动元素下方新增一个空的块元素(行内元素无效),并给元素声明 clear:both
 
12345678910
<style> .cf { clear: both; } </style> <div> ....... ....... <div class="cf"></div> </div>

1.4 css优先级算法

  • 优先级就近原则,同权重情况下样式定义最近者为准;
  • 载入样式以最后载入的定位为准;

优先级为:
同权重: 内联样式表(标签内部)> 嵌入样式表(当前文件中)> 外部样式表(外部文件中)。
!important > id > class > tag
!important 比 内联优先级高
CSS优先级及权重计算
css优先级计算规则——权重

1.5 清除浮动的几种方式及原理

  1. 利用伪元素 ::after,并声明样式 clear: both
  2. 创建父级 BFC(overflow:hidden)

清除浮动简单,但这题要引出的是BFC,BFC也是必考的基础知识点

BFC (块级格式化上下文),是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

触发条件:

  • 根元素
  • position: absolute/fixed
  • display: inline-block / table
  • float 元素
  • ovevflow !== visible

规则:

  • 属于同一个 BFC 的两个相邻 Box 垂直排列
  • 属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
  • BFC 的区域不会与 float 的元素区域重叠
  • 计算 BFC 的高度时,浮动子元素也参与计算
  • 文字层不会被浮动层覆盖,环绕于周围

1.6 响应式布局

(1) 响应式布局有哪几种方式

弹性布局 flex
媒体查询 @media
百分比%
vw/vh
rem
利用UI框架实现响应式布局(栅格系统)

(2) 1rem、1em、1vh、1px各自代表的含义

rem
rem是全部的长度都相对于根元素元素。通常做法是给html元素设>置一个字体大小,然后其他元素的长度单位就为rem。

em
子元素字体大小的em是相对于父元素字体大小
元素的width/height/padding/margin用em的话是相对于该元素的>font-size

vw/vh
全称是 Viewport Width 和 Viewport Height,视窗的宽度和高度,相当于 屏幕宽度和高度的 1%,不过,处理宽度的时候%单位更合适,处理高度>的 话 vh 单位更好。

px
px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。
一般电脑的分辨率有{19201024}等不同的分辨率
1920
1024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素

(3) 画一条0.5px的直线

考查的是css3的transform

 
12
height: 1px; transform: scale(0.5);

1.7 水平垂直居中有哪些方法

水平居中

  • 行内元素: text-align: center
  • 块级元素: margin: 0 auto
  • position:absolute +left:50%+ transform:translateX(-50%)
  • display:flex + justify-content: center

垂直居中

  • 设置line-height 等于height
  • position:absolute +top:50%+ transform:translateY(-50%)
  • display:flex + align-items: center
  • display:table+display:table-cell + vertical-align: middle;

// 不知道宽高

 
123456
width: 78px; height: 78px; position: absolute; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%);

// 知道宽高

 
12345678910
height: 100px; width: 100px; position: absolute; left:50%; top: 50%; margin-left: -50px; margin-top: -50px; display:flex; justify-content: center; align-content: center;

1.8 iframe有哪些优缺点

优点

  • iframe 能够原封不动的把嵌入的网页展现出来。
  • 如果有多个网页引用 iframe,那么你只需要修改 iframe 的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
  • 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用 iframe 来嵌套,可以增加代码的可重用。
  • 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由 iframe 来解决。

缺点

  • 框架结构中出现各种滚动条
  • iframe 会阻塞主页面的 Onload 事件
  • 搜索引擎的检索程序无法解读这种页面,不利于 SEO
  • iframe 和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。

1.9 CSS选择符有哪些?哪些属性可以继承?

css选择符

  1. id选择器( # myid)
  2. 类选择器(.myclassname)
  3. 标签选择器(div, h1, p)
  4. 相邻选择器(h1 + p)
  5. 子选择器(ul > li)
  6. 后代选择器(li a)
  7. 通配符选择器( * )
  8. 属性选择器(a[rel = "external"])
  9. 伪类选择器(a:hover, li:nth-child)

可继承的样式
font-size font-family color, UL LI DL DD DT

不可继承的样式
border padding margin width height

2. JavaScript篇

2.1 数据类型

(1)数据类型有哪些

  • 基本数据类型:字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。
  • 引用数据类型: 对象(Object)、数组(Array)、函数(Function)、日期(Date)、正则(RegExp)、内置对象(Global、Math)。

(2)数据类型检测方法

  • typeof
  • instanceof
  • constructor
  • Object.prototype.toString.call()

typeof 对于基本数据类型来说,除了 null 都可以显示正确的类型,typeof 对于对象来说,除了函数都会显示 objec

 
12345678910
typeof 5 // 'number' typeof '5' // 'string' typeof undefined // 'undefined' typeof false// 'boolean' typeof Symbol() // 'symbol' console.log(typeof null) //object console.log(typeof NaN) //number typeof [] // 'object' typeof {} // 'object' typeof console.log // 'function'

instanceof通过原型链来判断数据类型的

 
12
p1 = new Person() p1 instanceof Person // true

constructor判断,null、undefined不是基本包装类型,无constructor方法

 
12345678910
console.log(("1").constructor === String) console.log((1).constructor === Number) console.log((true).constructor === Boolean) //console.log((null).constructor === Null) //console.log((undefined).constructor === Undefined) console.log(([]).constructor === Array) console.log((function() {}).constructor === Function) console.log(({}).constructor === Object) 结果都为true

Object.prototype.toString.call()可以检测所有的数据类型,算是一个比较完美的方法了。

 
1234
var obj={} var arr=[] console.log(Object.prototype.toString.call(obj)) //[object Object] console.log(Object.prototype.toString.call(arr)) //[object Array]

js检测数据类型四种办法

(3)深拷贝和浅拷贝

浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。

JavaScript基础心法——深浅拷贝

(4)如何实现一个深拷贝
目前实现深拷贝的方法,主要是两种:

  1. 利用 JSON 对象中的 parse 和 stringify
  2. 利用递归来实现每一层都重新创建对象并赋值
    递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值的操作,代码如下:
 
1234567891011121314
function deepClone(source){ const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象 for(let keys in source){ // 遍历目标 if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下 targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepClone(source[keys]); }else{ // 如果不是,就直接赋值 targetObj[keys] = source[keys]; } } } return targetObj; }

2.2 作用域

(1)作用域

变量或者函数的有效作用范围。

(2)作用域链

因为函数的嵌套形成作用域的层级关系。

当函数执行时,查找某个变量值,会先在当前作用域查找,如果找不到会向上层作用域查找,直至全局函数,这种向上查找的链条关系,叫作作用域链。

(3)var、let及const区别

var声明变量存在变量提升,let和const不存在变量提升
let、const都是块级局部变量
同一作用域下let和const不能声明同名变量,而var可以
const声明常量时必须进行初始化,且初始化后值不可再修改

(4)变量声明提升

在 JavaScript 中,函数声明(function aa(){})与变量声明var)经常被 JavaScript 引擎隐式地提升到当前作用域的顶部。

函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变量声明。

声明语句中的赋值部分并不会被提升,只有变量的名称被提升。

预解析时会进行变量提升,预解析过程:

  1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
  2. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
  3. 先提升var,再提升function。
  4. 提升完后其他代码位置不变

变量声明提升的本质原因:

  • js 引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。

为什么会进行变量提升呢?主要有以下两个原因:

变量提升笔试题

 
1234567
var bar=1; function test(){ console.log(bar); // undeifned var bar=2; console.log(bar); // 2 } test();
 
123
var foo=function() { console.log(1); } function foo() { console.log(2); } foo(); // 结果为1

(5)闭包
闭包定义

当一个函数能够记住并访问到其所在的词法作用域及作用域链,特别强调是在其定义的作用域外进行的访问,此时该函数和其上层执行上下文共同构成闭包。

闭包的实质是因为函数嵌套而形成的作用域链
闭包的定义即:函数A内部有一个函数B,函数B可以访问到函数A中的变量,那么函数B就是闭包。

闭包应用的注意事项

内存泄漏
闭包会阻止垃圾回收机制对变量进行回收,因此变量会永远存在内存中,即使该变量不再被使用,而这样会造成内存泄漏,会严重影响页面的性能。因此当变量对象不再使用时,我们要将其释放。

闭包的应用

模块
一个模块应该具有私有属性、私有方法和公有属性、公有方法。而闭包能很好的将模块的公有属性、方法暴露出来。

防抖节流
"setTimeout"方法里应用了闭包,隔离作用域,使其内部能够记住每次循环所在的词法作用域和作用域链。

闭包特性及优缺点

闭包有三个特性:

  • 函数嵌套函数
  • 函数内部可以引用外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收

闭包的好处:

  • 希望一个变量长期存储在内存中
  • 避免全局变量的污染
  • 私有成员的存在

闭包的缺点:

  • 常驻内存,增加内存使用量。
  • 使用不当会很容易造成内存泄露

面试绕不过的闭包
破解前端面试(80% 应聘者不及格系列):从闭包说起

(6)执行上下文

js 代码解析执行时所处的环境,顾名思义。

js 代码运行的环境构成了执行上下文,执行上下文决定哪段代码可以访问变量、函数、对象等。

[译] 理解 JavaScript 中的执行上下文和执行栈
JS执行上下文的两个阶段做了些啥?

2.3 原型

(1)函数对象和普通对象

JavaScript中,对象分为函数对象和普通对象。所谓的函数对象,其实就是JavaScript的用函数来模拟的类实现。JavaScript中的Object和 Function就是典型的函数对象。

JavaScript 中万物皆对象,而对象皆出自构造(构造函数)。

(2)构造函数

所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。

constructor 返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串

(2)原型

JavaScript 是一种基于原型的语言 (prototype-based language),这个和 Java 等基于类的语言不一样。

每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype 属性上,而非对象实例本身。

(3) 原型链

每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。这种关系被称为原型链 (prototype chain),通过原型链一个对象会拥有定义在其他对象中的属性和方法。

一文吃透所有JS原型相关知识点
重新认识构造函数、原型和原型链
说说原型(prototype)、原型链和原型继承

(4) 创建对象的几种方式

  1. 对象字面量
  2. Object.create
  3. new 构造函数
  4. new Object()
    JavaScript 创建对象的 7 种方法

2.4 继承

(1)继承的多种方式和优缺点

  1. 原型链继承
    缺点:引用类型的属性被所有实例共享;在创建 Child 的实例时,不能向Parent传参

  2. 借用构造函数(经典继承)
    优点:避免了引用类型的属性被所有实例共享;可以在 Child 中向 Parent 传参
    缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。

  3. 组合继承 -- 原型链继承和经典继承双剑合璧
    优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。

  4. 原型式继承
    缺点:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

  5. 寄生式继承
    缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

  6. 寄生组合式继承
    《JavaScript高级程序设计》中对寄生组合式继承的夸赞:这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

JavaScript深入之继承的多种方式和优缺点
继承方式深入分析
彻底弄懂JS原型与继承

(2)JS 如何实现一个类
构造函数法

 
1234567
function P(name, age){ this.name = name; this.age= age; } P.prototype.sal= function() {} var pel= new P("jj", 1); pel.sal()

缺点:用到了 this 和 prototype,编写复杂,可读性差

ES6 语法糖 class

 
12345678910
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3);

2.5 new、this

(1) this对象的理解

this是什么?首先记住this不是指向自身!this 就是一个指针,指向调用函数的对象。

(2) this的绑定规则有哪些

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定(硬绑定)
  4. new绑定

(3) 绑定优先级
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

(4) 箭头函数
箭头函数是ES6中新增的,它和普通函数有一些区别,箭头函数没有自己的this,它的this继承于外层代码块中的this。箭头函数在使用时,需要注意以下几点:

  1. 函数体内的this对象,继承的是外层代码块的this。
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  5. 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向。

(5)如何准确判断this指向的是什么?

  1. 函数是否在new中调用(new绑定),如果是,那么this绑定的是新创建的对象。
  2. 函数是否通过call,apply调用,或者使用了bind(即硬绑定),如果是,那么this绑定的就是指定的对象。
  3. 函数是否在某个上下文对象中调用(隐式绑定),如果是的话,this绑定的是那个上下文对象。一般是obj.foo()
  4. 如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到undefined,否则绑定到全局对象。
  5. 如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
  6. 如果是箭头函数,箭头函数的this继承的是外层代码块的this。
    嗨,你真的懂this吗?

(6) new是什么

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

new命令的作用,就是执行构造函数,返回一个实例对象。

(7) new操作干了什么?

  1. 创建一个空对象,作为将要返回的对象实例
  2. 将这个空对象的原型,指向构造函数的prototype属性
  3. 将这个空对象赋值给函数内部的this关键字
  4. 开始执行构造函数内部的代码

解析: 也就是说,构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。

(8) new的模拟实现

 
1234567
function _new() { var obj = new Object(); // 1.创建一个空对象,作为将要返回的对象实例。 var Constructor = [].shift.call(arguments); // 取出构造函数。 obj.__proto__ = Constructor.prototype; // 2.将这个空对象的原型,指向构造函数的prototype属性。 var ret = Constructor.apply(obj, arguments); // 3.将这个空对象赋值给函数内部的this关键字。4.开始执行构造函数内部的代码。 return typeof ret === 'object' ? ret : obj; }

面试官:说说new操作符具体干了什么?
JavaScript深入之new的模拟实现

2.6 call/apply/bind

总结

  • 三者都是用来改变函数的this指向
  • 三者的第一个参数都是this指向的对象
  • bind是返回一个绑定函数可稍后执行,call、apply是立即调用
  • 三者都可以给定参数传递
  • call给定参数需要将参数全部列出,apply给定参数数组

JavaScript基础心法——call apply bind
JavaScript深入之call和apply的模拟实现
JavaScript深入之bind的模拟实现

2.7 事件循环(event loop)

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

(1)浏览器中的事件循环

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

宏任务大概包括:

  • script(整体代码)
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI render

微任务大概包括:

  • process.nextTick
  • Promise
  • Async/Await(实际就是promise)
  • MutationObserver(html5新特性)

(2)node中的事件循环
node 中的事件循环
浏览器中有事件循环,node 中也有,事件循环是 node 处理非阻塞 I/O 操作的机制,node中事件循环的实现是依靠的libuv引擎。由于 node 11 之后,事件循环的一些原理发生了变化。

node 中也有宏任务和微任务,与浏览器中的事件循环类似,其中,
宏任务大概包括:

  • setTimeout
  • setInterval
  • setImmediate
  • script(整体代码)
  • I/O 操作等。

微任务大概包括:

  • process.nextTick(与普通微任务有区别,在微任务队列执行之前执行)
  • new Promise().then(回调)等。

(3)node 和 浏览器 eventLoop的主要区别
两者最主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的。

面试题:说说事件循环机制(满分答案来了)
一次弄懂Event Loop(彻底解决此类面试问题)
这一次,彻底弄懂 JavaScript 执行机制
浏览器与Node的事件循环(Event Loop)有何区别?

2.8 promise

(1)什么是Promise?

Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。
promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行。

(2) 我们用Promise来解决什么问题?

promise是用来解决两个问题的:

  • 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输>入这种现象
  • promise可以支持多个并发的请求,获取并发请求中的数据
  • 这个promise可以解决异步的问题,本身不能说promise是异步的

(3) promise优缺点

  • 优点:有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
  • 缺点:首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

(4) promise基本API

  1. Promise.resolve(value) // 生成一个成功的promise对象
  2. Promise.reject(reason) // 生成错误的一个promise对象
  3. Promise.prototype.then() // 核心部分返回一个新的Promise
  4. Promise.prototype.catch(onRejected) // 异常捕获
  5. Promise.all(iterable) // 都成功的时候才会触发成功,一旦有任何一个失败,则立即触发该promise对象的失败
  6. Promise.race(iterable) // 最先执行的promise结果

Promise不会??看这里!!!史上最通俗易懂的Promise!!!
学习Promise基础及手写Promise

3. Webpack篇(工程化)

3.1 什么是前端工程化?

前端工程指的就是:将工程方法系统化地应用到前端开发中,以系统、严谨、可量化的方法开发、运营、维护前端应用程序,目的是降本提效。

前端工程绕开发、构建、发布 3 条主线展开,以工具化、自动化等手段解决各个环节所遇到的问题。一方面控制前端开发的复杂度,提高前端生产效率,另一方面降低架构升级、协作模式变化等带来的迁移、适配成本,提升开发体验。

工程学是一门庞大且复杂的学科,核心是结合实际建立科学的、规范的设计和生产流程,最终目的是降低生产成本,提高生产效率。

面向过程视角下的前端工程体系

3.2 什么是前端模块化?

模块化其实是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块 的过程,每个模块完成一个特定的子功能(单一职责),所有的模块按某种方法组装起来,成为一个整体,从而完成整个系统所要求的功能。

将独立的功能代码封装成一个独立的文件,其他模块需要使用,在进行引用。

模块化有利于代码的拆分和架构上的解耦,模块化在服务端领域已经早已成熟,nodejs 也已经支持模块化。

3.3 前端模块化方案有哪些及差异?

  1. commonjs

commonjs 规范应用于 nodejs 应用中,在 nodejs 应用中每个文件就是一个模块,拥有自己的作用域,文件中的变量、函数都是私有的,与其他文件相隔离。

  1. ES6模块化

ECMA推出了官方标准的模块化解决方案,使用 export 导出,import 导入,编码简洁,从语义上更加通俗易懂。

ES6 支持异步加载模块的模块不是对象,而是在编译的时候就完成模块的引用,所以是编译时才加载的。

  1. AMD、CMD规范的模块化

ADM用require.config()指定引用路径等,用define()定义模块,用require()加载模块。

CMD用define()定义模块, seajs.use 引用模块。

AMD 和 CMD 这两个规范都离不开 require.js 和 sea.js,这是早些年,为了解决浏览器异步加载模块而诞生的方案。随着打包工具的发展,commonjs和es6都可以在浏览器上运行了,所以 AMD、CMD 将逐渐被替代。

  1. UMD(Universal Module Definition)

即通用模块定义,从名字就可以看出来,这东西是做大统一的。

可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行,也就是说同一个 JavaScript 包运行在浏览器端、服务区端甚至是 APP 端都只需要遵守同一个写法

  1. ES6模块与CommonJS模块的差异

3.4 webpack是什么

webpack 是一种前端资源构建的工具,一个静态模块打包器(module bundler)。在 webpack 看来,前端的所有资源文件(js / json / css / img / less/...)都会作为模块处理(chunk)。它根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

3.5 webpack核心概念

  1. entry
    入口(entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。
  2. output
    输出(output)指示 webpack打包后的资源 bundles 输出到哪里去,以及如何命名。
  3. loader
    loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只能够识别 JavaScript),比如css、less等,在这里可以理解为loader就是一个"翻译官"。
  4. plugins
    插件(plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量等,在这里可以理解为比loader的功能更加强大。
  5. mode
    模式(mode)指示 webpack 使用相应模式的配置,只有两种模式,分别为开发模式(development)和生产模式(production)。

3.6 webpack构建流程

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程 :

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。
  3. 确定入口:根据配置中的 entry 找出所有的入口文件。
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  5. 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

3.7 loader和plugin区别

webpack默认支持处理JS与JSON文件,其他类型都处理不了,这里必须借助Loader来对不同类型的文件的进行处理。Loader本身是一个函数。一个Loader的职责是单一的,只需要完成一种转化。

plugin通常是在webpack在打包的某个时间节点(命周期的钩子)做一些操作,如打包优化、压缩、复制文件、定义环境变量等。plugin是一个类,作用于webpack生命周期。

3.8 手写 Webpack --> 手写 Webpack

webpack 入门到精通(上)

webpack 入门到精通 (下)

webpack

4. 浏览器+HTTP篇

4.1 浏览器渲染过程

  1. 处理 HTML 标记并构建 DOM 树。
  2. 处理 CSS 标记并构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,计算每个节点的几何信息。
  5. 将各个节点绘制到屏幕上。

注意:

  1. 这五个步骤并不一定是按顺序一次性完成,当DOM 或 CSSOM被CSS或Javascript修改时,以上步骤就会重复执行。
  2. 关键渲染路径要求我们同时具有 DOM 和 CSSOM 才能构建渲染树。

4.2 阻塞渲染

CSS阻塞渲染

  1. 因为CSS被视为阻塞渲染的资源,所以浏览器在CSSOM构建完毕之前,不会渲染任何已处理的内容。
  2. CSSOM 构建时,JavaScript执行将暂停,直到CSSOM 就绪。
  3. CSS 不会阻塞HTML的解析,但是会阻塞渲染树的构建
    所以我们需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。 也就是说,我们尽量把引入css的标签放在head标签内。

JS阻塞渲染

  1. JS会阻塞DOM解析和渲染。
  2. JavaScript 既可以读取和修改 DOM 属性,又可以读取和修改 CSSOM 属性。所以,script 标签的位置很重要,在实际应用中我们通常把script标签放在body底部。

改变阻塞模式:defer与async

defer
defer表示延迟执行引入的 JavaScript。
文档按顺序解析,当遇到JavaScript脚本时开始下载,同时HTML也继续在解析,这两个过程是并行的。如果脚本下载完成,会等到文档解析完毕才会开始执行。

async
async表示异步引入Javascript。
和defer前面一样,文档按顺序解析,当遇到JavaScript脚本时开始下载,同时HTML也继续在解析,但不同的是,脚本下载完成之后,如果文档还未解析完成,则停止解析,先执行下载好的脚本。

注意:
defer、async 对 inline-script 无效

总结:

  • 无async或defer 同步请求+立即执行
  • async 异步请求+立即执行
  • defer 异步请求+延迟执行

图解 script 标签中的 async 和 defer 属性

浏览器对页面渲染的过程

4.3 重排和重绘

重排概念
当DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。

重排也叫回流。

常见引起重排属性和方法
任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发重排,下面列一些栗子:

  1. 添加或者删除可见的DOM元素;
  2. 元素尺寸改变——边距、填充、边框、宽度和高度
  3. 内容变化,比如用户在input框中输入文字
  4. 浏览器窗口尺寸改变——resize事件发生时
  5. 计算 offsetWidth 和 offsetHeight 属性
  6. 设置 style 属性的值

重绘概念
当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

常见的引起重绘的属性
color
border-style
visibility
background
border-radius

重排优化建议

  1. 分离读写操作
  2. 样式集中改变
  3. 缓存布局信息
  4. 离线改变dom
  5. position属性为absolute或fixed

浏览器重绘(repaint)重排(reflow)与优化

4.4 浏览器缓存机制

什么是浏览器缓存
简单来说,浏览器缓存其实就是浏览器保存通过HTTP获取的所有资源,是浏览器将网络资源存储在本地的一种行为。

缓存的资源去哪里了

  • memory cache 内存
  • disk cache 磁盘

二者区别:memory cache 退出进程时数据会被清除,一般存脚本、字体、图片;disk cache 退出进程时数据不会被清除,一般存非脚本会存,如css等。

三级缓存原理(访问缓存优先级)

  1. 先在内存中查找,如果有,直接加载。
  2. 如果内存中不存在,则在硬盘中查找,如果有直接加载。
  3. 如果硬盘中也没有,那么就进行网络请求。
  4. 请求获取的资源缓存到硬盘和内存。

浏览器缓存的分类

  1. 强缓存
  2. 协商缓存

浏览器再向服务器请求资源时,首先判断是否命中强缓存,再判断是否命中协商缓存!

强缓存
浏览器在加载资源时,会先根据本地缓存资源的 header 中的信息判断是否命中强缓存,如果命中则直接使用缓存中的资源不会再向服务器发送请求。

这里的 header 中的信息指的是 expires 和 cahe-control.

http1.0 --> expires
http1.1 --> Cache-Control
expires、Cache-Control同时使用,Cache-Control优先级最高

协商缓存
当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务器根据 header 中的部分信息来判断是否命中缓存。如果命中,则返回 304 ,告诉浏览器资源未更新,可使用本地的缓存。

这里的 header 中的信息指的是 Last-Modify/If-Modify-Since 和 ETag/If-None-Match.

Last-Modify/If-Modify-Since --> 修改时间
ETag/If-None-Match --> 校验码

Last-Modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。

实践这一次,彻底搞懂浏览器缓存机制

5. React篇

5.1 React的生命周期有哪些?

React 通常将组件生命周期分为三个阶段:

  • 挂载阶段(Mount),组件第一次在DOM树中被渲染的过程;
  • 更新过程(Update),组件状态发生变化,重新更新渲染的过程;
  • 卸载过程(Unmount),组件从DOM树中被移除的过程;

(1) 组件挂载阶段

挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount

(2) 更新

当组件的 props 改变了,或组件内部调用了 setState/forceUpdate,会触发更新重新渲染,这个过程可能会发生多次。这个阶段会依次调用下面这些方法:

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

(3) 卸载

卸载阶段只有一个生命周期函数

  • componentWillUnmount() 会在组件卸载及销毁之前直接调用。

在此方法中执行必要的清理操作:

  • 清除 timer,取消网络请求或清除
  • 取消在 componentDidMount() 中创建的订阅等;

5.2 Redux

(1) redux是什么,解决什么问题?

Redux是一个用来管理数据状态和UI状态的JavaScript应用工具。

React中组件间通信的数据流是单向的。当项目越来越大,state管理变得非常困难。

Redux 提供了一个叫 store 的统一仓储库,组件通过 dispatch 将 state 直接传入store。使用了 Redux,所有的组件都可以从 store 中获取到所需的 state,他们也能从store 获取到 state 的改变。这比组件之间互相传递数据清晰明朗的多。

主要解决的问题:
单纯的Redux只是一个状态机,是没有UI呈现的,react- redux作用是将Redux的状态机和React的UI呈现绑定在一起,当你dispatch action改变state的时候,会自动更新页面。

(2) redux工作流程

工作流程

  • const store= createStore(fn)生成数据;
  • action: {type: Symble('action01), payload:'payload' }定>- 义行为;
  • dispatch发起action:store.dispatch(doSomething('action001'));
  • reducer:处理action,返回新的state;

通俗点解释:

首先,用户(通过View)发出Action,发出方式就用到了dispatch方法
然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
State—旦有变化,Store就会调用监听函数,来更新View

(3) mobx 和 redux 有什么区别?

Redux更多的是遵循Flux模式的一种实现,是一个 JavaScript库
Mobx是一个透明函数响应式编程的状态管理库

redux将数据保存在单一的store中,mobx将数据保存在分散的多个store中

redux使用plain object保存数据,需要手动处理变化后的操作;mobx适用observable保存数据,数据变化后自动处理响应的操作

redux使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx中的状态是可变的,可以直接对其进行修改

5.3 Hooks

(1) 什么是hooks

Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hooks 译为钩子,Hooks 就是在函数组件内,负责钩进外部功能的函数。

React Hooks 提供了一种简洁的、函数式(FP)的程序风格,通过纯函数组件和可控的数据流来实现状态到 UI 的交互(MVVM)。

(2) 为什么要用 Hooks(解决了什么问题)?

  • 组件之间很难重用有状态逻辑
  • 复杂的组件变得难以理解
  • 类 class 混淆了人和机器
  • 更符合 FP 的理解, React 组件本身的定位就是函数,一个吃进数据、吐出 UI 的函数
  • 更细粒度的代码复用,并且不会产生过多的副作用
  • 函数式编程风格,代码更简洁,同时降低了使用和理解门槛
  • 减少组件嵌套层数
  • 组件数据流向更清晰

(3) hooks使用规则

  • 只在 React 函数中调用 Hook
  • 不要在循环、条件或嵌套函数中调用 Hook

React Hooks 万字总结
React-你有完全了解 Hooks 吗

5.4 虚拟DOM

(1) 什么是vdom

虚拟DOM就是使用javascript对象来表示真实DOM,是一个树形结构。

(2) 什么是diff算法

React需要同时维护两棵虚拟DOM树:一棵表示当前的DOM结构,另一棵在React状态变更将要重新渲染时生成。React通过比较这两棵树的差异,决定是否需要修改DOM结构,以及如何修改。这种算法称作Diff算法。

(3) React 中 keys 的作用是什么?

key 属性是React用来匹配新旧组件树中子组件的标识,能够让 React 了解哪些组件需要被变更, key 属性能够大大提高启发式算法的性能,并保证 React 正常运行。

(4) 为什么不用Index作为key?

轻松理解为什么不用Index作为key

(5) React列表循环为什么需要key

React列表循环为什么需要key | 面试题

谈谈React虚拟DOM和diff算法
让虚拟DOM和DOM-diff不再成为你的绊脚石

6. Vue篇

7. Node.js篇

8. 数据结构和算法篇

9. 性能优化篇

10. 其他

微前端、正则、服务端渲染、CI/CD、微服务、后端、typescript

【读书笔记-JS高级程序设计】第一章 JavaScript 简介
通过Ref获取React类组件实例总结
评论
luoqiangweb开发China
文章29
分类13
标签7