#前端重铸之路 Day1 🔥

323 阅读21分钟

前置涩话

掘金的小伙伴们大家好,这里是家里蹲选手sin,因为某些原因已经空窗两年啦哈哈哈哈哈。虽然尝试过别的行业,但是也并不顺利,所以今天痛定思痛,拿起自己的老本行,准备给自己开启一个前端重建计划!就是一次小小的挣扎(一个复习日记),结果如何就先不去思考了,只是希望能再做一颗被风扬起的小小灰尘~

✅ 精读业界各类前端八股文

事件循环

单线程是异步产生的原因
事件循环是异步实现方式
事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。
队列优先级
微队列:Promise.resolve().then(立即加入微队列的函数) 优先级『最高』
交互队列:用于存放用户操作后产生的事件处理任务,优先级「高」
延时队列:用于存放计时器到达后的回调任务,优先级「中」
微队列和多类型队列组成的消息队列
JS计时器不能精确计时,受事件循环影响,计时器回调函数只能在渲染主线程空闲时运行。
浏览器输入网址按下回车发生了什么?
域名解析(获取IP地址)、建立HTTP连接、发送HTTP请求、数据传输、渲染网页、断开HTTP连接。 浏览器网络线程收到html文档后,会产生一个渲染任务,并传递给渲染主线程的消息队列。 在事件循环机制的作用下,渲染主线程取出消息队列的渲染任务,开启渲染流程。
整个渲染流程分为多个阶段:HTML解析、样式计算、布局、分层、绘制、分块、光栅化、画

浏览器渲染原理

网络进程接收到HTML,产生渲染任务,传递给主线程的消息队列,然后事件循环开启渲染流程。生成dom树和cssom树 JS解析会阻塞渲染
可以给script加上defer,表示JS文件并行下载,但放在后面顺序执行,没有任何依赖的JS文件可以加上async,下载完成后立即执行

2a04c8ede82fb267c22777d2f764914.jpg
重绘和回流
更改外观不影响布局,比如改变color为重绘
布局和几何信息改变为回流,回流成本高
减少回流重绘,使用动画属性,使用requestAnimationFrame

网络内容与安全

HTTP与HTTPS

HTTP 超文本运输协议,实现网络通信的一种规范
HTTP传递信息是以明文形式发送内容,并不安全。为了保证隐私数据加密运输,让HTTP加上SSL/TLS就是HTTPS。 HTTP和HTTPS连接方式不同,默认端口也不同,前者是80,后者是443。
HTTPS由于需要设计加密以及多次握手,性能不如HTTP
HTTPS需要SSL证书,需要💰

网络协议,UDP和TCP

UDP是面向无连接的,正式传递数据之前不需要连接起双方,只是数据报文的搬运工,不保证有序且不丢失的传递到对端,没有控制流量的算法,更加简便。
TCP基本和UDP反着来,建立连接断开需要进行握手挥手。传输数据过程通过各种算法保证数据的可靠性。
三次握手
第一次,客户端发送syn报文,服务端接收 结论:客户端可发,服务端可接
第二次:服务端发送syn报文,客户端接收
客户端可接,服务端可发,服务端不确认客户端
第三次:客户端发ack报文,值为服务器的isn+1,服务端收到,此时双方建立连接
四次挥手
第一次,客户端发送fin报文,指定一个序列号,等待服务端确认
第二次,服务端收到,会发送ack报文(为客户端的序列号+1),表明已收到
第三次,如果服务端也想断开,会和客户端第一次挥手一样
第四次,客户端收到后,发送ack报文作为应答,将服务端序列号+1作为自己的ack报文,等待以确保服务端收到ack报文,服务端收到后,连接就断开了
websockets的目标是在一个单独的持久连接上提供全双工、双向通信。创建websocket后,使用http协议交换为websocket协议。
只能发送纯文本数据
DNS 域名系统,进行域名与之相对应的IP地址转换的服务器
CDN 内容分发网络,根据用户位置分配最近资源。就近获取内容
静态资源使用cdn加载,由于浏览器对于单个域名有并发请求上限,可以使用多个cdn域名。对于cdn加载静态资源需要注意cdn域名要与主站不同,否则每次请求都会带上主站cookie
XSS:可执行代码注入网页攻击
防御:转义字符 对用户输入进行过滤 csp建立白名单 不要使用inneHtml可执行脚本的函数
CSRF:跨站请求伪造,构造后端请求地址,诱导或自动请求。
防御:get请求不改数据,用户cookie不让第三方访问,阻止第三方网站请求(验证Referer),请求附带验证信息(token)

