1.websocket原理
websocket的原理
websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信
在websocket出现之前,web交互一般是基于http协议的短连接或者长连接
websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"
2. websocket与http的关系
2.1 相同点:
都是基于tcp的,都是可靠性传输协议 都是应用层协议
2.2 不同点:
WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息 HTTP是单向的 WebSocket是需要浏览器和服务器握手进行建立连接的 而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接
2.3 联系:
WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的
2.4 总结(总体过程)
首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等; 然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据; 最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。
3.组件封装注意事项
比如做后台管理中,很多模块经常会复用,比如侧边导航组件、项目中常用的 echarts图表的封装(比如折线图、柱状图等)
封装组件需要考虑复用性:
预留插槽slot, 多次调用如果 子组件视图结构不一样那么就要 在 子组件template预留好 插槽(单个插槽、具名插槽,作用域插槽)
考虑到数据传递,定义props 组件接收父组件传递的数据,同时需要注意单向数据流,props不能直接修改,$emit自定义事件,父组件修改
业务逻辑不要在子组件中处理,子组件在不同父组件中调用时,业务处理代码不同,切记不要直接在子组件中处理业务,应该子组件 $emit自定义事件,将数据传递给父组件,父组件处理业务。
4.cdn
1.[CDN]定义
Content Delivery Network,即内容分发网络
各地部署多套静态存储服务,本质上是空间换时间
自动选择最近的节点内容,不存在再请求原始服务器
适合存储更新很少的静态内容,文件更新慢
举个栗子:
你,要喝水,每次都要去水房里接水喝,你觉得很麻烦,所以你就选择了水壶去装水,这样就不用每一次都要去水房接水,就可以选择最近的水壶进行接水。
3.工作原理?
传统访问:用户在浏览器输入域名发送请求-解析域名获取服务器IP地址-根据IP地址找到对应的服务器-服务器响应并返回数据
使用CDN访问:用户发送请求-智能DNS的解析(根据IP判断地理位置、接入网类型、选择路由最短和负载最轻的服务器)-取得缓存服务器IP-把内容返回给用户(如果缓存中有)-向源站发起请求-将结果返回给用户-将结果存入缓存服务器
5.http相关
6.webpack 相关
7.addEventListenter 第三参数
第三个参数可以设置啥?
从官方文档看,addEventListener 方法使用如下:
**
target.addEventListener(type, listener, options); target.addEventListener(type, listener, useCapture);还有一个兼容性不好的使用方法就不提了,也不太常用。
主要关注下第三个参数,可以设置为bool类型(useCapture)或者object类型(options)。
-
options包括三个布尔值选项:
- capture: 默认值为false(即 使用事件冒泡). 是否使用事件捕获;
- once: 默认值为false. 是否只调用一次,if true,会在调用后自动销毁listener
- passive: if true, 意味着listener永远不远调用preventDefault方法,如果又确实调用了的话,浏览器只会console一个warning,而不会真的去执行preventDefault方法。根据规范,默认值为false. 但是chrome, Firefox等浏览器为了保证滚动时的性能,在document-level nodes(Window, Document, Document.body)上针对touchstart和touchmove事件将passive默认值改为了true, 保证了在页面滚动时不会因为自定义事件中调用了preventDefault而阻塞页面渲染。
-
useCapture: 默认值为false(即 使用事件冒泡)
前端实现单点登录
我们登录成功后,后台会返回一个token给我们,我们需要把它存到Cookie里面,方便后面使用。然后请求接口的时候,会在接口带一个token过去给服务器,做为验证当前系统是否已经登录,或者登录过期的依据。
vue请求页面接口的时候,做一个请求拦截,如果在Cookie里面有token,则直接请求数据,正常进入页面;如果没有token,说明项目没有登录,则控制路由跳转去登录页面。
所以只要在不同级别的域名下,获取保存在Cookie里面的token就可以了,只要在设置cookie的时候把domain设置为主域名,就可以在其他二级、三级域名下找到token,这就是主要流程了。
解决移动端1px像素问题
那么为什么会产生这个问题呢?主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为100%的情况下,设备像素和CSS像素的比值。
window.devicePixelRatio=物理像素 /CSS像素复制代码
目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏来说,设备的物理像素要实现1像素,而DPR=2,所以css 像素只能是 0.5。一般设计稿是按照750来设计的,它上面的1px是以750来参照的,而我们写css样式是以设备375为参照的,所以我们应该写的0.5px就好了啊! 试过了就知道,iOS 8+系统支持,安卓系统不支持。 解决方案一:使用伪类缩放 使用伪类缩放需要主要的是:
设置全边框的时候,box-sizing要设置为border-box,否则伪元素上下左右各会多1px 需要将父元素设置为relative 注意 transform 的起点,上边距要用左上角,下边距用左下角
/* 下边框 */
.one-px-border2:after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
border-bottom: 1px solid red;
transform: scaleY(.5);
transform-origin: left bottom;
}
0.5px上边框
/* 上边框 */
.one-px-border1:before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
border-top: 1px solid red;
transform: scaleY(.5);
transform-origin: left top;
}
0.5px全边框
.one-px-border3:after {
content: "";
position: absolute;
bottom: 0;
left: 0;
border: 1px solid red;
transform-origin: left bottom;
width: 200%;
height: 200%;
transform: scale(.5);
/* box-sizing要设置为border-box,否则伪元素上下左右各会多1px */
box-sizing: border-box;
/* 设置圆角border等 */
border-radius: 10px;
}
解决方案三:使用图片 切图,使用图片结合border-image进行css样式设置
.border-bottom-1px {
border-width: 0 0 1px 0;
border-image: url(linenew.png) 0 0 2 0 stretch;
}
缺点:不够灵活,换颜色需要换图片
解决方案四:使用box-shadow模拟
.box-shadow-1px {
width: 100px;
height: 100px;
box-shadow: inset 0px -1px 1px -1px red;
}
useEffect 模拟生命周期
这里就得用到一个hooks来模拟钩子函数,这个hooks就是useEffect,这个useEffect可以模拟三个钩子函数,分别是componentDidMount,componentWillUnmount和componentDidUpdate。 先贴代码为敬
React.useEffect(() => {
console.log("这是模拟componentDidMount钩子函数")
return () => {//return出来的函数本来就是更新前,销毁前执行的函数,现在不监听任何状态,所以只在销毁前执行
console.log("这是模拟componentWillUnmount钩子函数")
}
},[])//第二个参数一定是一个空数组,因为如果不写会默认监听所有状态,这样写就不会监听任何状态,只在初始化时执行一次。
通过上面这种写法就可以同时模拟componentDidMount和componentWillUnmount钩子函数。接下来说componentDidUpdate
//在此之前需要使用useRef这个hooks
const flag = React.useRef(null)
React.useEffect(() => {
if(!flag.current){
flag.current = true
} else {
console.log("更新了")
}
})
在这里我们没有传第二个参数,也就是说他默认监听所有状态,只要有状态发生改变,他就会执行,但又有一个问题,他初始化的时候也会执行,为了解决这样的问题我们采用了useRef作为标记,初始化的时候flag.current肯定为false,所以我们将它设置成true,所以他就不会初始化执行了。
实现一个类型判断函数
function getType(obj) {
if (obj === null) return String(obj);
return typeof obj === 'object'
? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase()
: typeof obj;
}
react 组件复用
-
组件复用的方式: render props模式 高阶组件(HOC) 注意:以上两种方式不是新的API,而是演化而成的一种固定模式(写法)
-
render props模式 思路:将要复用的state和操作state的方法封装在一个组件里面(在组件中提供复用的状态逻辑代码,即状态和操作状态的方法) 这时,我们就要思考两个问题:(1)状态是组件内部私有的,那么如何在复用组件的时候拿到组件内部的state呢?----> 可以在使用组件的时候,添加一个值为函数的props,那么就可以通过函数参数来获取组件内部的state。(2)复用组件时,需要渲染的UI结构会不一样,那么怎么在复用组件时实现渲染任意的UI呢?----->将函数的返回值作为要渲染的UI 注意:复用的组件并没有渲染任何的UI结构,而且通过函数的返回值来渲染的。 以下通过一个简单的例子来演示使用render props模式实现的组件复用(一个效果是随着鼠标移动,获取鼠标的位置,另一个效果是图片随着鼠标移动而移动,这两个效果的实现都要获取x和y坐标,所以可以考虑用组件的复用来实现) Mouse组件(要复用的组件)
import React from "react";
class Mouse extends React.Component {
//复用的state
state = {
x: 0,
y: 0
}
//操作state的方法
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
//监听鼠标移动事件
componentDidMount() {
window.addEventListener("mousemove", this.handleMouseMove)
}
render(){
return this.props.render(this.state) //通过函数参数暴露组件内部的状态
}
}
export default Mouse;
Mouse_Tree组件(复用Mouse组件的组件)
import React from "react";
import Mouse from "./Mouse.js";
import img from "./images/tree.PNG"
class Mouse_Cat extends React.Component {
render(){
return(
<div>
<Mouse render={ mouse => {
return(
<p>X坐标为:{mouse.x} Y坐标为:{mouse.y}</p>
)
}}/>
<Mouse render={ mouse => {
return(
<img src={img} alt="树" style={{
position: 'absolute',
top: mouse.y,
left: mouse.x
}}/>
)
}}/>
</div>
)
}
}
export default Mouse_Cat;
以上则是组件复用的一个简单应用
另外注意,这个虽然叫做render props模式,但是所添加的props不一定需要命名为render,可以是任意名称的props。在我们的实际应用中,更推荐使用children代替render属性,可以把以上代码做如下修改: //Mouse_Tree组件内部
{/* <Mouse render={ mouse => {
return(
<p>X坐标为:{mouse.x} Y坐标为:{mouse.y}</p>
)
}}/> */}
<Mouse>
{
mouse => {
return(
<p>X坐标为:{mouse.x} Y坐标为:{mouse.y}</p>
)
}
}
</Mouse>
//Mouse组件内部
render(){
// return this.props.render(this.state) //通过函数参数暴露组件内部的状态
return this.props.children(this.state);
}
setState到底是异步还是同步?
先给出答案: 有时表现出异步,有时表现出同步
-
setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout中都是同步的。 -
setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数setState(partialState, callback)中的callback拿到更新后的结果。 -
setState的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
为什么列表循环渲染的key最好不要用index
用 index 作为 key 时,在对数据进行,逆序添加,逆序删除等破坏顺序的操作时,会产生没必要的真实 DOM更新,从而导致效率低
前端框架原理
前端框架
卡颂大佬在《React 设计原理》中,提出了一个观点:现代前端框架的实现原理都可以用以下公式进行概括:
UI = f(state)
其中:
- state —— 当前的视图的状态
- f —— 框架内部的运行机制
- UI —— 宿主环境的视图
这个公式说明,框架内部运行机制根据当前状态渲染视图,这也能看出现代框架的一个重要特性:数据驱动
数据驱动部分,按 state 变化后,引起框架的 UI 变更的抽象层级,对框架进行了分类,分为应用级、组件级、元素级框架。