vscode eslint vue文件格式的自动修复(eslint vue auto fix)

这篇文章主要描述 vscode 的 eslint 插件如何自动修复VUE文件内的格式错误

问题

修复方式

在 vscode 中的用户配置中给 eslint.validate 中的VUE配置设置 autoFixtrue

1
2
3
4
5
6
7
8
9
10
{
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "vue",
"autoFix": true
}
]
}

然后就好了~~

END

2017-08-26 完成

2017-08-26 立项

node开发包集合

这篇文章将集合我在使用node开发一些特别功能时,所找到的简单易用,功能强大的包,用来和大家分享。

NODE开发提升

require别名

module-alias

npm : https://www.npmjs.com/package/module-alias

repository : https://github.com/ilearnio/module-alias

推荐理由:

还在为../../../../../index.js这样恶心的代码烦恼吗,那就来用别名吧!!!

网络解析相关

FROM表单

multer

npm : https://www.npmjs.org/package/multer

repository : https://github.com/expressjs/multer

推荐理由:

一个集合express的 multipart/form-data的解析功能,当然,最强大的还是它的文件解析功能~~用了都说好!

form-data

npm : https://www.npmjs.com/package/form-data

repository : https://github.com/form-data/form-data

推荐理由:

FormData的node api,和浏览器的操作一致,本质是一个可写流,可以通过流传递数据

文件相关

压缩与解压

archiver

npm : https://www.npmjs.org/package/archiver

repository : https://github.com/archiverjs/node-archiver

推荐理由:

尝试了多个node端的压缩工具,(不要给我说 cmd 才是王),这个包封装的更高,使用方便,解决了 tar 莫名其妙的路径问题,简单易用~~~

unzip

npm : https://www.npmjs.org/package/unzip

repository : https://github.com/EvanOxfeld/node-unzip

推荐理由:

archiver 提供了方便的压缩功能,当然需要一个方便的解压包啦~~,对于ZIP文件这个包提供了直接解压文件到文件目录的功能

计算相关

大数计算

bignumber.js

npm : https://www.npmjs.com/package/bignumber.js

repository : https://www.npmjs.com/package/bignumber.js

推荐理由:

bignumber.js 实现了大数计算的相关底层算法,方便快捷,并且活跃,big-number 已经2年没有更新了

解析操作相关

HTML字符串解析操作

jsdom

npm : https://www.npmjs.com/package/jsdom

repository: https://github.com/jsdom/jsdom

推荐理由:

jsdom 可以在node端解析DOM并给你和在浏览器相同的体验

配置相关

config

npm : https://www.npmjs.com/package/config

repository: https://github.com/lorenwest/node-config

推荐理由:

服务器都需要读取配置,config可以帮助你快速有效的管理不同环境的配置

END

2019-04-09 添加 config:用于管理node服务器运行配置

2019-02-18 添加 module-alias:用于node引用文件使用别名

2019-01-24 添加 form-data:用于node端生成form表单

2018-09-22 添加 jsdom 用于node端HTML解析和对象操作

2017-12-19 添加 bignumber.js 用于大数计算

2017-08-11 添加 multer 和 unzip

2017-08-08 添加 压缩文件包 archiver

2017-08-07 立项

CSS 弹性盒子模型 Flex 简介

CSS 弹性盒子模型已经提出很久了,但是由于兼容相关的原因,一直没有大规模采用,随着技术的发展大量的浏览器已经可以支持 Flex 属性了,这篇文章将会简单讲述和CSS 弹性盒子模型的相关内容。

弹性盒子简介

个人认为,弹性布局模型就是根据所处的设备,视图大小,进行自动的宽高改变的的一个具有更强的空间可塑能力的模型。

由于现代智能设备的种类繁多,不同的设备间展示都存在一定的差距,只依靠浮动布局已经不能特别好的满足展示所需要的功能效果。

弹性盒子模型的出现便是为了解决这一痛点。当下由于低端浏览器的使用率依然很高,导致弹性布局无法大量的普及,主流的CSS布局框架依然采用的浮动布局。然而我相信弹性布局终将会成为未来的主流布局方案。

弹性容器相关概念


(图片来自MDN)

弹性容器(flex container)

包含着弹性项目的父元素。通过设置 display 属性的值为 flex 或 inline-flex 来定义弹性容器。

弹性项目(flex item)

弹性容器的每个子元素都称为弹性项目。弹性容器直接包含的文本将被包覆成匿名弹性单元。

**轴(axis)

每个弹性框布局包含两个轴。弹性项目沿其依次排列的那根轴称为主轴(main axis)。垂直于主轴的那根轴称为侧轴(cross axis)。

弹性盒子使用方法

弹性盒子模型主要有两个东西 弹性容器(flex-container) 和 弹性项目(flex-item)。看名字都知道是什么意思了。