常见状态码

1xx消息 临时响应 2xx 成功 3xx 重定向
4xx客户端错误 5xx服务器错误

AJAX

xmlHttpRequest (简称XHR) Fetch API (无法监控请求进度)
第三方库
Axios、umi-request

8217c7c89361decaf03ba1ccbe897d5.jpg

前端路由

前端路由就是监听URL的变化,匹配路由规则,显示相应页面,无需刷新页面。
Hash模式
History 模式
对比:
hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 www.npc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
hash模式只可以更改#后面内容,history可以通过api(history.pushState history.replaceState)设置任意同源URL。
Hash无需后端配置,兼容性好。history模式在用户手动输入地址或者刷新页面时会发起URL请求,后端需要配置index.html页面用于匹配不到的静态资源。
hash通过location.hash改变跳转,前进后退用的是window绑定添加hashchange事件

HTML语义化

1.代码可读性高
2.有利于SEO
3.利于页面结构化
常见的语义化标签:header、footer、aside、main、h1-h6、input、textarea、video、audio

盒模型

css盒子的组成包括margin、border、padding、content;
盒子模型一共三种:标准盒模型(content-box)、怪异盒模型(border-box); 标准盒模型在设置width和height时设置的是content的大小,盒子的大小还要加上padding、border; 怪异盒模型设置width和height时设置的是盒子的大小,会压缩content区域。 Padding-box属性是指一个盒子的内边距宽度(Padding)不再包含在宽度(Width)和高度(Height)的计算之内,而是在宽度和高度的外层绘制出其它内容。这种盒子一般称为“内边距盒子”。
width:100%,子盒子宽度与父元素的content相等
width:auto,子盒子的宽度与父元素的(content+padding+border)相等
height:100%,根据父元素高度决定
height:auto,随内容高度撑开

BFC 块级格式化上下文

html 根元素
float: left right
position:absolute fixed
display:inline-block flex grid flow-root
overflow : auto
解决高度坍塌,给父级加overflow:auto
解决外边距重叠

清除浮动

增加尾元素
创建父级BFC
父级设置高度
::after伪元素 clear:both
link同步加载css,@import等页面加载完才加载
link可以使用js动态引入 @import不行

:伪类 ::伪元素

伪类操作元素是文档上已有元素,伪元素是创建了一个文档外的元素(一个有内容的虚拟容器)
可以使用多个伪类,但只能使用一个伪元素
常用伪类::ative、:focus、:hover
:enabled、:disabled、:checked
:nth-child(n)、first-child等等
n可以用关键字even偶数,odd奇数
常用伪元素:::before、::after
::first-letter首字母、::first-line首行
::selection匹配用户选择部分

圣杯布局

左右两边等宽,中间自适应
用flex布局 左右定width 中间flex:1
利用float 和计算属性calc

定位

相对定位(Relative Positioning)

相对定位是通过设置元素的 position 属性为 relative 来实现的。使用相对定位时,元素会相对于其正常位置进行偏移,但其原本占据的空间仍然保留。这意味着,即使元素被移动,它原本应该占据的位置仍然会被保留。

绝对定位(Absolute Positioning)

绝对定位是通过设置元素的 position 属性为 absolute 来实现的。绝对定位的元素会脱离文档流,不再占据原来的空间。这意味着,如果一个绝对定位的元素被移动,它原本占据的空间将不再被保留。绝对定位的元素会相对于其最近的已定位(即 position 不是 static 的)祖先元素进行定位。如果没有这样的祖先元素,它将相对于初始包含块(通常是 <html> 或 <body> 元素)进行定位。

flex布局

flex-direction属性决定主轴的方向(即项目的排列方向)。 flex-wrap属性定义,如果一条轴线排不下,如何换行。 flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap
justify-content属性定义了项目在主轴上的对齐方式。

  • space-between:两端对齐,项目之间的间隔都相等。贴合边框
  • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。不贴合边框
  • space-evenly为项目之间间距与项目与容器间距相等,相当于除去项目宽度,平均分配了剩余宽度作为项目左右margin。

gap设置弹性项目间的统一间距

BEM

Block Element Modify(修饰)
如:el-button--success el-input__inner
clip-path(裁剪)
SvgPathEditor

var、let和const

全局污染 var
块级作用域 for循环 、if{ }
暂时性死区 用let const class定义的有变量提升 但会形成暂时性死区 声明之前无法调用
能否重复声明

new

调用new发生四件事
1.新生成一个对象
2.链接到原型 新对象constructor属性为构造函数 _proto_指向构造函数的prototype
3.绑定this到新对象
4.返回新对象

面向对象

class Product {
static 属性 静态成员
static 方法 静态方法
不会被实例继承
constructor(){
} 实例成员
jump(){
} 实例方法
get total(){
} 访问器成员
(# 属性 私有属性 )
(# 方法 私有方法 )
get #x(){}
set #x(){}
} 'extends'关键字 继承
'super'关键字 代表父类的构造函数,返回的是子类的实例
作为函数super(),只能用在子类构造函数(constructor)中代表父类的构造函数。
作为对象,在普通方法中,指向父类的原型对象,所以定义在父类实例上的方法或属性,无法调用。在静态方法中,指向父类。

原型链

let p = new Product()
p.proto === Product.prototype
Product.prototype.construtor === Product
我们创建的每个函数都有一个“prototype(原型)”属性,是一个指针,指向一个对象,包含可以由特定类型的所有实例共享的属性和方法。
Function.proto = Function.prototype

1deb9300344422fb0321e8b50e67670.jpg
重写原型对象,会导致原型对象的constructor属性指向Object,导致关系混乱,所以应该重写原型对象时指定
Product.prototype = {
constructor:Product
xxx
}
注意:会导致Enumerable(可枚举)变成true

闭包

一个函数内部创建返回另一个函数,内部函数可以访问外部函数作用域中的变量。(引用了其他函数作用域中变量的函数)

有两个作用:

保护:函数会形成一个私有作用域,不受外部干扰,适合模块化开发。

保存:函数执行完毕后,其执行环境的作用域链会被销毁,因为内部函数仍在引用这个活动对象,所以活动对象仍留在内存中,直到匿名函数销毁才会释放。(容易内存泄露)

手动将引用的变量变为null,销毁闭包

1、持有不再需要的函数引用,会导致函数关联的词法环境无法销毁,从而导致内存泄露。

2、当多个函数共享词法环境,会导致存在无法触达也无法回收的内存空间。

内存泄露

意外声明的全局变量

被遗忘的定时器

使用不当的闭包

未清理的dom引用

垃圾回收机制,将再也不能触达的内存空间回收

在进行回收时,会暂停js脚本,等待完成

标记清除、引用计数

this指向

this指向是在函数调用时确定的,无调用就取决于环境,浏览器是window,node是空对象{}

箭头函数没有this,基于闭包(顺着作用域链往上找),会找外层this,闭包属于词法作用域是编译时态,所以this取决定义时位置

this规则优先级 new bind等函数 obj.foo()

最后是foo这种调用方式

对于new,this被永远绑定,不会改变

对于bind,无论多少次,都是第一次
箭头函数可以消除函数二义性
new.target可以看函数调用方式

Promise

本质是一个对象,用来传递异步操作的信息。
异步编程的一种解决方案,在异步操作的成功和失败分别绑定相应的处理方法。优化了回调地狱,有三种状态,初始为pending(进行中),可以通过函数resolve和reject,将状态转变为fulfilled(已成功)或者rejected(已失败)状态,状态一旦改变不能再变。
实例方法(在prototype上的):
then()是实例状态发生改变时的回调函数,返回一个新的Promise实例
catch()发生错误时的回调函数
finally()不管最后状态如何,都执行的操作
构造方法:
Promise.resolve()等于new Promise((resolve) => resolve())
Promise.reject()
Promise.all()多个promise包装成一个新实例,全部状态成功fulfilled,才会成功fulfilled,一个失败rejected,第一个reject的返回值,就是回调函数。
Promise.race()跟上面一样,只是只要一个改变状态,新实例就跟着改变,返回值传递成回调函数。(可以设置图片请求超时)
Promise.any()任何第一个成功的状态
Promise.allSettled()也是,不过是等全部返回结果,不管是什么状态,包装实例才会结束。结果是一个数组,包含每一个promise结果
在其他地方改变promise状态,定义变量
新api
const { promise,resolve,reject }
= Promise.withResolvers()
中断调用链
在最后返回一个永远pending的promise
中断promise
可以用race包装,提供abort方法,在合适的时间把pending的promise reject掉
中断不会终止,只是不会再关心结果

async函数

Generator 函数的语法糖,返回一个promise对象,可以用then方法添加回调函数。
async/await 用来简化promise异步操作,await等待的是右侧表达式的返回值。

Reflect(反射)

调用对象的基本方法 读取、修改、删除和遍历等对象属性时(语法层面上,如obj.a)实际是调用了一些基本操作(内部函数调用,如[[get]]),而且还是封装过的,所以使用Reflect可以跨过这些封装函数。(因为是直接调用基本方法)

ed3379bc5f07cfe9e060ed15af59f67.jpg

Proxy

Proxy 代理,它内置了一系列”陷阱“用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Object.defineProperty是对象的内部基本方法之一(操作的是对象的属性),而proxy是对象所有内部基本方法的拦截器。可以捕获到对象或数组的所有改变(直接操作对象)
const proxy = new Proxy (target,handler)
target 目标对象(任何类型的对象,原生数组,函数,甚至另一个代理)
通过 Proxy 创建原始对象的代理对象,从而在代理对象中使用 Reflect 达到对于 JavaScript 原始操作的拦截。

39830a8fa15e86466413456b3dd4f23.jpg

Proxy为什么要配合Reflect使用?

同样有参数receiver,触发代理对象的劫持时保证正确的 this 上下文指向。 Proxy 中接受的 Receiver 形参表示代理对象本身或者继承于代理对象的对象。 Reflect 中传递的 Receiver 实参表示修改执行原始操作时的 this 指向。 你可以简单的将 Reflect.get(target, key, receiver) 理解成为 target[key].call(receiver),不过这是一段伪代码,但是这样你可能更好理解。

Set

类似数组的数据结构,值都是唯一的,没有重复,本身是一个构造函数,加入的值不会类型转换(5和'5')NaN等于NaN,{ } 对象总是不相等。
WeakSet与Set类似,但是WeakSet成员只能是对象和Symbol值,不能是其他。数组作为参数,是数组的成员成为WeakSet的成员,所以数组的成员必须是对象。WeakSet中的对象都是弱引用,一旦不被引用就会被回收内存。

Map

类似对象的数据结构,本质是键值对的集合,但是它的键不限制类型。不仅是数组,任何具有遍历器接口,且每个成员都是双元素的数组都可以当做Map构造函数的参数,所以Set和Map都可以用来生成新的Map。键跟内存地址是绑定的,只要内存地址不一样,就视为两个键。
WeakMap与Map类似,但是只接受对象(null除外)和symbol作为键名。WeakMap的键名所指向的对象,是弱引用,不计入垃圾回收机制。

模块化

解决命名冲突
提供复用性
提高代码可维护性
ES6
// file a.js
export function a() { }
export function b() { }
import {a,b} from './a.js'
// file b.js
export default function () { }
import xxx from './b.js
CommonJS 最后导出的是module.exports对象
// a.js
module.exports = {
a:1
}
// or
exports.a = 1
// b.js
var module = require ('./a.js')
module.a
CommonJS支持动态导入,也就是require(${path}/xx.js)。

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用,导入值会随导出值变化而变化。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

实用技巧与方法

parseFloat((0.1+0.2).toFixed(10)) === 0.3
typeof null [ ] { } // 'object'
typeof 函数 'function'
判断对象是否属于实例关系用instanceof
instanceof的原理是基于对象原型链进行判断,会一层一层向上遍历对象原型链,直到找到指定的构造函数(或类),或到达原型链末端。
普通类型 2 instanceof Number false
引用类型 function(){} instanceof Function true
hasOwnProperty可以确定访问的属性是来自实例还是原型对象
getOwnPropertyNames返回一个数组,包含对象自身所有属性的名称(包括不可枚举不包括symbol),不会查找原型链上
Object.keys包括可枚举,不包括symbol
判断是否为空对象?
Reflect.ownKeys(obj).length === 0
通过实例对原型属性的更改,会反映在所有实例上面。
object.keys():判断对象自身可枚举属性
in关键字:判断属性是否存在于对象或者对象的原型链上
for-in枚举 遍历对象中除symbol外的可枚举属性,包括从原型链继承的属性。为了避免迭代到不是自身的属性,可以使用hasOwnProperty判断是否为对象自身属性。(迭代顺序不确定)
for-of循环是迭代可迭代对象的方法,可以遍历数组、Map、Set等。不能遍历普通对象(因为没有iterator迭代器)
for-in遍历的是key,for-of是value
for-in会遍历对象原型链,
for-of是可迭代对象,值是可枚举的
for循环对比forEach
for循环性能好,可以break中断,支持异步等待
forEach和map都是循环遍历数组,匿名函数中this都是指向window,都支持三个参数,分别为item,index,arr(原数组)
区别:forEach 只会对数组每个元素执行回调函数,返回值为undefined,而map是映射数组的方法,可以根据指定规则对原数组每个元素执行回调函数,并返回新的数组。
forEach对遍历的原数组对应的值会发生变化,而map对遍历的原数组没有改变。
forEach可以遍历伪数组
reduce(callback回调函数,initiaValue初始值)
回调函数有四个传入参数,prev和cur(必需),index和arr
init初始值就是往数组最前插入一个元素
reduce用法
数组求和arr.reduce((prev,cur) => prev+cur)
最大值arr.reduce((prev,cur) => Math.max(prev,cur))
every()每一项都满足条件就返回true,只要有一项不满足就返回false,对空数组会返回true
some()有一项满足就true,全部不满足就为false
[ ]访问的是动态属性,可以使用数字
.访问的是静态属性,不可以使用数字
除了symbol外的所有类型作为数组的索引,都会先被转为字符串
数字、对象和函数本身不能作为数组的索引,所以当使用这些作为索引时会被转换为字符串,而symbol不会被转换

48b36ebcee3b66c86399ccf16bf9f28.jpg 解构的原理就是使用object.iterator()方法返回一个迭代器,然后不停调用它的next方法
??(空值合并运算符) 和||的区别是??只处理undefined和null,||还处理false、' '和0
undefined/null ?? [ ] // 返回[ ]
== 比较会进行类型转换
两端都是原始类型,转成数字比较
Null == undefined(true)
一边出现NaN为false
=== 比较不会进行类型转换
+0 === -0(true)不区分正负零
NaN === NaN(false)
Object.is(+0,-0)false
Object.is(NaN,NaN)true
对象转原始类型
调用内置toPrimitive函数
调用x.valueOf( ),如果转换为基础类型,就返回转换的值
调用x.toString( ),如果转换为基础类型,就返回转换的值
如果都没有返回基础类型,就会报错
可以重写Symbol.toPrimitive,优先级最高
let a = {
valueOf() {
return 0
},
toString() {
return '1'
},
Symbol.toPrimitive {
return 2
}
}
1 + a // 3

SPA单页应用

优点:具有桌面应用的及时性、网站的可移植性和可访问性
用户体验好、快,内容改变不需要重新加载整个页面
前后端分离,分工明确
缺点:不利于搜索引擎的抓取(SEO)
首次渲染速度慢
SPA实现SEO优化
1、SSR服务端渲染,将页面或组件通过服务器生成HTML,再返回给浏览器,如nuxt.js
2、静态化
3、phantomjs针对爬虫处理
SPA首屏加载慢原因
网络延时
资源文件体积过大
资源重复发送请求
加载脚本时,堵塞渲染内容
解决方案
减少入口文件体积
静态资源本地缓存
UI框架按需加载
图片资源压缩
组件重复打包
开启GZip压缩
使用SSR

bfe362794d8b3c458e21389385e71fe.jpg

前端工程化

模块化
包管理 npm
eslint 代码风格检测
Babel JS编译器,解决代码兼容性
css预编译器 sass、less、stylus
postCSS tailwind
核心:构建工具
webpack
打包压缩、文件指纹、开发服务器
vite=esbuild+rollup
开发基于esbuild,打包是rollup

结尾涩话

终于是整理出来啦,实在是太多啦!前端的知识这么杂的吗o(╥﹏╥)o 可能还是有很多遗漏,但是第一天就先这样吧,实在是记不住了。