暂时未有相关云产品技术能力~
1. websocket 是什么?WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。2. 作用场景即时通讯(各种聊天软件)多玩家在线游戏(数据需要同步刷新)协同编辑/编程股票基金报价体育实况、新闻更新…通常来说,对数据敏感度高、需要及时更新数据的产品基本都会用到 websocket3. 传统http实现推送技术的弊端传统的技术一般使用 http + 定时器的方式(轮询)去实现很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即:浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源4. WebSockt 常用API实例化websocket的对象 const ws = new WebSocket('ws://127.0.0.1:8080') 监听连接状态ws.onopen = function () { console.log('ws连接状态:' + ws.readyState); // 成功则发送一个数据 ws.send('111'); }接收消息ws.onmessage = function(data){ // data.data 就是接收到的数据 }发送消息ws.send(data) // data必须是字符串监听连接关闭事件ws.onclose = function () { console.log('ws连接状态:' + ws.readyState); }监听错误ws.onerror = function (error) { console.log(error); }5. WebSocket实现聊天室1. 项目结构2.app.js 代码let WebSocketServer = require('ws').Server; wss = new WebSocketServer({ip: '0.0.0.0', port: 8080 }); let wsolist = [] console.log('启动服务器:ws://127.0.0.1:8080'); wss.on('connection', function (ws, req) { wsolist.push(ws) console.log('和客服端' + req.connection.remoteAddress + '建立了一个连接,是连接的第'+ (wsolist.length +1) +'个用户'); ws.on('message', function (message) { console.log(message.toString()); wsolist.forEach((wso) => { wso.send(message.toString()) }) }); ws.on('close', function close() { wsolist = wsolist.filter(obj => obj !== ws) }); }); 3. index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .container{ margin: 10px 0; border: 1px solid pink; } </style> </head> <body> <h1>websocket聊天室</h1> <div> <input type="text" placeholder="请输入消息内容" id="msg"> <button id="sendBtn">发送</button> <button id="closeBtn">关闭ws聊天室</button> </div> <div class="container"> </div> </body> </html> 4. js 代码// 1. 获取元素 let msg = document.querySelector('#msg') let send_btn = document.querySelector('#sendBtn') let close_btn = document.querySelector('#closeBtn') let container = document.querySelector('.container') // 2. 建立ws连接 let ws = new WebSocket('ws://192.168.29.54:8080') // 3. 实现消息的发送 send_btn.addEventListener('click', function(){ ws.send(msg.value) msg.value = '' }) // 4. 实现消息的接收 ws.onmessage = function(data){ console.log(data.data); container.innerHTML += ` <div>${data.data}</div>` } // 5. 关闭ws连接 close_btn.addEventListener('click', function(){ ws.close() })
这玩意很简单,记录一下吧,给入门的小白用下1. token是什么?用来做什么token通常译为令牌,暗号。举个例子:我是古时候皇城中大内的高手(看门的大爷),进皇宫需要皇帝发的特制令牌,没有指定的令牌是不能进的,我会把你拦住,说不定还会把你胖揍一顿。皇宫在这里就相当于我们项目,为了项目的安全性,设置了这个token,进项目的人都需要有这个玩意,证明你有这个权限。token是怎么生成的? 通过哈希算法(不常用)、uuid(雪花算法,可保持全球唯一性),jwt工具等生成随机字符串(也可能会拼接上用户的一些信息)为什么token要有有效期?而且设置得这么短? 为了提高token的安全性,不能永久有效,降低被别人劫持的风险有时候登录的时候为什么会有多个token?1. 常规 token:使用频繁,有效期比较短2. refresh_token:当token过期的时候,用来得到新的token的,使用不频繁,有效期比较长(一般为一个月)我们怎么知道token过期了? 通过调接口时返回的状态码,如果状态码是401(一般情况下),就说明没有携带token,或者token过期了2. token存储在哪?过期了怎么办?token由服务端生成,通过接口传给前端。一般在登录接口会给token值。存储看具体的项目场景,一般存储在本地缓存里,有两种方案供君选择:sessionStorage(会话级别,网页一关就歇逼了)localStorage(永久的,需要手动去清除)token过期处理重新登录 => 清空token,跳转登录页refresh_token => 得到一个全新的token3. 请求拦截与响应拦截执行时机(面试重点)这玩意面试问的多,看图很清晰:请求拦截器执行时机:axios调用之后,浏览器真正发起请求之前响应拦截器执行时机:浏览器接受到响应数据之后,axios拿到数据之前4. 解决token过期方案一: 重新登录知道出现了401的错误: 使用响应拦截器进行页面的跳转此种方案用户体验不是很好(目前大部分企业及项目都这么干)import store from '@/store' // 响应拦截器 request.interceptors.response.use( function (response) { // 响应成功(响应状态码是 2xx)时执行第一个回调函数 console.log('响应成功....') return response }, function (error) { // 网络异常或响应失败(响应状态码是非2开头)时执行第二个回调函数 console.log('响应失败....') // 1. 判断响应的状态码是不是401,如果不是401就不用做任何处理 if (error.response && error.response.status === 401) { // 2. 如果是401,就先清空token, 并跳转到登录页 store.commit('setUser', null) router.push('/login') } return Promise.reject(error) } ) 5. 方案二:使用 refresh_token 方案判断有没有refresh_token,如果没有跳转登录页如果有refresh_token,调用刷新token的接口,拿到新的token更新vuex和loaclStoreage存储的token为原来调用接口方,重新去调用接口如果利用refresh_token,刷新token的接口失败,则还是跳转到登录页附上了详细的注释,讲道理大佬们应该看得懂import axios from 'axios' import store from '@/store' import router from '@/router' const baseURL = 'http://xxx.xxx.net/' const request = axios.create({ baseURL // 接口的基准路径 }) // 请求拦截器 // Add a request interceptor request.interceptors.request.use(function (config) { // 请求发起会经过这里 // config:本次请求的请求配置对象 const { user } = store.state if (user && user.token) { config.headers.Authorization = `Bearer ${user.token}` } // 注意:这里务必要返回 config 配置对象,否则请求就停在这里出不去了 return config }, function (error) { // 如果请求出错了(还没有发出去)会进入这里 return Promise.reject(error) }) request.interceptors.response.use( function (response) { // 响应成功(响应状态码是 2xx)时执行第一个回调函数 return response }, async function (error) { // 网络异常或响应失败(响应状态码是非2开头)时执行第二个回调函数 console.log('响应失败时执行的代码....') if (error.response && error.response.status === 401) { const user = store.state.user if (user && user.refresh_token) { // 1. 判断有没有refresh_token,如果没有跳转登录页 // 2. 如果有refresh_token,调用刷新token的接口,拿到新的token try { var res = await axios({ baseURL: baseURL, method: 'PUT', url: '/xxx/xxx', headers: { Authorization: `Bearer ${user.refresh_token}` } }) } catch { // 5. 如果refresh_token过期,进行清空token并跳转到登录页的处理 store.commit('setUser', null) router.push('/login') } // 3. 更新vuex和loaclStoreage存储的token store.commit('setUser', { refresh_token: user.refresh_token, token: res.data.data.token }) // 4. 为原来调用接口方,重新去调用接口 console.log(error.config) return request(error.config) } else { // 1. 判断有没有refresh_token,如果没有跳转登录页 store.commit('setUser', null) router.push('/login') } } // 3. 如果利用refresh_token,刷新token的接口失败,则还是跳转到登录页 // eslint-disable-next-line prefer-promise-reject-errors return Promise.reject(error) } ) export default request
1.template 部分<template> <div class="editPage__video"> <div class="title">视频设置</div> <div class="img__con"> <el-upload class="avatar-uploader" :action="uploadImgUrl" :data="uploadImgData" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload" :on-progress="uploadVideoProcess" > <video width="100%" v-if="imageUrl" controls="controls" :key="menuKey"> <source :src="rightData.props.src" type="video/mp4" /> </video> <!-- <img v-if="imageUrl" :src="imageUrl" class="avatar" /> --> <i v-else class="el-icon-plus avatar-uploader-icon"></i> <el-progress v-if="videoFlag == true" type="line" :percentage="videoUploadPercent" style="margin-top: 30px" ></el-progress> </el-upload> <p> 说明: 视频格式为mp4格式,每个视频大小不超过300m </p> </div> </div> </template> 考虑到有些小伙伴不一定需要进度条,所以顺便说下怎么把它干掉,代码如下:对应的变量和方案也干掉就是了:on-progress="uploadVideoProcess" <el-progress v-if="videoFlag == true" type="line" :percentage="videoUploadPercent" style="margin-top: 30px" ></el-progress> 2. script 部分在我的实际业务中,this.rightData是父级组件传过来的值,大家用的时候记得去掉,替换成自己的就成data() { return { imageUrl: this.rightData.imageUrl, // 视频上传 uploadImgUrl: `${process.env.VUE_APP_BASE_API}/common-server/aliFile/upLoadVideo`, uploadImgData: { busiName: 32 }, // 应付多个组件的情况 记录当前组件的key值 componentKey: null, menuKey: 1, // 用来强制刷新 videoFlag: false, // 进度条相关的 videoUploadPercent: 0, // 进度条相关的 }; }, methods: { // 上传成功的函数 handleAvatarSuccess(res, file) { // 进度条恢复到初始状态 this.videoFlag = false; this.videoUploadPercent = 0; ++this.menuKey; // console.log(res) this.imageUrl = res.data.url; // 这里是向父级传值,不需要就去掉 this.$emit("childRightFn", { ...this.rightData, ...{ imageUrl: this.imageUrl }, ...{ props: { src: this.imageUrl } }, }); }, beforeAvatarUpload(file) { // file.type === "image/jpg" || // file.type === "image/png"; const isMp4 = file.type === "video/mp4"; // 限制文件最大不能超过 300M const isLt2M = file.size / 1024 / 1024 < 300; if (!isMp4) { this.$message.error("视频只能是mp4格式!"); } if (!isLt2M) { this.$message.error("上传头像图片大小不能超过 300MB!"); } return isMp4 && isLt2M; }, //进度条 uploadVideoProcess(event, file, fileList) { this.videoFlag = true; this.videoUploadPercent = parseInt(file.percentage); }, } 有个东西重点说一下:menuKey++this.menuKey;如果video的src发生变化,预览是不会变化的所以给此标签加了唯一的key值,每次上传成功key都会变化,让其强制刷新3. scss 部分这里也请大家按需取用,不需要删掉就是,不要原封不动的搬<style lang="scss" scoped> .editPage__video { .avatar-uploader .el-upload { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; } .avatar-uploader .el-upload:hover { border-color: #409eff; } .avatar-uploader-icon { font-size: 16px; color: #8c939d; width: 350px; height: 30px; line-height: 30px; text-align: center; } .avatar { width: 350px; height: auto; display: block; } .title { font-size: 18px; margin-bottom: 20px; } .img__con { .el-button { width: 100%; margin: 10px 0 20px 0; } } } </style>
前言最近需要用到element ui的这个插件做地区的四级联动,但是碰了一些问题:官网的说明太泛泛然,不易看懂网上的教程乱七八糟,代码一堆一堆的看这篇就对了!!!效果图 => 点击确认后的值 1. template <el-cascader size="mini" :props="props" @change="handleChange" v-model="value" style="width: 300px" ></el-cascader>// props => 控制动态加载的配置// @change => 监听变化// value => 值2. methods() // 获取省市区街道 provinceFn(id) { let data = { up_id: id, }; // 此处是一个promise 直接返回 return postRequest(url.getlowerlevelarea, data); }, // 监听数据变化 handleChange(value) { console.log(value); }3. data()配置 data() { return { value: [], // 多级联动的值 => 会是一个数组 props: { lazy: true, lazyLoad: (node, resolve) => { // node 节点数据 node.value => 当前节点的值 // level: 层级 => 1,2,3,4 const { level } = node; // 动态节点 const nodes = []; // 为1代表第一次请求 let type = level == 0 ? "1" : node.value; this.provinceFn(type) .then((res) => { if (res.code == -1) { this.msgFn("error", res.message); return; } // 节点数组 res.data.map((item) => { // obj里的键值是官方要求的 let obj = { value: item.city_id, label: item.city_name, leaf: node.level >= 3, }; nodes.push(obj); }); // resolve 节点返回 resolve(nodes); }) .catch((error) => { console.log(error); }); }, }, }; }FAQ注释其实已经非常详细了,整体思路是这样的:根据官方文档,首先得知道 lazyLoad方法中的参数node, resolve分别是什么配置自己的接口请求,我的是地区数据(省市区四级联动)需要注意的是 props需要在data()里赋值,所以数据请求也要放进去获取到数据后,需要按照它的规范赋值 => value:值,label:文字,leaf:层级如果是三级,那么leaf值是=> 0, 1, 2,以此类推。我的是四级,所以是 => node.level >= 3尽量不要用hover(效果会一闪一闪的,用户体验不好,click最佳)
那么接下来分析一波(本文讲的非常详细,争取大家都能看懂,对大家有所帮助):1. Vue3.0里有哪些是值得我们重点关注的点?2. Vue3.0中,哪些是面试官喜欢问的高频率问题?1. Vue3.0 里为什么要用 Proxy API 替代 defineProperty API?—— 响应式优化(高频,重点!!!)这是在面试中问的最多的一个问题,无论是大厂还是中小型公司,都喜欢问,也是Vue更新的重点。a. defineProperty API 的局限性最大原因是它只能针对单例属性做监听。Vue2.x中的响应式实现正是基于defineProperty中的descriptor,对 data 中的属性做了遍历 + 递归,为每个属性设置了 getter、setter。这也就是为什么 Vue 只能对 data 中预定义过的属性做出响应的原因,在Vue中使用下标的方式直接修改属性的值或者添加一个预先不存在的对象属性是无法做到setter监听的,这是defineProperty的局限性。b. Proxy API的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作, 这就完全可以代理所有属性,将会带来很大的性能提升和更优的代码。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。c. 响应式是惰性的在 Vue.js 2.x 中,对于一个深层属性嵌套的对象,要劫持它内部深层次的变化,就需要递归遍历这个对象,执行 Object.defineProperty 把每一层对象数据都变成响应式的,这无疑会有很大的性能消耗。在 Vue.js 3.0 中,使用 Proxy API 并不能监听到对象内部深层次的属性变化,因此它的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部属性才会变成响应式,简单的可以说是按需实现响应式,减少性能消耗。基础用法: let datas = { num: 0 } let proxy = new Proxy(datas, { get(target, property) { return target[property] }, set(target, property, value) { target[property] += value } })2. Vue3.0 编译做了哪些优化?(底层,源码)a. 生成 Block treeVue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,单个组件内部 需要遍历该组件的整个 vnode 树。在2.0里,渲染效率的快慢与组件大小成正相关:组件越大,渲染效率越慢。并且,对于一些静态节点,又无数据更新,这些遍历都是性能浪费。Vue.js 3.0 做到了通过编译阶段对静态模板的分析,编译生成了 Block tree。Block tree 是一个将模版基于动态节点指令切割的嵌套区块,每个 区块内部的节点结构是固定的,每个区块只需要追踪自身包含的动态节点。所以,在3.0里,渲染效率不再与模板大小成正相关,而是与模板中动态节点的数量成正相关。b. slot 编译优化Vue.js 2.x 中,如果有一个组件传入了slot,那么每次父组件更新的时候,会强制使子组件update,造成性能的浪费。Vue.js 3.0 优化了slot的生成,使得非动态slot中属性的更新只会触发子组件的更新。动态slot指的是在slot上面使用v-if,v-for,动态slot名字等会导致slot产生运行时动态变化但是又无法被子组件track的操作。c. diff算法优化(此知识点进大厂可能会问到,由于篇幅较长,大家可以去官网看下)3. Vue3.0新特性 —— Composition API 与 React.js 中 Hooks的异同点(难点问题)a. React.js 中的 Hooks 基本使用React Hooks 允许你 "勾入" 诸如组件状态和副作用处理等 React 功能中。Hooks 只能用在函数组件中,并允许我们在不需要创建类的情况下将状态、副作用处理和更多东西带入组件中。React 核心团队奉上的采纳策略是不反对类组件,所以你可以升级 React 版本、在新组件中开始尝试 Hooks,并保持既有组件不做任何更改。案例:import React, { useState, useEffect } from "react"; const NoteForm = ({ onNoteSent }) => { const [currentNote, setCurrentNote] = useState(""); useEffect(() => { console.log(`Current note: ${currentNote}`); }); return ( <form onSubmit={e => { onNoteSent(currentNote); setCurrentNote(""); e.preventDefault(); }} > <label> <span>Note: </span> <input value={currentNote} onChange={e => { const val = e.target.value && e.target.value.toUpperCase()[0]; const validNotes = ["A", "B", "C", "D", "E", "F", "G"]; setCurrentNote(validNotes.includes(val) ? val : ""); }} /> </label> <button type="submit">Send</button> </form> ); };useState 和 useEffect 是 React Hooks 中的一些例子,使得函数组件中也能增加状态和运行副作用。我们也可以自定义一个Hooks,它打开了代码复用性和扩展性的新大门。b. Vue Composition API 基本使用Vue Composition API 围绕一个新的组件选项 setup 而创建。setup() 为 Vue 组件提供了状态、计算值、watcher 和生命周期钩子。并没有让原来的 API(Options-based API)消失。允许开发者 结合使用新旧两种 API(向下兼容)。<template> <form @submit="handleSubmit"> <label> <span>Note:</span> <input v-model="currentNote" @input="handleNoteInput"> </label> <button type="submit">Send</button> </form> </template> <script> import { ref, watch } from "vue"; export default { props: ["divRef"], setup(props, context) { const currentNote = ref(""); const handleNoteInput = e => { const val = e.target.value && e.target.value.toUpperCase()[0]; const validNotes = ["A", "B", "C", "D", "E", "F", "G"]; currentNote.value = validNotes.includes(val) ? val : ""; }; const handleSubmit = e => { context.emit("note-sent", currentNote.value); currentNote.value = ""; e.preventDefault(); }; return { currentNote, handleNoteInput, handleSubmit, }; } }; </script>c. 原理React hook 底层是基于链表实现,调用的条件是每次组件被render的时候都会顺序执行所有的hooks。vue hook 只会被注册调用一次,vue 能避开这些麻烦的问题,原因在于它对数据的响应是基于proxy的,对数据直接代理观察。(这种场景下,只要任何一个更改data的地方,相关的function或者template都会被重新计算,因此避开了react可能遇到的性能上的问题)。react 中,数据更改的时候,会导致重新render,重新render又会重新把hooks重新注册一次,所以react复杂程度会高一些。4. Vue3.0是如何变得更快的?(底层,源码)a. diff方法优化Vue2.x 中的虚拟dom是进行全量的对比。Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比带有patch flag的节点,并且可以通过flag 的信息得知当前节点要对比的具体内容化。b. hoistStatic 静态提升Vue2.x : 无论元素是否参与更新,每次都会重新创建。Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。c. cacheHandlers 事件侦听器缓存默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。说在后面其实很多小伙伴都存在这样一种情况:Vue2.x都用了一年多了,官方文档都还没有去详细过一遍,遇到问题就百度,所以Vue3.0一出就惊慌失措。所以我建议正确的学习姿势应该是:当我们去接触一门新技术的时候,首要任务就是看官方文档,先大致过一遍,知道有哪些东西。开发的时候遇到问题再去针对具体的知识点细看。遇到实在搞不定的,官方文档看不懂的,再行百度也不迟。
better-scroll是什么1.BetterScroll 是一款重点解决移动端各种滚动场景需求的开源插件,适用于滚动列表、选择器、轮播图、索引列表、开屏引导等应用场景。简而言之,移动端滚动神器。以下简称BS。2.基于原生js开发,不依赖任何插件,所以既可以原生 JavaScript 引用,也可以与目前流行的前端 MVVM 框架结合使用。3.目前最好用的移动端滚动插件,没有之一,1.8版本之后PC端也可以使用阅读本文你可以学到什么网上有大量的BS配合MVVM使用的案例,所以我这不多说,本系列文章讲的是BS如何配合原生JS使用,考虑到很多公司或企业并没有使用MVVM类框架。开始使用1.初始布局<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>Document</title> <style> </style> <style> body{ margin: 0; } body,html{ height: 100%; } ul,ol{ list-style: none; padding: 0; margin: 0; } .con{ width: 100vw; height: 100vh; /* 外框一定要设置高度 如果不设置 将没有效果 */ overflow: hidden; } /* 横向滑屏 */ ul{ /* width: 150vw; */ } ul>li{ width: 100%; height: 40px; line-height: 40px; text-indent: 20px; border-bottom: 1px solid #666666; color: #333333; } .demo{ width: 100%; height: 100px; background: hotpink; } </style> </head> <body> <div class="con"> <ul class="list"> </ul> <!-- 没效果或被盖住 不建议这么使用 --> <div class="demo"> </div> </div> </body> </html> 基础的列表布局完成,那么现在引入BS,此文用的是BS v1.12.1 (CDN)版本,大家使用的时候请务必注意版本号,版本低的可能有些功能不支持<script type="text/javascript" src="https://unpkg.com/better-scroll@1.12.1/dist/bscroll.min.js"></script> 模拟初始数据<script> function getData(){ var list = document.querySelector(".list"); var html = ""; for(var i = 0; i < 30; i++){ html += "<li>我是第"+(i+1)+"个li</li>" } list.innerHTML = html; } window.onload = () => { getData(); } </script> 现在我们刷新页面,能看到的是生成了一个ul列表,但是无法滑动,因为设置了overflow:hidden;初始化容器 //初始化con function con(){ var con = document.querySelector(".con"); var bscroll = new BScroll(con,{ //x轴偏移量 // startX: 150, //y轴偏移量 // startY: 150 //横向是否可以滑屏 scrollX:true, //纵向是否可以滑屏 scrollY:true, //缓冲动画 momentum:true }); //事件 bscroll.on("beforeScrollStart",()=>{ //每次开始滚动的时候 console.log("我要滚动了"); }) } window.onload = () => { getData(); con(); } 初始化之后,我们看到系统自带的滚动条已经消失不见了。这个区域还可以正常滑动,那么就是组件生效了为什么不建议使用原生滚动条,主要有以下几点原生的滚动条可能自带回弹如果有叠层 后面显示不了或显示不全阻止事件穿透只能清除默认事件FAQBScroll滚动的元素的是当前元素下的第一个子元素(下标为0), 以上案例为.con下的ul。如果在ul下再次添加元素,比如以上案例中class为demo的div,它不仅无法滚动,甚至会被ul覆盖外框(以上案例的con)一定要设置高度,否则无法滚动
2022年09月