简单的实现

要让一元素成为一个弹性容器,我们需要设置它的 displayflex,这样才能使这个元素成为一个弹性容器,这样下面的元素才能成为弹性项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

<style>
.container{
display: flex;
height: 100%;
width: 100%;
}

.item{
flex: 1;
text-align:center;
font-size: 50px;
border:1px solid red;
}

</style>

<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>

DEMO1

这样就实现了一个最简单的弹性布局。下面我们来详细的看一下和弹性布局有关的CSS属性

弹性容器相关属性

弹性容器的相关属性有:align-contentalign-itemsjustify-contentflex-directionflex-wrapflex-flow

这些属性主要是用于规定弹性项目的排序展示方式。

align-content

align-content 属性定义了当作为一个弹性盒子容器的属性时,浏览器如何在容器的侧轴围绕弹性盒子项目分配剩余空间。

属性值 描述
flex-start 所有行紧靠开始点
flex-end 所有行紧靠结束点
center 所有行居中紧帖
space-between 行与行之间平均留空
space-around 行与行之间和行与四周平均留空

可以在 DEMO2 试一试改变属性值的效果

在MDN中还描述了一些别的属性但是大部分都是没有支持的。

align-items

align-items 属性就比较容易理解了,它控制的元素在侧轴上的对齐方式。

属性值 描述
flex-start 元素向侧轴起点对齐
flex-end 元素向侧轴终点对齐
center 元素在侧轴居中。如果元素在侧轴上的高度高于其容器,那么在两个方向上溢出距离相同。

可以在 DEMO3 试一试改变属性值的效果

justify-content

justify-content 属性与 align-items 属性对应。它控制的元素在主轴上的对齐方式。

属性值 描述
flex-start 所有行紧靠开始点
flex-end 所有行紧靠结束点
center 所有行居中紧帖
space-between 行与行之间平均留空
space-around 行与行之间和行与四周平均留空

可以在 DEMO4 试一试改变属性值的效果

flex-direction

flex-direction 属性是控制弹性盒子容器内弹性项目的分布方向(横向、竖向)。

属性值 描述
row 横向排列
row-reverse 横向反向排列
column 竖向排列
column-reverse 竖向反向排列

可以在 DEMO5 试一试改变属性值的效果

flex-wrap

flex-wrap 属性规定flex元素是否可以多行显示。

属性值 描述
nowrap flex 的元素被摆放到到一行,这可能导致溢出 flex 容器
wrap flex 元素被打断到多个行中。
wrap-reverse 和 wrap 的行为一样,但是打断后行的排列方向是反的

可以在 DEMO5 试一试改变属性值的效果

flex-flow

flex-wrap 属性是 flex-directionflex-flow 的简写属性

syntax: <’flex-direction’> || <’flex-wrap’>


以上就是弹性容器的CSS属性了,主要就是对内部弹性项目的排列展示顺序的控制。

弹性项目相关属性

弹性项目的相关属性有:flex-growflex-shrinkflex-basisflex

它们都主要是负责控制弹性项目的大小。

flex-grow

flex-grow 这个属性控制的弹性项目的拉伸因子,当所处行存在空白的时候会根据该行的元素的拉伸因子分配多余的空间。默认值为0,即不会自动扩展。

flex-shrink

flex-shrink 这个属性与 flex-grow 相反是收缩因子。默认值为1,即会自动收缩。

flex-basis

flex-basis 属性指定了 flex 元素在主轴方向上的初始大小。如果不设置一般会读取 width;

flex

这个属性就是上面三个属性的集合体。

order

order 属性可以更改元素出现的顺序,即会优先根据设置的order来排序。默认为0;


这些属性可以在DEMO6中试一试~~

小提示

  1. 通过设置弹性项目的 widthheightminmax可以限制自动伸缩的极限哦~~

  2. flex-warp 设置为 warp 后,如果有基础宽度那么自动收缩将失效,当达到基础宽度会切换到下一行,除非只剩一个了。

参考资料

MDN_Flex

icanuse_flex

END

2017-07-30 立项

2017-07-28 立项

semver(语义化版本)

写了一年多的代码了,从以前随便开个文件夹就写,到开始用版本控制管理项目的版本,但之前对版本这个东西一直是混乱的,到底大版本和小版本应该如何区分等等。后来发现了社区里的 semver(语义化版本),用于解决混乱的版本号,这篇文章将介绍semver的内容以及npm上版本号上的一些符号的意思。

semver 2.0.0 简介

一般情况下semver的版本都是`x.y.z`的格式。例如 1.1.0,2.4.5等等

`x`代表的主版本号,当有大量更新,重大意义的更新时才会增加,比如API改变,核心代码实现变动等等。

`y`代表的次版本号,当有兼容性的修改或者新的特性(API)加入时才会改变。

