Canvas 是 HTML5 提供的一个用于展示绘图效果的标签. Canvas 原意为画布, 在 HTML 页面中用于展示绘图效果. 最早 Canvas 是苹果提出的一个方案, 今天已经在大多数浏览器中实现。
canvas 的使用领域
游戏
大数据可视化数据
banner 广告
多媒体
模拟仿真
远程操作
图形编辑
判断浏览器是否支持 canvas 标签
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
console.log('你的浏览器支持Canvas!')
} else {
console.log('你的浏览器不支持Canvas!')
}
canvas 的基本用法
1、使用 canvas 标签, 即可在页面中开辟一格区域,可以设置其宽高,宽高为 300 和 150
<canvas></canvas>
2、获取 dom 元素 canvas
canvas 本身不能绘图. 是使用 JavaScript 来完成绘图. canvas 对象提供了各种绘图用的 api。
var cas = document.querySelector('canvas')
3、通过 cas 获取上下文对象(画布对象!)
var ctx = cas.getContext('2d')
4、通过 ctx 开始画画(设置起点 设置终点 连线-描边 )
ctx.moveTo(10, 10)
ctx.lineTo(100, 100)
ctx.stroke()
绘制线条
设置开始位置: context.moveTo( x, y )
设置终点位置: context.lineTo( x, y )
描边绘制: context.stroke()
填充绘制: context.fill()
闭合路径: context.closePath()
canvas 还可以设置线条的相关属性,如下:
CanvasRenderingContext2D.lineWidth 设置线宽.
CanvasRenderingContext2D.strokeStyle 设置线条颜色.
CanvasRenderingContext2D.lineCap 设置线末端类型,'butt'( 默认 ), 'round', 'square'.
CanvasRenderingContext2D.lineJoin 设置相交线的拐点, 'miter'(默认),'round', 'bevel',
CanvasRenderingContext2D.getLineDash() 获得线段样式数组.
CanvasRenderingContext2D.setLineDash() 设置线段样式.
CanvasRenderingContext2D.lineDashOffset 绘制线段偏移量.
封装一个画矩形的方法
function myRect(ctxTmp, x, y, w, h) {
ctxTmp.moveTo(x, y)
ctxTmp.lineTo(x + w, y)
ctxTmp.lineTo(x + w, y + h)
ctxTmp.lineTo(x, y + h)
ctxTmp.lineTo(x, y)
ctxTmp.stroke()
}
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
myRect(ctx, 50, 50, 200, 200)
绘制矩形
fillRect( x , y , width , height) 填充以(x,y)为起点宽高分别为 width、height 的矩形 默认为黑色
stokeRect( x , y , width , height) 绘制一个空心以(x,y)为起点宽高分别为 width、height 的矩形
clearRect( x, y , width , height ) 清除以(x,y)为起点宽高分别为 width、height 的矩形 为透明
绘制圆弧
绘制圆弧的方法有
CanvasRenderingContext2D.arc()
CanvasRenderingContext2D.arcTo()
6 个参数: x,y(圆心的坐标),半径,起始的弧度(不是角度 deg),结束的弧度,(bool 设置方向 ! )
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.arc(100, 100, 100, 0, degToArc(360))
ctx.stroke()
// 角度转弧度
function degToArc(num) {
return (Math.PI / 180) * num
}
绘制扇形
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.arc(300, 300, 200, degToArc(125), degToArc(300))
// 自动连回原点
ctx.closePath()
ctx.stroke()
function degToArc(num) {
return (Math.PI / 180) * num
}
制作画笔
声明一个变量作为标识
鼠标按下的时候,记录起点位置
鼠标移动的时候,开始描绘并连线
鼠标抬起的时候,关闭开关
点击查看效果图
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
var isDraw = false
// 鼠标按下事件
cas.addEventListener('mousedown', function () {
isDraw = true
ctx.beginPath()
})
// 鼠标移动事件
cas.addEventListener('mousemove', function (e) {
if (!isDraw) {
// 没有按下
return
}
// 获取相对于容器内的坐标
var x = e.offsetX
var y = e.offsetY
ctx.lineTo(x, y)
ctx.stroke()
})
cas.addEventListener('mouseup', function () {
// 关闭开关了!
isDraw = false
})
手动涂擦
原理和画布相似,只不过用的是clearRect()方法。
点击查看效果图
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.fillRect(0, 0, 600, 600)
// 开关
var isClear = false
cas.addEventListener('mousedown', function () {
isClear = true
})
cas.addEventListener('mousemove', function (e) {
if (!isClear) {
return
}
var x = e.offsetX
var y = e.offsetY
var w = 20
var h = 20
ctx.clearRect(x, y, w, h)
})
cas.addEventListener('mouseup', function () {
isClear = false
})
刮刮乐
首先需要设置奖品和画布,将画布置于图片上方盖住,
随机设置生成奖品。
当手触摸移动的时候,可以擦除部分画布,露出奖品区。
点击查看效果图
<div>
<img src="./images/2.jpg" alt="" />
<canvas width="600" height="600"></canvas>
</div>
css
img {
width: 600px;
height: 600px;
position: absolute;
top: 10%;
left: 30%;
}
canvas {
width: 600px;
height: 600px;
position: absolute;
top: 10%;
left: 30%;
border: 1px solid #000;
}
js
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
var img = document.querySelector('img')
// 加一个遮罩层
ctx.fillStyle = '#ccc'
ctx.fillRect(0, 0, cas.width, cas.height)
setImgUrl()
// 开关
var isClear = false
cas.addEventListener('mousedown', function () {
isClear = true
})
cas.addEventListener('mousemove', function (e) {
if (!isClear) {
return
}
var x = e.offsetX
var y = e.offsetY
ctx.clearRect(x, y, 30, 30)
})
cas.addEventListener('mouseup', function () {
isClear = false
})
function setImgUrl() {
var arr = ['./images/1.jpg', './images/2.jpg', './images/3.jpg', './images/4.jpg']
// 0-3
var random = Math.round(Math.random() * 3)
img.src = arr[random]
}
更多demo,请查看 github.com/Michael-lzg…
简单来说,v-if 的初始化较快,但切换代价高;v-show 初始化慢,但切换成本低
都是动态显示DOM元素
(1)手段:
v-if是动态的向DOM树内添加或者删除DOM元素;
v-show是通过设置DOM元素的display样式属性控制显隐;
(2)编译过程:
v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;
v-show只是简单的基于css切换;
(3)编译条件:
v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载);
v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
(4)性能消耗:
v-if有更高的切换消耗;
v-show有更高的初始渲染消耗;
(5)使用场景:
v-if适合运营条件不大可能改变;
v-show适合频繁切换。
虽然Vue 3还没有正式发布,但是热爱新技术的我早已按捺不住自己的内心,开始尝试在小项目中使用它了。
根据这篇《今日凌晨Vue3 beta版震撼发布,竟然公开支持脚手架项目!》我搭建了一个Vue 3的脚手架项目,用这种方式搭建的脚手架项目不仅仅只有vue是新版的,就连vue-router、vuex都是的。
给大家截一下package.json的图:
可以看到vue-router和vuex都已经开启4.0时代啦!
不过其实我并没有去了解过vue-router 4.0的新用法什么的,因为我觉得它不像vue 3.0都已经进行到beta的版本不会有特别大的变动。
而vue-router 4.0还是alpha的阶段,所以我认为现在去学习它有些为时尚早。但却就是它!差点酿成了一场惨剧。
旧版vue + vue-router的使用方式
假如你在路由里面定义了一个动态参数通常都会这么写:
{
path: '/:id'
}
然后用编程式导航的时候通常会这样去写:
this.$router.push('/123')
在组件中是这样获取这个参数的:
this.$route.params.id
我以为的新版vue + vue-router的使用方式
由于vue 3.0的Composition API中没有this了,所以我想到了通过获取组件实例的方式来获取$route:
import { defineComponent, getCurrentInstance } from 'vue'
export default defineComponent((props, context) => {
const { ctx } = getCurrentInstance()
console.log(ctx.$route)
})
没想到打印出来的居然是undefined!
这是咋回事呢?
于是我又打印了一遍ctx(ctx是当前组件上下文):
没有$的那些字段是我在组件中自己定义的变量,带$的这些就是vue内置的了,找了半天发现没有$route了,只剩下了一个$router,估计vue-router 4.0把当前路由信息都转移到$router里面去了。
带着猜想,我点开了$router:
currentRoute! 看名字的话感觉应该就是它了!于是乎我:
import { defineComponent, getCurrentInstance } from 'vue'
export default defineComponent((props, context) => {
const { ctx } = getCurrentInstance()
console.log(ctx.$router.currentRoute.value.params.id)
})
果然获取到了!好开心!
实际的新版vue + vue-router用法
在接下来的过程中我用ctx.$router代替了原来的this.$router、用ctx.$router.currentRoute.value代替了原先的this.$route。
尽管在接下来的进度中并没有出现任何的bug,程序一直都是按照我所设想的那样去运行的。
但在项目打包后却出现了意想不到的bug:在跳转路由的时候报了一个在undefined上面没有push的错误。
奇了怪了,在开发阶段程序都没有任何的报错怎么一打包就不行了呢?根据我多年的开发经验,我很快就定位到了是vue-router的错误。
难道这样写是错的吗?可是我打印了ctx,它里面明明有一个$router、$router里面明明就有currentRoute、currentRoute里面明明就有一个value、value里面明明就有params、params里面我一点开明明就看到了传过来的参数啊:
估计可能是vue-router的bug,果然alpha阶段的产物不靠谱,我开始后悔使用新版的vue脚手架项目了。
vue-router里的hooks
不过这时我突然灵光一现,vue 3不是受到了react hooks的启发才产生了Composition API的吗?
那么估计vue-router肯定也会受到react-router的启发了!
还好我学过react,果然技多不压身啊!估计里面肯定是有一个useXxx,就像这样:
import { useXxx } from 'vue-router'
那么应该是use什么呢?按理来说应该会尽量的和以前的API保持一定的联系,我猜应该是useRoute和useRouter吧!
为了验证我的想法,我打开了node_modules找到了vue-router的源码:
果不其然,在第2454和第2455行我发现它导出了useRoute和useRouter,那么就是它了:
import { defineComponent } from 'vue'
import { useRoute, useRouter } from 'vue-router'
export default defineComponent(_ => {
const route = useRoute()
const router = useRouter()
console.log(route.params.id)
router.push('/xxx/xxx')
})
使用这种方式不但可以成功跳转路由,也同样可以获取到路由传过来的参数,这次再打包试了一下,果然就没有之前的那个报错了。
结语
估计以后的vue全家桶要开启全民hooks的时代了,在翻看源码的同时我发现他们把一些示例都写在了vue-router/playground文件夹下了,在里面我发现了一些有趣的用法。
如果有时间的话我会仔细研究一下然后出一篇更加深入的文章给大家,当然如果已经有小伙伴等不及我出新文章的话可以直接进入vue-router-next的github地址:
https://github.com/vuejs/vue-router-next
它的示例都放在了playground这个文件夹下,期待你们研究明白后出一篇更加深入的文章!
最近碰到个需要自动生成表格的任务,作为前端的我,就想在 node 和浏览器中生成强大的表格,所以特此研究了很多关于表格的 npm 库
支持读写 Excel 的 node.js 模块
node-xlsx: 基于 Node.js 解析 excel 文件数据及生成 excel 文件,仅支持 xlsx 格式文件
js-xlsx: 目前 Github 上 star 数量最多的处理 Excel 的库,支持解析多种格式表格 XLSX / XLSM / XLSB / XLS / CSV,解析采用纯 js 实现,写入需要依赖 nodejs 或者 FileSaver.js 实现生成写入 Excel,可以生成子表 Excel,功能强大,但上手难度稍大。不提供基础设置 Excel 表格 api 例单元格宽度,文档有些乱,不适合快速上手;普通版本不支持定义字体、颜色、背景色等,有这个功能需要的可以使用 pro 版,是要联系客服收费的,害我照着 API 设置调试了好多次都失败。好在样式设置问题有一些教程,通过研究本人已解决,可设置宽度颜色等等,见根目录本人修改的 xlsx.js
xlsx-style 基于 xlsx 封装的样式库,可以在 xlsx 的基础上设置样式。样式不全,宽度都设置不了,好多年前作者就不维护了.宽度设置问题本人已解决了,见修改的 xlsx-style.js 文件
exceljs 在使用此库之前,本人已花费了很大的精力,用以上库做好了表格,但是发现不能设置页眉页脚,添加图片,打印选项设置等等,直到发现了这个库,文档齐全,功能强大,并且还免费.但是star较少,差一点就错过了。本教程主要针对这个库
代码库地址
https://github.com/lingxiaoyi/excel
安装
npm install
npm install -g nodemon
调试使用,替代 node 命令,实现保存文件,node 自动重新启动执行,必须全局安装才能运行
使用
nodemon app.js
js-xlsx 具体 api 使用方法请参考 main.js demo 使用,app.js 中修改为 require('./src/main.js');
exceljs 具体 api 使用方法请参考 main-exceljs.js demo 使用,app.js 中修改为 require('./src/main-exceljs.js');
因为每次生成完表格,每次都需要打开表格查看样式,在 windows 电脑中,打开表格之后就锁定不能生成新文件了,本来想着能导出一个 html 文件对应表格的样式
node 调试
vscode 中打开调试右侧设置编辑,将下方代码复制进去,点 nodemon 启动就可以进行 debug 调试了
{
"type": "node",
"request": "launch",
"name": "nodemon",
"runtimeExecutable": "nodemon",
"program": "${workspaceFolder}/app.js",
"restart": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": ["<node_internals>/**"]
},
webpack 目录的作用
每次生成完新表格,都需要重新打开表格查看样式,在 windows 电脑中,打开表格之后就锁定了,再次生成新表格就会报错,文件已锁定,不能写入,对于想偷懒的我,能不能实现像 webpack 热更新功能那种,修改样式 js 页面自动更新呢?
wps 自带另存 html 文件功能,但是没有提供生成的 api ,网上也搜索不到对应的转换功能,
本来以为自己要实现一套表格转 html 的功能。通过不断尝试,偶然间发现手机浏览器可以直接打开预览 xlsx 文件,内心狂喜啊
使用方法
进入 webpack 目录安装依赖包,安装好之后执行
npm run dev
启动成功之后,会自动打开带有 ip 地址的预览地址,此时在电脑浏览器会自动下载 xlsx 文件,忽略不管,用手机直接打开此地址,就能看到 xlsx 表格的内容了,并且每次新修改内容和样式,都会自动刷新页面显示新表格.
小技巧
谷歌浏览器插件:
生成二维码的插件生成二维码方便手机扫描
划词翻译 用来翻译一些看不懂的英文文档
browser 目录
浏览器中实现生成 xlsx 表格方法
进入 browser 目录安装依赖包,安装好之后执行
npm run dev
启动成功之后,拖动根目录 src 下的李四表格到页面上的输入框里,成功生成表格之后会生成一个下载链接地址,右键在新标签页打开链接,即会生成一个新的表格文件出来,完整 api 使用和 demo 文件请参考 index.js
vue 和 react 用法可以参考此例子,如果有必要也可以此版本库的例子
一些概念
在使用这个库之前,先介绍库中的一些概念。
workbook 对象,指的是整份 Excel 文档。我们在使用 js-xlsx 读取 Excel 文档之后就会获得 workbook 对象。
worksheet 对象,指的是 Excel 文档中的表。我们知道一份 Excel 文档中可以包含很多张表,而每张表对应的就是 worksheet 对象。
cell 对象,指的就是 worksheet 中的单元格,一个单元格就是一个 cell 对象。
xlsx 使用注意事项
constXLSX = require('xlsx');
let html = XLSX.utils.sheet_to_html(workbook.Sheets.Sheet1)
生成 html 的用法,并且不会有任何样式
exceljs 使用注意
读取文件问题
因为 exceljs 读取文件不支持 sync 同步读取,给的实例也是 await 例子.导致我读取完遇到一个问题,就是老是生成不成功,最后发现必须要把所有逻辑全部放入函数中,像下方这样
(async function (params) {
let res = await workbook.xlsx.readFile(`${__dirname}/赵六.xlsx`);
//执行所有数据处理逻辑
//执行写的逻辑
workbook.xlsx.writeFile(path.resolve(__dirname, '../webpack/test222.xlsx'));
});
所有逻辑全部要写入这个函数中,这样本来是可以的,但是出错调试几率较大,并且读取到的数据庞大还需要额外处理,所以我读取数据逻辑就用的 node-xlsx,十分简单方便,如果你用的 exceljs 读取文件数据出现问题,大概率是异步同步逻辑搞错了,多加注意即可
宽度设置
列宽不知道是以什么为单位,反正不是像素(已测量),例子中是以厘米为单位再乘以 4.7 的结果设置的,4.7 是不断测试的结果.
快捷查看列宽的方法,打开 wps 表格,长按列与列字母间的竖线,就能看到列宽,取厘米的单位即可.见下图
前景色
前景色设置必须右键单元格选择设置单元格格式,然后选择图案样式选择颜色,就可以前景色填充
worksheet.getCell('A2').fill = { type: 'pattern', pattern:'darkTrellis', fgColor:{argb:'FFFFFF00'}, bgColor:{argb:'FF0000FF'} };
背景色
worksheet.getCell('A2').fill = { type: "pattern", pattern: "solid", fgColor: { argb: next.bgColor }, }
排版不一致的问题
解决 Mac 下编辑 Microsoft Office Word 文档与 Windows 排版不一致的问题,,不同的系统用 wps 打开相同的表格,打印预览的时候,表格宽度显示不一样
问题详细说明地址
我的解决办法就是 mac 下显示正常,按 mac 下的宽度来设置就可以了
参考资料
exceljs
node-xlsx
js-xlsx
函数节流与函数防抖是我们解决频繁触发DOM事件的两种常用解决方案,但是经常傻傻分不清楚。。。这不,在项目中又用遇到了,在此处记录一下
函数防抖 debounce
原理:将若干函数调用合成为一次,并在给定时间过去之后,或者连续事件完全触发完成之后,调用一次(仅仅只会调用一次!!!!!!!!!!)。
举个栗子:滚动scroll事件,不停滑动滚轮会连续触发多次滚动事件,从而调用绑定的回调函数,我们希望当我们停止滚动的时,才触发一次回调,这时可以使用函数防抖。
原理性代码及测试:
// 给盒子较大的height,容易看到效果
<style>
* {
padding: 0;
margin: 0;
}
.box {
width: 800px;
height: 1200px;
}
</style>
<body>
<div class="container">
<div class="box" style="background: tomato"></div>
<div class="box" style="background: skyblue"></div>
<div class="box" style="background: red"></div>
<div class="box" style="background: yellow"></div>
</div>
<script>
window.onload = function() {
const decounce = function(fn, delay) {
let timer = null
return function() {
const context = this
let args = arguments
clearTimeout(timer) // 每次调用debounce函数都会将前一次的timer清空,确保只执行一次
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
let num = 0
function scrollTap() {
num++
console.log(看看num吧 ${num}
)
}
// 此处的触发时间间隔设置的很小
document.addEventListener('scroll', decounce(scrollTap, 500))
// document.addEventListener('scroll', scrollTap)
}
</script>
</body>
此处的触发时间间隔设置的很小,如果匀速不间断的滚动,不断触发scroll事件,如果不用debounce处理,可以发现num改变了很多次,用了debounce函数防抖,num在一次上时间的滚动中只改变了一次。
调用debouce使scrollTap防抖之后的结果:
直接调用scrollTap的结果:
补充:浏览器在处理setTimeout和setInterval时,有最小时间间隔。
setTimeout的最短时间间隔是4毫秒;
setInterval的最短间隔时间是10毫秒,也就是说,小于10毫秒的时间间隔会被调整到10毫秒。
事实上,未优化时,scroll事件频繁触发的时间间隔也是这个最小时间间隔。
也就是说,当我们在debounce函数中的间隔事件设置不恰当(小于这个最小时间间隔),会使debounce无效。
函数节流 throttle
原理:当达到了一定的时间间隔就会执行一次;可以理解为是缩减执行频率
举个栗子:还是以scroll滚动事件来说吧,滚动事件是及其消耗浏览器性能的,不停触发。以我在项目中碰到的问题,移动端通过scroll实现分页,不断滚动,我们不希望不断发送请求,只有当达到某个条件,比如,距离手机窗口底部150px才发送一个请求,接下来就是展示新页面的请求,不停滚动,如此反复;这个时候就得用到函数节流。
原理性代码及实现
// 函数节流 throttle
// 方法一:定时器实现
const throttle = function(fn,delay) {
let timer = null
return function() {
const context = this
let args = arguments
if(!timer) {
timer = setTimeout(() => {
fn.apply(context,args)
clearTimeout(timer)
},delay)
}
}
}
// 方法二:时间戳
const throttle2 = function(fn, delay) {
let preTime = Date.now()
return function() {
const context = this
let args = arguments
let doTime = Date.now()
if (doTime - preTime >= delay) {
fn.apply(context, args)
preTime = Date.now()
}
}
}
需要注意的是定时器方法实现throttle方法和debounce方法的不同:
在debounce中:在执行setTimeout函数之前总会将timer用setTimeout清除,取消延迟代码块,确保只执行一次
在throttle中:只要timer存在就会执行setTimeout,在setTimeout内部每次清空这个timer,但是延迟代码块已经执行啦,确保一定频率执行一次
我们依旧可以在html页面中进行测试scroll事件,html和css代码同debounce,此处不赘述,运行结果是(可以说是一场漫长的滚轮滚动了):
最后再来瞅瞅项目中封装好的debounce和throttle函数,可以说是很优秀了,考虑的特别全面,希望自己以后封装的函数也能考虑的这么全面吧,加油!
/*
空闲控制 返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
@param {function} func 传入函数,最后一个参数是额外增加的this对象,.apply(this, args) 这种方式,this无法传递进函数
@param {number} wait 表示时间窗口的间隔
@param {boolean} immediate 设置为ture时,调用触发于开始边界而不是结束边界
@return {function} 返回客户调用函数
/
const debounce = function(func, wait, immediate) {
let timeout, args, context, timestamp, result;
const later = function() {
// 据上一次触发时间间隔
let last = Number(new Date()) - timestamp;
// 上次被包装函数被调用时间间隔last小于设定时间间隔wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.call(context, ...args, context);
if (!timeout) {
context = args = null;
}
}
}
};
return function(..._args) {
context = this;
args = _args;
timestamp = Number(new Date());
const callNow = immediate && !timeout;
// 如果延时不存在,重新设定延时
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (callNow) {
result = func.call(context, ...args, context);
context = args = null;
}
return result;
};
};
/*
频率控制 返回函数连续调用时,func 执行频率限定为 次 / wait
@param {function} func 传入函数
@param {number} wait 表示时间窗口的间隔
@param {object} options 如果想忽略开始边界上的调用,传入{leading: false}。
如果想忽略结尾边界上的调用,传入{trailing: false}
@return {function} 返回客户调用函数
*/
const throttle = function(func, wait, options) {
let context, args, result;
let timeout = null;
// 上次执行时间点
let previous = 0;
if (!options) options = {};
// 延迟执行函数
let later = function() {
// 若设定了开始边界不执行选项,上次执行时间始终为0
previous = options.leading === false ? 0 : Number(new Date());
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function(..._args) {
let now = Number(new Date());
// 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。
if (!previous && options.leading === false) previous = now;
// 延迟执行时间间隔
let remaining = wait - (now - previous);
context = this;
args = _args;
// 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口
// remaining大于时间窗口wait,表示客户端系统时间被调整过
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
//如果延迟执行不存在,且没有设定结尾边界不执行选项
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
本文旨在通过一个简单的例子,练习vuex的几个常用方法,使初学者以最快的速度跑起来一个vue + vuex的示例。
学习vuex需要你知道vue的一些基础知识和用法。相信点开本文的同学都具备这个基础。
另外对vuex已经比较熟悉的大佬可以忽略本文。
基于vue-cli脚手架生成一个vue项目
常用npm命令:
npm i vue-vli -g vue --version vue init webpack 项目名
进入项目目录,使用npm run dev先试着跑一下。
一般不会出现问题,试跑成功后,就可以写我们的vuex程序了。
使用vuex首先得安装vuex,命令:
npm i vuex --save
介绍一下我们的超简单Demo,一个父组件,一个子组件,父组件有一个数据,子组件有一个数据,想要将这两个数据都放置到vuex的state中,然后父组件可以修改自己的和子组件的数据。子组件可以修改父组件和自己的数据。
先放效果图,初始化效果如下:
如果想通过父组件触发子组件的数据,就点“改变子组件文本”按钮,点击后效果如下:
如果想通过子组件修改父组件的数据,就在子组件点击“修改父组件文本”按钮,点击后效果如下:
首先是Parent.vue组件
<template> <div class="parent"> <h3>这里是父组件</h3> <button type="button" @click="clickHandler">修改自己文本</button> <button type="button" @click="clickHandler2">修改子组件文本</button> <div>Test: {{msg}}</div> <child></child> </div> </template> <script> import store from '../vuex' import Child from './Child.vue' export default { computed: {
msg(){ return store.state.testMsg;
}
}, methods:{
clickHandler(){
store.commit('changeTestMsg', '父组件修改自己后的文本')
},
clickHandler2(){
store.commit('changeChildText', '父组件修改子组件后的文本')
}
}, components:{ 'child': Child
},
store,
} </script> <style scoped> .parent{ background-color: #00BBFF; height: 400px;
} </style>
下面是Child.vue子组件
<template> <div class="child"> <h3>这里是子组件</h3> <div>childText: {{msg}}</div> <button type="button" @click="clickHandler">修改父组件文本</button> <button type="button" @click="clickHandler2">修改自己文本</button> </div> </template> <script> import store from '../vuex' export default { name: "Child", computed:{
msg(){ return store.state.childText;
}
}, methods: {
clickHandler(){
store.commit("changeTestMsg", "子组件修改父组件后的文本");
},
clickHandler2(){
store.commit("changeChildText", "子组件修改自己后的文本");
}
},
store
} </script> <style scoped> .child{ background-color: palegreen; border:1px solid black; height:200px; margin:10px;
} </style>
最后是vuex的配置文件
import Vue from 'vue' import Vuex from 'vuex';
Vue.use(Vuex) const state = { testMsg: '原始文本', childText:"子组件原始文本" } const mutations = {
changeTestMsg(state, str){
state.testMsg = str;
},
changeChildText(state, str){
state.childText = str;
}
} const store = new Vuex.Store({ state: state, mutations: mutations
}) export default store;
通过该vuex示例,了解vuex的常用配置及方法调用。希望对不怎么熟悉vuex的同学快速上手vuex项目有点帮助。
因为没太多东西,我自己也是刚接触,本例就不往GitHub扔了,如果尝试了本例,但是没有跑起来的同学,可以一起交流下。
无论是 pc 端还是移动端,无可避免都会涉及到列表查询有关的操作,但对于这两种不同的设备,其列表查询的最佳处理方式也是完全不同。
对于 pc 端列表查询来说,前端通常是给与服务端当前需要获取的数据量(如 pageCount,limit 等参数)以及所需要获取数据的位置(如 pageSize,offset 等参数)作为查询条件。然后服务端然后返回数据总数,以及当前数据,前端再结合这些数据显示页面总数等信息。这里我称为相对位置取数。
对于移动端而言,没有pc 端那么大的空间展示以及操作,所以基本上都会采用下拉取数这种方案。
那么我们在处理移动端列表查询时候使用这种相对位置取数会有什么问题呢?
通过相对位置取数会具有性能问题,因为一旦使用 offset 信息来获取数据,随着页数的增加,响应速度也会变的越来越慢。因为在数据库层面,我们每次所获取的数据都是“从头开始第几条”,每次我们都需要从第一条开始计算,计算后舍弃前面的数据,只取最后多条数据返回前端。
当然了,对于相对位置取数来说,数据库优化是必然的,这里我就不多做赘述了。对于前端开发来说,优秀的的查询条件设计可以在一定方面解决此问题。
事实上,对于一个实际运行的项目而言,数据更新才是常态,如果数据更新的频率很高或者你在当前页停留的时间过久的话,会导致当前获取的数据出现一定的偏差。
例如:当你在获取最开始的 20 条数据后,正准备获取紧接着的后 20 条数据时,在这段时间内 ,发生了数据增加,此时移动端列表就可能会出现重复数据。虽然这个问题在 pc 端也存在,但是 pc 端只会展示当前页的信息,这样就避免了该问题所带来的负面影响。
我们在上面的问题中说明了,移动端下拉加载中使用相对位置查询取数是有问题的。
那么,如果当前不能迅速结合前后端进行修改 api 的情况下,当服务端传递过来的数据与用户想要得的数据不一致,我们必须在前端进行处理,至少处理数据重复问题所带来的负面影响。
因为当前分页请求时无状态的。在分页取到数据之后前端可以对取得的数据进行过滤,过滤掉当前页面已经存在的 key(例如 id 等能够确定的唯一键)。
通过这种处理方式,我们至少可以保证当前用户看到的数据不会出现重复。同时当列表数据可以编辑修改的时候,也不会出现因为 key 值相同而导致数据错乱。
如果不使用相对位置获取数据,前端可以利用当前列表中的最后一条数据作为请求源参数。前端事先记录最后一条数据的信息。例如当前的排序条件为创建时间,那么记录最后一条数据的创建时间为主查询条件(如果列表对应的数据不属于个人,可能创建时间不能唯一决定当前数据位置,同时还需要添加 ID 等信息作为次要查询条件)。
当我们使用绝对位置获取数据时候,虽然我们无法提供类似于从第 1 页直接跳转 100 页的查询请求,但对于下拉加载这种类型的请求,我们不必担心性能以及数据重复显示的问题。
对于相对位置取数来说,前端可以根据返回数据的总数来判断。但当使用绝对位置取数时,即使获取数据总数,也无法判断当前查询是否存在后续数据。
从服务器端实现的角度来说,当用户想要得到 20 条数据时候,服务端如果仅仅只向数据库请求 20 条数据,是无法得知是否有后续数据的。服务端可以尝试获取当前请求的数据条数 + 1, 如向数据库请求 21 条数据,如果成功获得 21 条数据,则说明至少存在着 1 条后续数据,这时候,我们就可以返回 20 条数据以及具有后续数据的信息。但如果我们请求 21 条数据却仅仅只能获取 20 条数据(及以下),则说明没有后续数据。
如可以通过 “hasMore” 字段来表示是否能够继续下拉加载的信息。
{ data: [], hasMore: true }
事实上,前面我们已经解决了移动端处理列表查询的问题。但是我们做的还不够好,前端还需要结合排序条件来处理并提供请求参数,这个操作对于前端来说也是一种负担。那么我们就聊一下 HATEOAS 。
HATEOAS (Hypermedia As The Engine Of Application State, 超媒体即应用状态引起) 这个概念最早出现在 Roy Fielding 的论文中。REST 设计级别如下所示:
HATEOAS 会在 API 返回的数据中添加下一步要执行的行为,要获取的数据等 URI 的链接信息。客户端只要获取这些信息以及行为链接,就可以根据这些信息进行接下来的操作。
对于当前的请求来说,服务端可以直接返回下一页的信息,如
{ data: [], hasMore: true, nextPageParams: {}
}
服务端如此传递数据,前端就不需要对其进行多余的请求处理,如果当前没有修改之前的查询以及排序条件,则只需要直接返回 “nextPageParams” 作为下一页的查询条件即可。
这样做的好处不但符合 REST LEVEL 3,同时也减轻了前端的心智模型。前端无需配置下一页请求参数。只需要在最开始查询的时候提供查询条件即可。
当然,如果前端已经实现了所有排序添加以及查询条件由服务端提供,前端仅仅提供组件,那么该方案更能体现优势。 前端是不需要知道当前业务究竟需要什么查询条件,自然也不需要根据查询条件来组织下一页的条件。同时,该方案的输入和输出都由后端提供,当涉及到业务替换( 查询条件,排序条件修改)时候,前端无需任何修改便可以直接替换和使用。
一旦涉及到移动端请求,不可避免的会有网络问题,当用户在火车或者偏远地区时候,一旦下拉就会涉及取数,但是当前数据没有返回之前,用户多次下拉可能会有多次取数请求,虽然前端可以结合 key 使得渲染不出错,但是还是会在缓慢的网络下请求多次,无疑雪上加霜。这时候我们需要增加条件变量 loading。
伪代码如下所示:
// 查询 function search(cond) {
loading = true api.then(res => {
loading = false }).catch(err => {
loading = false })
} // 获取下一页数据 function queryNextPage() { if (!nextPageParams) return if (!loading) return search(nextPageParams)
}
为了降低开发的复杂度,以后端为出发点,比如:Struts、SpringMVC 等框架的使用,就是后端的 MVC 时代;
以 SpringMVC 流程为例:
优点:
前后端职责很清晰: 前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测 试数据的模拟不难,前端可以本地开发。后端则可以专注于业务逻辑的处理,输出 RESTful等接 口。
前端开发的复杂度可控: 前端代码很重,但合理的分层,让前端代码能各司其职。这一块蛮有意思 的,简单如模板特性的选择,就有很多很多讲究。并非越强大越好,限制什么,留下哪些自由,代 码应该如何组织,所有这一切设计,得花一本书的厚度去说明。
-部署相对独立: 可以快速改进产品体验
缺点:
代码不能复用。比如后端依旧需要对数据做各种校验,校验逻辑无法复用浏览器端的代码。如果可 以复用,那么后端的数据校验可以相对简单化。
全异步,对 SEO 不利。往往还需要服务端做同步渲染的降级方案。 性能并非最佳,特别是移动互联网环境下。
SPA 不能满足所有需求,依旧存在大量多页面应用。URL Design 需要后端配合,前端无法完全掌控。
NodeJS 带来的全栈时代
前端为主的 MV* 模式解决了很多很多问题,但如上所述,依旧存在不少不足之处。随着 NodeJS 的兴 起,JavaScript 开始有能力运行在服务端。这意味着可以有一种新的研发模式:
————————————————
版权声明:本文为CSDN博主「叁有三分之一」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/iME_cho/article/details/105654633
块级元素(inline):
块级元素可以包含行内元素和其它块级元素,且占据父元素的整个空间,可以设置 width 和 height 属性,浏览器通常会在块级元素前后另起一个新行。
常见块级元素:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> div{ width: 150px; height: 150px; background-color: cadetblue; } </style> </head> <body> <div>块级元素1</div> <div>块级元素2</div> </body> </html>
分析:
块级元素的高和宽可以被修改,而且块级元素会在一个块级元素之后另起一行。
行级元素
行级元素(block):
一般情况下,行内元素只能包含内容或者其它行内元素,宽度和长度依据内容而定,不可以设置,可以和其它元素和平共处于一行。
常见行级元素:
a,b,strong,span,img,label,button,input,select,textarea
特点:
和相邻的行内元素在一行上
高度和宽度无效,但是水平方向上的padding和margin可以设置,垂直方向上的无效
默认的宽度就是它本身的宽度
行内元素只能容纳纯文本或者是其他的行内元素(a标签除外)
例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> span{ width: 150px; height: 150px; font-size: 40px; background-color: cadetblue; } </style> </head> <body> <span>行级元素1</span> <span>行级元素2</span> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> span{ width: 150px; height: 150px; font-size: 20px; background-color: cadetblue; display: inline-block; } </style> </head> <body> <span>以前我是行级元素,</span> <span>现在我只想做行内块级元素。</span> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> div{ width: 150px; height: 150px; font-size: 30px; background-color: cadetblue; display: inline; } </style> </head> <body> <div>我以前是块级元素,</div> <div>现在我是行级元素!</div> </body> </html>
分析:
在VSC中,修改宽高的代码已经出现了波浪线,证明他是错误的,所以现在的div
已经变成了行级元素。
————————————————
版权声明:本文为CSDN博主「董小宇」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lolly1023/article/details/105715892
其实VSCode编辑器本身自带了一个功能(Interactive Editor Playground :可以让你快速了解VSCode的特性,并且是可以交互的),
但很可惜它的内容是全英文的(将VSCode设置为中文也没用哦~),
我将每一部分截图下来,并为你说明关键内容,教你学会使用 Interactive Editor Playground
还有一些显而易见的特性,我不会再用文字叙述一遍(它们都是潜移默化的)
在下文中会涉及到大量快捷键的介绍,如果看不懂快捷键请自行百度
鼠标 = 文本光标 = 光标
本文成于2020年4月22日,随着VSCode的版本更迭,此部分内容可能会略有差异(小更改不影响观看,有较大影响的更新请在评论区告之,我会及时更新的)
打开VSCode > Help > Interactive Playground
你将会打开 Interactive Editor Playground 页面
VS代码中的核心编辑器包含许多特性。此页高亮显示了10个特性,每个特性介绍中都提供了代码行供你编辑
接下来的10行内容(你可以理解为目录,对应10个特性)
多光标编辑(Multi-Cursor Editing)- 选择一块区域,选择所有匹配项,添加其余光标等
智能感应(intelliSense)- 获取代码和外部模块的代码帮助和参数建议
行操作(Line Actions )- 快速移动行以重新排序代码
重命名重构(Rename Refactoring)- 快速重命名代码库中的符号(比如变量名、函数名)
格式化(Formatting)- 使用内置文档和选择格式使代码看起来很棒
代码折叠(Code Folding) - 通过折叠其他代码区域,关注代码中最相关的部分
错误和警告(Errors and Warnings)- 写代码时请参阅错误和警告
片段(Snippets)- 花更少的时间输入片段
Emmet - 只需要敲一行代码就能生成你想要的完整HTML结构等(极大方便前端开发)
JavaScript Type Checking- 使用零配置的TypeScript对JavaScript文件执行类型检查。
Multi-Cursor Editing
使用多光标编辑可以同时编辑文档的多个部分,极大地提高了工作效率
框式选择
键盘同时按下 Shift + DownArrow(下键)、Shift + RightArrow(右键)、Shift + UpArrow(上键)、Shift + LeftArrow(左键) 的任意组合可选择文本块
也可以用鼠标选择文本时按 Shift + Alt 键
或使用鼠标中键拖动选择(可用性很高)
添加光标
按 Ctrl + Alt + UpArrow 在行上方添加新光标
或按 Ctrl + Alt + DownArrow 在行下方添加新光标
您也可以使用鼠标和 Alt + Click 在任何地方添加光标(可用性很高)
在所有出现的字符串上创建光标
选择字符串的一个实例,例如我用鼠标选中所有background,然后按 Ctrl + Shift + L,文本中所有的background都将被选中(可用性很高)
IntelliSense
Visual Studio Code 预装了强大的JavaScript和TypeScript智能感知。
在代码示例中,将文本光标放在错误下划线的上面,会自动调用IntelliSense
这只是智能提示的冰山一角,还有悬停在函数名上可以看到参数及其注释(如果有)等等,它会潜移默化的带给你极大帮助
其他语言在安装对应插件后,会附带对应语言的IntelliSense
Line Actions
分别使用 Shift + Alt + DownArrow 或 Shift + Alt + UpArrow 复制光标所在行并将其插入当前光标位置的上方或下方
分别使用 Alt + UpArrow 和 Alt + DownArrow 向上或向下移动选定行(可用性很高)
用 Ctrl + Shift + K 删除整行(可用性很高)
通过按 Ctrl + / 来注释掉光标所在行、切换注释(可用性很高)
Rename Refactoring
重命名符号(如函数名或变量名)
重命名操作将在项目中的所有文件中发生(可用性很高)
代码如果没有良好的编写格式,阅读起来是一个折磨
Formatting可以解决编写格式问题:无论你的代码的格式写的有多么糟糕,它可以将代码格式化为阅读性良好的格式
格式化整个文档 Shift + Alt + F (可用性很高)
格式化当前行 Ctrl + K Ctrl + F(即先按Ctrl,再按K,最后按F)
鼠标右键 > Format Document (格式化整个文档)
将格式化操作设置为自动化(保存时自动格式化整个文档):Ctrl + , 输入 editor.formatOnSave
鼠标操作,自己尝试一下,秒懂
快捷键:
折叠代码段是基于基于缩进
错误和警告将在你出现错误时,高亮该代码行
在代码示例中可以看到许多语法错误(如果没有,请你随便修改它,让它出现错误)
按F8键可以按顺序在错误之间导航,并查看详细的错误消息(可用性很高)
Snippets
通过使用代码片段,可以大大加快编辑速度
在代码编辑区,你可以尝试输入try并从建议列表中选择try catch,
然后按Tab键或者Enter,创建try->catch块
你的光标将放在文本error上,以便编辑。如果存在多个参数,请按Tab键跳转到该参数。
Emmet
Emmet将代码片段的概念提升到了一个全新的层次(前端开发的大宝贝)
你可以键入类似Css的可动态解析表达式,并根据在abrevision中键入的内容生成输出
比如说:
然后Enter
————————————————
版权声明:本文为CSDN博主「索儿呀」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Zhangguohao666/article/details/105676173
蓝蓝设计的小编 http://www.lanlanwork.com