一、H5新特性
1、语义化标签
优点:语义化强烈,便于开发人员和机器的编辑及读取,利好于SEO优化(搜素引擎优化)
SEO优化的使用场景:
①使用语义化标签
②meta标签的name=discription属性
③img标签的alt属性
④更加规范的编码习惯
2、新的表单控件
date、time、email、submit
正则:
手机号正则:/^1[3-9][0-9]{9}$/
邮箱正则:/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+.[a-zA-Z]{2,6}$/
3、canvas绘图
不能通过非内联样式设置宽高
let ctxNode = document.querySelector('#box')
let ctx = ctxNode.getContext('2d')
绘制矩形
ctx.fillRect()
绘制线形
ctx.moveTo()
ctx.lineTo()
ctx.stroke()
绘制圆形
ctx.closePath()
ctx.beginPath()
ctx.arc()
ctx.stroke()
4、本地存储
①、localStorage
特点:永久存储(硬盘)、存储量:5M左右
使用场景:存储用户的标识数据token(单一的数据、体积较小的数据、标识的数据)
②、sessionStorage
特点:会话存储(内存)、存储量:5M左右
使用场景:在Vue的路由权限管理中,在访问动态路由下,刷新页面会导致白屏,其原因是因为刷新页面以后,Vuex中的数据会重新初始化,用户动态路由信息会丢失,会导致动态路由失效(路由表routes没有对应的动态路由)
解决方案:将Vuex中用户数据在刷新页面之前【利用window绑定beforeUnload事件】存储到sessionStorage中,在刷新页面之后将保存的数据更新到Vuex中
③、cookie(解决http无状态协议问题)
生命周期:1、持久化cookie 2、会话cookie
存储量:4KB左右
缺点:1、不安全,容易被截获 2、现代浏览器都支持用户禁用cookie
运作过程:1、服务器生成 2、保存在浏览器端
④、session
运作过程:1、服务器生成 2、保存在服务器端
如何使用:1、将sessionId传递给浏览器端,利用cookie作为载体传递
缺点:cookie的缺点它都有
5、小程序
生命周期:永久存储
存储量:1、单个key的大小上限1M 2、所有key的上限10M
6、token
一、token由几部分组成? 三部分组成
①、header: 1、type:TWT(json web token) 2、alg:加密的算法,HS256
②、payload: 用户信息的映射,如id,name,时间戳,过期时间(可选)
③、signature 加密的密钥的映射,加密的密钥保存在服务器本地,为了安全
获取token
var token = jwt.sign({foo:bar},密钥)
token反编译
jwt.vertify(token,密钥)
二、7 || 15 天免登录
1、用户登录以后,获取对应的token,保存在本地
2、以后每次发起业务请求都会携带token,每次请求的时候都会先验证token有效性
3、通常设置token的有效期就是7 || 15 天
4、当用户在7 || 15 天之内再次访问网站,会先验证本地是否有token,如果有就直接携带本地的token进行业务请求,如果token没有过期就能正常使用,如果token过期需要重新登录获取新的token
三、怎样实现无感刷新token
1、理解:用户在进行核心业务逻辑的时候(支付、操作订单等),token即将要过期要在用户不知情的情况下,悄悄的刷新token换成有效token
2、实现:
①:用户登录以后会返回给用户两个token
②:两个token:
1、访问令牌:access token
①:带有有效期的token
②:正常发起请求携带的token必然是访问令牌而不是刷新令牌
2、刷新令牌:refresh token
①、没有过期时间
②、在token即将过期的时候,或者刚刚过期的时候用户正在发起业务请求,如当用户发起支付请求的时 候,检测到访问令牌即将要过期或者刚刚过期不久,使用刷新令牌发起请求获取新的带有有效token, 访问令牌
③、使用刷新令牌获取的访问令牌的分类:
1、短期有效:几分钟到几个小时(使用最多)
2、中期有效:几天到几周
3、长期有效:几乎不用
7、Web Worker
进程:一个程序的执行
线程:一个进程至少有一个线程 每一个线程之间时相互独立的 多个线程之间是可以共享进程中数据的
js特点:
1、单线程:所有的js代码最终在该线程(主线程)上执行
—为什么要这么设计?
--js可以操作DOM(增删改)
--如果设计成多线程会导致页面渲染发生混乱,甚至是报错
Web Worker
1、理解:是一个单独的线程 受js主线程的管控
2、特点:worker线程等同于异步任务,不能操作DOM,不能使用window对象,不会阻 塞后续代码的执行,可以在worker线程中执行运算量大的代码
let myworker = new Worker('文件路径')
发送数据给分线程:myworker.postmessage(arr)
分线程接收主线程的数据:onmessage = function(res){
postmessage('处理后的数据')
}
主线程接收分线程处理好的数据:myworker.onmessage = function(res) {
}
可以使用alert证明js是单线程
8、iframe
iframe是内联框架,可以在当前的文档(页面)嵌套另一个文档(页面)
同源策略:协议、域名、端口号必须完全一致
http默认端口号为80,https默认端口号为443
域名和服务器的IP地址是映射关系
DNS域名解析系统
9、自定义事件
一、相关:1、Web Worker 2、Vue2事件总线对象 3、pubsub
二、理解:
1、绑定事件:1、事件名 2、callback 3、消息的订阅 4、需要数据的一方
2、触发事件:1、事件名 2、传递的数据(会作为实参传递自定义事件绑定的callback中) 3、消息的发布 4、提供数据的一方
3、注意点:绑定事件和触发事件的对象必须是同一个对象 必须是先绑定再触发
二、CSS3
1. 新增特性
-
新增了选择器
:last-child
匹配父元素的最后一个子元素:nth-child(n)
匹配父元素的第 n 个子元素
-
边框特性
border-radius
圆角
-
颜色与不透明度
opacity: 0.5;
color: rgba(0, 0, 0, 0.5)
-
阴影
text-shadow
文字阴影box-shadow
盒子阴影
-
transform 变形
transform: rotate(9deg)
旋转transform: scale(0.5)
缩放transform: translate(100px, 100px)
位移
-
过渡与动画
transition
过渡animation
动画
-
媒体查询
@media
用来做响应式布局 地址:developer.mozilla.org/zh-CN/docs/…- 百分比布局 VW适配
- 百分比是相对于 包含块 的计量单位,通过对属性设置百分比来适应不同的屏幕
- rem布局
- rem(font size of the root element)是指相对于根元素的字体大小的单位,rem只是一个相对单位
2. 盒模型
2.1 概念
页面渲染时,DOM 元素所采用的布局模型。 可通过 box-sizing 进行设置。
2.2 分类
- 标准盒模型(W3C)
- content-box
- 当给元素设置 width 和 height 时,只会改变 width + height
- 怪异盒模型(IE)
- border-box box-sizing
- 当给元素设置 width 和 height 时,会改变 width + height + padding
3. BFC
3.1 概念
BFC,又称为块级格式化上下文,指的是:一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。
3.2 触发条件(开启BFC)
-
设置浮动,不包括 none
-
设置定位,absolute 或者 fixed
-
行内块显示模式,inline-block
-
设置 overflow,即 hidden,auto,scroll
-
表格单元格,table-cell
3.3 BFC特点
-
BFC 是一个块级元素,块级元素在垂直方向上依次排列。
-
BFC 是一个独立的容器,内部元素不会影响容器外部的元素。
-
属于同一个 BFC 的两个盒子,外边距 margin 会发生重叠,并且取最大外边距。
-
计算 BFC 高度时,浮动子元素也要参与计算。
3.4 应用
-
阻止 margin 重叠
-
包含内部浮动: 清除浮动,防止高度塌陷
-
排除外部浮动:阻止标准流元素被浮动元素覆盖
4. 选择器权重&优先级
!important
>行内样式
>#id
>.class
>tag
>*
>继承
>默认
- CSS 选择器浏览器是 从右往左 依次解析
5. CSS预处理器(Sass/Less/Stylus)
5.1 概念
-
CSS 预处理器定义了一种新的语言,主要是通过用一种专门的编程语言,为 CSS 添加一些编程特性,再编译生成 CSS 文件。
-
它可以帮助我们编写可维护的、与时俱进的代码,也可以减少需要编写的 CSS 数量,对于那些需要大量样式表和样式规则的大型用户界面是非常有帮助的。
-
CSS 预处理器可以更方便的维护和管理 CSS 代码,让整个网页变得更加灵活可变。
5.2 语法示例
-
使用变量,常量
$primary-color: pink; .title { color: $primary-color; }
-
层级嵌套
.parent { color: red; .child { color: red; } }
-
混入
/* 定义混合 */ @mixin clearfix { &:after { content: ""; display: block; height: 0; clear: both; visibility: hidden; } } /* 使用混合 */ .content{ @include clearfix; }
-
继承
.border { border: 1px solid pink; } .content { @extend .border; font-size: 20px; }
6. flex(伸缩盒模型)布局
6.1 概念
- Flex 是 Flexible Box 的缩写,意为**"弹性布局"**,用来为盒状模型提供最大的灵活性。
- 采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。
- 容器默认存在两根轴:主轴和交叉轴(也叫做侧轴)。默认水平方向的为主轴,垂直方向为侧轴。
6.2 容器的属性
-
flex-direction
定义主轴的方向 -
flex-wrap
定义是否换行 -
flex-flow
是 flex-direction 属性和 flex-wrap 属性的简写形式 -
justify-content
定义项目在主轴上的对齐方式 -
align-items
定义项目在侧轴上的对齐方式
6.3 项目(伸缩个体)的属性
-
order
定义项目的排列顺序。数值越小,排列越靠前,默认为 0。 -
flex-grow
定义项目的放大比例,默认为 0,即如果存在剩余空间,也不放大。 -
flex-shrink
定义了项目的缩小比例,默认为 1,即如果空间不足,该项目将缩小。 -
flex-basis
定义了在分配多余空间之前,项目占据的主轴空间。它的默认值为 auto,即项目的本来大小。 -
flex
是 flex-grow, flex-shrink 和 flex-basis 的简写,默认值为 0 1 auto。 -
align-self
允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。 -
请回答: flex:1代表什么?
flex-grow: 1
如果存在剩余空间, 该项目会放大。flex-shrink: 1
如果剩余空间不足,该项目会缩小。flex-basis: 0%
设置为 0% 之后,即不占据主轴空间,但是因为有 flex-grow 和 flex-shrink 的设置,该项目会自动放大或缩小。
7. 经典布局
7.1 实现两栏布局(左侧固定 + 右侧自适应布局)
-
DOM结构
<div class="container"> <div class="left">左侧</div> <div class="right">右侧</div> </div>
-
css样式(flex实现)
.container { display: flex; height: 100px; } .left { width: 200px; height: 100%; background: pink; } .right { flex: 1; height: 100%; background: deeppink; }
-
css样式(float实现)
.container { width: 100%; height: 100px; } .left { float: left; width: 200px; height: 100%; background: pink; } .right { height: 100%; background: red; margin-left: 200px; }
8. 隐藏页面元素方式
display: none
不占位。不会响应 DOM 事件。opacity: 0
占位,但不可见。会响应 DOM 事件。visibility: hidden
占位,但不可见。不会响应 DOM 事件。position: absolute; left: -10000px
移动到屏幕外z-index: -1
将别的定位元素遮盖掉当前元素
9. 让元素水平垂直居中方式
-
利用绝对定位 + transform, 子元素未知宽高
.father { position: relative; } .son { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }
-
利用绝对定位 + margin(负值), 子元素必须明确宽高
.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; }
三、JavaScript
1. 基本数据类型
number
string
boolean
undefined
null
symbol
常用于对象上添加唯一属性BigInt
用于表示任意精度整数的数据类型,旨在解决JavaScript中处理超出Number类型表示范围的大整数(超过Number.MAX_SAFE_INTEGER
或小于Number.MIN_SAFE_INTEGE
)的问题
2. 引用数据类型
-
object
-
array
-
function
3. 如何判断js数据类型
3.1 数据类型
-
分类
- string、number、boolean、null、undefined、symbol、bigint、object、function、array
-
typeof返回值
-
string、number、boolean、undefined、symbol、bigint、
object(object,null,array)
、function -
instanceof
- 语法: A instanceof B
- 作用: 检测B的显示原型是否出现在A的原型链上
-
精准检测数据的数据类型
Object.prototype.toString.call(target).slice(8, -1)
3.1 typeof
typeof 123; // 'number'
typeof "123"; // 'string'
typeof null; // 'object'
typeof undefined; // 'undefined'
typeof Symbol(); // 'symbol'
typeof true; // 'boolean'
typeof {}; // 'object'
typeof function () {}; // 'function'
typeof []; // 'object'
3.2 instanceof
- 语法: a instanceof b
- 简单理解:检查 a 是否是 b 的实例、检测B的显示原型是否出现在A的原型链上
- 真正理解:检查 a 的隐式原型属性(包含隐式原型上的隐式原型)是与 b 的显示原型属性指向同一个对象
({}) instanceof Object; // true
([]) instanceof Object; // true
null instanceof Object; // false
([]) instanceof Array; // true
({}) instanceof Array; // false
3.3 如何准确获取指定目标数据的数据类型
Object.prototype.toString.call(obj).slice(8, -1)
Object.prototype.toString.call(123).slice(8, -1); // 'Number'
Object.prototype.toString.call("123").slice(8, -1); // 'String'
Object.prototype.toString.call(null).slice(8, -1); // 'Null'
Object.prototype.toString.call(undefined).slice(8, -1); // 'Undefined'
Object.prototype.toString.call(Symbol()).slice(8, -1); // 'Symbol'
Object.prototype.toString.call(true).slice(8, -1); // 'Boolean'
Object.prototype.toString.call({}).slice(8, -1); // 'Object'
Object.prototype.toString.call(function () {}).slice(8, -1); // 'Function'
Object.prototype.toString.call([]).slice(8, -1); // 'Array'
Object.prototype.toString.call(new Date()).slice(8, -1); // 'Date'
Object.prototype.toString.call(new RegExp()).slice(8, -1); // 'RegExp'
3.4 判断是否是数组
Array.isArray()
3.5 全等 === 判断类型和值是否相等
NaN === NaN // false
4. 数组常用方法
4.1 更新数组的方法
-
push
给数组最后添加一个元素 -
pop
删除数组最后一个元素 -
unshift
给数组最前面添加一个元素 -
shift
删除数组最前面一个元素 -
splice
删除/新增指定下标元素 -
sort
排序 -
reverse
反转
4.2 遍历元素的方法
-
forEach
遍历 -
map
返回一个新数组,新数组长度和原数组一致,但内部的值往往会发生变化。(长度不变,值变) -
- 在 React 中更新数据中某个值,往往使用
map
方法。
- 在 React 中更新数据中某个值,往往使用
-
filter
返回一个新数组,新数组长度往往比原数组更少,但内部的值和原数组一致。(长度变,值不变) -
- 在 React 中删除数据中某个值,往往使用
filter
方法。
- 在 React 中删除数据中某个值,往往使用
-
reduce
常用于统计、累加和求和等功能。 -
- 购物车模块,计算总价。
-
find
查找某个元素,找到返回这个元素,找不到返回 undefined。 -
findIndex
查找某个元素的下标,找到返回这个元素下标,找不到返回 -1。 -
every
所有返回 true 整体才返回 true,只要有一个返回 false,整体就返回 false。 -
some
只要有一个返回 true,整体就返回 true,只有全部返回 false,整体才返回 false。
4.3 其它方法
-
slice
截取数组中某些元素 -
concat
拼接数组 -
join
将数组内部元素以某种方式拼接成字符串 -
includes
判断是否包含某个元素,包含返回 true,不包含返回 false -
indexOf
判断是否包含某个元素,包含返回其下标,不包含返回-1
5. 常见的DOM操作有哪些
5.1 新增DOM元素
document.createElement()
创建DOM元素xxxDom.appendChild()
将某个dom元素插入到xxxDom内
5.2 删除DOM元素
xxxDom.removeChild()
将xxxDom下面某个子元素删除xxxDom.remove()
将xxxDom自身删除
5.3 修改DOM元素
xxxDom.innerText / xxxDom.textContent
设置元素的文本内容xxxDom.innerHTML
设置元素的html内容
5.4 获取/查询DOM元素
document.getElementById()
根据id选择器获取某个DOM元素document.querySelector()
根据任意选择器获取找到的第一个DOM元素document.querySelectorAll()
根据任意选择器获取找到的所有DOM元素集合
6. 作用域
-
概念
- 用于约束变量的查找范围及作用范围
- 主要是用于隔离变量
-
设计思想
- 隔离变量,防止命名冲突及变量污染
-
理解:
- 本质其实是抽象的概念
-
作用域链:
- 查找变量的时候,先在当前作用域下的变量对象中查找,如果有就使用,如果没有就向上一级作用域下的变量对象中查找
- 如果有就使用,如果还没有就继续向上查找,直到找到全局作用域下的变量对象global,如果还没有就报错,xxx is not defined
- 函数的作用域链有几种?
- 第一种:[local, global]
- 第二种: [local, closure, global]
- 第三种: [local, closure1, closure2, ....., global]
-
产生时机:
- 代码定义的时候, 如果是函数的话就是函数体被定义的时候,作用域一旦产生是不可以修改的
-
完整话术:
- 首先作用域是用于约束变量的查找范围及作用范围,设计它的初衷就是用于隔离变量,防止命名冲突及变量污染,作用域链就是查找变量的时候,先在当前作用域下的变量对象中查找,如果有就使用,如果没有就向上一级作用域下的变量对象中查找,如果有就使用,如果还没有就继续向上查找,直到找到全局作用域下的变量对象global,如果还没有就报错,xxx is not defined
- 作用域分为:全局作用域、局部作用域、块级作用域和eval作用域
- 我认为作用域本质是一个抽象的概念,真正落地的是作用域下的变量对象,这个对象是变量提升的产物
- 变量提升是在JS代码定义以后,正式执行之前,JS引擎会先做预解析工作,预处理工作包括:创建一个空的变量对象,会收集当前作用域下的变量、函数以及函数的参数(针对函数的预解析),收集的依据是关键字,var和function,遇到var之后,会将var后边的变量提前声明到变量对象中,但是不赋值,其值是undefined,遇到function以后,会提前在变量对象中定义该函数体,该函数必须是有效函数(被调用或被return),它会确认this的指向,创建完整的作用域链
- 我认为的作用域链是一个数组,在该数组中存放的是一个个的变量对象,第一个变量对象一定是当前函数作用域下的变量对象,最后一个变量对象一定是全局的global, 在中间可能会掺杂闭包对象,有没有闭包对象有无函数嵌套,内部函数是否引用了外部函数局部变量,产生**作用域链的时机是在函数体定义的时候,**一个函数的作用域一旦产生了就是固定的,不可以修改
7. 预解析 & 变量提升
7.1 预解析
函数预解析
-
第一次预解析
- 发生时机: 函数体定义的时候
- **目的:**确认当前函数的上级作用域链中是否应该存在闭包
-
第二次预解析
- **发生时机:**函数被调用,函数体代码正式执行之前
- **目的:**变量提升,需要确认提升哪些数据
-
代码定义之后,执行之前,js引擎会对其进行预解析操作
-
全局预解析工作内容:
- 创建
变量对象
(global) - 收集当前作用域下的变量,函数,函数的参数
- 收集依据:var, function关键字
- 变量声明但是不赋值,遇到function关键字后,定义函数体并对函数体内容进行预解析
- 创建
-
函数预解析工作内容
- 检测是否形成闭包
- 创建上级作用域链,如果当前函数作用域下有闭包,则闭包引用数据也会作为对象存放至当前作用域下上级作用域链的数组中
7.2 变量提升
-
概念:
- 当js代码定义之后,正式执行之前,js引擎会先做预解析的工作
- 预解析工作内容:
- 创建一个空的
变量对象
- 收集当前作用域下的变量,函数,以及函数的参数(函数的变量提升)
- 收集的依据是:
- 关键字: var ,function
- 遇到var以后,将var后边的变量提前声明在变量对象中,但是不赋值
- 遇到function以后,提前在变量对象中定义该函数体, 能够被提升的函数必须是有效函数(被调用 || 被return)
- 确认this的指向
- 创建完整的作用域链
- 创建一个空的
-
分类:
- 全局变量提升
- 函数变量提升
-
注意点:
-
ES6中let,const定义的变量也会被变量提升,但是提升的容器和var提升变量对象不是一个对象, ES6的变量提升会单独放入script对象中
-
虽然会被提升但是不能在初始化之前进行访问,会报错: `Cannot access 'e' before initialization
- 提升内容
- 创建
变量对象
2. 收集当前作用域下的变量,函数,函数的参数 3. 收集依据:var, function
关键字 1. 变量声明但是不赋值,遇到function关键字后,定义函数体并对函数体内容进行预解析 2. 提升时机: 1. 全局:代码定义之后,执行之前 2. 函数: 函数调用后,函数体代码执行之前
- 创建
- 特点: 1. 在var 定义的变量之前可以访问该变量不会报错 2. 在声明式定义函数之前可以调用函数
- 好处: 1. 代码正式执行的时候更快
- 提升内容
-
8. 说说你对闭包的理解
8.1 闭包产生条件
- 函数嵌套
- 内部函数引用外部函数局部变量
- 外部函数调用
- 内部函数为有效函数(被调用 || 作为外部函数返回值)
闭包不是在内部函数调用的时候产生的,而是在内部函数体定义的时候就产生(内部函数对象定义的时候)
8.2 闭包作用
- 延长局部变量的生命周期
- 让函数外部能间接操作内部的局部变量
- 可以从函数外部访问函数内部的局部变量(作用域)
8.3 使用闭包 & 释放闭包
- 使用闭包: 执行内部函数
- 释放闭包: 让内部函数对象成为垃圾对象, 断开指向它的所有引用
8.4 闭包应用
- React 中高阶函数(用来复用函数的方式)
- Vue2 响应式原理中 dep 对象以闭包的形式保管在 get/set 函数中
- Vue & React & 小程序生命周期函数中自定义方法内部使用this
闭包(完整)
- 理解:
- 纯概念: 函数极其外部所处的词法环境组合的关系
- 可落地: 是一个闭合的对象,闭合的数据来源外部函数的局部变量
2. 产生的条件:
- 函数嵌套
- 内部函数引用外部函数的局部变量
- 外部函数调用
- 内部函数是有效函数
3. 产生的时机:
- 内部函数体定义的时候,而不是调用的时候
- 产生以后保存在内部函数对象 [[scopes]]属性上
4. 闭包的作用
- 延长外部函数局部变量的生命周期
- 可以从函数的外部访问到函数内部的私有变量
5. 设计思想?
- 当函数调用执行完以后要出栈,随即销毁对应的变量对象,如果没有闭包,当内部函数在外部调用的时候就无法访问外部函数的局部变量
6. 闭包对象和外部的变量对象关系?
- 它们是两个不同的对象
- 闭包对象中的属性有哪些取决于内部函数用了哪几个外部函数的局部变量,值是问外部函数的局部变量要的
7. 如何合理使用闭包?
- 使用完以后,如果确认不再使用内部函数的时候,要及时清除闭包
- 如何清除? 让内部函数成为垃圾对象
8. 闭包的使用场景?
- React中高阶函数, 扩展: 将一个函数作为参数传递给另外一个函数,或者将一个函数作为返回值被return
- Vue中生命周期函数中, 比如: 在生命周期函数中开启定时器,在定时器中使用当前组件的实例this
- Vue源码中,响应式原理依赖收集的时候,dep对象创建的时候
9. 说说原型及原型链
原型
原型: 1. 理解: - 原型的本质是一个对象,该对象中存公共的方法,可以给多个实例复用 - 公共的数据(方法)池 - 实例对象身上都有__proto__属性,实例对象的__proto__属性和其构造函数的prototype指向的是同一个对象,该对象就是他们的原型对象 - 我们一般称__proto__为隐式原型, prototype为显示原型 2. 设计思想 - 因为我们需要在公共的容器中保存多个数据,而且我们不确定后期开发者会添加什么公共的方法进来,最好的方式通过key:value的映射关系保存,方便后期查找使用 - 公共的数据池数据是为了给所有的实例访问,好处是减少定义的次数,节省内存空间 - 因为是公共数据,所以修改数据的入口必须是唯一的,而构造函数是唯一的,所以给构造函数添加prototype属性可以访问原型对象 - 为了保证公共数据的安全,所有的实例对象只能拥有可读权限 3. 原型链: - 当查找 对象的属性 的时候 现在自身找, 如果有就使用,如果没有没有,会沿着__proto__原型链去查找,如果有就使用 - 如果还没有就继续沿着原型链向上查找,直到找到Object.prototype对象,如果还没有会返回undefined - 注意: - Object.prototype.__proto__隐式原型上有和Object.prototype一样的方法,比如: toString,这一层我们称之为Object.prototype的保护层 - 原型链的终点一定是null,null相当于对象属性查找结束的条件 4. 原型继承: - 子类的原型 成为 父类的实例 - Child.prototype = new Parent(); - 问题: 原型继承会导致子类原型对象上的constructor构造器属性丢失, - 解决: 重新设置子类原型上的构造器属性 5. class类继承: - 实现: extends关键字 - 总结和原型继承有什么区别? - 原型继承是修改子类原型对象的指针, 会导致子类原型对象上的constructor构造器属性丢失 - class类继承: - 子类的原型 成为 父类的实例 - 自动又给子类的原型添加了constructor属性,同时将其他的属性移除
9.1 原型
-
我们说的原型,指的是两个原型属性:
__proto__
和prototype
-
prototype
叫做显示原型属性 -
__proto__
叫做隐式原型属性 -
每个函数都有一个显式原型属性,它的值是一个对象,我们叫做原型对象。
-
这个原型对象上默认会有一个
constructor
方法,指向函数本身,有一个__proto__
属性,指向 Object 的原型对象 -
每个实例都有一个隐式原型属性,它的值指向其对应构造函数的原型对象。

9.2 原型链
-
概念:从对象的
__proto__
开始, 连接的所有对象, 这个结构叫做原型链,也可称为“隐式原型链” -
作用:用来查找对象的属性
-
规则:在查找对象属性或调用对象方法时,会先在对象自身上查找, 找不到就会沿着原型链查找,找到就返回属性的值,最终来到
Object.prototype.__proto__
,找不到返回 undefined -
应用:利用原型链可以实现继承
-
- Vue 中全局事件总线
$bus
- Vue 中全局事件总线
-
- 项目中
$api / $http
汇总所有接口函数
- 项目中
9.3 继承: 借用构造函数 + 原型
- 子类的原型成为父类的实例
- 注意:使用构造函数继承后,会导致子类原型的构造方法丢失,需要重新单独设置
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.showInfo = function(){
console.log(this.name, this.age);
}
// 实现继承: 子类的原型成为父类的实例
Child.prototype = new Person();
// 重新设置子类的构造函数
Child.prototype.constructor = Child;
function Child(name, age, sex){
this.name = name;
this.age = age;
this.sex = sex;
}
let child = new Child('小明', 18, 'man');
child.showInfo();
10. 事件轮询(循环)机制
事件轮询机制
- js代码分为
同步任务
和异步任务
, 而异步任务又分为宏任务
和微任务
- js代码执行先执行js主线程上全局的同步任务, 在这个过程可能会产生异步任务,异步任务的回调不会立马执行,而是被根据异步任务的分类分别放入宏任务队列或者是微任务队列
- 当全局的同步任务执行完先看有无微任务队列,如果有就会把当前微任务队列中的所有微任务回调都依次执行再看宏任务队列中有无宏任务,如果有就执行一个(第一个被放入队列的回调)宏任务,执行完以后再看有无微任务队列,如果有就全部清空所有的微任务,如果没有执行下一个宏任务
10.1 概念
异步代码执行机制。
10.2 详情
-
所有任务(同步/异步)都在主线程上执行,形成一个执行栈。
-
执行栈之外有用于存储待执行异步回调的任务队列(宏任务队列与微任务队列)
-
浏览器中有在其它分线程执行相关管理模块
-
- 定时器管理模块
-
- ajax 请求管理模块
-
- DOM 事件管理模块
-
执行代码的顺序:
-
- 在执行栈中执行初始化同步代码
-
- 执行过程中如果有启动异步任务, 交给对应的管理模块处理, 管理模块会在后面特定时间,将回调函数放入任务队列中待执行
-
- 在执行栈中所有代码都执行完后, 依次取出任务队列中的回调到执行栈中依次执行
10.3 宏任务与微任务
- 宏任务
- script(整体代码)
- setTimeout / setInterval
- Ajax
- DOM 事件监听
- postMessage (H5, 向其它窗口分发异步消息)
- setImmediate(Node.js 环境)
- 微任务
- Promise
- async & await
- mutationObserver.observe(H5, 监视 DOM 元素变化)
- 整体的执行顺序
- script(整体代码)
- 所有微队列中的微任务
- 宏队列中的第一个宏任务
- 所有微队列中的微任务
- 宏队列中的第一个宏任务
- 所有微队列中的微任务
- ...
11. ES6常用语法
简单的语法
- const 与 let
- 解构赋值
- 形参默认值
- 扩展运算符: ...
- 模板字符串
- 对象的属性与方法简写
- 模块化语法
- 。。。
比较复杂的语法
- 箭头函数(扩展总结 this 指向)
- class 与 extend(扩展原型和继承)
- promise / generator / async & await(扩展js 事件循环机制)
- Proxy(扩展vue2 和 vue3 响应式原理)
- Map / Set / WeakMap / WeakSet (扩展 vue3 响应式原理)
- 。。。
Map对象和Object对象的区别
- Map对象
- key名不能重复,后添加key重名的键值对会覆盖之前的同名键值对
- map对象的键值对个数可以通过自4114101身的size属性轻松获取
- map对象是可以迭代的,使用for of循环(有iterator接口)
- 不支持元素的序列化及解析
- Map对象的键名可以是任意类型
- Object对象
- 后添加的key重名的键值对的值会覆盖之前的值,ES5中的严格模式不允许Object对象中有重复的key
- 对象的键名必须是字符串或者是Symbol
- 自身无法获取,只能手动数获取依赖Object.keys(target).length获取
- 不能迭代,自身及原型上没有iterator接口
- 支持元素的序列化及解析
- Map对象
Map和Object,Set,WeakMap的区别
Set容器:
特点:
- 里边保存多个不重复的数据
- 如果传递给Set容器的数据中有重复的个体,它会自动去重
- 身上有迭代器对象,可以被for of循环遍历
- 不能像数组一样通过index下标直接操作,对于内部元素的操作都是使用指定的api方法,如: add,delete等
应用场景:
- 保存不重复的数据
- 给数组去重
- 例如: 保存单一的商品id信息,使用Set容器
Map容器:
特点:
- 可以保存多个key不重复的键值对, 如果key重名了后者会覆盖前者的内容
- key可以是任意数据类型
- 身上有迭代器对象,可以三点运算符或者是for of等进行消费
- 操作键值对的时候,必须使用指定的方法,而不能向对象一样直接对象.属性
- 不能被JSON序列化
应用场景:
- 保存映射关系,而不是单一的数据
- key是非string类型的时候,Map容器是首选
- 例如: 保存的是商品的id及其对应的商品详情信息的映射,而且id是number的时候
Object对象:
- key可以重复,后者会覆盖前者
- key必须是字符串
- 没有迭代器对象,不能直接使用三点运算符等
- 可以被JSON序列化
WeakMap容器:
特点:
- key也是不能重复的
- key必须是对象
- key是弱引用,如果该key属性指向的value身上没有其它的引用指针,那么下一个垃圾回收机制会自动回收该数据
- 没有size属性,因为键值对的映射内容随时可能会被回收掉
理解:
- 弱引用存在的意义:
- 解决的是不需要手动设置映射的指针指向null才能回收对应的数据
- 能够解决两个对象身上互相保存对方内存的引用永远不会被回收的问题
12. 说说ES6的promise
promise
promise
1. 作用:
- 解决 回调地狱 的问题
- 利用then方法的链式调用,使用同步的流程来表达异步的行为
2. 特点:
1) 三种状态: pending(初始化), fulfilled(成功状态),rejected(失败状态)
2) 状态切换只能有两种方式,pending ---》 fulfilled || pending ---》 rejected, 只能切换一次,而且是不可逆的
3. 使用流程:
- 当初始化一个promise实例以后,该实例的状态为pending
- 在Promise的执行器函数中执行异步任务
- 根据异步任务的执行结果来决定如何修改promise的状态
4. Promise对象的方法:
- resolve
- reject
- all: 管理的所有promise的实例的状态都为成功状态的时候,返回的promise的状态才是成功的
- 应用: 大文件切片上传
- race: 会根据 第一个发生改变的 promise的状态来决定返回promise的状态
- allsettled:管理的所有promise的实例的状态都发生改变以后,就会返回成功的promise
5. 补充的内容
- promise并没有很好的解决回调地狱的问题,因为其本身也需要使用then方法中的回调
- 所以后期推出了async, await
- 现在解决回调地狱的终极方案, promise + async + await
6. async, await
- async: 异步函数
- await:
- 必须搭配async函数使用
- await 后边通常需要跟一个异步任务,异步任务的返回值通常是一个promise实例
- 在await后边紧跟的promise实例状态没有发生改变之前,await语句会阻塞后续代码的执行,直到promise实例的状态为成功状态才执行后续的代码
- awiat不需要回调通知异步的结果
12.1 概念
- 用同步的流程解决异步回调地狱问题
- 用于表示一个异步操作的最终完成(成功或失败)及其结果值。
12.2 promise对象的3种状态
- pending 初始化状态
- resolved / fulfilled 成功状态
- rejected 失败状态
12.3 如何改变promise的状态
-
调用
resolve()
, 改成成功状态 -
调用
reject()
, 改为失败状态 -
throw new Error()
, 改为失败状态 -
注意:状态只能变化一次,且不可逆
-
pending
-->resolved
-
pending
-->rejected
-
12.4 promise实例对象的方法
-
then
接受两个回调(一般只接受一个),第一个是成功回调,第二个是失败回调 -
catch
接受一个回调,是失败回调 -
finally
接受一个回调,不管成功/失败都会触发
12.5 Promise构造方法上的方法
-
Promise.resolve()
返回一个成功的 promise 对象 -
- 也可能返回失败的 promise 对象,比如
Promise.resolve(Promise.reject())
- 也可能返回失败的 promise 对象,比如
-
Promise.reject()
返回一个失败的 promise 对象 -
Promise.all([promise1, promise2, ...])
只有所有 promise 成功才成功,只要有一个 promise 失败就会失败 -
Promise.allSettled([promise1, promise2, ...])
只要所有 promise 状态发生变化就成功,结果值包含所有 promise 的结果值(不管成功/失败) -
Promise.race([promise1, promise2, ...])
只要有一个 promise 成功/失败,就成功/失败。
12.6 应用
- 在项目中一般是使用 axios 发送请求时会使用,返回值是一个 promise 对象,结合 async await 来处理
- 如果同时要发送多个请求的话,可以使用 Promise.all() 方法来处理
- 从多个源中获取数据,将多个数据合并成作为一个结果
- 对于需要等待多个异步操作完成的复杂计算,使用promise.all()可以提高性能
13. 谈谈js模块化语法
js模块化
- 理解:
- 具备特定功能的js代码集合
2. 特点:
- 每个模块具备特定的功能
- 每个模块内部的变量都是私有的, 除非当前模块向外暴露内容
- 当要使用指定的模块的时候,需要先引入对应的模块对象
3. 重点记忆:
- 如何向外暴露
- 如何引入
- CommonJs
- 暴露: exports || module.exports
- 引入: require xxx
- ES
- 暴露: export || export default
- 引入: import xxx from '模块的路径' || import {x} from '模块路径'
4. 思考: 模块化向外的内容是什么?暴露的是引用地址还是值本身?
- 向外暴露的是一个原始对象, js模块化内部会创建一个新的对象,该对象用于包裹向外暴露的内容
- 引入的模块对象的内容是引用地址
- 在两个不同的文件中引入同一个模块的内容, 如果在其中的一个文件中操作模块向外暴露的内容,另一个文件也会受影响
13.1 CommonJs
- 应用:主要用于服务器端NodeJs
- 语法:
- 暴露:
exports || module.exports
- 引入: require(‘模块路径’)
- 暴露:
13.2 ES6 module
- 应用: 主要用于浏览器端
- 语法:
- 暴露:
export || export default
- 暴露:
- 重点:
- 如果模块采用默认暴露: import xxx from ‘模块路径’
- 如果模块采用分别/统一暴露:
- 如果需要引入模块部分内容:import { xxx } from '模块路径'
- 如果需要引入模块全部内容:import * as 模块别名 from ‘模块路径’
14. this & 箭头函数
14.1 this指向
- 理解:
- 格式化上下文对象
2. this分类:
- 全局this: window
- 函数this: 看函数如何被使用
3. 函数的this特点:
- 函数的this在函数调用的决定的,而不是定义的时候
- 函数自调用this: window
- 对象.方法调用this: 对象
- new 构造函数this: 实例对象
- 强制绑定this: bind, apply,call, this指向的是指定的对象
- 箭头函数this: 没有自己的this,this指向的外部作用域的this
4. call, apply, bind的区别
- call,apply都是指定this以后立即调用该函数
- call,apply指定this的同时传递参数的方式不一样,call是从第二个参数开始依次往后传递, apply如果需要传递参数,第二个参数是数组,需要传递的所有参数
都会放入该数组中
- bind会返回一个新的函数,该返回的函数this指向的是bind中指定的this,bind指定this的同时如果需要传递参数同call传递的方式一样
15. 深度克隆 VS 浅克隆
15.1 为什么要深度克隆
- 区分:
- 值传递,引用传递
- 数据拷贝拷贝的是数据本身还是对应的引用地址?
- 浅克隆问题:
- 修改克隆后的数据,会影响原数据,
针对的是引用数据
- 修改克隆后的数据,会影响原数据,
15.2 浅克隆
-
object.assign()
-
扩展运算符
:{ ...obj }
-
Array.prototype.slice()
-
Array.prototype.concat()
15.3 深度克隆
-
JSON.parse(JSON.stringify())
- 不能处理函数
-
lodash.cloneDeep()
-
自定义
- 核心思想: 最终拷贝的数据一定是基本数据类型才进行拷贝, 递归实现
// 检查数据类型 function checkType(target) { return Object.prototype.toString.call(target).slice(8, -1); } // 深度克隆 function cloneDeep(target) { let result; const type = checkType(target); if (type === "Object") { result = {}; } else if (type === "Array") { result = []; } else { return target; } for (let key in target) { // 如果拷贝的是基本数据类型,直接拷贝 if (checkType(target[key]) !== 'Object' && checkType(target[key]) !== 'Array') { result[key] = target[key]; } else { // 如果拷贝的是引用数据类型,递归操作 result[key] = cloneDeep(target[key]); } } return result; }
16. 垃圾回收机制
- js引擎针对内存空间进行优化的一个方案
- 在短时间内重复进行垃圾回收机制的计算,找到所谓的垃圾对象进行回收,从而释放内存空间
- 垃圾回收机制常用的分为两种:分别是1. 引用计数法,2.标记清除法
- 引用计数法指的就是查看内存地址有几个引用(指针), 当引用个数为0的时候就回收对应的内存数据,释放对应的内存空间,但是引用计数法有问题,当两个对象中互相保存对方的引用地址以后,这两个对象永远不会被回收掉,浪费内存空间
- 现在常用的回收机制是
标识清除法
,在标记阶段将有效的对象进行标记,在清除阶段清除没有做标记的数据,但是直接清除会导致内存碎片化
, 从而导致之后的内存分配的速度变慢,内存空间的利用率低,因此在原有的基础上升级标记整理清除
, 就是将未标记的数据空间统一放至一侧,然后统一清除释放内存空间,这样做的好处没有内存碎片化
四、 TypeScript
4.1 理解
- js的超集,在原有js的基础上升级扩展了增强型的语法,ts是一个强类型的语言,对语法及数据类型的要求更加的严格,可以保证我们能够精准的使用对应的数据类型,避免因为数据类型的不匹配导致的程序错误,ts相对于js而言新增了很多的语法,比如: interface,type,泛型,元祖,enum,函数重载等。
4.2 interface和type的区别
-
interface
- 用于流动的数据(动态的数据)
- 只要有管道(函数的形参位置),就需要使用接口
- interface可以通过extends实现继承
- interface可以重复定义,而且重复定义的接口会自动合并接口中的内容
-
type
-
用于描述静态的数据
-
比如: 定义一个人的信息,定义一个num等
-
type不能使用extends继承
-
type可以类型交叉,语法: aType
&
bType- 引用类型交叉,取并集
- 基础类型交叉, 取交集
-
不能重复定义,会报错
/* type: 类型 */ type a = number | string; type b = number | boolean; /* 基础类型交叉,取交集 */ type c = a & b; type ParentType = { name: string, age: number } type ChildType = { name: string, sex: string } /* 引用类型交叉,取并集 */ type PersonType = ParentType & ChildType; let person: PersonType = { name: 'curry', age: 35, sex: '男' }
-
4.3 函数重载
- 函数签名
- 由四部分内容组成
- 函数名 + 形参名 + 形参的类型 + 返回值的类型
- 分类:
- 重载签名
- 没有函数体,只有函数名 + 形参名 + 形参的类型 + 返回值的类型
- 实现签名
- 有函数体,实现签名就是针对重载签名的具体实现
- 重载签名
- 由四部分内容组成
- 函数重载的特点:
- 实现签名中形参的类型及返回值的类型必须要包含函数重载签名声明过的
- 重载签名存在的意义:
- 重载签名是对实现签名的约束
- 有了重载签名以后,ts可以精准的推断出传入的实参的数据类型及返回值的类型
- 什么时候使用函数重载
- 函数的参数类型及返回值的类型不止单一的类型, 有多种类型的可能性
- 扩展理解:
- ts: 根据函数参数的类型不同,从而决定函数体的行为不同
- java: 根据函数参数的个数不同,从而决定函数体的行为不同
五、服务器端
HTTP 和 HTTPS 协议的区别
- HTTP 协议是超文本传输协议,信息是明文传输的,HTTPS 则是具有安全性的 SSL 加密传输协议;
- 使用不同的连接方式,端口也不同,HTTP 协议默认端口是 80,HTTPS 协议默认端口是 443;
- HTTP 协议连接很简单,是无状态的;HTTPS 协议是有 SSL 和 HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 更加安全。
- HTTPS 协议需要申请 CA 证书,一般免费的证书很少。
5.1 上班后如何和后端人员沟通
- 定制接口
- 协商添加字段
- 协商接口如何编写,如:接口地址,请求的方式(后端人员编写的)
- 调用接口
- 平时调接口数据,参考的是后端人员提供的接口文档
- 万一根据后端人员提供的接口文档获取不到数据
- 只需要确认发送的请求是严格按照接口文档来的(url,method,请求的参数)
- 必须要和后端人员沟通,同后端人员进行开发联调`前后端配合检测找出调取数据的原因
- 测试阶段:
- 前端开发的项目要交给后端人员,由后端人员部署到服务器上
- 前后端要
测试联调
, 在局域网内模拟线上的环境,进行测试,在测试的过程中一旦发现有问题,前后端人员需要快速的确认问题的原因及解决方案 - 测试项目没有问题以后,要将项目部署到线上, 上线!!!
- 调试bug联调:
- 当线上环境中项目有bug,需要在线下环境进行bug调试及修改, 前后端人员需要联合去解决
5.2 TCP的三次握手及四次挥手
5.2.1 三次握手
- 客户端先发起
第一握手
,发送syn包给服务器端- syn: 是为了证明客户端有发起请求的能力
- 服务器接收到请求以后,发起
第二次握手
,将syn包再返回给客户端,同时携带ack包- 返syn:通知客户端
- ack包: 有响应数据的能力
- 客户端接收服务器端返回的数据,发起第
三次握手
,将ack包提交给服务器端- 返ack包: 通知服务器端你确实有这个能力,因为客户端已经收到你的响应数据
- 三次握手一旦成功了以后,就可以发起正常的前后端通信请求
5.2.2 四次挥手
目的: 双方都需要明确这次通信要中断
- 第一次挥手
- 客户端发起,明确客户端要主动断开了
- 第二次挥手
- 服务器端接收到客户端第一次挥手请求以后,做出挥手响应
- 基于客户端单方面通信就中断了,客户端不能再发送数据给服务器端了
- 第三次挥手
- 服务器端发起,明确服务器端要主动断开了
- 第四次挥手
- 客户端在接收到服务器端挥手请求以后,做出挥手的响应
- 基于服务器端单方面通信就中断了,服务器再不能发送数据给客户端
5.3 强缓存和协商缓存
- 浏览器端第一次发请求给服务器端
- 服务器返回对应的响应数据,状态码200,如果需要做缓存,在响应头会携带expires和cache-control,同时还会携带etag和last-Modified
- 如果浏览器发现有缓存的字段,就会将该数据进行缓存处理,同时保存对应的字段
- 下一次浏览器发起同样的数据请求,先看本地是否有缓存,先看有无expires和cache-control字段,如果没有,说明没有缓存,直接发起请求, 如果有,看是否过期,如果没有过期,直接使用,此时是不需要发起请求的,直接从内存或者是硬盘中读取缓存数据
- 如果是从内存中读取的,状态是200(from memroy cache)
- 如果是从硬盘中读取的, 状态是200(from disk cache)
- 如果缓存的时间过期了,就看etag和last-Modified字段,当浏览器刷新或重新请求资源时,会在请求头携带 If-None-Match(它的值就是之前保存的 Etag) 和 If-Modified-Since(它的值就是之前保存的 Last-Modified)两个字段发送给服务器,服务器会判断 If-None-Match 和 If-Modified-Since 和服务器保存的 Etag 和 Last-Modified 字段是否一致;
- 如果不一致,说明服务器端保存的数据修改过,证明不能使用浏览器端的过期的缓存数据了,此时会直接返回新的资源数据,同时将新的Etag 和 Last-Modified 返回去,如果一致,会返回304的状态码,浏览器发现状态为304会直接重定向到缓存区读取缓存数据
有了last-modified为什么还要设计etag???
- last-modified只能单纯的根据修改的时间判断是否能够使用浏览器的过期缓存数据,但是容易被一些假象迷惑,如:进行两次修改,第二次修改之后将文件数据还原成修改第一次修改之前的样子,文件内容不变,但是last-modified的时间改变,服务器会自动判定浏览器不能使用缓存了,浪费资源
- etag是根据文件的所有的索引节点(文件中的每一个节点标识),大小没有变,即使修改时间变了,etag也不会变,这样能够精准的判定浏览器是否应该使用缓存数据
5.4 包管理工具
- npm
- 仓库在国外
- package-lock.json
- 缓存已下载包的依赖关系
- 好处: 当再次下载同样的包的时候,不需要解析当前下载包的依赖关系,可以直接下载了
- 当通过npm下载一个包的时候,先解析它的依赖关系
- cnpm
- 淘宝 镜像
- yarn
- 最早的优势: 做缓存,同样的包二次下载会更快
- cyarn
- pnpm
- 用法:
- npm i pnpm -g
- 命令大部分同npm一样
- 在pnpm安装的根目录下做一个硬盘的缓存,将下载过的包直接缓存到硬盘中
- 用法:
5.5 当在浏览器输入一个地址,按下回车发生了什么
5.5.1. 解析 URL
解析 URL 合法性和有效性。
分析所需要使用的传输协议和请求的资源的路径。如果输入的 URL 中的协议或者主机名不合法,将不会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。
5.5.2. 缓存判断
判断是否有缓存。
如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。
5.5.3. DNS 解析
将域名地址解析为 ip 地址。
首先会判断本地是否有该域名的 IP 地址的缓存,如果有则使用,如果没有则向本地 DNS 服务器发起请求。本地 DNS 服务器也会先检查是否存在缓存,如果没有就会先向根域名服务器发起请求,获得负责的顶级域名服务器的地址后,再向顶级域名服务器请求,然后获得负责的权威域名服务器的地址后,再向权威域名服务器发起请求,最终获得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给请求的用户。用户向本地 DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。
5.5.4. TCP 三次握手
三次握手的目的是为了确认双方的接收能力和发送能力是否正常。
5.5.5. 发送请求
页面将请求内容以请求报文形式发送给服务器,由服务器分析处理。
5.5.6. 返回数据
当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接收到响应后,开始对 html 文件进行解析,开始页面的渲染过程。
5.5.7. 页面渲染
浏览器首先会根据 html 文件构建 DOM 树,根据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树。渲染树构建好后,会根据渲染树来进行布局。布局完成后,最后使用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示出来了。
5.5.8. TCP 四次挥手
四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代表不能再向对方发送数据,连接处于的是半释放的状态。
5.6 Web Socket
-
理解
- 是H5的新特性, H5提供了构造函数WebSocket
- 全双工通信
- 基于TCP层进行通信的,只需要一次握手就可以,一旦握手成功会保持长链接
-
应用:
- 实时通信, 如:微信, 客服聊天
-
实际开发中需要考虑的场景:
-
链接断开
- 特点: close事件触发了
- 处理方案:
自动重连机制
- 次数不能是只有一次(没有链接成功的情况),而且也不能是无限次链接(没有链接成功的情况),通常来说我们会设置自动重连的次数(比如:10次),这样做的目的是尽可能保证通信能够恢复正常,同时能够减轻服务器的压力
-
长时间没有收到服务器的消息
- 特点:
- 通道没有中断的情况
- 长时间没有收到服务器的消息
- 处理方案:心跳检测机制
- 利用定时器定时给服务器发送心跳检测包,时间间隔通常是10s-60s之间,这个时间不宜过短,因为过短导致对服务器的压力过大,也不宜过长,过长会失去检测的意义
- 特点:
-
通道正常的情况下,检测心跳,但是没有收到心跳的响应
-
处理方案:
-
自动断开连接
socket.close();
-
重新握手
new WebSocket('ws://localhost:8080')
-
-
-
5.7 ajax请求及跨域
-
什么是跨域?
-
浏览器针对于ajax请求的安全策略,叫同源策略,所谓的同源策略指的是协议,域名,端口号三者必须完全一样,否则就是不同源, 不同源就是跨域
-
解决跨域的方案?
-
proxy代理
-
分类:
1. 正向代理: webpack-dev-server 1. 理解: 代理服务器在客户端,服务器端不知道由谁发出来的请求 2. 开发环境 2. 反向代理: nginx 1. 理解: 代理服务器在服务器端nginx, 不知道请求由哪台服务器发出的 2. 线上环境
-
理解:
1. 正常由浏览器发请求,给指定的服务器,如果不同源有跨域问题,导致请求的数据浏览器端不能直接使用 2. 代理就是将原本由浏览器发起的请求交给代理服务器发,而服务器和服务器之间没有跨域问题
- 配置: 项目的服务器: http://www.xxx.com 请求资源的服务器: http://www.atguigu.com/g/getUserInfo 请求资源的服务器2: http://www.xxx.com/a/getOthenInfo - 请求地址设置: /api/getUserInfo - 实际发出去的请求: 目标服务器地址 + /getUserInfo /api: { target: 目标服务器地址(http://www.atguigu.com), pathRewrite: { ^/api: '' } }
-
-
cors
- 实现原理:
- 服务器端设置响应头: Access-Control-Allow-Origin: 指定的域 || *
- 实现原理:
-
JSONP(JSON with padding) padding垫子 垫片
-
实现原理:
- 利用script标签的src属性不受跨域的限制
- 本质:发送并不是ajax请求
-
问题:只能解决get请求的跨域
function cb(data){ console.log('data就是服务器端返回的数据', data); } // 客户端: <script src="http://www.atguigu.com/g/getUserInfo?callback=cb"></script> // 服务器端: res.send(cb + '(' + data + ')')
相关面试题:
-
手写原生ajax发请求
-
ajax请求浏览器鉴定是否有必要发送跨域请求的时机?
-
简单的请求:直接发送,根据服务器端返回的结果来鉴定
- get,post请求
-
复杂的请求: 先**
发送预检请求
**,询问服务器端是否设置cors- put,delete请求
- 自定义请求头
-
- 预检请求的目的:
- 根据服务器返回的预解结果判断本次正式请求是否有必要发送
-
预检请求特点:
-
浏览器自动发送
- 普通的http请求
-
请求方法:OPTIONS
-
-
-
-
-
六、工程化
1、babel
1、概念:
Javascript编辑器,可以将ES6+语法编译成ES5语法
2、主要能做的事情:
1、ES6转ES5
2、React中JSX语法转js
3、通过Polyfill方式在目标环境中添加缺失的功能
1、ES6、ES7的部分新语法,有的浏览器不支持
2、Polyfill进行补丁操作,不支持的通过语法包装可以支持
3、可以使我们使用最新的API而不会报错
4、如:core-js
2、Webpack
1、概念
webpack是一个静态资源模块的打包工具,在webpack的世界里认为所有的文件都是模块, webpack本身只能识别js, json文件,如果要打包其他的资源模块需要对应的loader进行加载识别,如果要扩展打包的功能,比如合并,压缩文件内容需要对应的plugins
2、url-loader
首选会将图片转换成base64的编码,并且打包到js文件中, 这样做的好处是减少图片请求的个数,减轻服务器的压力!但是不是所有的图片都会转成base64,对于图片转换要求是最大体积8kb,大于8kb的图片就不转base64,如果大于8kb的图片也转成base64的话,会导致加载图片的时候过慢,甚至导致图片失真, 如果图片较大,图片就会放在数据库中,通过连接访问而不是扔在本地。
3、项目优化
1、项目上线以后发现运行过慢,怎么处理???
1、体积方面下手
1、先检查项目中是否有映射文件, 如果有就关闭映射,重新打包 2、检查是否有未压缩的文件,如果有未压缩的文件,就进行压缩再上线 3、检查项目中的静态资源中是否体积较大的内容(图片,音视频,css文件),如果有将这些静态资源存放到 线上环境,通过链接访问即可 4、如果以上的操作都做到极致了,整体还运行慢,合并代码,减少冗余
2、如果发现项目中有部分页面运行慢,怎么处理???
1、并发请求过多
问题:渲染慢,甚至导致页面卡顿 优化:
1、合并请求(将功能作用类似的数据整合到一个接口中)
2、延迟发送(请求的优先级,按需发请求)
2、操作DOM(增删改)过于频繁
问题:操作DOM会导致页面重绘,重排 优化:减少DOM的操作 扩展:重绘,重排 1、重绘就是页面重新渲染, 重绘不一定有重排 1、修改颜色会导致重绘 2、重排就是页面结构重新排列, 重排必然伴随着重绘 1、实际开发中尽可能减少重排的次数 2、修改元素的大小,对DOM节点的增删改的任意操作
3、页面中运算量大的代码端在阻塞渲染
问题: 阻塞页面渲染及后续代码的执行,甚至导致页面白屏 优化:web Worker 将js主线程的同步耗时代码放入worker线程中转换成异步任务
4、webpack优化!!!
1、开发优化:利好于开发人员
1、source-map资源映射,能够精准提示错误在原文件的位置
2、hotmodulereplacement热模替换, 热加载,能够让再次编译的速度加快
3、oneof较少loader对同一个文件进行二次或者多次加载,较少加载的时间
2、生产优化:利好于用户
1、压缩代码,如:html,css,js,图片
2、代码切割,需要配合import()函数,最终按需加载
如果不进行代码切割,所有的代码在一个js文件中,会导致首屏渲染变慢
3、预加载preload
1、提前加载即将要使用的代码
2、扩展: 图片预加载,小程序分包的预下载
5、webpack VS vite
1、webpack底层基于js编写的,运行速度毫秒级别
2、vite底层基于go语言,运行速度纳秒级别
3、webpack启动方式类似于编译性语言,先编译,后渲染
4、vite先启动,然后按需编译,渲染,vite首屏渲染的性能不及webpack
5、现在来看vite的生态链不及webpack
3、Git
- 分支介绍
- 公司团队
- 主分支: 用于分布
- 测试分支: 用于测试
- 临时调试分支:
- 用于调试
- 创建的临时调试分支的命名通常和这一次调试的任务相关
- 开发主分支
- 所有协同开发人员最终开发后合并的分支
- 个人
- 开发分支
- 测试分支
- 调试分支
- 备份分支
- 提交规范
- 通过关键字以及文字的描述精准的表达出这一次提交的目的
- 公司团队
- 扩展:
- 入职后,手里应该有哪些文件??
- git账号,仓库地址
- 接口文档
- 开发文档:
- 命名规范
- 所有的命名要见名知意
- css和js约束
- 小驼峰,常量(大写)
- css多个单词连接通常用_ -
- git使用规范
- 命名规范
- 项目维护文档(开发需求文档)
- 标注项目发展迭代版本
- 当前开发的需求(prd)
- 入职后,手里应该有哪些文件??
七. Vue技术栈
7.1 生命周期
-
特殊的
- Vue2中可以使用vm.$destroy()主动销毁对应的组件
- Vue3中取消了该方法,Vue3中没有能够直接销毁组件的API,但是我们可以利用v-if控制组件的销毁
- setup函数被设计的初衷用来代替beforeCreate和created,更重要的是在setup函数内部可以进行composition API的形式开发
-
错误捕获errorCaptured
-
errorCaptured本身只能够捕获后代组件的错误,在捕获的钩子函数中可以获取到错误信息,错误的来源地,发生错误的组件实例
- 如果没有return false错误会自动向上传递直到全局
- 如果return false会阻止错误继续向上传递
-
全局捕获
main.js Vue.config.errorHandler = function(){}
- 使用场景:
- 全局捕获用于捕获线上环境中在用户的终端发生的不可预期的错误
- 一旦捕获到错误就上传错误,进行错误日志统计,将错误信息发送给服务器,进行汇总统计然后分析错误,解决错误
- 使用场景:
-
-
总结:
- 必须要说11个
- 常规的8个
- keep-alive缓存组件2个
- 错误捕获1个
- 必须要说11个
7.2 埋点
- 意义:
- 收集用户的行为
- 收集用户的轨迹
- 收集数据的访问量
- 实现:
- 利用绑定事件埋点
- 利用定时器埋点
- 用户访问页面的时间
- 用户停留某一个板块的时间
- 好处:
- 可以根据埋点获取的信息分析总结得出网站上哪些板块用户比较喜欢,哪些板块用户无人问津,进行及时的调整
- 可以得出当前用户的喜好进行针对性的内容推荐
7.3 组件通信方案
-
props:
- 父子双向通信
- 如果要实现子传父的话,需要配合自定义事件
-
v-model
-
作用在表单项身上:
-
:value = ‘num’
-
:Input=‘num = $event.target.value’
<input type="text" v-model="num"> <input type="text" :value="num" @input="handleInput">
-
-
作用在自定义组件身上:
-
Vue2
- :value = ‘num’
- :Input=‘num = $event’
-
Vue3
-
:modelValue = ‘num’
-
update:modelValue = ‘num = $event’
<p>vue3中v-model传入的自定义属性: {{modelValue}}</p> <button @click="$emit('update:modelValue', 123123)">点击我触发vmodel绑定的自定义事件‘update:modelValue’</button>
-
-
-
插槽
-
普通的插槽
-
具名插槽
<slot name="main"></slot> <template #main> <p>我是内容区</p> </template>
-
作用域插槽
理解:
每个组件实例身上的属性及数据都是当前组件实例私有的数据,如果想要访问别的组件身上的数据等同于在跨作用域访问数据,
利用作用域插槽实现了子作用域的数据导给了父组件实例作用域中供父组件使用
<slot name="header" :num='num'></slot> <template #header='headerProps'> <p>我是头部</p> <p>子组件通过slot传递给父组件的数据: {{headerProps.num}}</p> </template>
-
-
provide,inject和props的区别
- props可以实现双向通信
- provide,inject只能实现由祖先组件向后代组件传递数据
- props要实现给后代组件传递数据,需要逐层传递
- provide祖先组件和后代组件直接对接
- 优缺点:
- provide,inject在开发中使用更方便,因为不需要逐层写代码
- props在数据传递及使用的过程中性能相对更高一些,因为inject在需要数据的时候,需要逐层向外去查找对应的provide组件
-
Vue事件总线对象
- 核心思想:自定义事件
- 实现:
- 定义事件总线对象:Vue.prototype.$bus = new Vue();
- 定义自定义事件: this.on(‘eventName’, callback)
- 触发自定义事件:this.emit(‘eventName’, data)
- vm和vc的关系
- vm是Vue的实例
- vc是组件的实例
- vc ----> VueComponent.prototype ---> Vue.prototype ---> Object.prototype
- 可以人为vm是vc的原型
注意:
- 大型的项目中最好不要使用Vue的事件总线对象
- 会导致Vue核心函数原型对象上定义的内容越来越多,响应就会变慢
- 如果是协同开发的话,可能会导致自定义事件名重名
- 大型的项目中最好不要使用Vue的事件总线对象
-
-
设计思想: 为什么mutation需要同步修改state的数据
- 因为异步无法把控最终的执行时机, 容易导致操作state的数据发生混乱
- 会导致Vue开发工具调试Vuex的数据的时候,会失效,无法查看mutation的payload数据
-
pinia为什么舍弃了mutation
- Vuex当初在设计的时候,没有完整的异步解决方案,异步不可控
- 现在ES6+退出了完善的异步解决方案,promise + async + await
-
pinia的优点
- 更契合Vue3设计及ts的使用
- pinia自带模块化,Vuex需要单独使用module进行配置