`z`代表的修订版本号,顾名思义就是在修复一些缺陷的时候发布新的版本号。

到这里差不多就明白那些版本号大概是如何增长的了

semver 2.0.0 详细

这一段就是semver 2.0.0的标准说明,但还是建议直接去semver官网看~~

  1. 使用语义化版本控制的软件 必须 定义公共 API。该 API 可以在代码中被定义或出现于严谨的文件内。无论何种形式都应该力求精确且完整。

    PS:公共API便是暴露这个项目的依赖方式的途径,如果没有公共 API 那么也就没有了版本控制的依据,个人认为这个API可以不一定是提供调用的,也可以是内部的实现等等

  2. 标准的版本号必须采用 XYZ 的格式,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须以数值来递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。

  3. 标记版本号的软件发行后,禁止改变该版本软件的内容。任何修改都必须以新版本发行。

    PS:有一次就是内容没有校验好就发了一个版本,那时我想的是可以直接再交一个吗,然而是不行的,这样的做法只会带来混乱,其实也是要对提交的内容负责吧~

  4. 主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。

  5. 1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。

  6. 修订号 Z(x.y.Z | x > 0)必须在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。

    PS:也就是修正版本号(Z) 主要是针对一些内部BUG的修复

  7. 次版本号 Y(x.Y.z | x > 0)必须在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须递增。也可以在内部程序有大量新功能或改进被加入时递增,其中可以包括修订级别的改变。每当次版本号递增时,修订号必须归零。

    PS:即当存在大量改动,但是API依然向下兼容的时候

  8. 主版本号 X(X.y.z | X > 0)必须在有任何不兼容的修改被加入公共 API 时递增。其中可以包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须归零。

    PS:主版本号主要是在有不兼容的修改的情况下做出的,而对于不向下兼容的改动对低版本用户来说很困扰的事情,因为升级将会耗费大量的资源。

  9. 先行版本号可以被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符号来修饰。标识符号必须由 ASCII 码的英数字和连接号 [0-9A-Za-z-] 组成,且 禁止 留白。数字型的标识符号 禁止 在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法达到兼容的需求。范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。

  10. 版本编译信息可以被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符号来修饰。标识符号必须由 ASCII 的英数字和连接号 [0-9A-Za-z-] 组成,且禁止留白。当判断版本的优先层级时,版本编译信息可以被忽略。因此当两个版本只有在版本编译信息有差别时,属于相同的优先层级。范例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。

  11. 版本的优先层级指的是不同版本在排序时如何比较。判断优先层级时,必须把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译信息不在这份比较的列表中)。由左到右依序比较每个标识符号,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较,例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。例如:1.0.0-alpha < 1.0.0。有相同主版本号、次版本号及修订号的两个先行版本号,其优先层级必须透过由左到右的每个被句点分隔的标识符号来比较,直到找到一个差异值后决定:只有数字的标识符号以数值高低比较,有字母或连接号时则逐字以 ASCII 的排序来比较。数字的标识符号比非数字的标识符号优先层级低。若开头的标识符号都相同时,栏位比较多的先行版本号优先层级比较高。范例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。

semver npm Advanced Range Syntax(范围语法)

多个版本号组合

npm上版本号是可以通过 || 来表示组合的,比如 1.0.0 || 2.0.0,但会优先下载最高级的。

Hyphen Ranges X.Y.Z - A.B.C

例子: 1.2.3 - 2.3.4 := >=1.2.3 <=2.3.4

如果前部分的文字有省略,那么省略的都以0替换,比如 1.2 - 2.3.4 := >=1.2.0 <=2.3.4

如果是后半部分便将省略部分的前一即提升一级,但不能达到或者更高,不如:1.2.3 - 2.3 := >=1.2.3 < 2.4.0

X-Ranges 1.2.x 1.X 1.2.* *

X,x,*这3个字符都是表示的任意的意思,应该很好理解

比如:

* := >=0.0.0 任意版本,特别是当值为""的时候,也默认为*

1.x := >=1.0.0 <2.0.0

1.2.x := >=1.2.0 <1.3.0

其实省略部分就默认填充了一个*

比如 1 := 1.x.x := >=1.0.0 <2.0.0

Tilde Ranges ~1.2.3 ~1.2 ~1

开头~的符号意思是允许对最小的定义的版本进行修改

比如:

~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 < 1.3.0
~1.2 := >=1.2.0 <1.(2+1).0 := >=1.2.0 < 1.3.0
~1 := >=1.0.0 <(1+1).0.0 := >=1.0.0 < 2.0.0

Caret Ranges ^1.2.3 ^0.2.5 ^0.0.4

开头^的符号意思是允许在最大已确定的版本号内变动,感觉和~相反

比如:

^1.2.3 := >=1.2.3 <2.0.0
^0.2.3 := >=0.2.3 <0.3.0
^0.0.3 := >=0.0.3 <0.0.4

可以看出主要目的是在当前达到的最大的开发级别作为范围

后记

看完了有关semver的资料后终于搞懂了各个位置的数字的规律,以前就是完全想着来的,版本号也是写死的。

参考资料

semver 2.0.0

npm semver 说明

semver git 地址

END

2017-06-23 完成

2017-05-23 立项

浏览器事件流(event-flow)简介

这篇文章将讲简单述详细浏览器DOM树的事件流(event-flow)相关的知识,主要包括事件的流程、事件的监听等。

简介

在web前端开发中,为了即时响应用户的操作,浏览器主要通过事件的形式来通知脚本进行响应,例如点击事件(click),鼠标滚轮的滚动(mousewheel),键盘的按下(keydown)等等。

对于事件的了解最基本的就是事件如何监听,事件的对象有哪些主要属性,事件的传递流程。

讲解用的HTML结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<style media="screen">
#out{
width: 200px;
height: 200px;
border: 1px blue solid;
}

#in{
margin: 10px;
width: 100px;
height: 100px;
border: 1px red solid;
}

</style>
<div id="out">
<div id="in">
<input id="btn" type="button" name="" value="an">
</div>
</div>

事件的监听

一切的开始都是要先学会用,用都不会很难去深入理解其背后的原因。

原始的事件绑定

JS最原始的事件绑定是通过对DOM对象的事件属性绑定函数实现。可以通过内联的方式调用要执行的代码,或者使用JS获取DOM对象来设置事件属性。

1
2
3

<input id="btn" type="button" name="" value="an" click="clickFun()">

1
2
3
4
5
6
7

function clickFun(){
console.log("button!")
}

document.getElementById("btn").onclick = clickFun

这样当点击id为btn的元素时,便会输出button!

这样就实现了一个事件的监听,但是这个存在一定的问题,当有多个地方需要监听相同事件时,便会冲突

1
2
3
4
5
6
7
8
9

document.getElementById("btn").onclick=function(){
console.log("button!")
}
//something...
document.getElementById("btn").onclick=function(){
console.log("button?")
}

这时点击只会输出button?,因为第二个绑定的函数覆盖了前一个函数。对同一个节点的监听只能同时有一个实例。

事件监听器(EventListener)

为了解决上面原始的监听方法的弊端,推出了事件监听器,它允许多个实例对事件进行订阅,同时可以选择在事件的哪个阶段触发监听器,而原始的绑定方法都是在只在冒泡阶段触发。

addEventListener(type,callback,capture = false)

addEventListener是对元素添加事件监听器的方法。

type是需要监听的事件类型,callback是监听的回调函数,capture标记的监听的所处阶段,true为捕获阶段,false为冒泡阶段。

1
2
3
4
5
6
7
8
9

document.getElementById("out").addEventListener("click",function(){
console.log("out!")
})
//something
document.getElementById("in").addEventListener("click",function(){
console.log("in!")
})

现在我们点击就可以看到 in!out! 一起输出了~

removeEventListener(type, callback, capture = false)

removeEventListener是用于移除监听器的方法。

type是需要移除监听的事件类型,callback是使用addEventListener绑定时的回调函数,capture标记的监听的所处阶段,true为捕获阶段,false为冒泡阶段。

1
2
3
4
5
6
7
8
9
10
11
12

function clickout(){
console.log("out!");
}

document.getElementById("out").addEventListener("click",clickout);
document.getElementById("out").removeEventListener("click",clickout);

document.getElementById("in").addEventListener("click",function(){
console.log("in!")
})

这样将会每次点击只输出in!了,这里提取clickout出来,是为了保证添加和移除的函数是相同的,如果写成这样

1
2
3
4
5
6
7
8
9
10
11
12

document.getElementById("out").addEventListener("click",function(){
console.log("out!")
})
document.getElementById("out").removeEventListener("click",function(){
console.log("out!")
})

document.getElementById("in").addEventListener("click",function(){
console.log("in!")
})

是没有用的,因为填充的是匿名函数,虽然代码执行是相同的,但是在计算机内是不同的两个对象。

小提示

1.当属性绑定和监听器绑定相同函数时,依然会触发两次

1
2
3
4
5
6
7
function clickout(){
console.log("out!");
}

document.getElementById("out").onclick = clickout
document.getElementById("out").addEventListener("click",clickout);
document.getElementById("out").addEventListener("click",clickout);

事件对象

当我们监听一个事件后,事件触发会调用我们设置的回调函数,同时会传入一个事件对象,这个事件对象将描述触发的事件相关信息。

这里简单的讲述下最基础的事件对象所包含的一些重要信息,其他的事件对象都是基于这个对象扩展出来,包含一些其他特别信息的对象。

1
2
3
4
5

document.getElementById("out").addEventListener("click",function(event){
console.log(event)
});

  1. event.bubbles

    一个布尔值,用来表示该事件是否在DOM中冒泡。比如focus,blur这些事件就不会冒泡。

  2. event.cancelBubble

    stopPropagation()以前的别名。通过在一个事件处理程序返回前设置这个属性的值为真,来阻止事件的传播。

  3. event.cancelable

    一个布尔值,用来表示这个时间是否可以取消。

  4. event.currentTarget

    当前注册事件的对象的引用,就是注册这个事件监听的对象,这个值会在传递的途中进行改变(因为沿途监听的对象也在变啊)。

  5. evnet.defaultPrevented

    一个布尔值,表示是否已经阻止默认行为

  6. event.eventPhase

    指示事件流正在处理哪个阶段。

  7. event.target

    对事件起源目标的引用,即哪个对象触发的事件流。

  8. event.type

    事件的名称(不区分大小写)。

事件流程

浏览器的事件流分为捕获(capture),触发(target)以及冒泡(bubble)3个阶段。

捕获(capture)阶段

这个阶段事件的消息会从 window 对象向下朝触发的元素传递。

比如这样监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

window.addEventListener("click",function(e){
console.log("window",e);
},true);

document.addEventListener("click",function(e){
console.log("document",e);
},true);

document.body.parentElement.addEventListener("click",function(e){
console.log("html",e);
},true);

document.body.addEventListener("click",function(e){
console.log("body",e);
},true);

document.getElementById("out").addEventListener("click",function(e){
console.log("out",e);
},true);

document.getElementById("in").addEventListener("click",function(e){
console.log("in",e);
},true);

那么点击id为in的元素时,输出的结果就会按着window 对象到id为in的元素的传递顺序显示出来

触发(target)阶段

这个阶段其实就是消息到达目标后,目标做出响应的阶段,如果这个消息是不会冒泡的,那么这个阶段以后就不会在冒泡了。

冒泡(bubble)阶段

这个阶段事件的消息和捕获相反,会从触发的元素向上朝 window 对象传递。

比如这样监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

window.addEventListener("click",function(e){
console.log("window",e);
},false);

document.addEventListener("click",function(e){
console.log("document",e);
},false);

document.body.parentElement.addEventListener("click",function(e){
console.log("html",e);
},false);

document.body.addEventListener("click",function(e){
console.log("body",e);
},false);

document.getElementById("out").addEventListener("click",function(e){
console.log("out",e);
},false);

document.getElementById("in").addEventListener("click",function(e){
console.log("in",e);
},false);

结果就是这样了

总结

  1. 任何事件都有捕获和触发阶段,但是不一定有冒泡阶段

    例如 focusblur等事件,虽然不能在冒泡阶段阶段获取消息,但是可以在捕获阶段获取消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    document.getElementById("in").addEventListener("focus",function(e){
    console.log("in",e);
    },true);

    document.getElementById("text").addEventListener("focus",function(e){
    console.log("text",e);
    },true);

    document.getElementById("in").addEventListener("focus",function(e){
    console.log("in",e);
    },false);

    从结果我们可以看到捕获阶段的监听是触发了的。

  2. stopPropagation() 函数将会直接中断整个传播流程,而不是只中断冒泡。

参考资料

UI Events W3C Working Draft, 04 August 2016

MDN_addEventListener

MDN_Event

END

2017-09-26 更新了简单的内联调用的说明,添加了一个小提示

2017-06-14 完成

2017-05-06 立项

CSS boder-image 图片边框属性详解

最近使用boder-image来实现了一个看起来可能也许好看的边框,中间对boder-image的了解很少,查到了很久才明白了一些,这篇文章将讲述boder-image的使用以及我遇到的一些问题。

最简单的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<style media="screen">
div{
width: 100px;
height: 100px;
}

.borderImage{
border-image-source: url("border1.png");
border-image-slice: 20;
border-style: solid;
}
</style>
<div class="borderImage"></div>

要使用图片做边框背景我们至少需要border-image-source 以及 border-image-sliceborder-style

这里 border-style 有些浏览器不需要有些需要,视浏览器而定。

border-image-source

这个属性代表的图片,这里可以使用 url 来引入图片,当然也可以使用CSS的渐变属性绘制的图片,比如 linear-gradient

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<style media="screen">
div{
width: 100px;
height: 100px;
}

.borderImage{
border-image-source: linear-gradient(red, blue);
border-image-slice: 20;
}
</style>
<div class="borderImage"></div>

这样我们就实现了一个简单的渐变色边框

border-image-slice

这个属性会通过规范将 border-image-source 的图片明确的分割为9个区域:四个角,四边以及中心区域。这个将会通过指定的四个内向距离来实现。

(ps:哈哈,懒得弄图了,直接用MDN的了=、=)

上图的4条红色的虚线便是 border-image-slice 的值定义出来的。

border-image-slice:top right bottom left

border-image-slice的值定义的是这些切线距离边的距离,不需要加上 px

比如将上方示例代码弄成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<style media="screen">
div{
width: 100px;
height: 100px;
}

.borderImage{
border-image-source: linear-gradient(red, blue);
border-image-slice: 20 0 0 0;
border-style: solid;
}
</style>
<div class="borderImage"></div>

这样就只有上边有图片了,因为其他区域为0,切出来的图片区域为空,放上去当然也是没有啦。

这里基本对这个属性的作用就明白了,但还有一些需要注意的。

fill

border-image-slice 的值可以插入一个fill值,这样本来无用的中心区域就变成了背景图片了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<style media="screen">
div{
width: 100px;
height: 100px;
}

.borderImage{
border-image-source: linear-gradient(red, blue);
border-image-slice: fill 20 0 0 0;
border-style: solid;
}
</style>
<div class="borderImage"></div>

这个 fill 在MDN上说任意位置都可以但是谷歌浏览器上只能放在两边,个人认为还是放在两边好(好看~~)。

如何展示

这个属性只负责截取图片区域,这些图片的区域如何使用将有其他属 :border-image-widthborder-image-outset border-image-repeat。与它截取的大小等等都没有关系!。

其他属性

border-image-width

这个属性代表的图片展示的宽度,默认当然就是 border-width,但是可以独立出来,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<style media="screen">
div{
width: 100px;
height: 100px;
}

.borderImage{
border-image-source: linear-gradient(red, blue);
border-image-slice: 20;
border-style: solid;
border-width: 10px;
border-image-width: 10px 20px 30px 40px;
}
</style>
<div class="borderImage"></div>

可以看到其实边框的宽度依然为10px,但是因为我们定义的 border-image-width 更大所以图片延伸到了内部,在里面加点字就更加明了了。

这个只是图片延伸了过去,不会影响内部的排版。

border-image-outset

上面的情况,图片宽度超出时,图片会向内部扩展。当然我们可能也需要向外,但是这个不是简单的设置一个 向外 就搞定了。

border-image-outset 属性便是可以实现这个效果的属性。这个属性将会让边框图片向外偏移设置的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<style media="screen">
div{
width: 100px;
height: 100px;
}

.borderImage{
border-image-source: linear-gradient(red, blue);
border-image-slice: 20;
border-style: solid;
border-width: 10px;
border-image-width: 10px 20px 30px 40px;
}
</style>
<div class="borderImage"></div>

border-image-repeat

这个属性负责当图片尺寸需要展示的区域尺寸不同时,如何展示。

主要有三个值 stretchrepeatround

stretch 这个值代表拉伸图片

repeat 这个值代表平铺图片,将不会压缩图片

round 这个值是stretchrepeat的折中,会优先拉伸,当可以放两个时在放两个,依次增加。

后记

boder-image 相关的属性一共就这五个,理解起来还是很快的。虽然用的少,但是说不定那天就要提枪上阵了呢。

参考资料

MDN border-image

END

2017-06-06 完成

2017-06-01 立项

MongoDB备份和恢复

简单讲述MongoDB备份和恢复的方法。

备份

备份主要使用的 mongodump 指令

这里简单的讲述主要的用法(备份整个数据库)

mongodump -h dbhost -d dbname -o dbdirectory

  1. -h dbhost

    MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017

  2. -d dbname

    数据库的名字

  3. -o dbdirectory

    备份的数据存放位置,例如:c:\dbdata,该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个和数据库名相同的目录,这个目录里面存放该数据库实例的备份数据。

比如我执行

mongodump -h 127.0.0.1 -d my-blog -o F:\dbdata

就将我的数据备份出来了

恢复

恢复主要使用的 mongorestore 指令

主要的用法是这样的

mongorestore -h dbhost -d dbname dbdirectory

  1. -h dbhost

    MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017

  2. -d dbname

    需要恢复的数据库实例,就是将数据恢复到哪去

  3. dbdirectory

    备份数据的所在位置,与上面的北方对应的就是 F:\dbdata\my-blog

比如我执行

mongorestore -h 127.0.01 -d test F:\data\my-blog

就会将数据加载到 test 这个数据库中

我们就可以看到我的数据加载了进来

注意

  1. 恢复重复操作只会添加和修改ID对应的数据,不会删除没有了的,如果需要的话需要加上 –drop

  2. 恢复的时候在windows下,中文名词的路径可能存在问题

END

2017-05-31 完成

2017-05-31 立项

初识RPC

这篇文章将讲述什么是RPC(Remote Procedure Call Protocol)。ps:我才不会说是一开始弄错了,快写完了才发现

常用的服务请求方式

最原始的调用方式

平时开发一般直接采用的是调用 ajax 的方式来实现与服务端的通信:

1
2
3

ajax.post(path,data,options)

这样的请求方式每次都需要明确的请求地址,方式等等,如果直接在开发中这样写,将会使代码耦合度增加,当修改一个接口时,就会导致大量的地方需要修改(很可能修改不完全带来潜藏的BUG)

简单的封装

上面遇到的情况我们一般会采用将一个个API封装成对应的各个调用函数,来构建一个API层,在程序中引入对应的函数来解耦。

1
2
3
4
5
6
7
8
9

//某些地方
dosome(...)

//定义的地方
function dosome(obj){
ajax.post(...)
}

这样一个接口修改大部分都只需要修改接口函数内部的内容了。比如,修改请求方式,请求地址,请求格式等等。

但是这个依然与网络请求有大量的耦合(路径,方式等等),对于开发者将会去考虑网络通信的配置将是一个繁杂的事情。

RPC 的核心就是去除对网络通信的依赖,让开发者致力于对服务的处理。

RPC

RPC 全称 Remote Procedure Call Protocol,中文名称 远程过程调用协议

它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

说白了其实就是将远程的函数调用伪装成本地调用罢了。

使用RPC,提供相应的接口,将底层的网络请求透明化。

结构

客户端函数通过调用封装好的请求接口向服务端请求通信,服务端也同样通过封装好的响应接口来接受处理。

请求数据的结构

网络请求必然伴随着数据,但是数据必定需要有固定的格式才能相互理解。

  1. 协议版本

    网络通讯首要的就是通讯的协议版本是相同的,这样才能让双方采用相同的请求(当然在服务端和客户端开发时可以约定固定的版本号,减少不必要的网络请求数据)

  2. 程序号

    这个标识着需要请求的服务程序号。当然是必须的~

  3. 过程号

    这个标识这一次请求的序号,用于区别不同的请求。

  4. 数据体

    请求或应答的数据体

这4个是 RPC 所必须的内容,各种RPC协议都至少拥有这四个内容。RPC 协议有很多,也许有些为了解决特定需求有一定的扩充(反正都是各自定义各自的嘛-。-)

缺点

  1. 由于构建在应用层之上,应用层协议自带的大量特性将会无法使用有点亏。

    比如,由于透明化了网络请求的方式,我们将不能直接使用 HTTP 等协议的特性,比如请求头,cookie等等,这些东西都将是透明化的,所以 session 的权限控制方法将会失效,我们需要使用 token 之类的东西来控制相应的网络请求权限。

  2. 异常处理,由于这不是本地调用,所以可能发生的异常不仅仅只有函数运行抛出的异常,还可能是来自网络,服务框架等等地方的问题

总结

PRC 是一个屏蔽远程调用间的网络通讯,使调用者感觉是在调用本地函数的协议,用于解耦不同的服务到不同的服务区块。忽视服务所处的环境,将不同的服务采用适合的架构实现,抹平不同服务间运行结构上的差距,我想,这才是真正的意义。

后记

一开始想错了 RPC 的真正意义,快写完了才发现想错了 Sad= =、、

END

2017-05-25 完成

2017-04-11 立项

功能实现之简单的文本渲染引擎

某日突发奇想想到了一个页面渲染引擎的实现方法,这篇文章用来讲述其简单的原理

实现的功能

将下列字符串编译成正确的结果

1
2
3
4
5

<% for(let i = 0 ;i< this.l;i++){ %>
<li><%= i%></li>
<% } %>

l=2 时变成

1
2
3

<li>0</li><li>1</li>

实现过程

截取内容

通过截取开始符号和结束符号的方式,我们可以截取 渲染文本渲染脚本 交替的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

let arr = [str];
while (true){
let str = arr.pop();

let start = str.search('<%');
let end = str.search('%>');

if (start === -1){
arr.push(str)
break
}

let bstr = str.substring(0, start);
let instr = str.substring(start + 2, end);
str = str.substring(end + 2);

arr.push(bstr)
arr.push(instr)
arr.push(str)
}

return arr;

解析截取的数组

我们看过程可以看出 0,2,4... 都是用于渲染的文本 1,3,5... 是用于渲染的脚本。我们可以使用构建函数脚本的方式来实现渲染。

同时判断一下开头为等号的执行变量的赋值操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

let renderBuf = `let renderout = ''\n`;
arr.forEach((data, index) => {
if (index % 2 === 0 && data){
renderBuf += `renderout += "${data.trim()}"\n`
} else {
if (data[0] === '='){
renderBuf += `renderout += ${data.substr(1).trim()}\n`
} else {
renderBuf += `${data.trim()}\n`
}
}
})
renderBuf += `return renderout`;

于是就构建了我们的脚本-。-

运行脚本

1
2
3
4

let x = new Function('locals', renderBuf);
x.call(data)

这里使用 call 来导入环境变量,使用 this 来获取脚步外传入的值

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

function render(str, data){
let arr = [str];
while (true){
let str = arr.pop();

let start = str.search('<%');
let end = str.search('%>');

if (start === -1){
arr.push(str)
break
}

let bstr = str.substring(0, start);
let instr = str.substring(start + 2, end);
str = str.substring(end + 2);

arr.push(bstr)
arr.push(instr)
arr.push(str)
}

let renderBuf = `let renderout = ''\n`;
arr.forEach((data, index) => {
if (index % 2 === 0 && data){
renderBuf += `renderout += "${data.trim()}"\n`
} else {
if (data[0] === '='){
renderBuf += `renderout += ${data.substr(1).trim()}\n`
} else {
renderBuf += `${data.trim()}\n`
}
}
})
renderBuf += `return renderout`;

let x = new Function('locals', renderBuf);
return x.call(data)
}

这样就实现了一个特别简单的渲染引擎,当然这个还很简陋,
我们只需要进行如下改进,就可以实现一个更加好用的简单的页面渲染引擎了(其实就是个渲染引擎)

  1. 去除 \n

    由于可能在编写的时候为了可读性而换行,去除 \n 可以避免一些莫名其妙的问题

  2. 自定义开闭标识

    仅仅是支持着玩~

  3. 添加默认值

  4. 增加没有闭合的错误抛出

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

function render(str, data = {}, options = {}){
let {
startSign = "<%",
endSign = "%>"
} = options;


str = str.replace('\n', '');
let arr = [str];
while (true){
let str = arr.pop();

let start = str.search(startSign);
let end = str.search(endSign);

if (start === -1){
arr.push(str)
break
} else if (end === -1){
throw "no close !"
}

let bstr = str.substring(0, start);
let instr = str.substring(start + startSign.length, end);
str = str.substring(end + endSign.length);

arr.push(bstr)
arr.push(instr)
arr.push(str)
}

let renderBuf = `let renderout = ''\n`;
arr.forEach((data, index) => {
if (index % 2 === 0 && data){
renderBuf += `renderout += "${data.trim()}"\n`
} else {
if (data[0] === '='){
renderBuf += `renderout += ${data.substr(1).trim()}\n`
} else {
renderBuf += `${data.trim()}\n`
}
}
})
renderBuf += `return renderout`;

let renderFun = new Function('locals', renderBuf);
return renderFun.call(data)
}

后记

本来这个功能很早就想过实现,但当时没有想通,直到今天(2017-5-17)在写字符串模版信息提取时,写着写着写歪了,写成了模版渲染,我的内心是崩溃的。

END

2017-05-17 完成

2017-01-03 立项

功能实现之Tab键输入

最近弄博客的编辑器,发现Tab键无法直接输入,比较尴尬,为此用这篇文章来记录实现的方式。

实现过程

  1. 监听 keydown 事件,并屏蔽默认事件(preventDefault)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    function inputTab(dom){
    dom.addEventListener('keydown',function(e){
    if(e.keyCode === 9){
    //处理
    e.preventDefault();
    }
    })
    }

    这时候就已经按Tab键不会切换了

  2. 在光标位置添加需要键入的内容,比如:Tab 空格

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    function inputTab(dom){
    dom.addEventListener('keydown',function(e){
    if(e.keyCode === 9){
    let start = this.selectionStart;
    let l = this.selectionEnd - this.selectionStart;
    let value = this.value.split('');
    value.splice(start,l,'\t')
    this.value = value.join('');
    //处理
    e.preventDefault();
    }
    })
    }

    主要利用 selectionStartselectionEnd 来确定光标的位置,然后插入内容就可以了

扩展

同理当然还可以不止监听 Tab 键,可以还对其他的键位进行控制

这样改一改就可以实现了啦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

function changeInput(dom,keyCode,input){
dom.addEventListener('keydown',function(e){
if(e.keyCode === keyCode){
let start = this.selectionStart;
let l = this.selectionEnd - this.selectionStart;
let value = this.value.split('');
value.splice(start,l,input)
this.value = value.join('');
//处理
e.preventDefault();
}
})
}

事件版

这个函数将提供一个用于监听时间的函数,同时支持传入字符,自动判断keyCode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

function changeKeyDownInput(keyCode,input){
if(typeof keyCode === 'string'){
keyCode = keyCode.charCodeAt(0)
}

return function(e){
if(e.keyCode === keyCode){
let start = this.selectionStart;
let l = this.selectionEnd - this.selectionStart;
let value = this.value.split('');
value.splice(start,l,input)
this.value = value.join('');
//处理
e.preventDefault();
}
}
}

参考资料

MDN_HTMLTextAreaElement

END

2017-05-16 完成

2017-05-12 立项