Commit 547861a0 authored by 刘磊's avatar 刘磊
parents 318576df bb8b5d15
Pipeline #16615 canceled with stage
---
title: CSS隐藏元素及去除图片底部空白间隙的各种方式总结
date: 2022/8/30 22:42:20
tags:
- 前端
- CSS
author: 李燕
---
**一、CSS元素隐藏方式**
在某些场景下,让指定元素隐藏,是我们开发中常见的一个需求,其实现也很简单,光CSS方式就有好几种,可以简单总结如下:
<!-- more -->
1.设置元素的display为none
```
.hide {
 display:none;
}
```
将元素设置为display:none后,元素在页面上将彻底消失,元素本来占有的空间会被其他元素占有。
2.设置元素的visibility为hidden
```
.hide {
 visibility:hidden
}
```
和display:none的区别在于,元素在页面消失后,其占据的空间依旧会保留着。
3.将元素的透明度设置为0
```
.hide {
 opacity:0;
}
```
这种方法和visibility:hidden的一个共同点是元素隐藏后依旧占据着空间,而区别则在于元素只是隐身了没有消失,它依旧存在页面中。
4.设置height,width等盒模型属性为0
```
.hide{
 margin:0;
 border:0;
 padding:0;
 height:0;
 width:0;
 overflow:hidden;
}
```
5.设置元素的position与left、top、bottom、right等,将元素移出至屏幕外。
6.设置元素的position与z-index,将z-index设置成尽量小的负数
总结:隐藏元素最常用的方式为前2种,后面几种并不推荐,若有特殊场景需要则可适当选用。
**二、图片底部空白间隙去除方式**
在开发中经常会遇到这样一种情况:将一个图片放在一个盒子里,如果图片不做特殊处理,图片离盒子底部就会出现一段空白间隙,如下图:
![img](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a86a6dfc5e30449192553a4f0843fda5~tplv-k3u1fbpfcp-zoom-1.image)
这是因为,行内/行内块元素默认是与文字基线对齐的,而img是行内元素,它与文字基线对齐,但有的文字是有底线的(如下图中的"p"字母),基线与底线之间会有一段距离,所以造成了图片与盒子底部出现空白间隙。
![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7ca4bcb031ea4745854a49f033e5a0e1~tplv-k3u1fbpfcp-zoom-1.image)
#### 解决方法
1.修改img行内元素的垂直居中方式,让它不再以基线对齐。
```
img {
 vertical-align: top;
}
```
除了top值,还可以设置为middle或bottom 。
2.将img转为块元素,不在受行内基线的影响。
```
img {
 display: block;
}
```
3.修改img父级元素的行高或字体大小为0,这样基线下方的位置基本可以忽略。
```
div {
 font-size: 0px;
}
div {
 line-height: 0px;
}
```
---
title: JavaScript中Worker初探
date: 2022/8/30 17:20:00
author: 张顺
---
## 什么是Worker?他有什么作用?
因为js是单线程运行的,在遇到一些需要处理大量耗时的数据时,可能会阻塞页面的加载,造成面的假死。针对这个问题,H5出了新的功能worker,支持多线程,这时我们可以使用worker来开辟一个独立于主线程的子线程来进行一些耗时的大量运算。就可以用来解决因为大量耗时运算是造成页面假死的问题,详细内容查看![Worker](https://developer.mozilla.org/zh-CN/docs/Web/API/Worker)
### 如何使用Worker
```js
//首先创建 Worker 对象,第一个参数 worker 需要执行的 JS 文件的 URL。相对地址 URL 是相对于创建 Worker 的脚本所在文档(宿主文档)的访问地址。绝对地址 URL 必须要和宿主文档同源。
//第二个参数是可选参数,可以是一个对象,name属性,可以给当前Worker定义一个名称,可用来区分其他的worker
const worker = new Worker("worker.js",{name:'自定义的名称'})
//通过onmessage ,监听子线程的消息
worker.onmessage((e)=>`接收worker线程传递值:${e.data}`)
//通过postMessage ,向子线程发送消息
worker.postMessage('向子线程发送消息')
//worker.js 文件
//通过self.onmessage,来接收主线程传递的信息
self.onmessage((e)=>{
console.log(e)
})
//通过self.postMessage,来向主线程传递信息
self.postMessage('要传递的值')
```
### 方法和属性
主线程worker()对象,用来供主线程操作 Worker。worker 线程对象的属性和方法如下:
- worker.onerror:指定 error 事件的监听函数。
- worker.onmessage:指定 message 事件的监听函数,发送过来的数据在Event.data属性中。
- worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
- worker.postMessage():向 Worker 线程发送消息。
- worker.terminate():立即终止 Worker 线程。
worker 子线程全局属性和方法:
- self.onmessage:指定message事件的监听函数。
- self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
- self.close():关闭 worker 线程。
- self.postMessage():向产生这个 worker 线程发送消息。
- self.importScripts():加载 JS 脚本。
- self.name: worker 的名字。该属性只读,由构造函数指定。
### worker 使用注意点
1. 同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
2. DOM 限制
worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。
3. 通信联系
worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息传递完成。
4. 脚本限制
worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
5. 文件限制
worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
### 简单使用demo
模拟页面中插入耗时处理数据然后渲染元素,暂时不考虑页面渲染性能问题
```js
(() => {
let arrList = []  
let indexNum = 1000
for (let index = 0; index < indexNum; index++) {
let obj = {
name: index + 1
}
arrList.push(obj)
if (arrList.length == indexNum) {
//在子线程中,self代表worker子线程的全局对象,也可以用this代替self,或者省略也行
//this.postMessage(arrList);
//self.postMessage(arrList);
postMessage(arrList); //向主线程发送消息
}
onmessage = function(e) {
console.log(e.data)
console.log("我是worker线程接收到了你的信息,不客气,我就是做耗时操作的")
console.log("2妙后我还会给你发送个信息")
setTimeout(() => {
postMessage('如果还有耗时的操作,你还可以在开启一个worker线程呦')
}, 2000)
}
//worker自己关闭
// self.close()
}
})()
```
``` html
<!DOCTYPE html>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<html>
<body>
<div id="bt">
<p style="color:red">模拟元素1</p>
<p style="color:red">模拟元素2</p>
<p style="color:red">模拟元素3</p>
<p style="color:red">模拟元素4</p>
<div class="ld" style="color:green">数据加载中。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。</div>
<div class="main"></div>
<p style="color:red">模拟元素5</p>
<p style="color:red">模拟元素6</p>
<p style="color:red">模拟元素7</p>
</div>
<script>
// 引入的js文件必须和当前页面同源,不可跨域
let w = new Worker("./worker2.js");
$(".ld").show()
// console.log("Worker线程处理完耗时操作后,主线程接会收到处理数据")
w.onmessage = function(e) {
let listData = e.data
console.log(listData)
let strs = ''
for (let j = 0; j < listData.length; j++) {
const element = listData[j];
strs += '<p>' + '这是接收到的数据' + j + '</p>'
}
$(".main").append(strs)
$(".ld").hide()
}
w.postMessage('你发送的数据我收到了,辛苦了')
//主线程关闭子线程
// w.terminate()
</script>
</body>
</html>
```
![](/yifu-study-front-share/images/wk/nostop.png)<br />![](/yifu-study-front-share/images/wk/wk_pic.png)
### SharedWorker一种特殊的worker,详细内容查看![SharedWorker](https://developer.mozilla.org/zh-CN/docs/Web/API/SharedWorker)
sharedWorker 顾名思义,是 worker 的一种,可以由所有同源的页面共享。同Worker的api一样,传入js的url,就可以注册一个 sharedWorker 实例,
sharedWorker通过port来发送和接收消息
```js
let myWorker = new SharedWorker('worker.js');
//主线程和共享线程都是通过 postMessage() 方法来发送消息
myWorker.port.postMessage('发送消息');
//接收消息都是使用onmessage = (e)=>{},或者 addEventListener('message', (e)=>{})
myWorker.port.onmessage = (e)=>{console.log(e.data)} ;
//关闭启动和worker是有点区别的
线程通过 worker.port.start() 启动
线程通过 worker.port.close() 关闭
SharedWorker使用注意要点同worKer一样
```
### 简单使用Demo
- shareWk.js 共享线程,里面存储计数器counter
- A.html 刷新页面,通知counter+1
- B.html 刷新页面,查看counter
``` shareWk.js
// 计时器
let counter = 0
// 监听连接
self.addEventListener('connect', (e) => {
const port = e.ports[0]
port.onmessage = (res) => {
console.log('A,B页面共享的信息:', res.data)
switch (res.data) {
case 'add':
counter++
break
}
console.log('counter:', counter)
port.postMessage(counter)
}
})
```
``` A.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>A页面</title>
</head>
<body>
<h1>A页面</h1>
<script>
// 兼容性判断
if (!SharedWorker) {
throw new Error('当前浏览器不支持SharedWorker')
}
// 创建共享线程
const worker = new SharedWorker('shareWk.js')
// 启动线程端口
worker.port.start()
// 向共享线程发送消息
worker.port.postMessage('add')
// 线程监听消息 2种方式都可以
// worker.port.addEventListener('message', (e) => {
// console.log('A页面共享线程counter值:', e.data)
// })
worker.port.onmessage = (e) => {
console.log('A页面取到counter值:', e.data)
}
console.log(worker.port)
</script>
</body>
</html>
```
``` B.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>B页面</title>
</head>
<body>
<h1>B页面</h1>
<script>
// 兼容性判断
if (!SharedWorker) {
throw new Error('当前浏览器不支持SharedWorker')
}
// 创建共享线程
const worker = new SharedWorker('shareWk.js')
// 启动线程端口
worker.port.start()
// 向共享线程发送消息
worker.port.postMessage('get counter')
// 线程监听消息
// 线程监听消息 2种方式都可以
// worker.port.addEventListener('message', (e) => {
// console.log('B页面共享线程counter值:', e.data)
// })
worker.port.onmessage = (e) => {
console.log('B页面取到counter值:', e.data)
}
</script>
</body>
</html>
```
![](/yifu-study-front-share/images/wk/shwk.png)
***SharedWorker调试注意点***
SharedWorker不能直接在页面调试面板查看,需要浏览器输入 chrome://inspect
然后点击inspect查看
![](/yifu-study-front-share/images/wk/point.png)
\ No newline at end of file
...@@ -11,7 +11,7 @@ author: 方玉新 ...@@ -11,7 +11,7 @@ author: 方玉新
## 使用Hooks时的疑惑 ## 使用Hooks时的疑惑
Hooks的出现让我们对`Function Component`逐步拥有了对标 `Class Component`的特性,比如私有状态以及生命周期函数等。`useState``useReducer`这两个`Hooks`让我们可以在`Function Component`里使用到私有状态。而`useState`其实相当于简化版的`useReducer`,下面就将这两个放到一起简单介绍下。 Hooks的出现让我们对`Function Component`逐步拥有了对标 `Class Component`的特性,比如私有状态以及生命周期函数等。`useState``useReducer`这两个`Hooks`让我们可以在`Function Component`里使用到私有状态。而`useState`其实相当于简化版的`useReducer`,下面就将这两个放到一起简单介绍下。
<!-- more -->
#### 1、useState使用示例 #### 1、useState使用示例
```javascript ```javascript
function PersionInfo ({initialAge,initialName}) { function PersionInfo ({initialAge,initialName}) {
......
---
title: Vue+Element项目的批量导入组件封装
date: 2022/6/30 18:42:20
tags:
- 前端
- 批量导入
- JavaScript
author: 李燕
---
Excel导入、Excel导出是一对好兄弟,经常成双成对出现。所以,在封装了Excel导出方法 —— 《用xlsx-style玩转Excel导出——拿走即用的Excel导出方法封装》后,今天我们来封装一下Excel导入。
Excel导入的封装和Excel导出的封装不太一样,Excel导出是纯js封装的,但是Excel导入的封装可能是组件级别的封装。
<!-- more -->
**一、 安装依赖**
 
在正式开始封装之前,我们需要先安装xlsx。这个插件,能够方面我们将导入的文件,处理成我们需要的数据。
```js
npm install -S xlsx
```
安装后,在需要使用的地方引入即可。
```js
import XLSX from 'xlsx'
```
**二、上传文件**
因所做的涉及到Excel批量导入的项目,都是基于Vue+Element的,所以直接选用了Element的el-upload组件实现文件上传功能,如下:
```js
// BatchImport.vue
<template>
<el-upload
action="#"
accept=".xls,.xlsx"
:show-file-list="false"
:auto-upload="true"
:multiple="false"
:http-request="handleBatchImport"
>
<el-button size="small" type="primary">批量导入</el-button>
</el-upload>
</template>
```
**三、处理文件**
文件上传后,我们获取到导入的Excel文件,然后进行如下处理:
+ 通过new FileReader()读取文件
+ 将读取结果通过XLSX.read()方法转换成工作簿对象
+ 处理工作簿对象,获取有效的目标数据
```js
// BatchImport.vue
<script>
import XLSX from 'xlsx'
export default {
name: 'BatchImport',
methods: {
handleBatchImport(file) {
const that = this
// 通过FileReader对象读取文件
const fileReader = new FileReader();
fileReader.onload = (event) => {
try {
const { result } = event.target;
// 以二进制流方式读取得到整份excel表格对象
const workbook = XLSX.read(result, { type: "binary" });
// 存储获取到的数据
const excelData = {};
// 遍历获取每张工作表 除去隐藏表
const allSheets = workbook.Workbook.Sheets;
allSheets.forEach( item => {
const name = item.name;
if (workbook.Sheets.hasOwnProperty(name) && item.Hidden === 0) {
// 利用 sheet_to_json 方法将 excel 转成 json 数据
const data = [].concat(
XLSX.utils.sheet_to_json(workbook.Sheets[name])
);
// 处理表格有效内容后面的空格行,获得有效数据
excelData[name] = that.handleEmptyRow(data)
}
})
//将excelData暴露出去
that.$emit('importExcel', excelData)
} catch(e) {
that.$message.error('文件上传错误!')
that.$emit('importExcel', null)
return false
}
}
//以二进制方式打开文件
fileReader.readAsBinaryString(file.file);
},
// 处理表格空行问题
handleEmptyRow(data) {
// 去除最后的空格行
for (let i = data.length - 1; i >= 0; i--) {
// 判断数组是否全为空
const flag = Object.values(data[i]).find((item) => {
return String(item).replace(/(^s*)|(s*$)/g, '').length
})
if (!flag) {
data.splice(i, 1)
} else {
break
}
}
return data
},
}
}
</script>
```
**四、处理数据**
经过上述步骤,我们获取到目标数据,接下来,在父组件里对数据进行进一步处理和操作。
```js
// Father.vue
<template>
<batch-import @importExcel="importExcel"></batch-import>
</template>
<script>
import BatchImport from './BatchImport.vue'
export default {
components: {
BatchImport,
},
methods: {
importExcel(excelData) {
...
}
}
}
</script>
```
因数据具体如何处理,需要根据项目需求和后端要求而定,所以未将数据在BatchImport组件内进行处理,而是选择暴露出来在父组件内处理。
---
title: Vue项目中,路由都被缓存了,特定页面需要自定义刷新规则,怎么办?
date: 2022/07/29 10:42:20
tags:
- 前端
- 路由刷新
- JavaScript
author: 李燕
---
最近开发的几个后台管理系统,都是基于vue-element-admin框架开发的。框架中,默认将所有页面进行了keep-alive缓存。但是在实际需求中,某些页面每次打开都需要刷新数据。这就出现了一个问题:
<!-- more -->
##### ***对于已被缓存的页面,如何进行数据刷新?***
目前,在项目开发中,主要用到以下三种方式:
**一、监听路由,刷新数据**
```js
watch: {
$route: function (newVal) {
if (/data-count\/firmCount/.test(newVal.path)) {
this.init()
}
},
},
```
init()方法中,将页面所有缓存的内容、操作的痕迹都进行了重置,让页面回到初始状态。也就是,每次打开目标页面,都如强制刷新一样,没有任何缓存的痕迹。
**二、利用生命周期函数,刷新数据**
通过keep-alive缓存后的页面,大部分的生命周期函数是不走的。比如:created,mounted等。能够走的生命周期函数就是:activated与deactivated。所以可以在activated生命周期函数中,进行数据刷新。
```js
activated() {
this.init()
},
```
init()方法的写法同上。
**三、利用导航守卫清除缓存,刷新数据**
```js
beforeRouteLeave(to, from, next) {   //参数(马上去的页面,现在的页面,跳转)
    if(判断条件){
         to.meta.keepAlive = false  //将要去的那个页面的缓存清空
    }else{
       to.meta.keepAlive = true   //将要去的那个页面的缓存保留
    }
    next();
  },
```
相对来说,前两种方法比较简单粗暴,哪个页面需要就哪个页面使用,缺点就是每个页面需要重置的内容各不相同,处理比较繁琐。
而第三种方法,我们可以封装成一个公共的方法,在需要的页面引用即可。具体可以参考公司大佬的封装,如下:
```js
//RouteMixin.js
export default {
beforeRouteLeave(to, from, next) { // 离开路由之前执行的函数
if (this.hasCached(from) && this.hasCached(to)) {
const vnode = this.$vnode
const key = vnode.key// 当前关闭的组件名
if (key && vnode.parent) {
const cache = vnode.parent.componentInstance.cache// 缓存的组件
const keys = this.$vnode.parent.componentInstance.keys// 缓存的组件名
if (keys) {
keys.splice(0, keys.length)
}
if (cache) {
for (const k in cache) {
delete cache[k]
}
}
}
}
if (this.hasCached(to)) {
to.meta.keepAlive = true
} else {
to.meta.keepAlive = false
}
next()
},
methods: {
hasCached(route) { // 根据需要定制缓存与否的规则
const matched = route.matched
const current = matched[matched.length - 1]
const parent = current.parent
if (parent && (parent.components.default.name === 'Layout' || parent.components.default.name === 'BlankLayout')) {
return true
}
return false
},
},
}
```
需求是千变万化的,这不,在vue-element-admin项目中,关于页面的缓存刷新,最近又遇到一个新问题:
##### ***对于已被缓存的页面,如何进行前进刷新后退不刷新?***
也就是说,同一个页面,从上级页面或兄弟页面到达时,刷新,从下级页面返回时,不刷新。这应该怎么处理呢?
最终,进行一番搜索,解决如下:
**在beforeRouteLeave中做标记,在activated中根据标记进行刷新与否的处理**
```js
beforeRouteLeave(to, from, next) {
if (to.path === '/data-count/focusGroup') {//去下级页面时打标记
this.$route.meta.isBack = true
} else {
this.$route.meta.isBack = false
}
next()
},
activated() {
if (!this.$route.meta.isBack) {
this.init()
}
},
```
由此,又延申出一个问题:
##### ***从下级页面返回时,页面如何定位到历史位置?***
处理过前进刷新后退不刷新的问题后,这个问题变得相对简单了,我们只需在上述代码的基础上,进行如下处理:
**在beforeRouteLeave中记录scrollTop,在activated中根据记录的scrollTop进行位置处理**
```js
beforeRouteLeave(to, from, next) {
if (to.path === '/data-count/focusGroup') {
this.$route.meta.isBack = true
this.scrollTop = this.$refs.proTable.$el.scrollTop || 0 // 新增的代码
} else {
this.$route.meta.isBack = false
}
next()
},
activated() {
if (!this.$route.meta.isBack) {
this.init()
} else {
this.$refs.proTable.$el.scrollTop = this.scrollTop // 新增的代码
}
},
```
---
title: WebSocket
date: 2022/7/29 15:00:00
tags:
- WebSocket
author: 张芳利
---
<a name="d3db997e"></a>
## 一、什么是WebSocket
- WebSocket是一种通信协议,可在单个TCP连接上进行全双工通信。WebSocket使得客户端和服务器之间的数据交<br />换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。
1. WebSocket是一种通信协议
2. 区别于HTTP协议,HTTP协议只能实现客户端请求,服务端响应的这种单项通<br />而WebSocket可以实现客户端与服务端的双向通讯,,最大也是最明显的区别就是可以做到服务端主动将消息推送给客户端。
<a name="554bf4d4"></a>
## 二、为什么需WebSocket?
- 因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
> 由于http协议只能由客户端发起通信,如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。Websocket在解决这样的需求上非常方便。<br />如果不使用WebSocket与服务器实时交互,一般有两种方法。AJAX轮询和Long Polling长轮询。
- 上边这两种方式都有个致命的弱点,开销太大,被动性。假设并发很高的话,这对服务端是个考验。<br />而WebSocket一次握手,持久连接,以及主动推送的特点可以解决上边的问题,又不至于损耗性能。
1. AJAX轮询
- AJAX轮询也就是定时发送请求,也就是普通的客户端与服务端通信过程,只不过是无限循环发送,这样,可以保证服务端一旦有最新消息,就可以被客户端获取。
2. Long Polling长轮询
- Long Polling长轮询是客户端和浏览器保持一个长连接,等服务端有消息返回,断开。<br />然后再重新连接,也是个循环的过程,无穷尽也。。。<br />客户端发起一个Long Polling,服务端如果没有数据要返回的话,<br />会hold住请求,等到有数据,就会返回给客户端。客户端又会再次发起一次Long Polling,再重复一次上面的过程。
<a name="bed9b70f"></a>
## 三、WebSocket特点
- 最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
![websocket](/yifu-study-front-share/images/websocket/WebSocket1.png)
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
<a name="b15a1ba5"></a>
![websocket](/yifu-study-front-share/images/websocket/WebSocket2.png)
<a name="367b4b6d"></a>
## 四、WebSocket的API
1、WebSocket构造函数: 新建WebSocket实例后,会创建客户端与服务端的WebSocket连接
```jsx
let ws = new WebSocket(url)
```
2、 WebSocket实例的当前状态:
```jsx
ws.readyState
// 0: 'CONNECTING',表示正在连接
// 1: 'OPEN', 表示连接成功,可以通信了
// 2: 'CLOSING', 表示连接正在关闭
// 3: 'CLOSED', 表示连接已经关闭,或者打开连接失败。
```
3、WebSocket实例的事件:
```jsx
ws.onopen = function () {
// 指定连接成功后的回调函数
}
ws.onclose = function () {
// 指定连接关闭后的回调函数
}
ws.onmessage = function () {
// 指定收到服务器数据后的回调函数
}
ws.onerror = function () {
// 指定报错时的回调函数
}
```
4、WebSocket实例向后台推送消息的方法:
```jsx
ws.send(message) 向服务器发送数据
ws.close() 关闭当前连接
```
<a name="502a7fcf"></a>
## 五、WebSocket的解读
1、申请一个WebSocket对象,参数是需要连接的服务器端的地址,同http协议使用http://开头一样,WebSocket协议的URL使用ws://开头,另外安全的WebSocket协议使用wss://开头。
2、WebSocket对象一共支持四个消息 onopen, onmessage, onclose和onerror,<br />所有的操作都是采用消息的方式触发的,这样就不会阻塞UI,使得UI有更快的响应时间,得到更好的用户体验。<br />当Browser接收到WebSocketServer发送过来的数据时,就会触发onmessage消息,参数evt中包含server传输过来的数据;
```jsx
ws.onmessage = function(evt) {
};
```
3、WebSocket与TCP、HTTP的关系WebSocket与http协议一样都是基于TCP的,所以他们都是可靠的协议,Web开发者调用的WebSocket的send函数在browser的实现中最终都是通过TCP的系统接口进行传输的。
4、WebSocket和Http协议一样都属于应用层的协议,那么他们之间有没有什么关系呢?答案是肯定的,WebSocket在建立握手连接时,数据是通过http协议传输的,但是在建立连接之后,真正的数据传输阶段是不需要http协议参与的
![websocket](/yifu-study-front-share/images/websocket/WebSocket3.png)
<a name="14106cbc"></a>
## 六、WebSocket通讯详细解读
![websocket](/yifu-study-front-share/images/websocket/WebSocket4.png)
从图可以明显的看到,分三个阶段:
- 打开握手
- 数据传递
- 关闭握手
---
title: Docker 基础使用介绍
date: 2022/05/30 9:25:23
updated: 2022/05/30 9:25:23
tags:
- docker
- 容器
- 虚拟化
author: 蔡海飞
---
> Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。我们可以通过将开发代码打包到docker 镜像中,上传到docker hub中,待发布时通过拉取docker hub镜像启动docker 容器,实现代码运行环境的高度一致,并提高部署效率。
![docker运行系统图](/yifu-study-front-share/images/docker-system.png)
本篇文章就我们较为常用的docker 命令使用通过场景使用的不同来简单介绍。
<!-- more -->
### Docker 安装与镜像加速
#### Docker安装
docker 提供了各种不同系统的安装方式,因为我们常用的正式部署环境使用centos的缘故,我这里只介绍centos里如何安装docker以及如何配置docker国内镜像加速。
1. 使用官方提供的脚本自动安装
```
$ curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
```
2. 手动命令行安装
```
# 先卸载旧版本的docker安装包,后安装新版
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
$ sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
```
#### 镜像加速
1. 阿里云
```
$ sudo yum-config-manager \
--add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
```
2. 清华大学源
```
$ sudo yum-config-manager \
--add-repo \
https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo
```
3. 腾讯源
```
$ sudo yum-config-manager \
--add-repo \
https://mirror.ccs.tencentyun.com/docker-ce/linux/centos/docker-ce.repo
```
#### 启动docker 服务
```
$ sudo systemctl restart docker
```
### Docker 核心概念
![docker-system.jpeg](/yifu-study-front-share/images/docker-system.jpeg)
#### 镜像
Docker镜像是由文件系统叠加而成。一个镜像可以多个中间层组成,多个镜像可以共享同一中间层,我们也可以通过在镜像添加多一层来生成一个新的镜像。镜像在构建完成之后,便不可以再修改,而上面我们所说的添加一层构建新的镜像,这中间实际是通过创建一个临时的容器,在容器上增加或删除文件,从而形成新的镜像,因为容器是可以动态改变的。整理可以得出镜像的两个特点:
- 镜像是分层的
- 镜像是只读的
![docker-image.png](/yifu-study-front-share/images/docker-image.png)
1. 常用命令
```shell
$ docker image ls # 枚举本地主机中的docker镜像
$ docker image inspect nginx:latest # 获取nginx:latest镜像的层数据和元数据
$ docker rmi nginx:latest # 删除本地的nginx:latest,需要先删除应用该镜像的容器
$ docker tag nginx:latest nginx:test # 给本地nginx:latest镜像添加标签nginx:test
$ docker commit -a a404c6c174a2 mymysql:v1 # 基于当前容器a404c6c174a2创建新的镜像mymysql:v1
$ docker build -f Dockerfile -t myimage:0.0.1 . # 基于Dockerfile文件制作镜像myimage:0.0.1
```
#### 容器
容器是镜像的运行时实例。正如从虚拟机模板上启动 VM 一样,用户也同样可以从单个镜像上启动一个或多个容器。虚拟机和容器最大的区别是容器更快并且更轻量级——与虚拟机运行在完整的操作系统之上相比,容器会共享其所在主机的操作系统/内核。
Docker 容器是一个开源的应用容器引擎,让开发者可以以统一的方式打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何安装了docker引擎的服务器上(包括流行的Linux机器、windows机器),也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。几乎没有性能开销,可以很容易地在机器和数据中心中运行。最重要的是,他们不依赖于任何语言、框架包括系统。
1. 常用命令
```shell
$ docker ps -a # 列出当前主机中所有的docker容器
$ docker run -it nginx /bin/bash # 基于nginx镜像启动一个docker容器
$ docker pause containername # 暂停docker 容器
$ docker start containername # 启动docker容器
$ docker stop containername # 停止docker容器
$ docker restart containername # 重启docker容器
$ docker attach -it containername /bin/bash # 进入docker 容器,此种方式进入退出后会使得docker容器停止
$ docker exec -it containername /bin/bash # 进入docker 容器,此种方式进入退出后不会使得docker容器停止
$ docker export containername > containername.tar # 导出docker容器快照
$ docker rm containername # 删除docker 容器
$ docker logs -f containername # 持续输出docker容器的日志信息
$ docker cp containerID:container_path host_path # 复制主机文件到对应docker容器的地址
```
#### 仓库
Docker 仓库是用来包含镜像的位置。Docker提供一个注册服务器(Register)来保存多个仓库,每个仓库又可以包含多个具备不同tag的镜像。Docker运行中使用的默认仓库是 Docker Hub 公共仓库。
![docker-hub.png](/yifu-study-front-share/images/docker-hub.png)
1. 常用命令
```shell
$ docker pull nginx:latest # 从镜像仓库拉取nginx:latest镜像
$ docker search nginx # 搜索镜像库的nginx相关镜像
$ docker push nginx:0.0.1 # 提交本地镜像nginx:0.0.1到当前登录的镜像仓库
```
### 一个简单的docker 使用示例
1. 制作Dockerfile文件
```shell
# Dockerfile 文件
FROM nginx:alpine
ADD deploy/qas/default.conf /etc/nginx/conf.d
ADD dist /home/html/app/
# deploy/qas/default.conf
server{
listen 8080;
location ^~ /proxy-path/ {
#自定义头信息传递
proxy_pass http://****/;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
}
location / {
#配置代理参数
proxy_redirect off;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host:$server_port;#这里是重点,这样配置才不会丢失端口
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
root /home/html/app/;
index index.html;
try_files $uri /index.html;
}
}
# dist为打包文件
```
2. 制作Docker镜像
```shell
$ docker build -f ./deploy/qas/Dockerfile -t website:0.0.1 .
```
3. 上传Docker镜像
```shell
$ docker tag website:0.0.1 website/test
$ docker images
$ docker push website/test
```
4. 启动docker 容器
```shell
$ docker pull website/test
$ docker run -it --name website-test -p 80:80 -d website/tes
```
5. 查看docker 容器运行状态
```shell
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
90cdb9ad1ae1 website/tes "nginx -g 'daemon of…" a minute ago Up a minute 0.0.0.0:80->80/tcp website-test
```
\ No newline at end of file
---
title: jQuery从$开始
date: 2022/05/31 16:25:23
updated: 2022/05/31 16:25:23
tags:
- 前端
- JQuery
- 设计模式
author: 王许晓
---
**设计模式(Design pattern)** 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路。通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性好。我们使用设计模式最终的目的是实现代码的 **高内聚****低耦合**
<!-- more -->
适配器模式将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决在软件系统中,常常要将一些‘现存的对象’放到新的环境中,而新环境要求的接口是现对象不能满足的。
优点:1、可以让任何两个没有关联的类一起运行。2、提高了复用、灵活性好。
缺点:过多使用适配器会让系统非常混乱。适配器解决已经完成继续增加内容
的实用场景。
在jQuery中使用的适配器的例子:
jQuery.fn.css(),这个方法帮助规范了不同浏览器之间样式的应用方式,使我们使用简单的语法,这些语法被适配成为浏览器背后真正支持的语法
```
// 下面的例子将返回首个匹配元素的 background-color 值
$("p").css("background-color");
```
```
// 设置指定的 CSS 属性
$("p").css("background-color","yellow");
```
```
// 设置多个 CSS 属性
$("p").css({"background-color":"yellow","font-size":"16px"});
```
```
css: function( name, value ) {
return access( this, function( elem, name, value ) {
var styles, len,
map = {},
i = 0;
if ( Array.isArray( name ) ) {
styles = getStyles( elem );
len = name.length;
for ( ; i < len; i++ ) {
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
}
return map;
}
return value !== undefined ?
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
}, name, value, arguments.length > 1 );
}
```
# 外观模式
外观模式为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
解决问题:降低访问复杂系统的内部子系统的负责度,简化客户端之间的接口。
优点:1、减少系统相互依赖。2、提高灵活性。3、提高安全性。
缺点:不符合开闭原则,如果要改东西很麻烦。
# 观察者模式
在观察者模式下,系统中的对象可以在关注的事件发生的时候给其他对象发送消息,可以被其他对象所通知。
解决问题:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的写作。
优点:观察者和被观察者是抽象耦合的。
缺点:如果一个被观察者对象有很多的直接和间接的观察者,将所有的观察者都通知到会话费很多时间。2、如果观察者和被观察着之间有循环依赖的话,观察目标会触发他们之间进行循环调用,可能导致系统崩溃。
## 附录
开闭原则:对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_01.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_02.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_03.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_04.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_05.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_06.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_07.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_08.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_09.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_10.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_11.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_12.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_13.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_14.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_15.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_16.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_17.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_18.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_19.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_20.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_21.jpg)
![docker-system.jpeg](/yifu-study-front-share/images/wxx/JQuery中的设计模式_22.jpg)
...@@ -20,7 +20,7 @@ preload和prefetch的出现为我们提供了可以更加细粒度地控制浏 ...@@ -20,7 +20,7 @@ preload和prefetch的出现为我们提供了可以更加细粒度地控制浏
<link ref='preload'> <link ref='preload'>
<link ref='prefetch'> <link ref='prefetch'>
``` ```
<!-- more -->
#### preload特点 #### preload特点
- preload加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞onload事件; - preload加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞onload事件;
- preload可以支持加载多种类型的资源,并且可以加载跨域资源; - preload可以支持加载多种类型的资源,并且可以加载跨域资源;
......
--- ---
title: TypeScript泛型 title: TypeScript泛型
date: 2022/5/10 16:00:00 date: 2022/5/10 16:00:00
tags: tags:
- 前端 - 前端
- typescript - typescript
author: 杨靖 author: 杨靖
--- ---
## 泛型 ## 泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
...@@ -27,6 +28,7 @@ createArray(3, 'x'); // ['x', 'x', 'x'] ...@@ -27,6 +28,7 @@ createArray(3, 'x'); // ['x', 'x', 'x']
这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型: 这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型:
Array<any> 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value 的类型。 Array<any> 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value 的类型。
这时候,泛型就派上用场了: 这时候,泛型就派上用场了:
<!-- more -->
```typescript ```typescript
function createArray<T>(length: number, value: T): Array<T> { function createArray<T>(length: number, value: T): Array<T> {
......
---
title: TypeScript泛型
date: 2022/08/04 17:00:00
tags:
- 前端
- typescript
author: 杨靖
---
#### ts装饰器是什么
装饰器是通过添加标注的方式,来对类型进行扩展的一种方式。
- 只能在类中使用
- 减少冗余代码量
- 提高代码扩展性
#### ts装饰器的语法
装饰器的使用非常简单,装饰器本质就是一个函数,在特定的位置调用装饰器函数即可对数据(类、方法、甚至参数等)进行扩展。下面例子演示给 MyTestableClass 类添加静态属性 isTestable:
```typescript
@testable
class MyTestableClass {}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
```
#### 装饰器的分类
装饰器 是一个函数,它可以通过 @funName 在类、方法、访问符、属性、参数上,对它们进行包装,然后返回一个包装后的目标对象(类、方法 、访问符、属性、参数)
1. 类装饰器
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。下面例子演示给 MyTestableClass 类添加静态属性 isTestable:
```typescript
@testable
class MyTestableClass {}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
```
2. 类方法装饰器
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
成员的属性描述符。
下面的例子演示让类的方法变为只读。
(1)实例方法
```typescript
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class Cat {
@readonly
say() {
console.log("meow ~");
}
}
```
(2)静态方法
```typescript
class MyTestableClass {
@testable
static sleep() {}
}
function testable(target, name, descriptor) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
```
3. 类属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
属性描述符不会做为参数传入属性装饰器,这与TypeScript是如何初始化属性装饰器的有关。官方文档有说明。
```typescript
class MyTestableClass {
@testable
name: string;
constructor(name: string) {
this.name = name;
}
}
function testable(target, name) {
console.log(arguments)
}
```
4. 参数装饰器
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
参数在函数参数列表中的索引。
注意  参数装饰器只能用来监视一个方法的参数是否被传入。参数装饰器的返回值会被忽略。
```typescript
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}
```
#### 装饰器的执行顺序
实例装饰器:属性装饰 -> 访问器装饰 -> 参数装饰 => 方法装饰
静态装饰器: 属性 => 访问器 => 参数 => 方法
类装饰器
#### 复合装饰器
```typescript
@d1
@d2
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
function d1(target: Function) {}
function d2(target: Function) {}
```
#### 装饰器工厂
如果我们需要给装饰器执行过程中传入一些参数的时候,就可以使用装饰器工厂来实现,它返回一个装饰器函数。
```typescript
@Controller('user')
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
function Controller(path: string) {
return function (target: Function) {
(target as any).path = path;
}
}
```
## 路由懒加载 ---
title: 性能优化--通过优化首屏加载速度实现
date: 2022/5/24 14:30
tags:
- 前端
- 路由懒加载
- 性能优化
author: 刘磊
---
#### 什么是路由懒加载 #### 什么是路由懒加载
路由懒加载也叫延迟加载,即在需要的时候进行加载,随用随载。在单页面应用中浏览器默认会将所有的资源全部下载下来,这可能会导致首屏加载的时间过长。路由懒加载可以做到点击对应的路由再加载对应的文件资源 路由懒加载也叫延迟加载,即在需要的时候进行加载,随用随载。在单页面应用中浏览器默认会将所有的资源全部下载下来,这可能会导致首屏加载的时间过长。路由懒加载可以做到点击对应的路由再加载对应的文件资源
#### 路由懒加载的三种写法 (推荐用法:es6的import) #### 路由懒加载的三种写法 (推荐用法:es6的import)
...@@ -14,7 +25,7 @@ ...@@ -14,7 +25,7 @@
``` ```
configplugins.delete(prefetch) configplugins.delete(prefetch)
``` ```
<!-- more -->
- 在引用时配置 - 在引用时配置
``` ```
import(<!--webpackChunkName:'[...]',webpackPrefetch:false-->) import(<!--webpackChunkName:'[...]',webpackPrefetch:false-->)
......
---
title: 浅谈Vite
date: 2022/5/2515:00:00
tags:
- vite
- package.json
author: 张芳利
---
<a name="Sxg5O"></a>
# 1. Vite 简介
<a name="GUv9u"></a>
## 1.1 Vite是什么?
Vite是一个由原生 ES Module 驱动的 Web 开发前端构建工具,它能显著改善前端开发体验;<br />要由两部分组成:
- 一个开发服务器,它基于 [原生 ES 模块](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) 提供了 [丰富的内建功能](https://vitejs.cn/guide/features.html),如速度快到惊人的 [模块热更新(HMR)](https://vitejs.cn/guide/features.html#hot-module-replacement)
- 一套构建指令,生产环境,它使用 [Rollup](https://rollupjs.org/) 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。
<a name="V0wyB"></a>
<!-- more -->
## 1.2 Vite的特性
- 💡 **极速的服务启动:** 使用原生 ESM 文件,无需打包!
- ⚡️ **轻量快速的热重载:** 无论应用程序大小如何,都始终极快的模块热重载(HMR)
- 🛠️ **丰富的功能:** 对 TypeScript、JSX、CSS 等支持开箱即用。
- 📦 **优化的构建**:可选 “多页应用” 或 “库” 模式的预配置 Rollup 构建
- 🔩 **通用的插件**: 在开发和构建之间共享 Rollup-superset 插件接口。
- 🔑 **完全类型化的API**:灵活的 API 和完整 TypeScript 类型
<a name="Vv04S"></a>
# 2. Vite概念及设计思想
**打包:**使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。
<a name="vmIab"></a>
## 2.1 和webpack 的区别
1. **打包和速度:**
webpack: 分析依赖=> 编译打包=> 交给本地服务器进行渲染。首先分析各个模块之间的依赖,然后进行打包,在启动webpack-dev-server,请求服务器时,直接显示打包结果。webpack打包之后存在的问题:随着模块的增多,会造成打出的 bundle 体积过大,进而会造成热更新速度明显拖慢。<br />
![webpack](/yifu-study-front-share/images/viteImg/webpack.png)
vite: 启动服务器=> 请求模块时按需动态编译显示。是先启动开发服务器,请求某个模块时再对该模块进行实时编译,因为现代游览器本身支持ES-Module,所以会自动向依赖的Module发出请求。所以vite就将开发环境下的模块文件作为浏览器的执行文件,而不是像webpack进行打包后交给本地服务器。<br />
![vite](/yifu-study-front-share/images/viteImg/vite1.png)
2. **工具定位:**
webpack:是偏底层的东西,进行配置化,灵活度极高的工具,具有丰富的生态圈<br />vite: 是开箱即用,使用更简单的工具,但是目前是属于起步阶段
结论:
1. Vite主打特点就是轻快冷服务启动。冷服务的意思是,在开发预览中,它是不进行打包的。
1. 开发中可以实现热更新,也就是说在你开发的时候,只要一保存,结果就会更新。
1. 按需进行更新编译,不会刷新全部DOM节点。这功能会加快我们的开发流程度。
<a name="rEfXe"></a>
## 2.2 Esm 加载对比
1. esm 文件中,创建index.js , 引入main.js
```jsx
<!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>ESM Brower</title>
<!-- type module -->
<script src="main.js" type="module"></script>
</head>
<body>
</body>
</html>
```
2. 安装 lodash的包
`yarn add lodash-es`
3. 引入loadsh,并利用她的fiter 功能
```jsx
console.log('测试esm');
import {filter} from 'loadsh'
const arr = [{name:'张小张',age:'22'},{name:"王小王",age:'18'}]
const newArr = filter(arr,item=>item.age>20)
console.log(newArr);
```
报错:<br />Uncaught TypeError: Failed to resolve module specifier "loadsh". Relative references must start with either "/", "./", or "../".<br />浏览器原生解析路径的问题,所以换一下引入方式<br />node_modules中找到lodash-es,找到她的package,json,查看找到 "main": "lodash.js"
```jsx
import { filter } from './node_modules/lodash-es/lodash.js'
```
4. 利用**Live Server** 插件,项目启动
![live server启动](/yifu-study-front-share/images/viteImg/vite2.png)
5. 我们能看到打印出来的东西:
![打印1](/yifu-study-front-share/images/viteImg/vite3.png)<br />![打印2](/yifu-study-front-share/images/viteImg/vite4.png)<br />![打印2](/yifu-study-front-share/images/viteImg/vite5.png)
<br />从上面看到只是用到了一个过滤但是其他的模块等也都进行了请求,浪费了时间
**vite是怎么做的呢?**
1. 创建一个vite-esm 文件
`yarn create vite`
2. 同样 安装lodash-es
2. main.js 中同样操作
```jsx
console.log('测试esm');
import { filter } from 'lodash-es'
const arr = [{name:'张小张',age:'22'},{name:"王小王",age:'18'}]
const newArr = filter(arr,item=>item.age>20)
console.log(newArr);
```
4. yarn dev 后,页面中看到
![打印3](/yifu-study-front-share/images/viteImg/vite6.png)<br />![打印4](/yifu-study-front-share/images/viteImg/vite7.png)<br />对比之下可以看到,页面清爽了很多,很多没有必要的请求也没有了。
<a name="vXvPP"></a>
# 3. vite 的使用
> Vite 需要 [Node.js](https://nodejs.org/en/) 版本 >= 12.0.0。
- **npm **
```jsx
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
```
或者
```jsx
npm init @vitejs/app 项目名
npm init vite@latest
```
这个命令安装就是一步步自己选择一下是react还是vue,以及ts 等。如果选择vue,都是vue3.0的。
- **yarn**
```jsx
yarn create vite-app <project-name>
cd <project-name>
yarn
yarn dev
```
或者
```jsx
yarn create @vitejs/app 项目名
```
使用yarn有个问题 :文件名、目录名或卷标语法不正确<br />解决方法:[https://juejin.cn/post/6844904111570190349](https://juejin.cn/post/6844904111570190349)
vite中还会有一些模板可以选择,指定的所有相关项目模板都可以在[ https://github.com/vitejs/awesome-vite#templates]() 仓库中找到
> 一般都是默认创建的是vue3的项目,所以如果创建vue2,需要做一些修改之类的或者在模板库中查找使用
<a name="z548o"></a>
## 3.1 遇到的问题
控制台会显示 user --host to expose(使用 --host 暴露网络) <br />![host问题](/yifu-study-front-share/images/viteImg/vite8.png)<br />解决方法:
1. 在根目录下的 vite.config.js 文件中添加以下内容
```jsx
import vue from '@vitejs/plugin-vue'
/**
* https://vitejs.dev/config/
* @type {import('vite').UserConfig}
*/
export default {
plugins: [vue()],
server: { // ← ← ← ← ← ←
host: '0.0.0.0' // ← 新增内容 ←
} // ← ← ← ← ← ←
}
```
2. 通过 Vite CLI 配置
执行 npx vite --host 0.0.0.0 命令后,就可以通过 http://10.56.116.128:3000/ 访问了。
3. 修改npm 脚本
修改 package.json 文件中 scripts 节点下的脚本,如下
```jsx
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vite build",
"serve": "vite preview --host 0.0.0.0"
}
```
<a name="R0yEc"></a>
## 3.2 项目目录
```jsx
|-node_modules -- 项目依赖包的目录
|-public -- 项目公用文件
|--favicon.ico -- 网站地址栏前面的小图标
|-src -- 源文件目录,程序员主要工作的地方
|-assets -- 静态文件目录,图片图标,比如网站logo
|-components -- Vue3.x的自定义组件目录
|--App.vue -- 项目的根组件,单页应用都需要的
|--index.css -- 一般项目的通用CSS样式写在这里,main.js引入
|--main.js -- 项目入口文件,SPA单页应用都需要入口文件
|--.gitignore -- git的管理配置文件,设置那些目录或文件不管理
|-- index.html -- 项目的默认首页,Vue的组件需要挂载到这个文件上
|-- package-lock.json --项目包的锁定文件,用于防止包版本不一样导致的错误
|-- package.json -- 项目配置文件,包管理、项目名称、版本和命令
|--vite.config.js -- 配置文件
```
<a name="mLpTv"></a>
## 3.3 vite插件
Vite 旨在为常见的 web 开发工作提供开箱即用的支持。你可以使用插件进行扩展功能。<br />地址:[https://github.com/vitejs/awesome-vite#plugins](https://github.com/vitejs/awesome-vite#plugins)<br />例如:为传统浏览器提供支持
1. 下载
```jsx
npm i -D @vitejs/plugin-legacy
```
2. 需要将它添加到项目的 devDependencies 并在 vite.config.js 配置文件中的 plugins 数组中引入它
```jsx
// vite.config.js
import legacy from '@vitejs/plugin-legacy'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11']
})
]
})
```
<a name="YkAyf"></a>
## 3.4 vite.config.js
1. 是否自动在浏览器打开
`open: true`
2. 在生产中服务时的基本公共路径
`base: './',`
3. 与“根”相关的目录,构建输出将放在其中。如果目录存在,它将在构建之前被删除
`outDir: 'dist'`
4. 更改默认端口
`port:8080,`
5. 开启热更新
`hmr:true`
6. 配置别名路径
```jsx
resolve:{
alias:{
"@":resolve(__dirname,"src"),
"@c":resolve(__dirname,"src/components"),
}
},
```
7. 设置便捷图片路径引用
```jsx
resolve:{
alias:{
"/img":"src/assets/images/"
//这里不能通过path模块解析路径的写法
}
},
```
8. 把vite打包以后的js,css和img资源分别分门别类在js/css/img文件夹中
```jsx
build:{
assetsDir:"static",
rollupOptions:{ //自定义底层的 Rollup 打包配置,从 Rollup 配置文件导出的选项相同
input:{
index:resolve(__dirname,"index.html"),
project:resolve(__dirname,"project.html")
},
output:{
chunkFileNames:'static/js/[name]-[hash].js',
entryFileNames:"static/js/[name]-[hash].js",
assetFileNames:"static/[ext]/name-[hash].[ext]"
}
},
},
```
8. 跨域
```jsx
server: {
port: '3000',
open: false, //自动打开
base: "./ ", //生产环境路径
proxy: { // 本地开发环境通过代理实现跨域,生产环境使用 nginx 转发
// 正则表达式写法
'^/api': {
target: 'http://xxx.xxx.xxx.xxx:9999', // 后端服务实际地址
changeOrigin: true, //开启代理
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
```
<a name="agldq"></a>
## 3.5 给项目配置多个环境
1. 在项目根目录下分别新建.env.development,.env.test,.env.production文件
```jsx
//.env.devopment文件内容
NODE_ENV="development"
VITE_APP_BASEAPI="https://www.dev.com"
//.env.test文件内容
NODE_ENV="test"
VITE_APP_BASEAPI="https://www.test.com"
//.env.production文件内容
NODE_ENV="production"
VITE_APP_BASEAPI="https://www.production.com"
```
2. package.json
```jsx
"scripts": {
"dev": "vite --mode development",
"build": "vite build --mode production",
"test": "vite build --mode test",
"preview": "vite preview"
},
```
3. 项目中通过Import.meta.env.VITE_APP_BASEAPI来获取对应环境的值
```jsx
<template>
<div>
<Item></Item>
</div>
</template>
<script setup>
import { defineComponent, onMounted, ref } from 'vue'
import Item from "@c/item.vue"
console.log("env", import.meta.env.VITE_APP_BASEAPI)
console.log("可选链", obj?.gender || "male")
})
</script>
```
<a name="pgCX0"></a>
# 4. npm init vite@latest做了什么?
<a name="HsCxP"></a>
## 4.1 npm init
npm init :初始化一个简单的package.json文件,执行该命令后终端会依次询问 name, version, description 等字段<br />查询[文档](https://docs.npmjs.com/cli/v7/commands/npm-init)得知:<br />**npm init <initializer>:**can be used to set up a new or existing npm package.(可以用来建立一个新的或现有的NPM包)
<a name="oLGN9"></a>
## 4.2 npm init <initializer>
initializer in this case is an npm package named create-<initializer>, which will be installed by [npm-exec](https://docs.npmjs.com/cli/v7/commands/npm-exec), and then have its main bin executed -- presumably creating or updating package.json and running any other initialization-related operations.<br />(initializer 在这种情况下是一个名为 create-<initializer> 的 npm 包,这个包将由 [npm-exec](https://link.juejin.cn?target=https%3A%2F%2Fdocs.npmjs.com%2Fcli%2Fv7%2Fcommands%2Fnpm-exec)(也就是 **npx**) 来安装,然后执行 package.json 中 bin 属性对应的脚本,运行任何其他与初始化相关的操作。
init命令转化为对应的npm exec操作如下:
- npm init foo->npm exec create-foo
- npm init @usr/foo->npm exec @usr/create-foo
- npm init @usr->npm exec @usr/create
由上面得知:<br />npm init vite@latest => npx create-vite
<a name="ExHkk"></a>
## 4.3 查询create-vite
通过 [npmjs.com](https://link.juejin.cn/?target=http%3A%2F%2Fnpmjs.com) 查找 create-vite 或在 Vite 源代码中的 [packages](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvitejs%2Fvite%2Ftree%2Fmain%2Fpackages) 文件夹(如下图)中可以看到有 [create-vite](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvitejs%2Fvite%2Ftree%2Fmain%2Fpackages%2Fcreate-vite)
<br />![create-vite1](/yifu-study-front-share/images/viteImg/vite9.png)
<br />![create-vite2](/yifu-study-front-share/images/viteImg/vite10.png)
<br />查看在 create-vite 文件夹下的 package.json 中发现 [bin 入口](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvitejs%2Fvite%2Fblob%2Fmain%2Fpackages%2Fcreate-vite%2Fpackage.json%23L7)
<br />![create-vite3](/yifu-study-front-share/images/viteImg/vite11.png)
<a name="OVWEq"></a>
#
---
title: 浅谈TypeScript命名空间
date: 2022/6/20 15:00:00
tags:
- 前端
- typescript
author: 杨靖
---
***命名空间简介***
命名空间(在早期版本的 TypeScript 中称为“内部模块”)是一种用于组织和分类代码的 TypeScript 特定方式,使你能够将相关代码组合在一起。 命名空间允许将与业务规则相关的变量、函数、接口或类分组到一个命名空间,将安全性分组到另一个命名空间。
命名空间内的代码将从全局范围拉入到命名空间范围。 这种布局有助于避免全局命名空间中组件之间的命名冲突,并且在与可能使用类似组件名称的分布式开发团队合作时也会有好处。
***命名空间特点***
- 减少全局范围内的代码量,限制“全局范围污染”。
- 为名称提供上下文,有助于减少命名冲突。
- 提高可重用性。
TypeScript 中命名空间使用 namespace 来定义,例如:
```typescript
namespace namespaceA{
export interface interfaceA{}
export class classA{}
function getUserInfo() {
let name = getName()
return console.log(name + ',I am 26 years old')
}
function getName() {
return 'my name is Yj'
}
}
interfaceA // false,没有添加命名空间名称前缀namespaceA
namespaceA.interfaceA // true
namespaceA.classA // true
namespaceA.getName // false,没有export关键字
namespaceA.getUserInfo // 'my name is Yj, I am 26 years old'
```
以上定义了一个命名空间 namespaceA,如果我们需要在外部可以调用 namespaceA中的类和接口,则需要在类和接口添加 export 关键字。命名空间中定义的所有组件的作用域都限定为命名空间,并从全局范围中删除
***使用嵌套命名空间组织代码***
在命名空间中嵌套命名空间,从而提供更多的选项来组织代码,个人理解类似于在一个对象里面找对象属性的属性。
```typescript
namespace namespaceA{
export namespaceB{
export functionA() {
namespaceC.functionB
}
}
export namespaceC{
export functionB() {
return console.log('这是命名空间c里面的函数b!')
}
export functionC() {
return console.log('这是命名空间C里面的函数C!')
}
}
}
namespaceA.namespaceB.functionA // 这是命名空间c里面的函数b!
namespaceA.namespaceC.functionC // 这是命名空间C里面的函数C!
```
***定义命名空间别名***
TypeScript 创建一个易于导航的嵌套命名空间层次结构。 但是,随着嵌套命名空间变得越来越复杂,你可能需要创建一个别名来缩短和简化代码。 为此,请使用 **import** 关键字。
```typescript
//namespaceA 和 namespaceB来自上面的代码
import nameA = namespaceA.namespaceB;
nameA.functionA(); // '这是命名空间c里面的函数b!'
```
***编译单一文件命名空间***
编译单一文件命名空间的方式与编译任何其他 TypeScript 文件的方式相同。 因为命名空间是一个只包含 TypeScript 的构造,所以会从生成的 JavaScript 代码中删除它们,并将其转换为必要时嵌套的变量,以形成类似命名空间的对象。
如果一个命名空间在一个单独的 TypeScript 文件中,则应使用三斜杠 /// 引用它,语法格式如下:
```typescript
/// <reference path = "SomeFileName.ts" />
```
***使用多文件命名空间来组织代码***
可以通过跨多个 TypeScript 文件共享命名空间来扩展它们。 如果在多个文件中具有彼此相关的命名空间,则必须添加 reference 标记,使 TypeScript 编译器知道文件之间的关系。 例如,假定你有三个 Typescript 文件:
- interfaces.ts,它声明包含某些接口定义的命名空间。
- functions.ts,该文件使用在 interfaces.ts 中实现接口的函数声明命名空间。
- main.ts,它调用 functions.ts 中的函数,并表示应用程序的主代码。
若要告知 TypeScript interfaces.ts 与 functions.ts 之间的关系,请在 functions.ts 顶部使用三斜杠 (///) 语法向 interfaces.ts 添加 reference。 然后,在与 interfaces.ts 和 functions.ts 关联的 main.ts 中,将 reference 添加到这两个文件。
![在这里插入图片描述](https://img-blog.csdnimg.cn/e3767edc69a3430ab1757cbfb332d884.png#pic_center)
如果对多个文件进行引用,请从最高级别的命名空间开始,然后逐渐向下。 在编译文件时,TypeScript 将使用此顺序。
***编译多文件命名空间***
编译多个文件命名空间的方法有两种:每文件编译和单一文件编译。
默认情况下,当你在 main.ts 上运行 TypeScript 编译器时,它将检查文件中的 reference 语句,并为每个输入文件生成一个 JavaScript 文件。 如果选择此选项,请使用网页上的 <script> 标记按相应顺序加载每个发出的文件。
还可以使用 --outFile 选项指示编译器生成单个 JavaScript 输出文件。 在上面的示例中,命令 tsc --outFile main.js main.ts 指示编译器生成一个名为 main.js 的 JavaScript 文件。
---
title: 浅谈跨域
date: 2022/8/30 10:54:30
tags:
- JSONP
- postMessage
- WebSocket
author: 张芳利
---
<a name="XDoI3"></a>
## 一、什么是跨域
要知道什么是跨域,需要先知道另一个名词【**同源政策**】。<br />同源政策:是一个重要的安全政策,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。<br />而什么才是同源呢?同源就是两个页面具有相同的协议,域名和端口 均一致。所以根据上面的了解,**源=协议+主机/域名+端口,当源不同的时候也就是跨域了**<br />例如:<br />
![crossDomain](/yifu-study-front-share/images/crossDomain/img1.png)
| **当前页面url** | **被请求页面url** | **是否跨域** | **原因** |
| --- | --- | --- | --- |
| http://www.test.com/ | http://www.test.com/index.html | 否 | 同源 |
| http://www.test.com/ | https://www.test.com/index.html | 是 | 协议不同(http/https) |
| http://www.test.com/ | http://www.baidu.com/ | 是 | 主域名不同(test/baidu) |
| http://www.test.com/ | http://blog.test.com/ | 是 | 子域名不同(www/blog) |
| http://www.test.com:8080 | http://www.test.com:7001/ | 是 | 端口号不同(8080/7001) |
**注意:**
> 在默认情况下 http 可以省略端口 80, https 省略 443。
<a name="C9fUE"></a>
## 二、跨域的限制
- Cookie,LocalStorage,IndexDB 等存储性内容无法读取
- DOM 节点无法访问
- Ajax 请求发出去了,但是响应被浏览器拦截了
基本我们说的跨域是在第三点请求上面。<br />ajax请求报错:
```jsx
Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been block by CORS,
policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
```
<a name="HSQ39"></a>
## 三、解决跨域
<a name="uTL8Z"></a>
### 方法一:JSONP
JSONP 是 JSON with Padding 的缩写,主要就是利用了 script 标签没有跨域限制的这个特性来完成的。<br />**思路:网页通过添加一个<script>元素,向服务器请求 JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。**<br />缺点:只支持get请求,不支持post请求。<br />(下面的接口都是聚合数据里面的免费接口,如果想实验测试一下可以自己更换一下接口地址哈~)<br />**1、原生js**
```jsx
<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
<script type="text/javascript">
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
</script>
//或
<script type="text/javascript">
window.jsonpCallback = function(res) {
console.log(res);
};
script>
<script
src="http://localhost:8080/api/jsonp?msg=hello&cb=jsonpCallback"
type="text/javascript"
>script>
```
**2、vue.js ** <br />2.1 安装jsonp,利用jsonp插件
```jsx
yarn add jsonp
import jsonp from "jsonp";
// 这里的url 是聚合免费api
export const getTest = () => {
jsonp('http://v.juhe.cn/joke/content/list.php?key=79255ba6ab74a79d35edb174142c309c&page=2&pagesize=10&sort=asc&time=1418745237',{},(err,data)=>{
console.log(err)
console.log(data)
})
}
```
![crossDomain](/yifu-study-front-share/images/crossDomain/img2.png)
2.2 自己封装一下jsonp 函数方法
```jsx
const jsonpFun = ({ url, params })=>{
return new Promise((resolve, reject) => {
// 创建一个临时的 script 标签用于发起请求
const script = document.createElement('script');
// 将回调函数临时绑定到 window 对象,回调函数执行完成后,移除 script 标签
window['jsonCallBack'] =(result) => {
resolve(result)
document.body.removeChild(script);
}
// 构造 GET 请求参数,
const formatParams = { ...params, callback: 'jsonCallBack' };
const requestParams = Object.keys(formatParams)
.reduce((acc, cur) => {
return acc.concat([`${cur}=${formatParams[cur]}`]);
}, [])
.join('&');
// 构造 GET 请求的 url 地址
const src = `${url}?${requestParams}`;
script.setAttribute('src', src);
script.setAttribute('type','text/javascript');
document.body.appendChild(script);
});
}
// 使用
export const getTest = () => {
jsonpFun({
url:'http://v.juhe.cn/joke/content/list.php',
params:{
key:'79255ba6ab74a79d35edb174142c309c',
page:2,
pagesize:10,
sort:'asc',
time:1418745237
}
}).then(res=>{
console.log(res);
})
}
```
2.3 给axios 上面拓展一个jsonp
```jsx
// 给axios添加jsonp请求
axios.jsonp = (url) => {
if(!url){
console.error('Axios.JSONP 至少需要一个url参数!')
return;
}
return new Promise((resolve,reject) => {
window.jsonCallBack =(result) => {
resolve(result)
}
var JSONP=document.createElement("script");
JSONP.type="text/javascript";
JSONP.src=`${url}&callback=jsonCallBack`;
document.getElementsByTagName("head")[0].appendChild(JSONP);
setTimeout(() => {
document.getElementsByTagName("head")[0].removeChild(JSONP)
},500)
})
}
// 使用
export const getTest = () => {
axios.jsonp('http://v.juhe.cn/joke/content/list.php?key=79255ba6ab74a79d35edb174142c309c&page=2&pagesize=10&sort=asc&time=1418745237').then(res=>{
console.log(res);
})
}
```
<a name="LNDy8"></a>
### 方法二:CORS
CORS是基于http1.1的一种跨域解决方案,它的全称是**C**ross-**O**rigin **R**esource **S**haring,跨域资源共享。<br />**思路:如果浏览器要跨域访问服务器的资源,需要获得服务器的允许**<br />缺点:当你使用 IE<=9, Opera<12, or Firefox<3.5 或者更加老的浏览器,就不适合 <br />在 cors 中会有 简单请求 和 复杂请求的概念。简单请求:当请求**同时满足**以下条件时,浏览器会认为它是一个简单请求:<br />情况一: 使用以下方法(意思就是以下请求意外的都是非简单请求)
- GET
- HEAD
- POST
情况二: 人为设置以下集合外的请求头
- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
情况三:Content-Type的值仅限于下列三者之一:(例如 application/json 为非简单请求)
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
情况四:<br />请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。<br />情况五:<br />请求中没有使用 ReadableStream 对象。
```jsx
// 简单请求
fetch('http://crossdomain.com/api/news');
// 请求方法不满足要求,不是简单请求
fetch('http://crossdomain.com/api/news', {
method: 'PUT',
});
// 加入了额外的请求头,不是简单请求
fetch('http://crossdomain.com/api/news', {
headers: {
a: 1,
},
});
// 简单请求
fetch('http://crossdomain.com/api/news', {
method: 'post',
});
// content-type不满足要求,不是简单请求
fetch('http://crossdomain.com/api/news', {
method: 'post',
headers: {
'content-type': 'application/json',
},
});
```
不满足上面的就是复杂请求。一般CORS的设置都是在后端的。
**1、**普通跨域请求:只需服务器端设置Access-Control-Allow-Origin<br />2、带cookie跨域请求:前后端都需要进行设置<br />**【前端设置】根据xhr.withCredentials字段判断是否带有cookie**
```jsx
// 原生ajax
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
//jQuery ajax
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
data: {},
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
});
// vue-resource
Vue.http.options.credentials = true
// axios
axios.defaults.withCredentials = true
```
<a name="EBefT"></a>
### 方法三:代理
**思路:利用服务端请求不会跨域的特性,让接口和当前站点同域。**<br />代理适用的场景是:生产环境不发生跨域,但开发环境发生跨域。<br />
![crossDomain](/yifu-study-front-share/images/crossDomain/img3.png)
像是vue中,一般是这么处理。现在常用的vue等框架都有解决方案。
```jsx
// 在webpack中可以配置proxy来快速获得接口代理的能力。
devServer: {
port: 8000,
proxy: {
"/api": {
target: "http://localhost:8080"
}
}
},
// Vue-cli 2.x
// config/index.js
...
proxyTable: {
'/api': {
target: 'http://localhost:8080',
}
},
...
// Vue-cli 3.x
module.exports = {
devServer: {
port: 8000,
proxy: {
"/api": {
target: "http://localhost:8080"
}
}
}
};
// 等等
```
<a name="qs5RQ"></a>
### 方法四:Nginx 反向代理
Nginx 实现原理类似于 Node 中间件代理,需要你搭建一个中转 nginx 服务器,用于转发请求。<br />使用 nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。<br />主要是后端进行nginx配置,就不讲了。
<a name="OoScp"></a>
### 方法五:websocket
Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。什么是全双工通信 ?简单来说,就是在建立连接之后,server 与 client 都能主动向对方发送或接收数据。<br />以第**三方库 ws** 为例:
```jsx
........
const WebSocket = require('ws');
const ws = new WebSocket('ws://www.host.com/path');
ws.on('open', function open() {
ws.send('something');
});
ws.on('message', function incoming(data) {
console.log(data);
});
... ...
```
<a name="BuN2e"></a>
### 方法六:window.postMessage()
此方法可以安全地实现跨源通信。window.postMessage() 方法允许来自一个文档的脚本可以传递文本消息到另一个文档里的脚本,而不用管是否跨域,可以用这种消息传递技术来实现安全的通信。这项技术称为“**跨文档消息传递**”,又称为“**窗口间消息传递**”或者“**跨域消息传递**”。<br />它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
**发送消息**
```jsx
otherWindow.postMessage(message, targetOrigin, [transfer]);
```
> **otherWindow**:其他窗口的一个引用。比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。
> message:要发送的消息。它将会被结构化克隆算法序列化,所以无需自己序列化,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化。
> **targetOrigin:**“目标域“。URI(包括:协议、主机地址、端口号)。若指定为”*“,则表示可以传递给任意窗口,指定为”/“,则表示和当前窗口的同源窗口。当为URI时,如果目标窗口的协议、主机地址或端口号这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会发送。
**接收消息**<br />如果指定的源匹配的话,那么当调用 postMessage() 方法的时候,在目标窗口的Window对象上就会触发一个 message 事件。<br />获取postMessage传来的消息:为页面添加onmessage事件。
```jsx
window.addEventListener('message',function(e) {
var origin = event.origin;
// 通常,onmessage()事件处理程序应当首先检测其中的origin属性,忽略来自未知源的消息
if (origin !== "http://example.org:8080") return;
// ...
}, false)
```
> event 的属性有:
> data: 从其他 window 传递过来的数据副本。
> origin: 调用 postMessage 时,消息发送窗口的 origin。例如:“http://example.com:8080”。
> source: 对发送消息的窗口对象的引用。可以使用此来在具有不同 origin 的两个窗口之间建立双向数据通信。
例如:
1. vue项目中,页面http://localhost:4000/=>http://localhost:4000/created 传数据
```jsx
// http://localhost:4000/
const gtoSend = () => {
// 发送
const targetWindow = window.open('http://localhost:4000/created');
setTimeout(()=>{
targetWindow.postMessage('你好,postMessage', 'http://localhost:4000/')
},100)
}
onMounted(() => {
window.addEventListener('message', (e) => {
console.log(e.data) //我收到信息了
})
})
//http://localhost:4000/created
// 接收
onMounted(() => {
window.addEventListener('message', (e) => {
console.log(e)
console.log(e.source); // e.source 发送消息的窗口 http://localhost:4000/
console.log(e.origin); // e.origin 消息发向的网址 http://localhost:4000/created
console.log(e.data); // e.data 发送的消息 你好,postMessage'
test.value = e.data
if (e.origin !== "http://localhost:4000") return;
e.source.postMessage('我收到消息了', e.origin)
},false)
})
```
2.iframe 窗口
```jsx
// http://localhost:4000/
<template>
<div class="home">
<button @click="changeFor" style="margin-top:20px">请求</button>
<div style="width:120%">
<iframe id="iframe"
src="http://localhost:4000/created"
style="width:100%;height:300px">
</iframe>
</div>
</template>
<script>
onMounted(() => {
window.addEventListener('message', (e) => {
console.log('4000页面',e.data) //4000页面 你好,我是created页面
})
})
var iframe = document.getElementById('iframe');
const changeFor = () => {
window.frames[0].postMessage('来首页的消息的消息', 'http://localhost:4000/created');
}
</script>
//http://localhost:4000/created
onMounted(() => {
window.addEventListener('message', (e) => {
console.log('created页面',e)
console.log(e.source); // e.source 发送消息的窗口
console.log(e.origin); // e.origin 消息发向的网址
console.log(e.data); // e.data 发送的消息
if (e.origin !== "http://localhost:4000") return;
e.source.postMessage('你好,我是created页面', e.origin)
},false)
})
```
![crossDomain](/yifu-study-front-share/images/crossDomain/img4.png)
其他方法暂时不提了,基本常用的就是上面6种了。💯
---
title: 用xlsx-style玩转Excel导出——拿走即用的Excel导出方法封装
date: 2022/5/24 09:15:20
tags:
- 前端
- xlsx-style
- JavaScript
author: 李燕
---
在《用xlsx-style玩转Excel导出——像手动操作Excel表格一样用JS操作Excel》一文中,我们详细介绍了xlsx-style插件相关的概念、属性和方法,今天,我们将利用xlsx-style插件封装出一个通用的、可自定义的Excel导出方法,以期能够满足日常Web项目的导出需求。
<!-- more -->
**一、常见自定义需求**
在正式开始封装之前,我们先梳理一下,我们Excel导出一般都有那些自定义需求。
1. 导出的Excel文件名自定义
2. 支持单sheet、多sheet导出
3. sheet名自定义
4. 表头内容自定义
5. 表格内容自定义
6. 表格样式自定义
7. 支持合并单元格
8. 支持冻结单元格
**二、抽象导出方法名及参数**
根据上述自定义需求,我们可以初步将Excel导出方法及参数定义如下:
```js
/**
* 自定义导出excel
* @param { 导出的数据 } exportData
* @param { 工作簿配置 } workBookConfig
* @param { 导出文件名 } fileName
*/
function exportExcel(exportData, workBookConfig = defaultWorkBook, fileName = '未命名') {
}
```
**三、参数介绍及处理**
(一)exportData
参数exportData是一个对象数组,可以满足单sheet、多sheet的导出,同时可以配置每个sheet的表头、表格内容、样式等,格式如下:
```js
[
{
header: [] // 表头
dataSource: [], // 数据源
workSheetConfig: {}, // 工作表配置
cellConfig: {} // 单元格配置
sheetName: '' // sheet名
}
]
```
+ header
header是一个对象数组,用来设置表头,格式如下:
```js
[
{ title: '分站', dataIndex: 'subName', width: 140 },
{ title: '企业总数', dataIndex: 'enterpriseCount', width: 140 },
]
```
接收到header后,需将header内容处理成表格语言,具体如下:
```js
const _header = header.map((item, i) =>
Object.assign({}, {
key: item.dataIndex,
title: item.title,
// 定位单元格
position: getCharCol(i) + 1,
// 设置表头样式
s: data.cellConfig && data.cellConfig.headerStyle ? data.cellConfig.headerStyle : defaultCellStyle.headerStyle,
})
).reduce((prev, next) =>
Object.assign({}, prev, {
[next.position]: { v: next.title, key: next.key,s: next.s },
}), {},
)
```
header已经被处理成worksheet Object,而其中很重要一步,就是定位单元格,而我们Excel是用字母+数字的形式定位单元格的,所以我们可以通过如下代码定位单元格。
```js
position: getCharCol(i) + 1
```
其中,getCharCol()方法是用来生成单元格对应的字母的,代码如下:
```js
/**
* 生成ASCll值 从A开始
* @param {*} n
*/
function getCharCol(n) {
if( n > 25) {
let s = ''
let m = 0
while (n > 0) {
m = n % 26 + 1
s = String.fromCharCode(m + 64) + s
n = (n - m) / 26
}
return s
}
return String.fromCharCode(65 + n)
}
```
+ cellConfig
cellConfig是一个对象,用来配置单元格的样式,格式如下:
```js
{
headerStyle: { // 表头区域样式配置
border: {}
font: {},
alignment: {},
fill: {},
},
dataStyle: { // 内容样式配置
border: {}
font: {},
alignment: {},
fill: {},
}
}
```
+ dataSource
dataSource是一个对象数组,用来表示表格内容,格式如下:
```js
[
{ subName'合肥' },
{ enterpriseCount: '100' },
]
```
同样,接收到dataSource后,需将dataSource内容处理成表格语言,具体如下:
```js
let _data = {};
// 处理表格内容及样式
dataSource.forEach((item, i) => {
header.forEach((obj, index) => {
const key = getCharCol(index) + (i + 2);
const key_t = obj.dataIndex;
_data[key] = {
v: item[key_t],
s: cellConfig.dataStyle? cellConfig.dataStyle : defaultCellStyle.dataStyle
}
})
});
```
+ workSheetConfig
workSheetConfig是一个对象,用来配置工作表的合并单元格、冻结单元格等功能,格式如下:
```js
{
merges: [ // 合并单元格
{ s: { c: 1, r: 1 },e: { c: 3, r: 3 } }
],
freeze{ // 冻结单元格
xSplit: '1',
ySplit: '1',
topLeftCell: 'B2',
state: 'frozen',
}
}
```
(二)workBookConfig
workBookConfig是一个对象,用来配置工作簿的类型等,格式如下:
```js
{
bookType: 'xlsx', // 工作簿的类型
bookSST: false,
type: 'binary', // 输出数据类型
}
```
**四、生成workbook object**
```js
const wb = {SheetNames: [], Sheets: {}}
exportData.forEach( (data, index) => {
const output = Object.assign({}, _headers, _data);
const outputPos = Object.keys(output);
// 设置单元格宽度
const colWidth = data.header.map( item => { wpx: item.width || 60 })
const merges = data.workSheetConfig && data.workSheetConfig.merges
const freeze = data.workSheetConfig && data.workSheetConfig.freeze
// 处理sheet名
wb.SheetNames[index] = data.sheetName ? data.sheetName : 'Sheet' + (index + 1)
// 处理sheet数据
wb.Sheets[wb.SheetNames[index]] = Object.assign({},
output, //导出的内容
{
'!ref': `${outputPos[0]}:${outputPos[outputPos.length - 1]}`,
'!cols': [...colWidth],
'!merges': merges ? [...merges] : undefined,
'!freeze': freeze ? [...freeze] : undefined,
}
)
})
```
**五、new Blob转换成二进制类型的对象**
```js
const tmpDown = new Blob(
[ s2ab( XLSX.write(wb, workBookConfig))],
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}
)
// 字符串转字符流---转化为二进制的数据流
function s2ab(s) {
if (typeof ArrayBuffer !== 'undefined') {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
return buf;
} else {
const buf = new Array(s.length);
for (let i = 0; i !== s.length; ++i) buf[i] = s.charCodeAt(i) & 0xff;
return buf;
}
}
```
**六、下载Excel**
```js
downExcel(tmpDown, `${fileName + '.'}${workBookConfig.bookType == 'biff2' ? 'xls' : workBookConfig.bookType}`)
function downExcel(obj, fileName) {
const a_node = document.createElement('a');
a_node.download = fileName;
// 兼容ie
if ('msSaveOrOpenBlob' in navigator) {
window.navigator.msSaveOrOpenBlob(obj, fileName);
} else {
// 新的对象URL指向执行的File对象或者是Blob对象.
a_node.href = URL.createObjectURL(obj);
}
a_node.click();
setTimeout(() => {
URL.revokeObjectURL(obj);
}, 100);
}
```
**七、默认配置**
```js
// 默认工作簿配置
const defaultWorkBook = {
bookType: 'xlsx',
bookSST: false,
type: 'binary',
}
// 默认样式配置
const borderAll = { //单元格外侧框线
top: {
style: 'thin',
},
bottom: {
style: 'thin'
},
left: {
style: 'thin'
},
right: {
style: 'thin'
}
};
const defaultCellStyle = {
headerStyle: { // 表头区域样式配置
border: borderAll
font: { name: '宋体', sz: 11, italic: false, underline: false, bold: true },
alignment: { vertical: 'center', horizontal: 'center'},
fill: { fgColor: { rgb: 'FFFFFF' } },
},
dataStyle: { // 内容样式配置
border: borderAll
font: { name: '宋体', sz: 11, italic: false, underline: false },
alignment: { vertical: 'center', horizontal: 'left', wrapText: true},
fill: { fgColor: { rgb: 'FFFFFF' } },
}
}
```
**八、完整代码**
```js
import XLSX from 'xlsx-style'
// 默认工作簿配置
const defaultWorkBook = {
bookType: 'xlsx',
bookSST: false,
type: 'binary',
}
// 默认样式配置
const borderAll = {
top: {
style: 'thin',
},
bottom: {
style: 'thin',
},
left: {
style: 'thin',
},
right: {
style: 'thin',
},
}
const defaultCellStyle = {
// 表头区域样式配置
headerStyle: {
border: borderAll,
font: { name: '宋体', sz: 11, italic: false, underline: false, bold: true },
alignment: { vertical: 'center', horizontal: 'center' },
fill: { fgColor: { rgb: 'FFFFFF' } },
},
// 内容区域样式配置
dataStyle: {
border: borderAll,
font: { name: '宋体', sz: 11, italic: false, underline: false },
alignment: { vertical: 'center', horizontal: 'left', wrapText: true },
fill: { fgColor: { rgb: 'FFFFFF' } },
},
}
function exportExcel(exportData, workBookConfig = defaultWorkBook, fileName = '未命名') {
if (!(exportData && exportData.length)) {
return
}
// 定义工作簿对象
const wb = { SheetNames: [], Sheets: {} }
exportData.forEach((data, index) => {
// 处理sheet表头
const _header = data.header.map((item, i) =>
Object.assign({}, {
key: item.dataIndex,
title: item.title,
// 定位单元格
position: getCharCol(i) + 1,
// 设置表头样式
s: data.cellConfig && data.cellConfig.headerStyle ? data.cellConfig.headerStyle : defaultCellStyle.headerStyle,
})
).reduce((prev, next) =>
Object.assign({}, prev, {
[next.position]: { v: next.title, key: next.key, s: next.s },
}), {}
)
// 处理sheet内容
const _data = {}
data.dataSource.forEach((item, i) => {
data.header.forEach((obj, index) => {
const key = getCharCol(index) + (i + 2)
const key_t = obj.dataIndex
_data[key] = {
v: item[key_t],
s: data.cellConfig && data.cellConfig.dataStyle ? data.cellConfig.dataStyle : defaultCellStyle.dataStyle,
}
})
})
const output = Object.assign({}, _header, _data)
const outputPos = Object.keys(output)
// 设置单元格宽度
const colWidth = data.header.map(item => { return { wpx: item.width || 80 } })
const merges = data.workSheetConfig && data.workSheetConfig.merges
const freeze = data.workSheetConfig && data.workSheetConfig.freeze
// 处理sheet名
wb.SheetNames[index] = data.sheetName ? data.sheetName : 'Sheet' + (index + 1)
// 处理sheet数据
wb.Sheets[wb.SheetNames[index]] = Object.assign({},
output, // 导出的内容
{
'!ref': `${outputPos[0]}:${outputPos[outputPos.length - 1]}`,
'!cols': [...colWidth],
'!merges': merges ? [...merges] : undefined,
'!freeze': freeze ? [...freeze] : undefined,
}
)
})
// 转成二进制对象
const tmpDown = new Blob(
[s2ab(XLSX.write(wb, workBookConfig))],
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
)
// 下载表格
downExcel(tmpDown, `${fileName + '.'}${workBookConfig.bookType === 'biff2' ? 'xls' : workBookConfig.bookType}`)
}
/**
* 生成ASCll值 从A开始
* @param {*} n
*/
function getCharCol(n) {
if (n > 25) {
let s = ''
let m = 0
while (n > 0) {
m = n % 26 + 1
s = String.fromCharCode(m + 64) + s
n = (n - m) / 26
}
return s
}
return String.fromCharCode(65 + n)
}
// 字符串转字符流---转化为二进制的数据流
function s2ab(s) {
if (typeof ArrayBuffer !== 'undefined') {
const buf = new ArrayBuffer(s.length)
const view = new Uint8Array(buf)
for (let i = 0; i !== s.length; ++i) { view[i] = s.charCodeAt(i) & 0xff }
return buf
} else {
const buf = new Array(s.length)
for (let i = 0; i !== s.length; ++i) { buf[i] = s.charCodeAt(i) & 0xff }
return buf
}
}
function downExcel(obj, fileName) {
const a_node = document.createElement('a')
a_node.download = fileName
// 兼容ie
if ('msSaveOrOpenBlob' in navigator) {
window.navigator.msSaveOrOpenBlob(obj, fileName)
} else {
// 新的对象URL指向执行的File对象或者是Blob对象.
a_node.href = URL.createObjectURL(obj)
}
a_node.click()
setTimeout(() => {
URL.revokeObjectURL(obj)
}, 100)
}
export {
exportExcel,
}
```
This image diff could not be displayed because it is too large. You can view the blob instead.
This image diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment